과거 커밋을 어떻게 쉽게 수정할 수 있습니까?


116

나는 단지 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.

배관 도구 등을 사용하여 커밋을 다시 작성할 수있는 스마트 스크립트일까요?


커밋을 "재정렬"한다는 것은 무엇을 의미합니까? 히스토리를 변경하는 경우 변경된 커밋 이후의 모든 커밋 달라야하지만 연결된 질문에 대한 허용 된 대답은 의미있는 의미에서 커밋을 다시 정렬하지 않습니다.
CB Bailey

1
@Charles : HEAD ~ 5가 깨진 커밋이라는 것을 알게되면 링크 된 질문에서 수락 된 다음 답변은 HEAD (지점의 팁)를 고정 커밋으로 만듭니다. 그러나 HEAD ~ 5를 고정 커밋으로 만들고 싶습니다. 이것은 대화 형 리베이스를 사용하고 수정을 위해 단일 커밋을 편집 할 때 얻을 수있는 것입니다.
Frerich Raabe

예,하지만 rebase 명령은 마스터를 다시 체크 아웃하고 모든 후속 커밋을 고정 커밋으로 리베이스합니다. 이것이 당신이 운전하는 방식이 아닙니까 rebase -i?
CB Bailey

사실, 그 대답에는 잠재적 인 문제가 있습니다 rebase --onto tmp bad-commit master. 작성된대로 고정 된 커밋 상태에 잘못된 커밋을 적용하려고합니다.
CB Bailey

다음은 수정 / 리베이스 프로세스를 자동화하는 또 다른 도구입니다. stackoverflow.com/a/24656286/1058622
Mika Eloranta

답변:


166

업데이트 된 답변

얼마 전에 적합한 로그 메시지로 커밋을 구성하는 데 사용할 수 있는 새 --fixup인수가 추가되었습니다 . 따라서 과거 커밋을 수정하는 가장 간단한 방법은 다음과 같습니다.git commitgit 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

원래 답변

다음 git fixup은 내가 원래 질문에서 원했던 이 논리 를 구현하는 얼마 전에 작성한 Python 스크립트 입니다. 스크립트는 몇 가지 변경 사항을 준비한 다음 해당 변경 사항을 주어진 커밋에 적용한다고 가정합니다.

참고 :이 스크립트는 Windows 전용입니다. 를 사용하여 환경 변수를 찾고 git.exe설정합니다 . 다른 운영 체제에 대해 필요에 따라이를 조정하십시오.GIT_EDITORset

이 스크립트를 사용하여 내가 요청한 '손상된 소스 수정, 단계 수정, 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)

2
당신은 사용하지 수 git stashgit stash pop더 이상 깨끗한 작업 디렉토리가 필요로 REBASE 주위에
토비아스 Kienzler

@TobiasKienzler : 사용 정보 git stashgit stash pop: 불행하게도 당신이 맞아요,하지만 git stash입니다 많은 리눅스 또는 OS / X에보다 느리게 Windows에서. 내 작업 디렉토리는 일반적으로 깨끗하기 때문에 명령 속도를 늦추지 않기 위해이 단계를 생략했습니다.
Frerich Raabe

특히 네트워크 공유 작업을 할 때 확인할 수 있습니다. :-/
Tobias Kienzler 2012-08-08

1
좋은. 실수로했고 git rebase -i --fixup수정 된 커밋에서 시작점으로 기반을 두었 기 때문에 내 경우에는 sha 인수가 필요하지 않았습니다.
fwielstra

1
--autosquash를 자주 사용하는 사람들의 경우 기본 동작으로 설정하는 것이 유용 할 수 있습니다. git config --global rebase.autosquash true
taktak004

31

내가하는 일은 :

git add ... # 수정 사항을 추가합니다.
git commit # 커밋되었지만 잘못된 위치에 있습니다.
git rebase -i HEAD ~ 5 # 리베이스를 위해 마지막 5 개 커밋을 조사합니다.

편집 할 수있는 마지막 5 개의 커밋 목록과 함께 편집기가 열립니다. 변화:

08e833c 좋은 변화를 선택하십시오.
9134ac9 좋은 변화 선택 2.
pick 5adda55 나쁜 변화!
400bce4 좋은 변화 3.
2bc82n1 잘못된 변경 수정.

...에:

08e833c 좋은 변화를 선택하십시오.
9134ac9 좋은 변화 선택 2.
pick 5adda55 나쁜 변화!
f 2bc82n1 잘못된 변경 수정. # 위로 이동하고 'fixup'을 위해 'pick'을 'f'로 변경합니다.
400bce4 좋은 변화 3.

편집기를 저장하고 종료하면 수정 사항이 속한 커밋으로 다시 압축됩니다.

몇 번을 한 후에는 수면 중에 몇 초 만에 할 수 있습니다. 인터랙티브 리베이스는 정말 git에서 저를 팔 았던 기능입니다. 이것과 더 많은 것을 위해 엄청나게 유용합니다 ...


9
분명히 HEAD ~ 5를 HEAD ~ n으로 변경하여 더 뒤로 이동할 수 있습니다. 업스트림으로 푸시 한 기록에 개입하고 싶지 않을 것이므로 일반적으로 'git rebase -i origin / master'를 입력하여 푸시되지 않은 기록 만 변경하도록합니다.
Kris Jenkins

4
이것은 제가 항상했던 것과 매우 유사합니다. FWIW, 편집기에서 단계를 자동으로 재정렬하는 의 --autosquash스위치에 관심이있을 수 git rebase있습니다. git fixup명령 을 구현하기 위해 이것을 이용하는 스크립트에 대한 내 응답을 참조하십시오 .
Frerich Raabe

커밋 해시를 다시 정렬 할 수 있다는 것을 몰랐습니다.
Aaron Franke

그거 좋네! 모든 리베이스 작업이 완료되었는지 확인하기 위해서만 별도의 기능 분기가 있습니다. 그리고 마스터와 같은 공통 지점을 엉망으로 만들지 마십시오.
Jay Modi

22

파티에 조금 늦었지만 여기에 저자가 상상 한대로 작동하는 솔루션이 있습니다.

이것을 .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

그러나 단계 화되지 않은 변경 사항이있는 경우 rebase 전에 숨겨야합니다.

git add -p
git stash --keep-index
git fixup HEAD~5
git stash pop

경고를 표시하는 대신 별칭을 자동으로 숨기도록 수정할 수 있습니다. 그러나 수정이 제대로 적용되지 않으면 충돌을 수정 한 후 수동으로 숨김을 팝업해야합니다. 저장과 팝업을 수동으로 수행하는 것이 더 일관되고 덜 혼란스러워 보입니다.


이것은 꽤 도움이됩니다. 저에게 가장 일반적인 사용 사례는 이전 커밋에 대한 변경 사항을 수정하는 것이므로 git fixup HEAD별칭을 만든 것입니다. 내가 생각하기에 amend를 사용할 수도 있습니다.
메뚜기

감사합니다! 나는 또한 마지막 커밋에서 가장 자주 사용하지만 빠른 수정을 위해 다른 별칭이 있습니다. amend = commit --amend --reuse-message=HEAD그런 다음 커밋 메시지를 입력 git amend하거나 git amend -a편집기를 건너 뛸 수 있습니다 .
dschlyter

3
수정의 문제는 철자법을 기억하지 못한다는 것입니다. 나는 항상 생각해야한다. 그것이 수정인가 아니면 수정인가 그것은 좋지 않다.
메뚜기

12

하나의 커밋을 수정하려면 다음을 수행하십시오.

git commit --fixup a0b1c2d3 .
git rebase --autosquash -i HEAD~2

여기서 a0b1c2d3은 수정하려는 커밋이고 2는 변경하려는 커밋 +1 붙여 넣은 수입니다.

참고 : git rebase --autosquash without -i는 작동하지 않지만 -i 작동하면 이상합니다.


2016 년과 --autosquash없이는 -i여전히 작동하지 않습니다.
Jonathan Cross

1
맨 페이지에 나와 있듯이이 옵션은 --interactive 옵션을 사용하는 경우에만 유효합니다. 그러나 편집기를 건너 뛰는 쉬운 방법이 있습니다.EDITOR=true git rebase --autosquash -i
joeytwiddle 2011

두 번째 단계는 나를 위해 작동하지 않습니다. "리베이스하려는 분기를 지정하십시오."
djangonaut

자식 REBASE --autosquash -i HEAD ~ 2 변경할 것을 붙여 커밋 +1의 수는 2 (.
세르지오

6

업데이트 : 이제 스크립트의 더 깨끗한 버전은 https://github.com/deiwin/git-dotfiles/blob/docs/bin/git-fixup 에서 찾을 수 있습니다 .

나는 비슷한 것을 찾고 있었다. 이 Python 스크립트는 너무 복잡해 보이므로 내 자신의 솔루션을 모았습니다.

첫째, 내 자식 별칭은 다음과 같습니다 ( 여기 에서 빌려 왔습니다 ).

[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
}

이 코드는 먼저 모든 현재 변경 사항을 스테이징합니다 (파일을 직접 스테이징하려는 경우이 부분을 제거 할 수 있음). 그런 다음 수정 (필요한 경우 스쿼시도 사용할 수 있음) 커밋을 만듭니다. 그 후 --autosquash인수로 제공 한 커밋의 부모에 플래그를 사용하여 대화 형 리베이스를 시작합니다 . 그러면 구성된 텍스트 편집기가 열리므로 모든 것이 예상 한대로 작동하는지 확인할 수 있으며 편집기를 닫으면 프로세스가 완료됩니다.

그만큼 if [[ "$1" == HEAD* ]](빌린 부분 여기가 당신이 사용하는 경우, 예를 들어, HEAD ~ 2는 사용자의 확약 때문에)이 픽스 업이 생성 된 커밋 후 참조가 다음 HEAD가 난민됩니다 ((가) 당신이 현재 변경 사항을 수정하려는 커밋), 사용 동일한 커밋을 참조하려면 HEAD ~ 3을 사용해야합니다.


흥미로운 대안. +1
VonC

4

"null"편집기를 사용하여 대화식 단계를 피할 수 있습니다.

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

/bin/true대신 편집기로 사용 됩니다 /usr/bin/vim. 프롬프트없이 항상 git이 제안하는 모든 것을 받아들입니다.


실제로 이것은 2010 년 9 월 30 일의 '원래 답변'Python 스크립트 답변에서 정확히 수행 한 작업입니다 (스크립트 하단에라고 표시되어 있음 call(["set", "GIT_EDITOR=true", "&&", git, "rebase", "-i" ...).
Frerich Raabe

4

수정 작업 과정에서 정말 괴로웠 던 점은 매번 변경 사항을 적용하고 싶은 커밋을 스스로 파악해야한다는 것입니다. 나는 이것을 돕는 "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

이것은 단계적 변경이 작업 트리의 특정 커밋 (마스터와 HEAD 사이)에 명확하게 기여할 수있는 경우에만 작동합니다. 내가 이것을 사용하는 작은 변경의 유형, 예를 들어 주석의 오타 또는 새로 도입 된 (또는 이름이 변경된) 메소드의 이름이 매우 자주 발생합니다. 그렇지 않은 경우 적어도 후보 커밋 목록을 표시합니다.

나는 일상적인 워크 플로에서 이것을 많이 사용 하여 이전에 변경된 줄의 작은 변경 사항을 작업 분기의 커밋에 빠르게 통합합니다. 스크립트는 가능한 한 아름답 지 않고 zsh로 작성되었지만 다시 작성할 필요성을 느끼지 못해 한동안 충분히 잘 해왔습니다.

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


2

당신은 만들 수 있습니다 픽스 업 이 별칭을 사용하여 특정 파일을.

[alias]
...
# fixup for a file, using the commit where it was last modified
fixup-file = "!sh -c '\
        [ $(git diff          --numstat $1 | wc -l) -eq 1 ] && git add $1 && \
        [ $(git diff --cached --numstat $1 | wc -l) -eq 1 ] || (echo No changes staged. ; exit 1) && \
        COMMIT=$(git log -n 1 --pretty=format:"%H" $1) && \
            git commit --fixup=$COMMIT && \
            git rebase -i --autosquash $COMMIT~1' -"

일부 변경 사항을 적용 myfile.txt했지만 새 커밋에 넣지 않으려면 마지막으로 수정 된 커밋에 대한를 git fixup-file myfile.txt생성 한 다음 .fixup!myfile.txtrebase --autosquash


매우 영리 git rebase합니다. 자동으로 호출되지 않는 것이 좋습니다.
hurikhan77

2

commit --fixuprebase --autosquash 중대하다,하지만 충분하지 않습니다. 일련의 커밋이 A-B-C있고 기존 커밋 중 하나 이상에 속하는 작업 트리에 몇 가지 변경 사항을 더 작성하면 수동으로 기록을보고 어떤 변경 사항이 어떤 커밋에 속하는지 결정하고 스테이징하고 생성해야합니다. fixup!커밋. 하지만 git은 이미 저를 위해 모든 것을 할 수있는 충분한 정보에 접근 할 수 있습니다. 그래서 저는 Perl 스크립트 를 작성했습니다 .

각 덩어리에 대해 git diff스크립트의 대해 git blame관련 줄을 마지막으로 건드린 커밋을 찾고 git commit --fixup적절한 fixup!커밋 을 작성하도록 호출 하여 기본적으로 이전에 수동으로했던 것과 동일한 작업을 수행합니다.

유용하다고 생각되면 자유롭게 개선하고 반복하십시오. 언젠가는 그러한 기능이 git제대로 제공 될 것입니다. 인터랙티브 리베이스에 의해 도입되었을 때 병합 충돌이 어떻게 해결되어야하는지 이해할 수있는 도구를보고 싶습니다.


또한 자동화에 대한 꿈도있었습니다. git은 패치를 깨뜨리지 않고 가능한 한 역사 속으로 되돌려 놓아야합니다. 그러나 당신의 방법은 아마도 더 정상적 일 것입니다. 시도해 보니 반갑습니다. 나는 그것을 시도 할 것이다! (물론 수정 패치가 파일의 다른 위치에 나타나고 개발자 만 그것이 속한 커밋을 알고있는 경우가 있습니다. 또는 테스트 스위트의 새로운 테스트가 컴퓨터가 수정 사항이 어디로 가야하는지 알아내는 데 도움이 될 수 있습니다.)
joeytwiddle 2016

1

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 1
  fi

  # Get a static commit ref in case the commit is something relative like HEAD~
  commit_to_amend="$(git rev-parse "${commit_to_amend}")" || return 2

  #echo ">> Committing"
  git commit --no-verify --fixup "${commit_to_amend}" || return 3

  #echo ">> Performing rebase"
  EDITOR=true git rebase --interactive --autosquash --autostash \
                --rebase-merges --no-fork-point "${commit_to_amend}~"
}

alias gcf='git_commit_immediate_fixup'

그것은 사용 --autostash숨기고 필요한 경우 커밋되지 않은 변경 내용을 팝업.

--autosquash--interactiverebase 가 필요 하지만 dummy를 사용하여 상호 작용을 피합니다 EDITOR.

--no-fork-point드문 상황 에서 커밋이 자동으로 삭제되지 않도록 보호 합니다 (새 브랜치에서 분기하고 누군가 이미 과거 커밋을 리베이스 한 경우).


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

git fixup명령 을 구현하기 위해 이것을 이용하는 스크립트에 대한 내 응답을 참조하십시오 .
Frerich Raabe

@Frerich 라베 : 나는에 대해 알고 dind't, 좋은 소리--autosquash
토비아스 Kienzler

0

https://github.com/tummychow/git-absorb 추천합니다 .

엘리베이터 피치

커밋이 몇 개있는 기능 브랜치가 있습니다. 팀원이 브랜치를 검토하고 몇 가지 버그를 지적했습니다. 버그에 대한 수정 사항이 있지만 원자 적 커밋을 믿기 때문에 수정 사항이라고하는 불투명 한 커밋으로 모두 밀어 넣고 싶지는 않습니다. 에 대한 커밋 SHA를 수동으로 찾 git commit --fixup거나 수동 대화 형 리베이스를 실행하는 대신 다음을 수행하십시오.

  • git add $FILES_YOU_FIXED

  • git absorb --and-rebase

  • 또는: git rebase -i --autosquash master

git absorb수정하기에 안전한 커밋과 각 커밋에 속하는 인덱싱 된 변경 사항을 자동으로 식별합니다. 그런 다음 수정을 작성합니다! 각 변경 사항에 대해 커밋합니다. 신뢰할 수없는 경우 수동으로 출력을 확인한 다음 git의 기본 제공 자동 스쿼시 기능을 사용하여 수정 사항을 기능 분기로 접을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.