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

StackOverflow https://stackoverflow.com/questions/307828

Вопрос

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

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

Это было полезно?

Решение

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

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

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

(Обратите внимание, что на самом деле вам не нужна временная ветвь, вы можете сделать это с помощью «отсоединенного HEAD», но вам нужно записать идентификатор фиксации, сгенерированный шагом <=>, чтобы передать его в < => вместо использования временного имени ветви.)

Другие советы

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

На оригинальном плакате говорится:

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

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

Есть много разных способов, чтобы удалить историю из файла ГИТ:

  1. Внесение изменений в коммиты.
  2. Жесткий сброс (возможно, плюс перебазирование).
  3. Неинтерактивная перебазировка.
  4. Интерактивные перебазирования.
  5. Фильтрующие ветви.

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

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


Решение 1:Внесение изменений В Обязательства

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

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

Решение 2:Полная перезагрузка (возможно, плюс перебазирование)

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

git reset --hard HEAD^

Эта команда приведет к жесткому сбросу вашей ветки на предыдущую 1ST родитель совершает.

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


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

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


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

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

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

Это заставит 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)

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

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

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

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 master@{1}

Решение 5:Фильтрующие ветви

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

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

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

git diff master@{1}

Альтернатива фильтру-Отводу:Очиститель репозитория BFG

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

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

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

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

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

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

  1. Pro Git § 6.4 Инструменты Git - История перезаписи.
  2. страница руководства по git-filter-branch(1).
  3. страница руководства по git-фиксации (1).
  4. страница руководства по git-сбросу (1).
  5. страница руководства по git-rebase (1).
  6. Очиститель репозитория BFG (см . также этот ответ от самого творца).

Если с тех пор вы ничего не фиксировали, просто 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 означает, что команда не завершится ошибкой, если по какой-то причине имя файла.orig отсутствует в изменении. Это рекомендуемый способ из раздела «Примеры» на странице руководства git-filter-branch .

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

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

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

Редактировать

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


Например.чтобы удалить все *.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

Не знаю почему, поскольку у меня была только ОДНА главная ветвь.В любом случае, я, наконец, полностью очистил свое репозиторий 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 каталоги (возможно, через пару недель или месяц, на всякий случай ...)

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

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

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

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

Будет просмотрена вся история вашего репозитория и любой файл с именем filename.orig (которого нет в вашем последний коммит ) будет удален. Это значительно проще, чем использовать <=> для того же!

Полное раскрытие. Я являюсь автором репо-уборщика BFG.

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

Просто чтобы добавить это к решению Чарльза Бэйли, я просто использовал 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

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

(Я преобразовал его в скрипт bash)

#!/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 за то, что указал на это.

ПРИМЕЧАНИЕ

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

Определенно, git filter-branch это тот путь, по которому нужно идти.

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

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

git forget-blob filename.orig

Если это последний коммит, который вы хотите очистить, я попытался использовать 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

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

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

git reset HEAD file/path

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top