Как я могу легко исправить прошлый коммит?

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

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

88 голосов | спросил Frerich Raabe 23 J0000006Europe/Moscow 2010, 20:32:30

10 ответов


0

ОБНОВЛЕННЫЙ ОТВЕТ

Некоторое время назад в --fixup, который можно использовать для создания коммита с сообщением журнала, подходящим для 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 fixup':

#!/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)
ответил Frerich Raabe 30 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowThu, 30 Sep 2010 12:10:50 +0400 2010, 12:10:50
0

Что я делаю:

git add ... # Добавить исправление.
git commit # Совершено, но не в том месте.
git rebase -i HEAD ~ 5 # Изучите последние 5 коммитов для перебазирования.

Ваш редактор откроется со списком последних 5 коммитов, готовых для вмешательства. Изменение:

выбор 08e833c Хорошее изменение 1.
выберите 9134ac9 Хорошее изменение 2.
выбрать 5adda55 Плохое изменение!
выбрать 400bce4 Хорошее изменение 3.
pick 2bc82n1 Исправление плохих изменений.

... в:

выбор 08e833c Хорошее изменение 1.
выберите 9134ac9 Хорошее изменение 2.
выбрать 5adda55 Плохое изменение!
 f 2bc82n1 Исправление плохих изменений.  # Поднимитесь и измените «pick» на «f» для «fixup».
выбрать 400bce4 Хорошее изменение 3.

Сохранить & выйдите из редактора, и исправление вернется в коммит, к которому он относится.

После того, как вы сделали это несколько раз, вы сделаете это за секунды во сне. Интерактивная перебазировка - это функция, которая действительно продала меня на Git. Это невероятно полезно для этого и не только ...

ответил Kris Jenkins 29 thEurope/Moscowp30Europe/Moscow09bEurope/MoscowWed, 29 Sep 2010 23:56:00 +0400 2010, 23:56:00
0

Немного опоздал на вечеринку, но вот решение, которое работает так, как задумал автор.

Добавьте это в свой .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 $1) && 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

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

ответил dschlyter 16 Jam1000000amThu, 16 Jan 2014 01:50:29 +040014 2014, 01:50:29
0

ОБНОВЛЕНИЕ . Более чистую версию скрипта теперь можно найти здесь: https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup .

Я искал что-то подобное. Этот скрипт на Python кажется слишком сложным, поэтому я собрал собственное решение:

Сначала мои псевдонимы git выглядят так (заимствовано у здесь ):

[alias]
  fixup = !sh -c 'git commit --fixup=$1' -
  squash = !sh -c 'git commit --squash=$1' -
  ri = rebase --interactive --autosquash

Теперь функция bash становится довольно простой:

function gf {
  if [ $# -eq 1 ]
  then
    if [[ "$1" == HEAD* ]]
    then
      git add -A; git fixup $1; git ri $1~2
    else
      git add -A; git fixup $1; git ri $1~1
    fi
  else
    echo "Usage: gf <commit-ref> "
  fi
}

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

Часть if [[ "$1" == HEAD* ]] (заимствована из здесь ), потому что если вы используете, например, HEAD ~ 2 в качестве ссылки (фиксацию, с которой вы хотите исправить текущие изменения), то HEAD будет смещен после создания фиксации фиксации и вам нужно будет использовать HEAD ~ 3 для ссылки на тот же коммит.

ответил Deiwin 2 SunEurope/Moscow2012-12-02T21:34:33+04:00Europe/Moscow12bEurope/MoscowSun, 02 Dec 2012 21:34:33 +0400 2012, 21:34:33
0

Чтобы исправить один коммит:

git commit --fixup a0b1c2d3 .
git rebase --autosquash -i

где 0b1c2d3 - коммит, который вы хотите исправить.

Примечание: git rebase --autosquash без -i не работает, но с -i работает, что странно.

ответил Sérgio 24 22015vEurope/Moscow11bEurope/MoscowTue, 24 Nov 2015 07:07:21 +0300 2015, 07:07:21
0

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

$ EDITOR=true git rebase --autosquash -i ...

Это будет использовать /bin/true в качестве редактора вместо /usr/bin/vim. Он всегда принимает все, что предлагает git, без запроса.

ответил joeytwiddle 7 12016vEurope/Moscow11bEurope/MoscowMon, 07 Nov 2016 13:52:03 +0300 2016, 13:52:03
0

Что меня по-настоящему беспокоило в процессе исправления, так это то, что мне нужно было самому определить, в какой коммит я хочу вносить изменения каждый раз. Я создал команду "git 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

Это работает только в том случае, если поэтапные изменения можно однозначно отнести к конкретному коммиту в рабочем дереве (между master и HEAD). Я нахожу, что это очень часто встречается для типа небольших изменений, для которых я использую это, например, опечатки в комментариях или названиях вновь введенных (или переименованных) методов. Если это не так, он по крайней мере отобразит список возможных коммитов.

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

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

ответил Valodim 20 J000000Thursday17 2017, 11:27:10
0

commit --fixup и rebase --autosquash великолепны , но они не делают достаточно. Когда у меня есть последовательность коммитов A-B-C, и я записываю еще несколько изменений в мое рабочее дерево, которые принадлежат одному или нескольким из этих существующих коммитов, Я должен вручную просмотреть историю, решить, какие изменения принадлежат тем или иным коммитам, подготовить их и создать коммиты fixup!. Но у git уже есть доступ к достаточному количеству информации, чтобы сделать все это для меня, поэтому я написал Perl скрипт , который так и делает.

Для каждого блока в git diff скрипт использует git blame, чтобы найти коммит, который последним коснулся соответствующих строк, и вызывает git commit --fixup, чтобы написать соответствующий fixup! фиксирует, по сути, делая то же самое, что я делал ранее.

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

ответил Oktalist 30 MarpmWed, 30 Mar 2016 20:48:22 +03002016-03-30T20:48:22+03:0008 2016, 20:48:22
0

Я написал небольшую функцию оболочки под названием 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~~

Здесь находится . Вы можете вставить его в свой ~/.bashrc

 git_commit_immediate_fixup() {
  local commit_to_amend="$1"
  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 для хранения и извлечения любых незафиксированных изменений, если это необходимо.

ответил joeytwiddle 7 12016vEurope/Moscow11bEurope/MoscowMon, 07 Nov 2016 13:55:00 +0300 2016, 13:55:00
0

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

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
ответил Tobias Kienzler 11 PM00000010000004531 2010, 13:02:45

Похожие вопросы

Популярные теги

security × 330linux × 316macos × 2827 × 268performance × 244command-line × 241sql-server × 235joomla-3.x × 222java × 189c++ × 186windows × 180cisco × 168bash × 158c# × 142gmail × 139arduino-uno × 139javascript × 134ssh × 133seo × 132mysql × 132