자식 부모 포인터를 다른 부모로 설정


220

과거에 한 부모를 가리키는 커밋이 있지만 부모가 가리키는 부모를 변경하고 싶다면 어떻게해야합니까?

답변:


404

사용 git rebase. Git의 일반적인 "커밋을 가져 와서 다른 부모 (베이스)에서 실행"명령입니다.

그러나 알아야 할 사항 :

  1. 커밋 SHA는 부모를 포함하기 때문에 주어진 커밋의 부모를 변경하면 SHA는 모두 의 SHA와 마찬가지로 변경됩니다. 개발 라인에서 커밋 이후에 오는 커밋 .

  2. 다른 사람들과 함께 일하고 있고 문제의 커밋을 끌어온 위치로 이미 푸시 한 경우 커밋을 수정하는 것은 아마도 Bad Idea ™ 일 것입니다. 이것은 # 1 때문입니다. 따라서 SHA가 "동일한"커밋에 대해 더 이상 일치하지 않기 때문에 다른 사용자의 리포지토리에서 발생할 수있는 혼란이 발생합니다. (자세한 내용은 링크 된 매뉴얼 페이지의 "UPSTREAM REBASE 복구"섹션을 참조하십시오.)

즉, 현재 새로운 커밋으로 옮기고 싶은 커밋이있는 지점에 있다면 다음과 같이 보일 것입니다.

git rebase --onto <new-parent> <old-parent>

즉 모든 이동 <old-parent> 위에 앉아 현재의 지점에서 <new-parent>대신.


부모를 변경하기 위해 git rebase --on을 사용하고 있지만 단일 커밋으로 끝나는 것 같습니다. : 나는 거기에 질문을 올려 stackoverflow.com/questions/19181665/...
에두아르도 멜로

7
아하, 그래서 그것이 --onto사용되는 것입니다! 문서는 항상 나에게 가릴있다 하더라도 이 대답을 읽은 후에!
Daniel C. Sobral

6
이 줄 : 지금까지 읽은 다른 모든 질문과 문서 git rebase --onto <new-parent> <old-parent>를 사용 rebase --onto하는 방법에 대한 모든 것을 말해주십시오 .
jpmc26

3
나 에게이 명령은 rebase this commit on that one, 또는 이어야합니다 git rebase --on <new-parent> <commit>. 여기에 오래된 부모를 사용하는 것은 이해가되지 않습니다.
사미르 아귀 아르

1
@kopranb 오래된 부모는 머리에서 원격 머리까지의 경로를 버리고 싶을 때 중요합니다. 설명하는 사례는에 의해 처리됩니다 git rebase <new-parent>.
clacke

35

후속 커밋을 리베이스하지 않아야하는 경우 (예 : 히스토리 다시 쓰기를 유지할 수 없기 때문에) git replace (Git 1.6.5 이상에서 사용 가능)를 사용할 수 있습니다.

# …---o---A---o---o---…
#
# …---o---B---b---b---…
#
# We want to transplant B to be "on top of" A.
# The tree of descendants from B (and A) can be arbitrarily complex.

replace_first_parent() {
    old_parent=$(git rev-parse --verify "${1}^1") || return 1
    new_parent=$(git rev-parse --verify "${2}^0") || return 2
    new_commit=$(
      git cat-file commit "$1" |
      sed -e '1,/^$/s/^parent '"$old_parent"'$/parent '"$new_parent"'/' |
      git hash-object -t commit -w --stdin
    ) || return 3
    git replace "$1" "$new_commit"
}
replace_first_parent B A

# …---o---A---o---o---…
#          \
#           C---b---b---…
#
# C is the replacement for B.

위의 대체가 설정되면 오브젝트 B에 대한 모든 요청은 실제로 오브젝트 C를 리턴합니다. C의 컨텐츠는 첫 번째 상위 (동일한 상위 (첫 번째 제외), 동일한 트리, 같은 커밋 메시지).

대체는 기본적으로 활성화되지만 git--no-replace-objects 옵션 (명령 이름 이전)을 사용하거나 환경 변수 를 설정하여 활성화 할 수 있습니다 . (정상 이외에도)를 눌러 교체를 공유 할 수 있습니다 .GIT_NO_REPLACE_OBJECTSrefs/replace/*refs/heads/*

commit-munging이 마음에 들지 않으면 ( 위의 sed로 완료 ) 상위 명령을 사용하여 대체 커밋을 만들 수 있습니다.

git checkout B~0
git reset --soft A
git commit -C B
git replace B HEAD
git checkout -

큰 차이점은 B가 병합 커밋 인 경우이 시퀀스가 ​​추가 부모를 전파하지 않는다는 것입니다.


그런 다음 어떻게 기존 저장소에 변경 사항을 적용 할 수 있습니까? 그것은 로컬에서 작동하는 것처럼 보이지만 git push는 그 이후로 아무것도 밀지 않습니다.
Baptiste Wicht

2
@BaptisteWicht : 교체품은 별도의 참조 세트에 저장됩니다. refs/replace/참조 계층 을 푸시 (및 페치)하도록 준비해야합니다 . 한 번만 수행 git push yourremote 'refs/replace/*'하면 소스 리포지토리와 git fetch yourremote 'refs/replace/*:refs/replace/*'대상 리포지토리에서 수행 할 수 있습니다 . 여러 번 수행해야하는 경우 대신 해당 참조 스펙을 remote.yourremote.push구성 변수 (소스 리포지토리) 및 remote.yourremote.fetch구성 변수 (대상 리포지토리)에 추가 할 수 있습니다.
Chris Johnsen

3
이를 수행하는 가장 간단한 방법은을 사용하는 것 git replace --grafts입니다. 이것이 언제 추가되었는지는 확실하지 않지만 전체 목적은 커밋 B 와 동일 하지만 지정된 부모를 가진 대체를 작성하는 것입니다 .
Paul Wagland

32

Git에서 커밋을 변경하려면 그 뒤에 오는 모든 커밋을 변경해야합니다. 이 부분의 역사를 발표 한 경우에는 권장하지 않으며 누군가 변경 이전의 역사에 대한 작업을 수행했을 수도 있습니다.

Amber의 답변git rebase언급 된 다른 해결책 은 이식 메커니즘 ( Git 용어집 의 Git 이식 정의 및 Git Repository Layout 문서 의 파일 문서 참조)을 사용하여 커밋의 부모를 변경하고 일부 히스토리 뷰어 ( , 등)을 사용한 다음 맨 페이지의 "예제"섹션에 설명 된대로이를 사용하여 영구적으로 이식 한 다음 이식편을 제거하고 선택적으로 원본 참조를 제거합니다..git/info/graftsgitkgit log --graphgit filter-branchgit filter-branch 하거나 저장소를 복제하십시오.

echo "$ commit-id $ graft-id">> .git / info / grafts
git filter-branch $ graft-id..HEAD

노트 !!! 이 솔루션은 rebase 솔루션다릅니다.git rebase 이식 기반 솔루션은 변경 사항을 rebase / transplant 하는 반면, 이식 기반 솔루션은 기존 부모와 새 부모의 차이점을 고려하지 않고 커밋을 그대로 다시 지정합니다!


3
내가 이해하는 것 ( git.wiki.kernel.org/index.php/GraftPoint )에서 git replacegit 이식편을 대체했습니다 (git 1.6.5 이상이라고 가정).
Alexander Bird

4
@ Thr4wn : 대체로 사실이지만 기록다시 쓰 려면 이식편 + git-filter-branch (또는 대화 형 rebase 또는 빠른 내보내기 + 예 : reposurgeon)가 방법입니다. 역사보존 하고 싶다면 git-replace가 이식편보다 훨씬 뛰어납니다.
Jakub Narębski

JakubNarębski @, 당신은 당신이 무슨 뜻인지 설명 할 수 재 작성보존 역사를? 왜 git-replace+를 사용할 수 git-filter-branch없습니까? 내 테스트 filter-branch에서 전체 트리를 rebasing하여 교체를 존중하는 것처럼 보였다.
cdunn2001

1
@ cdunn2001 : git filter-branch이식 및 대체를 고려하여 기록을 다시 작성합니다 (그러나 기록 된 기록 교체는 무의미합니다 ). 밀어 넣기를 사용하면 fasf-forward가 아닌 변경이 발생합니다. git replace전체 대체 가능 교체를 작성합니다. 푸시는 빨리 진행되지만 refs/replace히스토리를 수정하려면 교체를 전송 하도록 푸시해야합니다 . HTH
Jakub Narębski

23

위의 답변을 명확히하고 내 자신의 스크립트를 뻔뻔스럽게 연결하십시오.

"rebase"또는 "reparent"여부에 따라 다릅니다. REBASE는 에 의해 제안, 황색 , 주위 이동 차이점을 . reparent는 에 의해 제안, 야쿱크리스 , 주위 이동 스냅 샷 전체 트리를. 부모님을 원한다면git reparent 수동으로 작업하는 대신 하는 .

비교

왼쪽에 그림이 있고 오른쪽 그림처럼 보이길 원한다고 가정하십시오.

                   C'
                  /
A---B---C        A---B---C

rebasing과 reparenting은 같은 그림을 만들지 만 그 정의는 C'다릅니다. 로 git rebase --onto A B,에 C'의해 도입 된 변경 사항이 포함되지 않습니다 B. 함께 git reparent -p A, C'동일 할 것이다 C(즉, 제외 B역사상되지 않음).


1
+1 reparent스크립트를 실험했습니다 . 그것은 아름답게 작동합니다. 적극 권장 합니다. 또한 전화를 걸기 전에 (지점 레이블이 이동함에 따라) 새 branch레이블을 만들고 checkout해당 지점으로 이동 하는 것이 좋습니다 .
Rhubbarb

또한 (1) 원래 노드는 그대로 유지하고 (2) 새 노드는 원래 노드의 자식을 상속하지 않습니다.
Rhubbarb

0

확실히 @Jakub의 대답은 몇 시간 전에 OP와 정확히 같은 것을 시도했을 때 도움이되었습니다.
그러나 git replace --graft이제 이식편에 관한 더 쉬운 해결책입니다. 또한 그 솔루션의 주요 문제점은 필터 브랜치가 HEAD의 브랜치로 병합되지 않은 모든 브랜치를 느슨하게 만들었습니다. 그런 다음 git filter-repo완벽하고 완벽하게 작업을 수행했습니다.

$ git replace --graft <commit> <new parent commit>
$ git filter-repo --force

자세한 정보 : 문서의 "재 이식 기록"섹션을 확인하십시오.

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