Как я могу легко исправить прошлую фиксацию?
Я только что прочитал изменение одного файла в прошлом фиксации в git, но, к сожалению, принятое решение "переупорядочивает" фиксации, что не то, что я хочу. Так вот мой вопрос:
время от времени я замечаю ошибку в своем коде при работе над (несвязанной) функцией. Быстро git blame затем показывает, что ошибка была введена несколько коммитов назад (я совершаю довольно много, поэтому обычно это не самый последний коммит, который ввел ошибку). На данный момент, я обычно делаю это:
git stash # temporarily put my work aside
git rebase -i <bad_commit>~1 # rebase one step before the bad commit
# mark broken commit for editing
vim <affected_sources> # fix the bug
git add <affected_sources> # stage fixes
git commit -C <bad_commit> # commit fixes using same log message as before
git rebase --continue # base all later changes onto this
однако, это происходит так часто, что выше последовательность становится раздражает. Особенно "интерактивная перебаза" скучна. Есть ли какой-либо ярлык для приведенной выше последовательности, который позволяет мне изменить произвольную фиксацию в прошлом с помощью поэтапных изменений? Я прекрасно понимаю, что это меняет историю, но я делаю ошибки так часто, что я действительно хотел бы иметь что-то вроде
vim <affected_sources> # fix bug
git add -p <affected_sources> # Mark my 'fixup' hungs for staging
git fixup <bad_commit> # amend the specified commit with staged changes,
# rebase any successors of bad commit on rewritten
# commit.
может быть, умный скрипт, который может переписать коммиты с помощью сантехники инструменты или что?
11 ответов:
ОБНОВЛЕННЫЙ ОТВЕТ
некоторое время назад, новый
--fixupаргумент был добавлен вgit commitкоторый может быть использован для построения фиксации с сообщением журнала, подходящим дляgit rebase --interactive --autosquash. Таким образом, самый простой способ исправить прошлую фиксацию теперь:$ git add ... # Stage a fix $ git commit --fixup=a0b1c2d3 # Perform the commit to fix broken a0b1c2d3 $ git rebase -i --autosquash a0b1c2d3~1 # Now merge fixup commit into broken commitОРИГИНАЛЬНЫЙ ОТВЕТ
вот небольшой скрипт Python, который я написал некоторое время назад, который реализует это
git fixupлогика, на которую я надеялся в своем первоначальном вопросе. Сценарий предполагает, что вы выполнили некоторые изменения, а затем применяете их изменения в данной фиксации.Примечание: этот скрипт специфичен для Windows; он ищет
git.exeи определяетGIT_EDITORпеременной окружения с помощьюset. Отрегулируйте это по мере необходимости для других операционных систем.С помощью этого скрипта я могу использовать, чтобы исправить поврежденные источники, стадии исправления, запустить бизнес-процесс исправления в Git ' я попросил:
#!/usr/bin/env python from subprocess import call import sys # Taken from http://stackoverflow.com/questions/377017/test-if-executable-exists-in python def which(program): import os def is_exe(fpath): return os.path.exists(fpath) and os.access(fpath, os.X_OK) fpath, fname = os.path.split(program) if fpath: if is_exe(program): return program else: for path in os.environ["PATH"].split(os.pathsep): exe_file = os.path.join(path, program) if is_exe(exe_file): return exe_file return None if len(sys.argv) != 2: print "Usage: git fixup <commit>" sys.exit(1) git = which("git.exe") if not git: print "git-fixup: failed to locate git executable" sys.exit(2) broken_commit = sys.argv[1] if call([git, "rev-parse", "--verify", "--quiet", broken_commit]) != 0: print "git-fixup: %s is not a valid commit" % broken_commit sys.exit(3) if call([git, "diff", "--staged", "--quiet"]) == 0: print "git-fixup: cannot fixup past commit; no fix staged." sys.exit(4) if call([git, "diff", "--quiet"]) != 0: print "git-fixup: cannot fixup past commit; working directory must be clean." sys.exit(5) call([git, "commit", "--fixup=" + broken_commit]) call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i", "--autosquash", broken_commit + "~1"], shell=True)
что я делаю:
git add ... # Add the fix. git commit # Committed, but in the wrong place. git rebase -i HEAD~5 # Examine the last 5 commits for rebasing.ваш редактор откроется со списком последних 5 коммитов, готовых к вмешательству. Изменение:
pick 08e833c Good change 1. pick 9134ac9 Good change 2. pick 5adda55 Bad change! pick 400bce4 Good change 3. pick 2bc82n1 Fix of bad change.... to:
pick 08e833c Good change 1. pick 9134ac9 Good change 2. pick 5adda55 Bad change! f 2bc82n1 Fix of bad change. # Move up, and change 'pick' to 'f' for 'fixup'. pick 400bce4 Good change 3.сохраните и выйдите из редактора, и исправление будет возвращено в фиксацию, с которой оно принадлежит.
после того как вы сделали это несколько раз, вы сможете сделать это за считанные секунды во сне. Интерактивная перебазировка-это функция, которая действительно продала меня на git. Это невероятно полезно для этого и больше...
немного опоздал на вечеринку, но вот решение, которое работает, как автор представлял себе.
добавить это к вашему .gitconfig:
[alias] fixup = "!sh -c '(git diff-files --quiet || (echo Unstaged changes, please commit or stash with --keep-index; exit 1)) && COMMIT=$(git rev-parse ) && git commit --fixup=$COMMIT && git rebase -i --autosquash $COMMIT~1' -"пример использования:
git add -p git fixup HEAD~5однако если у вас есть неиндексированных изменений, необходимо припрятать их до перебазирования.
git add -p git stash --keep-index git fixup HEAD~5 git stash popвы можете изменить псевдоним, чтобы спрятать автоматически, вместо того, чтобы давать предупреждение. Однако, если исправление не применяется чисто вам нужно будет поп тайник вручную после фиксации рознь. Выполнение как сохранения, так и выскакивания вручную кажется более последовательным и менее запутанным.
обновление: более чистую версию скрипта теперь можно найти здесь:https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup.
Я искал что-то подобное. Этот скрипт Python кажется слишком сложным, хотя, поэтому я сколотил свое собственное решение:
во-первых, мои псевдонимы git выглядят так (заимствовано из здесь):
[alias] fixup = !sh -c 'git commit --fixup=' - squash = !sh -c 'git commit --squash=' - ri = rebase --interactive --autosquashтеперь функция bash становится довольно просто:
function gf { if [ $# -eq 1 ] then if [[ "" == HEAD* ]] then git add -A; git fixup ; git ri ~2 else git add -A; git fixup ; git ri ~1 fi else echo "Usage: gf <commit-ref> " fi }этот код сначала выполняет все текущие изменения (вы можете удалить эту часть, если вы хотите разместить файлы самостоятельно). Затем создает исправление (сквош также может быть использован, если это то, что вам нужно) фиксации. После этого он начинает интерактивную перебазировку с
--autosquashфлаг на родителе фиксации вы даете в качестве аргумента. Это откроет ваш настроенный текстовый редактор, чтобы вы могли убедиться, что все так, как вы ожидаете, и просто закрыть редактор завершит процесс.The
if [[ "" == HEAD* ]]часть (заимствовано из здесь) используется, потому что если вы используете, например, HEAD~2 в качестве ссылки на фиксацию(фиксация, с которой вы хотите исправить текущие изменения), то голова будет смещена после создания фиксации, и вам нужно будет использовать HEAD~3 для ссылки на ту же фиксацию.
чтобы исправить одну фиксацию:
git commit --fixup a0b1c2d3 . git rebase --autosquash -iгде 0b1c2d3-фиксация, которую вы хотите исправить.
Примечание: git rebase --autosquash без -я не работал, но с -я работал, что странно.
вы можете избежать интерактивной стадии с помощью редактора" null":
$ EDITOR=true git rebase --autosquash -i ...Это позволит использовать
/bin/trueкак редактор, а не/usr/bin/vim. Он всегда принимает все, что предлагает git, без подсказки.
что действительно беспокоило меня в рабочем процессе fixup, так это то, что я должен был выяснить сам, какой коммит я хотел раздавить изменение каждый раз. Я создал команду "git fixup", которая помогает в этом.
эта команда создает фиксации fixup, с добавленной магией, которую она использует git-deps чтобы автоматически найти соответствующую фиксацию, поэтому рабочий процесс часто сводится к:
# discover and fix typo in a previously committed change git add -p # stage only typo fix git fixup # at some later point squash all the fixup commits that came up git rebase --autosquash masterэто работает только в том случае, если поэтапные изменения могут быть однозначно отнесены к конкретной фиксации на рабочем дереве (между мастером и головой). Я считаю, что это очень часто бывает для типа небольших изменений, которые я использую для этого, например, опечатки в комментариях или имена вновь введенных (или переименованных) методов. Если это не так, он по крайней мере отобразит список коммитов кандидатов.
Я использую этот много в моем ежедневном рабочем процессе, чтобы быстро интегрировать небольшие изменения в ранее измененные строки в фиксации на моей рабочей ветви. Скрипт не такой как красиво, как это может быть, и это написано в zsh, но он делал эту работу для меня достаточно хорошо на долгое время теперь, когда я никогда не чувствовал необходимости переписывать его:
commit --fixupиrebase --autosquashвелики, но они не делают достаточно. Когда у меня есть последовательность коммитовA-B-Cи я пишу еще несколько изменений в моем рабочем дереве, которые принадлежат одному или нескольким из этих существующих коммитов, я должен вручную посмотреть историю, решить, какие изменения принадлежат к каким коммитам, поставить их и создатьfixup!совершает. Но у git уже есть доступ к достаточной информации, чтобы сделать все это для меня, поэтому я написал Perl script что делает что.для каждого куска в
git diffскрипт используетgit blameчтобы найти фиксацию, которая в последний раз коснулась соответствующих строк, и вызываетgit commit --fixupнаписать соответствующееfixup!совершает, по сути, делая то же самое, что я делал вручную раньше.если вы найдете его полезным, пожалуйста, не стесняйтесь улучшать и повторять его, и, возможно, однажды мы получим такую функцию в
gitправильным. Я хотел бы видеть инструмент, который может понять, как конфликт слияния должен быть разрешен, когда он была введена интерактивная перебазировка.
я написал небольшую функцию оболочки под названием
gcfдля автоматического выполнения фиксации и перебазирования:$ git add -p ... select hunks for the patch with y/n ... $ gcf <earlier_commit_id> That commits the fixup and does the rebase. Done! You can get back to coding.например, вы можете исправить вторую фиксацию до последней с помощью:
gcf HEAD~~здесь функции:
git_commit_immediate_fixup() { local commit_to_amend="" if [ -z "$commit_to_amend" ] then echo "You must provide a commit to fixup!" return fi # We need a static commit ref in case the commit is something relative like HEAD~ commit_to_amend="$(git rev-parse "${commit_to_amend}")" || return echo ">> Committing" git commit --no-verify --fixup "${commit_to_amend}" || return echo ">> Performing rebase" # --autosquash requires -i, but we can avoid interaction with a dummy EDITOR EDITOR=true git rebase --interactive --autosquash --autostash \ --preserve-merges "${commit_to_amend}~" } alias gcf='git_commit_immediate_fixup'Он использует
--autostashStash и поп-любые незафиксированные изменения, если это необходимо.
Я не знаю автоматизированного способа, но вот решение, которое может быть проще для человека-ботиз:
git stash # write the patch git add -p <file> git commit -m"whatever" # message doesn't matter, will be replaced via 'fixup' git rebase -i <bad-commit-id>~1 # now cut&paste the "whatever" line from the bottom to the second line # (i.e. below <bad-commit>) and change its 'pick' into 'fixup' # -> the fix commit will be merged into the <bad-commit> without changing the # commit message git stash pop
вы можете создать исправления для конкретного файла с помощью этого псевдонима.
[alias] ... # fixup for a file, using the commit where it was last modified fixup-file = "!sh -c '\ [ $(git diff --numstat | wc -l) -eq 1 ] && git add && \ [ $(git diff --cached --numstat | wc -l) -eq 1 ] || (echo No changes staged. ; exit 1) && \ COMMIT=$(git log -n 1 --pretty=format:"%H" ) && \ git commit --fixup=$COMMIT && \ git rebase -i --autosquash $COMMIT~1' -"если вы внесли некоторые изменения в
myfile.txtно вы не хотите положить их в новый коммит,git fixup-file myfile.txtсоздатьfixup!для фиксации гдеmyfile.txtбыл изменен в последний раз, и тогда он будетrebase --autosquash.
Comments