programing tip

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

itbloger 2020. 8. 12. 07:54
반응형

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


의 위험에git rebase 대해 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. 어떻게 피할 수 있습니까? 그렇게하는 것이 현명할까요?

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

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

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

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


짧은 답변

를 실행했다는 사실을 생략하고 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.

If we run git pull, we see this log:

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

Or, shown another way:

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

And now we have duplicate commits locally. If we were to run git push we would send them up to the server.

To avoid getting to this stage, we could have run git push --force (where we instead ran git pull). This would have sent our commits with the new hashes to the server without issue. To fix the issue at this stage, we can reset back to before we ran git pull:

Look at the reflog (git reflog) to see what the commit hash was before we ran 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

Above we see that ba7688a was the commit we were at before running git pull. With that commit hash in hand we can reset back to that (git reset --hard ba7688a) and then run git push --force.

And we're done.

But wait, I continued to base work off of the duplicated commits

If you somehow didn't notice that the commits were duplicated and proceeded to continue working atop of duplicate commits, you've really made a mess for yourself. The size of the mess is proportional to the number of commits you have atop of the duplicates.

What this looks like:

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 로그

Or, shown another way:

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

In this scenario we want to remove the duplicate commits, but keep the commits that we have based on them—we want to keep C6 through C10. As with most things, there are a number of ways to go about this:

Either:

  • Create a new branch at the last duplicated commit1, cherry-pick each commit (C6 through C10 inclusive) onto that new branch, and treat that new branch as canonical.
  • Run git rebase --interactive $commit, where $commit is the commit prior to both the duplicated commits2. Here we can outright delete the lines for the duplicates.

1 It doesn't matter which of the two you choose, either ba7688a or 2a2e220 work fine.

2 In the example it would be 85f59ab.

TL;DR

Set advice.pushNonFastForward to false:

git config --global advice.pushNonFastForward false

I think you skipped an important detail when describing your steps. More specifically, your last step, git push on dev, would have actually given you an error, as you can not normally push non-fastforward changes.

So you did git pull before the last push, which resulted in a merge commit with C6 and C6' as parents, which is why both will remain listed in log. A prettier log format might have made it more obvious they are merged branches of duplicated commits.

Or you made a git pull --rebase (or without explicit --rebase if it is implied by your config) instead, which pulled the original C5 and C6 back in your local dev (and further re-rebased the following ones to new hashes, C7' C5'' C6'').

One way out of this could have been git push -f to force the push when it gave the error and wipe C5 C6 from origin, but if anyone else also had them pulled before you wiped them, you'd be in for a whole lot more trouble... basically everyone that has C5 C6 would need to do special steps to get rid of them. Which is exactly why they say you should never rebase anything that's already published. It's still doable if said "publishing" is within a small team, though.


I found out that in my case, this issue the consequence of a Git configuration problem. (Involving pull and merge)

Description of the problem:

Sympthoms: Commits duplicated on child branch after rebase, implying numerous merges during and after rebase.

Workflow: Here are steps of the workflow I was performing:

  • Work on the "Features-branch" (child of "Develop-branch")
  • Commit and Push changes on "Features-branch"
  • Checkout "Develop-branch" (Mother branch of Features) and work with it.
  • Commit and push changes on "Develop-branch"
  • Checkout "Features-branch" and pull changes from repository (In case someone else has commited work)
  • Rebase "Features-branch" onto "Develop-branch"
  • Push force of changes on "Feature-branch"

As conséquences of this workflow, duplication of all commits of "Feature-branch" since previous rebase... :-(

The issue was due to the pull of changes of child branch before rebase. Git default pull configuration is "merge". This is changing indexes of commits performed on the child branch.

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

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

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

참고 URL : https://stackoverflow.com/questions/9264314/git-commits-are-duplicated-in-the-same-branch-after-doing-a-rebase

반응형