Как я могу легко исправить прошлую фиксацию?



Я только что прочитал изменение одного файла в прошлом фиксации в 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.


может быть, умный скрипт, который может переписать коммиты с помощью сантехники инструменты или что?

513   11  
git

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, но он делал эту работу для меня достаточно хорошо на долгое время теперь, когда я никогда не чувствовал необходимости переписывать его:

https://github.com/Valodim/git-fixup

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'

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

Я не знаю автоматизированного способа, но вот решение, которое может быть проще для человека-ботиз:

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

    Ничего не найдено.