Как вы исправляете плохое слияние и воспроизводите свои хорошие коммиты на фиксированное слияние?

Я случайно зафиксировал нежелательный файл (filename.orig при разрешении слияния) в своем хранилище несколько коммитов назад, но я до сих пор не заметил этого. Я хочу полностью удалить файл из истории хранилища.

Можно ли переписать историю изменений так, чтобы filename.orig никогда не добавлялся в хранилище?

Ответ 1

Пожалуйста, не используйте этот рецепт, если ваша ситуация не та, что описана в вопросе. Этот рецепт предназначен для исправления плохого слияния и повторения ваших хороших коммитов на фиксированное слияние.

Хотя filter-branch будет делать то, что вы хотите, это довольно сложная команда, и я бы, вероятно, решил сделать это с помощью git rebase. Это, вероятно, личное предпочтение. filter-branch может сделать это в одной, немного более сложной команде, тогда как решение rebase выполняет эквивалентные логические операции по одному шагу за раз.

Попробуйте следующий рецепт:

# create and check out a temporary branch at the location of the bad merge
git checkout -b tmpfix <sha1-of-merge>

# remove the incorrectly added file
git rm somefile.orig

# commit the amended merge
git commit --amend

# go back to the master branch
git checkout master

# replant the master branch onto the corrected merge
git rebase tmpfix

# delete the temporary branch
git branch -d tmpfix

(Обратите внимание, что на самом деле вам не нужна временная ветка, вы можете сделать это с помощью "отсоединенной HEAD", но вам нужно принять к сведению идентификатор фиксации, сгенерированный шагом git commit --amend, чтобы указать git rebase вместо использования имени временной ветки.)

Ответ 2

Введение: у вас есть 5 доступных решений

Оригинальный плакат гласит:

Я случайно совершил нежелательный файл... в мой репозиторий несколько коммитов  назад... Я хочу полностью удалить файл из истории репозитория.

Это  можно переписать историю изменений таким образом, чтобы filename.orig никогда не был  добавлено в репозиторий в первую очередь?

Существует множество способов удалить историю файла полностью из git:

  • Изменение коммитов.
  • Жесткие сбрасывания (возможно, плюс rebase).
  • Неинтерактивная перезагрузка.
  • Интерактивные перестановки.
  • Фильтрация ветвей.

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

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


Решение 1: Изменение коммитов

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

git rm <file>
git commit --amend --no-edit

Решение 2: Жесткий Reset (возможно, плюс ребаза)

Как решение # 1, если вы просто хотите избавиться от своего предыдущего коммита, тогда вы также имеют возможность просто сделать жесткий Reset для своего родителя:

git reset --hard HEAD^

Эта команда будет жестче - reset ваша ветка на предыдущую родительскую строку st совершить.

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

# Create a new branch at the commit you want to amend
git checkout -b temp <commit>

# Amend the commit
git rm <file>
git commit --amend --no-edit

# Rebase your previous branch onto this new commit, starting from the old-commit
git rebase --preserve-merges --onto temp <old-commit> master

# Verify your changes
git diff [email protected]{1}

Решение 3: Неинтерактивная ребаза

Это будет работать, если вы просто хотите полностью удалить фиксацию из истории:

# Create a new branch at the parent-commit of the commit that you want to remove
git branch temp <parent-commit>

# Rebase onto the parent-commit, starting from the commit-to-remove
git rebase --preserve-merges --onto temp <commit-to-remove> master

# Or use `-p` insteda of the longer `--preserve-merges`
git rebase -p --onto temp <commit-to-remove> master

# Verify your changes
git diff [email protected]{1}

Решение 4: Интерактивные ребазы

Это решение позволит вам выполнить те же задачи, что и решения № 2 и # 3, т.е. Изменение или удаление завершается еще в истории, чем ваш previous commit, поэтому решение, которое вы решили использовать, зависит от вас. Интерактивные перестановки не очень подходят для перезагрузки сотен коммитов, для по производительности, поэтому я бы использовал неинтерактивные переустановки или ветвь фильтра (см. ниже) в таких ситуациях.

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

git rebase --interactive <commit-to-amend-or-remove>~

# Or `-i` instead of the longer `--interactive`
git rebase -i <commit-to-amend-or-remove>~

Это приведет к тому, что git перемотает историю фиксации обратно родительскому элементу зафиксируйте, что вы хотите изменить или удалить. Затем он представит вам список перематывание выполняется в обратном порядке в любом редакторе git для использования (это Vim по умолчанию):

pick 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`
pick 7668f34 Modify Bash config to use Homebrew recommended PATH
pick 475593a Add global .gitignore file for OS X
pick 1b7f496 Add alias for Dr Java to Bash config (OS X)

Конец, который вы хотите изменить или удалить, будет в верхней части этого списка. Чтобы удалить его, просто удалите его строку в списке. В противном случае замените "pick" на "edit" в строке 1 st например:

edit 00ddaac Add symlinks for executables
pick 03fa071 Set `push.default` to `simple`

Затем введите git rebase --continue. Если вы решили полностью удалить фиксацию, то все, что вам нужно сделать (кроме проверки, см. окончательный шаг для это решение). Если, с другой стороны, вы хотите изменить фиксацию, тогда git будет повторно применять фиксацию, а затем приостановить rebase.

Stopped at 00ddaacab0a85d9989217dd9fe9e1b317ed069ac... Add symlinks
You can amend the commit now, with

        git commit --amend

Once you are satisfied with your changes, run

        git rebase --continue

На этом этапе вы можете удалить файл и изменить фиксацию, а затем продолжить перебазироваться:

git rm <file>
git commit --amend --no-edit
git rebase --continue

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

git diff [email protected]{1}

Решение 5: Фильтрация ветвей

Наконец, это решение лучше всего, если вы хотите полностью уничтожить все следы существование файла из истории, и ни один из других решений не подходит для задача.

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>'

Это удалит <file> из всех коммитов, начиная с корневой фиксации. Если вместо этого вы просто хотите переписать диапазон фиксации HEAD~5..HEAD, тогда вы можете передайте это как дополнительный аргумент filter-branch, как указано в этот ответ:

git filter-branch --index-filter \
'git rm --cached --ignore-unmatch <file>' HEAD~5..HEAD

Снова, после завершения filter-branch, обычно рекомендуется проверить что нет никаких других неожиданных изменений, если вы разделите свою ветку с ее предыдущее состояние до операции фильтрации:

git diff [email protected]{1}

Альтернатива фильтра-фильтра: BFG Repo Cleaner

Я слышал, что инструмент BFG Repo Cleaner работает быстрее, чем git filter-branch, поэтому вы можете также проверить это как вариант. Он даже официально упоминался в документации по фильтрам как жизнеспособную альтернативу:

git -filter-branch позволяет создавать сложные перезаписываемые сценарии вашей истории git, но вам, вероятно, не нужна такая гибкость, если вы просто удаляете ненужные данные, такие как большие файлы или пароли. Для этих операций вы можете рассмотреть The BFG Repo-Cleaner, основанный на JVM альтернатива git -фильтрам, обычно не менее 10-50 раз быстрее для эти прецеденты и с совершенно разными характеристиками:

  • Любая конкретная версия файла очищается ровно один раз. BFG, в отличие от git -filter-branch, не дает вам возможности обрабатывать файл по-разному в зависимости от того, где и когда оно было совершено в вашем история. Это ограничение дает основную выгоду от BFG, и хорошо подходит для задачи очистки плохих данных - вы не где плохие данные, вы просто хотите, чтобы это исчезло.

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

  • Параметры более ограничительная, чем git -фильтра ветвь, и предназначена только для задачи удаления нежелательных данных, например: --strip-blobs-bigger-than 1M.

Дополнительные ресурсы

Ответ 3

Если вы ничего не совершали, просто git rm файл и git commit --amend.

Если у вас

git filter-branch \
--index-filter 'git rm --cached --ignore-unmatch path/to/file/filename.orig' merge-point..HEAD

будет проходить каждое изменение с merge-point до HEAD, удалить filename.orig и переписать изменение. Использование --ignore-unmatch означает, что команда не сбой, если по какой-либо причине filename.orig отсутствует в результате изменения. Это рекомендуется из раздела "Примеры" на странице git -filter-branch.

Примечание для пользователей Windows: путь к файлу должен использовать косые черты

Ответ 4

Это лучший способ:
http://github.com/guides/completely-remove-a-file-from-all-revisions

Только убедитесь, что сделали резервные копии файлов.

РЕДАКТИРОВАТЬ

Редактирование Неоном, к сожалению, было отклонено во время обзора.
Смотрите пост Neons ниже, он может содержать полезную информацию!


Например, чтобы удалить все файлы *.gz случайно *.gz в репозиторий git:

$ du -sh .git ==> e.g. 100M
$ git filter-branch --index-filter 'git rm --cached --ignore-unmatch *.gz' HEAD
$ git push origin master --force
$ rm -rf .git/refs/original/
$ git reflog expire --expire=now --all
$ git gc --prune=now
$ git gc --aggressive --prune=now

Это все еще не работает для меня? (Я сейчас нахожусь на git версии 1.7.6.1)

$ du -sh .git ==> e.g. 100M

Не знаю почему, так как у меня была только одна ветка master. В любом случае, я наконец-то получил чистое репозиторий git, запустив новый пустой и пустой репозиторий git, например

$ git init --bare /path/to/newcleanrepo.git
$ git push /path/to/newcleanrepo.git master
$ du -sh /path/to/newcleanrepo.git ==> e.g. 5M 

(да!)

Затем я клонирую это в новый каталог и перемещаю по нему папку.git в этот. например

$ mv .git ../large_dot_git
$ git clone /path/to/newcleanrepo.git ../tmpdir
$ mv ../tmpdir/.git .
$ du -sh .git ==> e.g. 5M 

(да! наконец-то прибрался!)

Убедившись, что все в порядке, вы можете удалить ../large_dot_git и ../tmpdir (возможно, через пару недель или месяц, на всякий случай...)

Ответ 5

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

Чтобы сделать это как можно проще, я рекомендую использовать BFG Repo-Cleaner, более быструю и быструю альтернативу git-filter-branch специально предназначенный для удаления файлов из истории Git. Одним из способов облегчения вашей жизни является то, что он фактически обрабатывает все ссылки по умолчанию (все теги, ветки и т.д.), Но также 10 - 50x быстрее.

Вы должны внимательно следить за шагами здесь: http://rtyley.github.com/bfg-repo-cleaner/#usage - но основной бит это просто: загрузите BFG jar (требуется Java 6 или выше) и выполните следующую команду:

$ java -jar bfg.jar --delete-files filename.orig my-repo.git

Вся ваша история репозитория будет отсканирована, и любой файл с именем filename.orig (который не находится в последнему коммит) будет удален. Это значительно проще, чем использовать git-filter-branch, чтобы сделать то же самое!

Полное раскрытие: я являюсь автором BFG Repo-Cleaner.

Ответ 6

You should probably clone your repository first.

Remove your file from all branches history:
git filter-branch --tree-filter 'rm -f filename.orig' -- --all

Remove your file just from the current branch:
git filter-branch --tree-filter 'rm -f filename.orig' -- --HEAD    

Lastly you should run to remove empty commits:
git filter-branch -f --prune-empty -- --all

Ответ 7

Чтобы добавить это к решению Чарльза Бейли, я просто использовал git rebase -i для удаления нежелательных файлов из более раннего коммита, и он работал как шарм. Шаги:

# Pick your commit with 'e'
$ git rebase -i

# Perform as many removes as necessary
$ git rm project/code/file.txt

# amend the commit
$ git commit --amend

# continue with rebase
$ git rebase --continue

Ответ 8

Самый простой способ, который я нашел, был предложен leontalbot (как комментарий), который является опубликованным Anoopjohn. Я думаю, что это стоит своего пространства в качестве ответа:

(я преобразовал его в bash script)

#!/bin/bash
if [[ $1 == "" ]]; then
    echo "Usage: $0 FILE_OR_DIR [remote]";
    echo "FILE_OR_DIR: the file or directory you want to remove from history"
    echo "if 'remote' argument is set, it will also push to remote repository."
    exit;
fi
FOLDERNAME_OR_FILENAME=$1;

#The important part starts here: ------------------------

git filter-branch -f --index-filter "git rm -rf --cached --ignore-unmatch $FOLDERNAME_OR_FILENAME" -- --all
rm -rf .git/refs/original/
git reflog expire --expire=now --all
git gc --prune=now
git gc --aggressive --prune=now

if [[ $2 == "remote" ]]; then
    git push --all --force
fi
echo "Done."

Все кредиты передаются Annopjohn и leontalbot для указания его.

Примечание

Имейте в виду, что script не включает проверки, поэтому убедитесь, что вы не ошибаетесь и у вас есть резервная копия, если что-то пойдет не так. Это сработало для меня, но это может не сработать в вашей ситуации. ИСПОЛЬЗУЙТЕ ЭТО С ПРЕДОСТЕРЕЖЕНИЕМ (перейдите по ссылке, если вы хотите узнать, что происходит).

Ответ 9

Определенно, git filter-branch - путь.

К сожалению, этого недостаточно, чтобы полностью удалить filename.orig из вашего репо, поскольку на него по-прежнему можно ссылаться теги, записи reflog, пульты и т.д.

Я рекомендую удалить все эти ссылки, а затем вызвать сборщик мусора. Вы можете использовать git forget-blob script из этот веб-сайт, чтобы сделать все это за один шаг.

git forget-blob filename.orig

Ответ 10

Если это последний коммит, который вы хотите очистить, я попытался использовать git версии 2.14.3 (Apple Git-98):

touch empty
git init
git add empty
git commit -m init

# 92K   .git
du -hs .git

dd if=/dev/random of=./random bs=1m count=5
git add random
git commit -m mistake

# 5.1M  .git
du -hs .git

git reset --hard HEAD^
git reflog expire --expire=now --all
git gc --prune=now

# 92K   .git
du -hs .git

Ответ 11

Это то, что git filter-branch предназначено для.

Ответ 12

вы должны использовать ветку git filter, представьте, что вы хотите удалить все target/dirs в ваших предыдущих коммитах, вы можете запустить всего 4 команды для этого:

$ git filter-branch --tree-filter 'rm -f */target' HEAD

$ rm -rf */target (you use this one to delete target/ dirs on current HEAD)

$ git commit -am 'delete all target/ dirs' --allow-empty

$ git push

Ответ 13

Вы также можете использовать:

git reset HEAD file/path