Обрезка нескольких последовательных коммитов в истории - PullRequest
2 голосов
/ 06 октября 2010

Есть ли способ удалить несколько последовательных коммитов в ветке?

Допустим, история выглядит следующим образом:

                      A -> B -> C -> D

Теперь я хотел бы удалить изменения, внесенные B иC, поэтому история выглядит следующим образом:

                           A -> D

Например, типичным сценарием может быть пьяный разработчик, который вносит мусор в коммиты B и C, при этом пишет хорошие вещи в D.

Я придумал довольно плохой (и, вероятно, не очень надежный способ сделать это):

# Get a patch for the good things
# Context lines are set to zero so applying
# the patch won't choke because of missing lines
# that were added by C or D
git format-patch --stdout -U0 revC..revD > CtoD

# Go back in time to last good revision before mayhem
git reset --hard revA

# Apply good things at this point
git apply --stat CtoD
git apply --check CtoD
git apply CtoD

# Add new files from patch
git add <any files that were created in CtoD patch>

# And commit
git commit -a -m "Removed B and C commits. Drunk dev fired"

Этот способ сделать это далеко не идеален.Удаление контекста для diff, вероятно, сделает git-apply choke во многих ситуациях, и файлы должны быть добавлены git-add'ом вручную.Я также мог бы полностью упустить смысл здесь ...

Может кто-нибудь указать мне правильный способ сделать это?

Спасибо за чтение!


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

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

Вот подробный (и длинный, извините!) список того, что было сделано:

##
# Create test environment
##
# all will happen below 'testing', so the mess can easily be wiped out
mkdir testing
cd testing

# Create 'origin' (sandbox_project) and the working clone (work)
mkdir sandbox_project work

# Create origin repos
cd sandbox_project
git init --bare

# Clone it
cd ../work
git clone ../sandbox_project
cd sandbox_project

# Create a few files :
# at each rev (A, B, C, D) we respectively create a fileA .. fileD
# at each rev, we also add a line to fileA

echo "This file was created in A" > fileA
git add .
git commit -a -m 'First revision'
git tag "revA"

for i in B C D; do
    echo "This file was created in $i" >> file$i
    echo "This change was done in $i" >> fileA
    git add file$i
    git commit -a -m "revision $i"
    git tag "rev$i"
done

# We push changes to origin
git push origin master

Теперь это выглядит так:

$ git log --graph --decorate --pretty=oneline --abbrev-commit

* e3dc9b7 (HEAD, revD, origin/master, master) revision D
* e21fd6a (revC) revision C
* a9192ec (revB) revision B
* a16c9dd (revA) First revision

Я хочу удалить то, что было введено в B и C:

$ git rebase -i HEAD~3

Automatic cherry-pick failed.  After resolving the conflicts,
mark the corrected paths with 'git add <paths>', and
run 'git rebase --continue'
Could not apply e3dc9b7... revision D

$ git status

# Not currently on any branch.
# Changes to be committed:
#   (use "git reset HEAD <file>..." to unstage)
#
#   new file:   fileD
#
# Unmerged paths:
#   (use "git reset HEAD <file>..." to unstage)
#   (use "git add/rm <file>..." as appropriate to mark resolution)
#
#   both modified:      fileA
#

Конечно, есть проблема с файлом A (проблема, которую я решил с помощью своего решения, применив diff, сгенерированный с -U0Здесь я редактирую файл A, удаляю конфликтующие строки и продолжаю перебазирование:

This file was created in A
<<<<<<< HEAD
=======
This change was done in B
This change was done in C
This change was done in D
>>>>>>> e3dc9b7... revision D

редактируется так:

This file was created in A
This change was done in D

А затем:

$ git add fileA
$ git rebase --continue

# leave mesasge as-is

[detached HEAD e2d4032] revision D
 2 files changed, 2 insertions(+), 0 deletions(-)
 create mode 100644 fileD
Successfully rebased and updated refs/heads/master.

$ git push

To ../sandbox_project
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to '../sandbox_project'
To prevent you from losing history, non-fast-forward updates were rejected
Merge the remote changes before pushing again.  See the 'Note about
fast-forwards' section of 'git push --help' for details.

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

$ git pull
Auto-merging fileA
CONFLICT (content): Merge conflict in fileA
Automatic merge failed; fix conflicts and then commit the result.

$ git log --graph --decorate --pretty=oneline --abbrev-commit

* e2d4032 (HEAD, master) revision D
* a16c9dd (revA) First revision

Опять файл A не может быть объединен, так как он имеетревизии B и C Cruft.Здесь мы снова идем, редактируем файл A и удаляем изменения, вводимые с помощью и C.

Затем:

$ git add fileA
$ git commit -a
[master b592261] Merge branch 'master' of ../sandbox_project
$ git push
Counting objects: 9, done.
Compressing objects: 100% (5/5), done.
Unpacking objects: 100% (5/5), done.
Writing objects: 100% (5/5), 674 bytes, done.
Total 5 (delta 0), reused 0 (delta 0)
To ../sandbox_project
   e3dc9b7..b592261  master -> master

Выглядит хорошо, но:

$ git log --graph --decorate --pretty=oneline --abbrev-commit

*   b592261 (HEAD, origin/master, master) Merge branch 'master' of ../sandbox_project
|\  
| * e3dc9b7 (revD) revision D
| * e21fd6a (revC) revision C
| * a9192ec (revB) revision B
* | e2d4032 revision D
|/  
* a16c9dd (revA) First revision

В то время как вконец, файл A в порядке, у меня все еще есть файл B и файл C, который я не хотел.И по ходу дела мне пришлось дважды разрешать конфликты слияния для файла A.

Любая подсказка?

1 Ответ

3 голосов
/ 06 октября 2010

Я бы использовал git rebase -i HEAD~4.

Это породит ваш $EDITOR с файлом, содержащим одну строку для каждого коммита от HEAD~4 до HEAD.Удалите строки для коммитов, которые вы хотите выбросить, сохраните и выйдите из редактора, и git применит только те коммиты, которые вы оставили в файле поверх HEAD~4.

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

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

$ git revert $commit_sha

Это создаст новый коммит, применяя разность $commit_sha в обратном порядке.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...