Определенное обратное действие.gitignore (как заставить Git ** правильно забыть ** (исторически) о файле, который сейчас находится в.gitignore)

Примечание. Этот вопрос пытается очистить путаницу, касающуюся применения.gitignore задним числом, а не только к настоящему / будущему. 1

обоснование

Я искал способ сделать мой текущий.gitignore принудительно принудительным, как если бы я создал.gitignore в первом коммите.

Правильное решение:

  • НЕ требует ручного указания файлов
  • Будет применяться задним числом ко всем коммитам всех веток
  • Игнорирует указанные в.gitignore файлы в рабочем каталоге, а не удаляет их (так же, как и исходный файл.gitignore с фиксацией root)
  • Будет использовать мерзавец, а не BFG
  • Будет применяться к исключениям.gitignore, таким как:
 *.ext
 !*special.ext

НЕ решения

git filter-branch --index-filter 'git rm --cached *.ext'

Это требует указания файлов вручную, а также удаляет указанные файлы из рабочего каталога!


Сноски

1 Здесь, на SO, есть много похожих постов, с не совсем определенными вопросами и даже с менее точными ответами. См. Этот вопрос с 23 ответами, где принятый ответ с ~4k голосов является неправильным в соответствии со стандартным определением "забыть", как отмечено одним главным образом правильным ответом, и только 2 ответа включают требуемые git filter-branch команда. Этот вопрос с 21 ответом помечен как дубликат предыдущего, но вопрос определен по-другому (игнорируйте и забудьте), поэтому, хотя ответы могут быть подходящими, он не является дубликатом. Этот вопрос наиболее близок к тому, что я искал, но ответы не работают во всех случаях (пути с пробелами для одного) и, возможно, немного сложнее, чем необходимо для создания внешнего к файл репозитория.gitignore и копирование его в каждый коммит.

2 ответа

Решение

Этот метод заставляет git полностью забыть игнорируемые файлы (прошлые/ настоящие / будущие), но не удаляет ничего из рабочего каталога (даже при повторном извлечении из удаленного).

Этот метод требует использования /.git/info/exclude (предпочтительно) ИЛИ ранее существовавший .gitignore во всех коммитах, в которых есть файлы, которые нужно игнорировать / забыть. 1

Все методы принудительного применения git игнорируют поведение за фактом, фактически переписывают историю и, таким образом, имеют значительные последствия для любых публичных / общих / совместных репозиториев, которые могут быть получены после этого процесса. 2

Общий совет: начните с чистого репо - все зафиксировано, ничего не ожидается в рабочем каталоге или индексе, и сделайте резервную копию!

Кроме того, комментарии / история изменений этого ответа ( и история изменений этого вопроса) могут быть полезными / поучительными.

#commit up-to-date .gitignore (if not already existing)
#this command must be run on each branch

git add .gitignore
git commit -m "Create .gitignore"

#apply standard git ignore behavior only to current index, not working directory (--cached)
#if this command returns nothing, ensure /.git/info/exclude AND/OR .gitignore exist
#this command must be run on each branch

git ls-files -z --ignored --exclude-standard | xargs -0 git rm --cached

#Commit to prevent working directory data loss!
#this commit will be automatically deleted by the --prune-empty flag in the following command
#this command must be run on each branch

git commit -m "ignored index"

#Apply standard git ignore behavior RETROACTIVELY to all commits from all branches (--all)
#This step WILL delete ignored files from working directory UNLESS they have been dereferenced from the index by the commit above
#This step will also delete any "empty" commits.  If deliberate "empty" commits should be kept, remove --prune-empty and instead run git reset HEAD^ immediately after this command

git filter-branch --tree-filter 'git ls-files -z --ignored --exclude-standard | xargs -0 git rm -f --ignore-unmatch' --prune-empty --tag-name-filter cat -- --all

#List all still-existing files that are now ignored properly
#if this command returns nothing, it's time to restore from backup and start over
#this command must be run on each branch

git ls-files --other --ignored --exclude-standard

Наконец, следуйте остальной части этого руководства GitHub (начиная с шага 6), которое включает важные предупреждения / информацию о командах ниже.

git push origin --force --all
git push origin --force --tags
git for-each-ref --format="delete %(refname)" refs/original | git update-ref --stdin
git reflog expire --expire=now --all
git gc --prune=now

Другие разработчики, которые извлекают данные из модифицированного удаленного репо, должны сделать резервную копию, а затем:

#fetch modified remote

git fetch --all

#"Pull" changes WITHOUT deleting newly-ignored files from working directory
#This will overwrite local tracked files with remote - ensure any local modifications are backed-up/stashed
#Switching branches after this procedure WILL LOOSE all newly-gitignored files in working directory because they are no longer tracked when switching branches

git reset FETCH_HEAD

Сноски

1 Потому что /.git/info/exclude может применяться ко всем историческим коммитам, используя приведенные выше инструкции, возможно, подробности о получении .gitignore файл в исторический коммит (ы), которые нуждаются в этом, выходят за рамки этого ответа. Я хотел правильное .gitignore быть в корневом коммите, как будто это было первое, что я сделал. Другие могут не заботиться, так как /.git/info/exclude может достичь того же, независимо от того, где .gitignore существует в истории коммитов, и, очевидно, переписывание истории является очень деликатным вопросом, даже если известно о последствиях.

FWIW, потенциальные методы могут включать git rebase или git filter-branch который копирует внешний .gitignore в каждый коммит, как и ответы на этот вопрос

2 Принуждение мерзавца игнорировать поведение после факта, передавая результаты автономного git rm --cached Эта команда может привести к удалению недавно игнорируемого файла при последующих извлечениях с пульта с принудительным нажатием. --prune-empty флаг в следующем git filter-branch Команда позволяет избежать этой проблемы, автоматически удаляя предыдущую фиксацию "удалить все игнорируемые файлы" только для индекса. Переписывание истории git также меняет хэши коммитов, что приведет к хаосу в будущих извлечениях из публичных / общих / совместных репозиториев. Пожалуйста, поймите все последствия, прежде чем делать это в таком репо. В этом руководстве GitHub указано следующее:

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

Альтернативные решения, которые не влияют на удаленное репо: git update-index --assume-unchanged </path/file> или же git update-index --skip-worktree <file>, примеры которых можно найти здесь.

Это может быть только частичный ответ, но вот как я выполнил ретроактивное удаление файлов из предыдущих коммитов git на основе моего текущего файла .gitignore:

  1. Сделайте резервную копию папки репо, над которой вы работаете. Я только что сделал архив .7z всей папки.
  2. Установите git-фильтр-репо
  3. Временно скопируйте файл .gitignore в другое место. Поскольку я в Windows и использую командную строку, я запустил copy .gitignore ..\и только что сделал временную копию только на уровне каталога
  4. Если в вашем файле .gitignore есть фильтры с подстановочными знаками (например, nbproject/Makefile-*), вам нужно будет отредактировать ваш временный скопированный файл .gitignore, чтобы эти строки читались glob:nbproject/Makefile-*
  5. Бежать git filter-repo --invert-paths --paths-from-file ..\.gitignore. Насколько я понимаю, это использует временную копию как список файлов/каталогов для удаления. Примечание. Если вы получили сообщение об ошибке, что ваш репозиторий не является чистым клоном, найдите «ПРОВЕРКА БЕЗОПАСНОСТИ СВЕЖЕГО КЛОНА И --FORCE» в справке git-filter-repo. Будь осторожен.

Для получения дополнительной информации см. Справку git-filter-repo (ищите «Фильтрация на основе многих путей»)

Отказ от ответственности: я понятия не имею, что я делаю, но это сработало для меня.

Другие вопросы по тегам