리베이스를 수행 한 후 Git 커밋이 동일한 분기에 복제됩니다.


131

나는 Rebasing의 위험에 대해 Pro Git에 제시된 시나리오를 이해합니다 . 저자는 기본적으로 중복 된 커밋을 피하는 방법을 알려줍니다.

공용 저장소에 푸시 한 커밋을 리베이스하지 마십시오.

Pro Git 시나리오에 정확히 맞지 않는다고 생각하고 여전히 중복 된 커밋으로 끝나기 때문에 내 특정 상황을 알려 드리겠습니다.

로컬에 해당하는 두 개의 원격 지점이 있다고 가정 해 보겠습니다.

origin/master    origin/dev
|                |
master           dev

4 개의 브랜치 모두 동일한 커밋을 포함하고 있으며 dev다음 에서 개발을 시작하겠습니다 .

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4
dev           : C1 C2 C3 C4

몇 번의 커밋 후 변경 사항을 origin/dev다음으로 푸시합니다 .

origin/master : C1 C2 C3 C4
master        : C1 C2 C3 C4

origin/dev    : C1 C2 C3 C4 C5 C6  # (2) git push
dev           : C1 C2 C3 C4 C5 C6  # (1) git checkout dev, git commit

master빠른 수정을 위해 돌아 가야합니다 .

origin/master : C1 C2 C3 C4 C7  # (2) git push
master        : C1 C2 C3 C4 C7  # (1) git checkout master, git commit

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C5 C6

그리고 다시 dev실제 개발에 빠른 수정 사항을 포함하도록 변경 사항을 다시 작성합니다 .

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6
dev           : C1 C2 C3 C4 C7 C5' C6'  # git checkout dev, git rebase master

내가 GitX와 커밋의 역사를 표시하는 경우 / I는 것을 알 수 gitk이 origin/dev이제 두 개의 동일한 커밋을 포함 C5'하고 C6'힘내에 차이가있다. 이제 변경 사항을 적용 origin/dev하면 결과가됩니다.

origin/master : C1 C2 C3 C4 C7
master        : C1 C2 C3 C4 C7

origin/dev    : C1 C2 C3 C4 C5 C6 C7 C5' C6'  # git push
dev           : C1 C2 C3 C4 C7 C5' C6'

Pro Git의 설명을 완전히 이해하지 못할 수도 있으므로 두 가지를 알고 싶습니다.

  1. Git이 리베이스하는 동안 이러한 커밋을 복제하는 이유는 무엇입니까? 적용 다만 그것을 대신 할 특별한 이유가 있나요 C5C6이후는 C7?
  2. 어떻게 피할 수 있습니까? 그렇게하는 것이 현명할까요?

답변:


86

여기서 rebase를 사용해서는 안되며 간단한 병합으로 충분합니다. 링크 한 Pro Git 책은 기본적으로이 정확한 상황을 설명합니다. 내부 작동 방식은 약간 다를 수 있지만 시각화하는 방법은 다음과 같습니다.

  • C5그리고 C6일시적으로 철수하는dev
  • C7 적용됩니다 dev
  • C5그리고 C6뒷면 상단에 재생 C7새로운 차이점 때문에 새 커밋을 만들어,

그래서, 당신의 dev지점, C5그리고 C6효과적으로 더 이상 존재하지 않는 : 그들은 지금 C5'하고 C6'. 당신이 누르면 origin/dev, 자식은보고 C5'C6'새로운 커밋과 역사의 마지막에 압정을있다. 사실, 당신은 차이를 보면 C5C5'origin/dev (가) 다른 커밋의 해시를 만드는 - 당신은 내용이 동일하지만, 줄 번호는 아마 다른 것을 알 수 있습니다.

Pro Git 규칙을 다시 설명하겠습니다 . 로컬 저장소가 아닌 곳에 존재했던 커밋을 절대 리베이스하지 마십시오 . 대신 병합을 사용하십시오.


동일한 문제가 있습니다. 지금 원격 지점 기록을 수정하려면 어떻게해야합니까? 지점을 삭제하고 체리 피킹으로 다시 만드는 것 외에 다른 옵션이 있습니까 ??
Wazery

1
@xdsy : Jave 이것이것 좀 봐 .
Justin ᚅᚔᚈᚄᚒᚔ

2
"C5 및 C6이 일시적으로 dev에서 제거됩니다 ... C7이 dev에 적용됩니다"라고 말합니다. 그렇다면 왜 C5와 C6이 origin / dev의 커밋 순서에서 C7 앞에 표시됩니까?
KJ50 2014

@ KJ50 : C5와 C6이 이미 origin/dev. 되면 dev으로 업데이트되어, 그 기록을 수정 (C5 / C6 일시적으로 제거되고 이후에 다시 적용 C7). 푸시 된 리포지토리의 기록을 수정하는 것은 일반적으로 수행중인 작업을 알지 않는 한 Really Bad Idea ™입니다. 이 간단한 경우, 문제에서 힘 푸시를 수행하여 해결할 수 devorigin/dev의 해제 작업을 다른 사람을 REBASE 후 통지 origin/dev그들은 아마도에 대한 나쁜 일이하기에 걸. 다시 한 번 더 나은 대답은 "그렇게하지 마십시오 ... 대신 병합을 사용하십시오"입니다.
Justin ᚅᚔᚈᚄᚒᚔ

3
한 가지주의 할 점 : C5와 C5 '의 해시는 확실히 다르지만 줄 번호가 다르기 때문이 아니라 다음 두 가지 사실에 대해 어느 하나라도 차이에 충분합니다. 1) 우리가 말하는 해시 델타 차이의 해시가 아니라 커밋 후 전체 소스 트리의 해시이므로 C5 '에는 C7에서 오는 모든 것이 포함되지만 C5에는 포함되지 않습니다. 2) C5'의 부모는 C5와 다릅니다.이 정보는 해시 결과에 영향을 미치는 커밋 트리의 루트 노드에도 포함됩니다.
Ozgur Murat 2017

113

짧은 답변

를 실행했다는 사실을 생략하고 git push다음 오류가 발생한 다음 계속 실행했습니다 git pull.

To git@bitbucket.org:username/test1.git
 ! [rejected]        dev -> dev (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

Git이 도움이 되려고하지만 'git pull'충고는 당신이하고 싶은 일이 아닐 가능성이 큽니다. .

당신이있는 경우:

  • "기능 브랜치"또는 "개발자 브랜치" 단독 으로 작업 한 다음 실행 git push --force하여 리베이스 이후 커밋으로 원격 업데이트를 실행할 수 있습니다 ( user4405677의 답변에 따라 ).
  • 동시에 여러 개발자와 브랜치에서 작업하는 경우 처음에는 사용하지 않아야 할 것입니다git rebase . 의 dev변경 사항으로 업데이트하려면을 실행 하는 master대신 git rebase master dev실행 git merge master하는 동안 실행해야합니다 dev( Justin의 답변에 따라 ).

약간 더 긴 설명

Git의 각 커밋 해시는 여러 요소를 기반으로하며, 그중 하나는 그 전에 오는 커밋의 해시입니다.

커밋을 재정렬하면 커밋 해시가 변경됩니다. 리베이스 (무언가를 할 때)는 커밋 해시를 변경합니다. 그것으로, 실행의 결과 git rebase master dev, dev와 동기화되어 master, 만듭니다 에 그와 같은 내용 (따라서 및 해시) 커밋 dev만에 커밋과 master그들 앞에 삽입합니다.

여러 가지 방법으로 이와 같은 상황에 처할 수 있습니다. 내가 생각할 수있는 두 가지 방법 :

  • 당신은 당신의 master기반을 원하는 커밋을 가질 수 있습니다dev작업의 .
  • dev이미 원격으로 푸시 된 커밋이있을 수 있으며 , 변경을 진행할 수 있습니다 (커밋 메시지 재정렬, 커밋 재정렬, 스쿼시 커밋 등).

무슨 일이 일어 났는지 더 잘 이해하겠습니다. 여기에 예가 있습니다.

저장소가 있습니다.

2a2e220 (HEAD, master) C5
ab1bda4 C4
3cb46a9 C3
85f59ab C2
4516164 C1
0e783a3 C0

저장소의 초기 선형 커밋 세트

그런 다음 커밋 변경을 진행합니다.

git rebase --interactive HEAD~3 # Three commits before where HEAD is pointing

(여기에서 내 말을 받아 들여야합니다. Git에서 커밋을 변경하는 방법에는 여러 가지가 있습니다.이 예제에서는의 시간을 변경 C3했지만 새 커밋을 삽입하고 커밋 메시지를 변경하고 커밋을 재정렬하고, 함께 커밋 스쿼시 등)

ba7688a (HEAD, master) C5
44085d5 C4
961390d C3
85f59ab C2
4516164 C1
0e783a3 C0

새 해시로 동일한 커밋

이것은 커밋 해시가 다르다는 것을 알아 차리는 것이 중요합니다. 이것은 당신이 그들에 대해 (무엇이든) 변경했기 때문에 예상되는 동작입니다. 괜찮지 만 :

마스터가 리모컨과 동기화되지 않았 음을 보여주는 그래프 로그

푸시를 시도하면 오류가 표시되고 실행해야한다는 힌트가 표시됩니다 git pull.

$ git push origin master
To git@bitbucket.org:username/test1.git
 ! [rejected]        master -> master (non-fast-forward)
error: failed to push some refs to 'git@bitbucket.org:username/test1.git'
hint: Updates were rejected because the tip of your current branch is behind
hint: its remote counterpart. Integrate the remote changes (e.g.
hint: 'git pull ...') before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.

를 실행하면 git pull다음 로그가 표시됩니다.

7df65f2 (HEAD, master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 (origin/master) C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

또는 다른 방법으로 표시 :

병합 커밋을 보여주는 그래프 로그

이제 로컬에 중복 커밋이 있습니다. 우리가 실행한다면 우리는 git push그것들을 서버로 보낼 것입니다.

이 단계에 도달하는 것을 피하기 위해 우리는 git push --force(대신에 우리 가 달리는 곳) 달릴 수 있습니다 git pull. 이것은 새로운 해시가있는 커밋을 문제없이 서버로 보냈을 것입니다. 이 단계에서 문제를 해결하기 위해 실행하기 전으로 재설정 할 수 있습니다 git pull.

우리가 실행 하기 전에git reflog 커밋 해시가 무엇인지 보려면 reflog ( )를보십시오 .git pull

070e71d HEAD@{1}: pull: Merge made by the 'recursive' strategy.
ba7688a HEAD@{2}: rebase -i (finish): returning to refs/heads/master
ba7688a HEAD@{3}: rebase -i (pick): C5
44085d5 HEAD@{4}: rebase -i (pick): C4
961390d HEAD@{5}: commit (amend): C3
3cb46a9 HEAD@{6}: cherry-pick: fast-forward
85f59ab HEAD@{7}: rebase -i (start): checkout HEAD~~~
2a2e220 HEAD@{8}: rebase -i (finish): returning to refs/heads/master
2a2e220 HEAD@{9}: rebase -i (start): checkout refs/remotes/origin/master
2a2e220 HEAD@{10}: commit: C5
ab1bda4 HEAD@{11}: commit: C4
3cb46a9 HEAD@{12}: commit: C3
85f59ab HEAD@{13}: commit: C2
4516164 HEAD@{14}: commit: C1
0e783a3 HEAD@{15}: commit (initial): C0

위에서 우리가 ba7688a실행하기 전의 커밋 임을 알 수 git pull있습니다. 해당 커밋 해시를 사용하여 해당 ( git reset --hard ba7688a)로 재설정 한 다음 실행할 수 있습니다.git push --force 있습니다.

그리고 우리는 끝났습니다.

하지만 잠깐, 중복 된 커밋을 기반으로 계속 작업했습니다.

어떻게 든 커밋이 중복 된 것을 알아 차리지 못하고 중복 커밋 위에서 계속 작업을 진행했다면 정말 엉망이 된 것입니다. 엉망의 크기는 중복 위에있는 커밋 수에 비례합니다.

이것이 어떻게 생겼는지 :

3b959b4 (HEAD, master) C10
8f84379 C9
0110e93 C8
6c4a525 C7
630e7b4 C6
070e71d (origin/master) Merge branch 'master' of bitbucket.org:username/test1
ba7688a C5
44085d5 C4
961390d C3
2a2e220 C5
85f59ab C2
ab1bda4 C4
4516164 C1
3cb46a9 C3
0e783a3 C0

중복 된 커밋 위에 선형 커밋을 보여주는 Git 로그

또는 다른 방법으로 표시 :

중복 된 커밋에 대한 선형 커밋을 보여주는 로그 그래프

이 시나리오에서는 중복 커밋을 제거하고이를 기반으로 한 커밋을 유지하려고합니다. C6에서 C10까지 유지하려고합니다. 대부분의 경우와 마찬가지로 다음과 같은 여러 가지 방법이 있습니다.

어느 한 쪽:

  • 커밋 중복 마지막에 새로운 지점을 만듭니다 1 ,cherry-pick 각각 그 새로운 지점에 (C6 C10의 포함을 통해) 커밋과 정식으로 치료하는 새로운 지점.
  • Run git rebase --interactive $commit, 여기서는 복제 된 커밋 2 이전$commit 의 커밋 입니다. 여기에서 중복 된 줄을 완전히 삭제할 수 있습니다.

1 그것은 하나, 당신은 선택이 어떤 중요하지 않습니다 ba7688a또는 2a2e220잘 작동.

2 예에서는 85f59ab.

TL; DR

설정 advice.pushNonFastForward합니다 false:

git config --global advice.pushNonFastForward false

1
줄임표가 "--rebase"옵션 (일명 "-r")을 숨긴다는 사실을 깨닫는 한 "git pull ..."조언을 따르는 것이 좋습니다. ;-)
G. 실비 데이비스

4
내가 사용하는 것이 좋습니다 git push이야 ' --force-with-lease가 더 나은 기본의로 요즘
Whymarrh

4
이 답변 또는 타임머신입니다. 감사!
ZeMoon

매우 깔끔한 설명 ... 반복적으로 리베이스를 시도한 후 내 코드를 5-6 번 복제 한 유사한 문제를 발견했습니다 ... 코드가 마스터와 최신 상태인지 확인하기 위해 ...하지만 매번 푸시 할 때마다 내 브랜치에 대한 새로운 커밋, 내 코드도 복제합니다. 내 브랜치에서 작업하는 유일한 개발자 인 경우 강제 푸시 (리스 옵션 포함)가 안전한지 알려주시겠습니까? 아니면 마스터를 내 대신 리베이스하는 것이 더 나은 방법입니까?
Dhruv Singhal

12

단계를 설명 할 때 중요한 세부 사항을 건너 뛴 것 같습니다. 좀 더 구체적으로 말하면, git pushdev 의 마지막 단계 는 일반적으로 빠르게 진행되지 않는 변경 사항을 푸시 할 수 없기 때문에 실제로 오류를 발생 시켰을 것입니다.

그래서 당신은 git pull 마지막 푸시 전에 수행 한 결과 C6 및 C6 '을 부모로 병합 커밋이 발생했기 때문에 둘 다 로그에 계속 표시됩니다. 더 예쁜 로그 형식으로 인해 중복 된 커밋의 병합 된 분기가 더 분명해 졌을 수 있습니다.

또는 대신 git pull --rebase(또는 --rebase구성에 의해 암시되는 경우 명시 적으로 명시하지 않음 ) 로컬 개발자에서 원래 C5 및 C6을 다시 가져 왔습니다 (그리고 다음 항목을 새 해시 C7 'C5' 'C6'로 다시 다시 작성). ').

이것의 한 가지 방법 git push -f은 오류가 발생했을 때 강제로 밀고 C5 C6를 원점에서 지우는 것이 될 수 있지만, 다른 사람이 그것을 지우기 전에 뽑았다면 훨씬 더 많은 문제가 발생할 것입니다 .. . 기본적으로 C5 C6을 가진 모든 사람들은 그것들을 제거하기 위해 특별한 조치를 취해야합니다. 그렇기 때문에 이미 게시 된 항목을 리베이스해서는 안된다고 말합니다. 그래도 "게시"가 소규모 팀 내에서 이루어지면 가능합니다.


1
생략 git pull은 매우 중요합니다. 에 대한 귀하의 추천은 git push -f위험하지만 독자들이 찾고있는 것일 수 있습니다.
Whymarrh

과연. 내가 실제로했던 질문을 작성했을 때 git push --forceGit이 무엇을 할 것인지보기 위해서였습니다. 그 이후로 Git에 대해 많은 것을 배웠고 요즘 rebase은 일반적인 작업 흐름의 일부입니다. 그러나 git push --force-with-lease다른 사람의 작업을 덮어 쓰지 않도록합니다.
elitalon

사용하기 --force-with-lease좋은 기본이되고, 나뿐만 아니라 내 대답 아래 덧글을 남길 수 있습니다
Whymarrh

2

제 경우에는이 문제가 Git 구성 문제의 결과라는 것을 알았습니다. (풀 앤 머지 포함)

문제에 대한 설명 :

Sympthoms : 리베이스 후 자식 브랜치에 복제를 커밋합니다. 이는 리베이스 도중과 후에 수많은 병합을 의미합니다.

워크 플로 : 수행하고 있던 워크 플로의 단계는 다음과 같습니다.

  • "Features-branch"작업 ( "Develop-branch"의 하위)
  • "기능-분기"에 대한 변경 사항 커밋 및 푸시
  • "Develop-branch"(기능의 마더 브랜치)를 확인하고 함께 작업하십시오.
  • "Develop-branch"에서 변경 사항 커밋 및 푸시
  • "Features-branch"를 체크 아웃하고 저장소에서 변경 사항을 가져옵니다 (다른 사람이 작업을 수행 한 경우).
  • "기능-분기"를 "개발-분기"로 리베이스
  • "기능-브랜치"에 대한 변경의 힘

이 워크 플로우의 결과로, 이전 rebase 이후 "Feature-branch"의 모든 커밋 복제 ... :-(

이 문제는 리베이스 이전에 자식 브랜치의 변경 사항을 끌어 오기 때문이었습니다. Git 기본 풀 구성은 "병합"입니다. 이것은 자식 브랜치에서 수행되는 커밋의 인덱스를 변경합니다.

해결책 : Git 구성 파일에서 리베이스 모드에서 작동하도록 pull을 구성합니다.

...
[pull]
    rebase = preserve
...

JN Grx에 도움이되기를 바랍니다.


1

현재와 ​​다른 원격 지점에서 가져 왔을 수 있습니다. 예를 들어 지점이 추적 개발을 개발할 때 마스터에서 가져 왔을 수 있습니다. Git은 추적되지 않은 브랜치에서 가져온 경우 중복 커밋을 충실하게 가져옵니다.

이 경우 다음을 수행 할 수 있습니다.

git reset --hard HEAD~n

어디 n == <number of duplicate commits that shouldn't be there.>

그런 다음 올바른 분기에서 당기고 있는지 확인한 다음 실행하십시오.

git pull upstream <correct remote branch> --rebase

풀을 --rebase사용하면 커밋 기록을 뒤죽박죽으로 만들 수있는 불필요한 커밋을 추가하지 않습니다.

다음은 git rebase에 대한 약간의 손 잡고 있습니다.

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