`git clean` удаляет игнорируемые файлы по умолчанию?

В соответствии с помощью без -x опция git clean должна оставить в стороне проигнорированные файлы, но это не так.

[[email protected] test]$ cat .gitignore
*.sar
[[email protected] test]$ mkdir -p conf/sar && touch conf/sar/aaa.sar
[[email protected] test]$ git status
# On branch master
nothing to commit, working directory clean
[[email protected] test]$ git clean -df
Removing conf/

conf/sar/aaa.sar удаляется. Это ошибка?

Ответ 1

Согласно man git clean:

-d
    Remove untracked directories in addition to untracked files.

В вашем случае каталог conf/sar не отслеживается - он не содержит файлов, которые отслеживаются с помощью git. Если у вас не было правила gitignore и выполнено git clean -fd, содержимое этого неподготовленного каталога было бы удалено - только в документации.

Теперь, если вы добавите .gitignore с правилом для игнорирования файлов *.sar, это не изменит базовый факт, что ваш каталог conf/sar/ по-прежнему не проверен и имеет необработанный файл aaa.sar, который имеет право на это правило gitignore не должен внезапно сделать его неустранимым на git clean -fd.

Но если вы добавите какой-либо файл отслеживания рядом с вашим игнорированным aaa.sar, то этот каталог не будет удален, и ваш файл останется в покое.

Другими словами, хотя это выглядит запутанным, это не ошибка, а git делает именно то, что говорит документация.

Ответ 2

Предупреждение: это поведение git clean немного изменится с Git 2.14 (3 квартал 2017 года)

"git clean -d" используется для очистки каталогов, которые игнорировали файлы, даже если команда не должна терять проигнорированные без" -x ".
"git status --ignored" не перечислял пропущенные и неотслеживаемые файлы без "-uall".

См. commit 6b1db43 (23 мая 2017 г.) и commit bbf504a, commit fb89888, commit df5bcdf, commit 0a81d4a, передайте b3487cc (18 мая 2017 г.) Самуэлю Лицзину (sxlijin).
(Merged by Junio C Hamano -- [TG46] -- in commit f4fd99b, 02 Jun 2017)

clean: научить clean -d сохранять игнорируемые пути

Существует неявное предположение, что каталог, содержащий только неотслеживаемые и игнорируемые пути, сам по себе должен считаться неотслеживаемым. Это имеет смысл в случаях использования, когда мы спрашиваем, следует ли добавить каталог в базу данных git, но не когда мы спрашиваем, можно ли безопасно удалить каталог из рабочего дерева; в результате clean -d будет предполагать, что "неотслеживаемый" каталог, содержащий игнорируемые пути, может быть удален, хотя это также приведет к удалению игнорируемых путей.

Чтобы обойти это, мы учим clean -d собирать игнорируемые пути и пропускать неотслеживаемый каталог, если он содержит игнорируемый путь, вместо этого просто удаляя его неотслеживаемое содержимое.
Для этого cmd_clean() должен собрать все неотслеживаемое содержимое неотслеживаемых каталогов, в дополнение ко всем игнорируемым путям, чтобы определить, какие неотслеживаемые каталоги должны быть пропущены (так как они содержат пропущенные пути), а какие не должны быть пропущены. быть пропущенным.


Git 2.24 (Q4 2019) иллюстрирует это git clean изменение поведения, вводящее регресс.

См. коммит 502c386 (25 августа 2019 года) SZEDER Gábor (szeder).
(Merged by Junio C Hamano -- [TG414] -- in commit 026428c, 30 Sep 2019)

t7300-clean: демонстрация удаления вложенного репо с игнорируемым разрывом файла

"git clean -fd" не должен удалять неотслеживаемый каталог, если он принадлежит в другой репозиторий Git или рабочее дерево.

К сожалению, если правило ".gitignore" во внешнем репозитории соответствует файлу во вложенном репозитории или рабочем дереве, то что-то идет не так, и "git clean -fd" удаляет содержимое рабочего дерева вложенного репозитория. за исключением того, что игнорируемый файл может привести к потере данных.

Add a test to 't7300-clean.sh' to demonstrate this breakage.

Эта проблема является регрессией, введенной в 6b1db43 (clean: учить clean -d для сохранения игнорируемых путей, 2017-05-23, Git v2.13.2).


Git 2.24 уточняет git clean -d:

Смотрите коммит 69f272b (01 октября 2019 г.) и коммит 902b90c, коммит ca8b539, коммит 09487f2, коммит e86bbcf, коммит 3aca580, коммит 29b577b, коммит 89a1f4a, коммит a3d89d8, коммит 404ebce, зафиксировать a5e916c, зафиксировать bbbb6b0, зафиксировать 7541cc5 (17 сентября 2019 г.) Элайджей Ньюреном (newren).
. (Merged by Junio C Hamano -- [TG424] -- in commit aafb754, 11 Oct 2019)

t7300: добавить тестовые случаи, показывающие, что не удалось очистить указанные пути

Кто-то принес мне контрольный пример, в котором было несколько вызовов git-clean требуется удалить ненужные файлы:

mkdir d{1,2}
touch d{1,2}/ut
touch d1/t && git add d1/t

При такой настройке пользователю нужно будет запустить

git clean -ffd */ut

дважды, чтобы удалить оба файла ut.

Небольшое тестирование показало несколько интересных вариантов:

  • Если бы существовал только один из этих двух файлов ut (либо один), то была бы необходима только одна команда clean.
  • Если в обоих каталогах отслеживались файлы, то для очистки обоих файлов потребуется только один git clean.
  • Если в обоих каталогах нет отслеживаемых файлов, команда очистки, приведенная выше, никогда не очистит ни один из неотслеживаемых файлов, несмотря на то, что спецификация пути явно вызывает их оба.

Анализ показал, что сбой при очистке файлов начался с commit cf424f5 ("clean: соблюдайте pathspecs с помощью" -d ", 2014-03-10, Git v1.9.1).
Однако это указывало на отдельную проблему: хотя первоначальный пользователь, который показал мне эту проблему, использовал флаг "-d", этот флаг должен был не иметь отношения к этой проблеме.
Повторное тестирование без флага "-d" показало, что такое же поведение с ошибками существует без использования этого флага и фактически существует с тех пор, как до cf424f5.

Итак:

clean: соблюдайте спецификации пути с помощью "-d"

git-clean использует директорию read_directory для заполнения struct dir потенциальными попаданиями. Однако read_directory фактически не проверяет нашу спецификацию пути. Он использует упрощенную версию, которая может привести к ложным срабатываниям. В результате нам нужно убедитесь, что любые совпадения соответствуют нашему пути.

Мы делаем это надежно для не -d -каторий.

Для каталогов, если "-d" не задано, мы проверяем, что спецификация пути точно совпадает (то есть, мы еще строже, и нам требуется явный "git clean foo" для очистки "foo/"). Но если задано "-d", а не ослаблять точное совпадение, чтобы разрешить рекурсивное сопоставление, мы вообще не проверяем спецификацию пути.

This regression was introduced in 113f10f (Make git-clean a builtin, 2007-11-11, Git v1.5.4-rc0).

dir: если наша спецификация пути может соответствовать файлам в каталоге, перейдите в него

Для git clean, если каталог полностью не отслеживается и пользователь не указал -d (соответствует DIR_SHOW_IGNORED_TOO), то мы обычно не хотим удалять этот каталог и, следовательно, не возвращаться в него.

Однако если пользователь вручную указал конкретные (или даже глобальные) пути где-то в этом каталоге для удаления, то нам нужно вернуться в каталог, чтобы убедиться, что мы удаляем соответствующие пути в этом каталоге, как запрашивал пользователь.

Обратите внимание, что это не означает, что каталог для повторного ввода будет добавлен в dir->entries для последующего удаления; что касается нескольких коммитов ранее в этой серии, есть еще одна более строгая проверка соответствия, которая запускается после возврата из возвращенного в каталог каталога, прежде чем принять решение добавить его в список записей.
Таким образом, это приведет только к файлам под указанным каталогом, которые соответствуют одной из спецификаций пути, добавляемых в список записей.

И:

dir: также проверять каталоги на соответствие путевых спецификаций

Даже если каталог не совпадает со спецификацией пути, возможно, в зависимости от точных спецификаций пути, это может сделать какой-то файл под ним.
Поэтому мы особый случай и вернемся в каталог для таких ситуаций.
Однако ранее мы всегда добавляли любой неотслеживаемый каталог, в который мы повторялись, в список неотслеживаемых путей, независимо от того, соответствовал ли сам каталог указанному пути.

Для случая git-clean и набора спецификаций пути "dir/file" и "more", это вызвало проблему, потому что мы получили записи dir для обоих:

"dir"
"dir/file"

Затем correct_untracked_entries() попытается тщательно обрезать дубликаты для нас, удалив "dir/file", так как он находится в "dir", оставив нам

"dir"

Поскольку исходная спецификация пути содержала только "dir/file", единственная оставленная запись не совпадает и не оставляет ничего для удаления.
(Обратите внимание, что если указана только одна спецификация пути, например, только "dir/file", то оптимизации common_prefix_len в fill_directory заставят нас обойти эту проблему, и в простых тестах мы сможем корректно удалить указанные пути спецификации вручную..)

Исправьте это, фактически проверив, действительно ли каталог, который мы собираемся добавить в список записей dir, действительно соответствует pathspec; выполнять эту проверку соответствия только после того, как мы уже вернулись из повторяющегося в каталог.

В результате:

clean: устранение неоднозначности определения -d

Флаг -d предварял -d возможность git-clean указывать пути.
Таким образом, по умолчанию для git-clean было удаление только неотслеживаемых файлов в существующий каталог и -d существовали, чтобы позволить ему вернуться в подкаталоги.

Взаимодействие путей и опция -d, по-видимому, не были тщательно рассмотрены, о чем свидетельствуют многочисленные ошибки и недостаток тестов, охватывающих такие пары в тестовом наборе.
Определение оказывается важным, поэтому давайте рассмотрим некоторые из различных способов интерпретации опции -d:

A) Без -d, смотреть только в подкаталогах, которые содержат отслеживаемые файлы под ними; с -d также ищите подкаталоги, которые не отслеживаются для очистки файлов.

B) Без указанных путей от пользователя, которые мы должны удалить, нам нужно какое-то значение по умолчанию, поэтому... без -d, смотрите только в подкаталогах, которые содержат отслеживаемые файлы под ними; с -d также ищите подкаталоги, которые не отслеживаются для очистки файлов.

Важным отличием здесь является то, что выбор B говорит о том, что наличие или отсутствие '-d' не имеет значения, если указаны пути.
Логика, лежащая в основе варианта B, заключается в том, что если пользователь явно попросил нас очистить указанный путь, то мы должны очистить все, что соответствует pathspec.

Некоторые примеры могут прояснить.

Should:

git clean -f untracked_dir/file

удалить нетронутый_каталог/файл или нет?
Кажется, это безумие, но строгое чтение варианта А говорит, что его нельзя удалять.
Как насчет:

git clean -f untracked_dir/file1 tracked_dir/file2

или

git clean -f untracked_dir_1/file1 untracked_dir_2/file2

?
Должен ли он удалить один или оба этих файла?
Нужно ли многократное выполнение, чтобы удалить оба перечисленных файла? (Если это звучит как сумасшедший вопрос, который можно даже задать, см. сообщение о коммите "t7300: Добавьте несколько тестовые сценарии, показывающие неспособность очистить указанные пути ", добавленные ранее в эта серия патчей.)
Что если бы -ffd использовалось вместо -f - должно ли это позволить их удалить? Должно ли это сделать несколько вызовов с -ffd?
Что если вместо указания названий каталогов использовать глобус (например, "отслеживаемый")?
Что если в именах файлов используются глобусы, например

git clean -f '*.o'

или

git clean -f '*/*.o'

?

Текущая документация фактически предлагает определение, которое немного отличается от выбора A, и реализация до этого серия дала что-то радикально отличное от вариантов A или B.
(Реализация, однако, была явно просто глючной).

Могут быть и другие варианты.
Однако, для почти любого данного выбора определения для -d, о котором я могу подумать, некоторые из приведенных выше примеров будут казаться пользователю ошибочными.
Единственный случай, в котором нет отрицательных сюрпризов, - это вариант B: обрабатывать указанный пользователем путь как запрос на очистку всех неотслеживаемых файлов, соответствующих этой спецификации пути, включая повторное обращение к любым неотслеживаемым каталогам.

Измените документацию и базовую реализацию, чтобы использовать это определение.

Было два регрессионных теста, которые косвенно зависели от текущего реализация, но ни одна из них не касалась обработки подкаталогов.
Эти два теста были введены в коммите 5b7570c ("git-clean: добавить тесты для относительного пути", 2008-03-07, Git v1.5.5-rc0), который был создан исключительно для добавления покрытия для изменений в коммите fb328947c8e ("git-clean: правильный путь печати при печати", 2008-03-07).
В обоих тестах указывался каталог, в котором, как оказалось, не было отслеживаемого подкаталога, но оба проверяли только, чтобы полученная распечатка удаленного файла отображалась с относительным путем.
Обновите эти тесты соответствующим образом.

Наконец, смотрите "Git clean исключить вложенный подкаталог".

Ответ 3

Да, git clean, похоже, ведет себя противоположно документам, удалив игнорируемый файл, даже если -x/-x не указан.

Кажется, что опция -d переопределяет отсутствие -x/-x. То есть git clean -df удалит ненужные каталоги, даже если они содержат незатребованные, но проигнорированные файлы.

Я не знаю, является ли это надзором или преднамеренным, но manpage явно неполна в этом отношении. Вы можете рассмотреть возможность отправки патча для man-страницы в список рассылки git.

Кстати, та же проблема обсуждается в вопросе Как сохранить все проигнорированные файлы в git clean -fd?. Там отмечается, что git clean -df не будет удалять каталоги, находящиеся в .gitignore. Поэтому, чтобы сохранить ваш conf/, вы можете добавить его в .gitignore.

Ответ 4

Чтобы получить нужное поведение, защищая неподписанный каталог от git clean -d и выборочно удаляя контент из этих неподготовленных каталогов, вы должны явно игнорировать весь самый верхний неподписанный каталог, в вашем случае

echo /conf/ >>.gitignore   # or .git/info/excludes if it just you

Теперь git clean не переписывается в неподготовленные каталоги, но, к счастью, это простая рукоятка:

# recursive x-ray git clean with various options:

git ls-files --exclude-standard '-x!*/' -oz  | xargs -0 rm -f   #
git ls-files                            -oz  | xargs -0 rm -f   # -x
git ls-files --exclude-standard '-x!*/' -oiz | xargs -0 rm -f   # -X

(или git ls-files --exclude-standard '-x!/conf/', чтобы пропустить только одну спецификацию). Одиночные кавычки существуют, потому что ! - это синтаксис интерактивной оболочки для вытягивания фрагментов предыдущих команд.

Чтобы очистить пустые каталоги, вы можете приблизиться к желаемому поведению с помощью

find -depth -type d -empty -delete
# -delete is -exec rm -f '{}' ';' on non-GNU userlands

Но это действительно принадлежит рецепту make файла, за которым следует пакет mkdir -p, чтобы воссоздать любую структуру, которую вы хотите сохранить, даже если она пуста, так как make создан для управления переходными процессами, такими как сборка/тестирование/установка продуктов.