git分支高级用法,从上游分支变基

p5cysglq  于 2023-01-01  发布在  Git
关注(0)|答案(2)|浏览(212)

跟进Git: rebase onto development branch from upstream,基本上与以下问题相同:
我有本地分支masterdevelop。我在develop上做了所有的工作,然后将它们合并到master中发布。有一个远程分支upstream/master,它有我想要的修改,但我想在develop中重定更改的基(它共享一个共同的祖先),并将它们放回develop中。
这是我面临的(有点复杂/高级)情况,从头开始。
1.我分叉了一个上游,并在我自己的新分支(dev/br-2)中它的开发分支 dev/br-1)和git push对我自己的repo进行了自己的更改。
1.如今,上游在其masterdevelop两个分支上都取得了进步。
1.向上游推进其develop分支的方式是通过rebase,即,在rebase * 之后,它自己的所有更改都被 * 放回
顶部

1.我的本地repo和我的旧机器一起消失了,我需要从我自己的repo中提取/git-clone来继续我的定制。
1.我想在所有上游变更的基础上对dev/br-2分支(共享一个共同的祖先)中的变更进行重定基。
1.我已经完成了git fetch upstream,并且能够用上游的master重新定基。
1.这是如何从上游重定我当前的dev/br-1,然后从dev/br-1分支重定dev/br-2,使我的头不停地旋转。
虽然这看起来像是一个非常具体的案例,但是如何进行git分支和重定基的原则仍然适用,并且这个答案对普通大众来说是非常有教育意义的。

  • 更新:* 所以我看了@torek建议的git rebase --onto命令,比如How to git rebase a branch with the onto command?,以及他们所有的refed文档,我认为我的问题仍然比我读到的要高出一两个层次(因为涉及到两个repos和5个分支)。

1号点的情况:

A---B---C---D  master (upstream)
    \
     E---F---G  dev/br-1 (upstream)
             \
              H---I---J dev/br-2 (myown)

第2点和第3点的情况:

A---B---C---D  master (upstream)
            \
              E'---F'---G'  dev/br-1 (upstream)

我甚至不知道应该在哪里画myown分支,下面是我目前的情况(可能已经被搞砸了,因为我看到HEAD可能在一个奇怪的地方):

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (HEAD -> dev/br-1, origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

最终的目标是把我自己的
H---I---J dev/br-2
在我自己的回购协议中,在赶上上游之后,在新重定基的G'之上的分支。即,在我自己的回购协议中,最后,它应该看起来像这样:

A---B---C---D  rebased master (upstream)
            \
             E'---F'---G'  rebased dev/br-1 (upstream)
                       \
                        H'---I'---J' rebased dev/br-2

如何做到这一点?
按命令进行更多解释:

cd /tmp
 mkdir upstream
 cd upstream
 # prepare its `master` and `dev/br-1` branches

 cd /tmp
 git clone upstream myfork
 # prepare my own changes based on the `dev/br-1` branch into `dev/br-2`

 cd /tmp/upstream
 # advance its `master` 
 . . .
 # and its`dev/br-1` branches
 git checkout dev/br-1
 git rebase -X theirs master dev/br-1
 . . .

现在上游已经进步了,无论是在它的master还是develop分支(通过rebase),我需要从我自己的回购中挑选,以 * 继续 * 我的定制。

cd /tmp
 mv myfork myfork0
 git clone myfork0 myfork1
 cd myfork1
 git remote -v
 git remote add upstream /tmp/upstream
 git remote -v
 git fetch upstream
 git rebase upstream/master

 git checkout --track origin/dev/br-1

$ git remote -v
origin  /tmp/myfork0 (fetch)
origin  /tmp/myfork0 (push)
upstream        /tmp/upstream (fetch)
upstream        /tmp/upstream (push)

$ git branch -avv
* dev/br-1                  0006c5e [origin/dev/br-1] more updates
  master                    dc45a94 [origin/master: ahead 1] update file-a from main
  remotes/origin/HEAD       -> origin/master
  remotes/origin/dev/br-1   0006c5e more updates
  remotes/origin/dev/br-2   ce2a804 update file-a
  remotes/origin/master     2f5eaaf add file-a
  remotes/upstream/dev/br-1 7aec18c more updates
  remotes/upstream/master   dc45a94 update file-a from main

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (HEAD -> dev/br-1, origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

更新2:

在上述状态下,当我按照@VonC的建议尝试--rebase-merges时,我得到:

$ git rebase --rebase-merges --onto master $(git merge-base dev/br-2 master) dev/br2
fatal: Not a valid object name dev/br-2
fatal: invalid upstream 'dev/br2'

$ git checkout --track origin/dev/br-2
Branch 'dev/br-2' set up to track remote branch 'dev/br-2' from 'origin' by rebasing.
Switched to a new branch 'dev/br-2'

$ git rebase --rebase-merges --onto master $(git merge-base dev/br-2 master) dev/br-2
Successfully rebased and updated refs/heads/dev/br-2.

$ git log --all --decorate --oneline --graph
* 344418c (HEAD -> dev/br-2) update file-a
* 4de3dec more updates
* 81af2ac more updates
* 1e3f9fb update file-a
| * 7aec18c (upstream/dev/br-1) more updates
| * b83c3f8 more updates
| * 4cf241f update file-a
| * 200959c update file-a from main
|/  
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1, dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

在这里,如何从上游重定我当前的dev/br-1,然后从dev/br-1分支重定dev/br-2(如果需要,可以在https://pastebin.com/Df8VbCp2找到详细的准备工作)。

o0lyfsai

o0lyfsai1#

以下是我目前的情况(可能已经被搞砸了,因为我看到HEAD可能在一个奇怪的地方):

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (HEAD -> dev/br-1, origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

好的,我可以把它水平画成:

A   <-- origin/master
|\
| B--C--D   <-- dev/br-1 (HEAD), origin/dev/br-1
 \       \
  \       E   <-- origin/dev/br-2
   \
    F   <-- master, upstream/master
     \
      G--B'-C'-H--I   <-- upstream/dev/br1

x一个m一个n一个x = x一个m一个n一个x x~1米~2米~1 x = x~1米~3米~1 x;等等。我选择了后面的那些作为基于主题行的"质数",尽管这可能是倒过来的:例如,标记为B的应该是B'。看起来尽管提交BE的主题行相同,但它们有不同的补丁ID(对file-a做不同的事情),很难说匹配这里的两个"更多更新"提交(调用最下面一行的第二个C')是否正确。
(Note这里没有dev/br2。)
但让我们回到这一点:
[We开头为]

A--B--C--D   <-- master (upstream)
    \
     E--F--G   <-- dev/br-1 (upstream)
            \
             H--I--J   <-- dev/br-2 (myown)

我将其稍微重新格式化为我自己喜欢的样式(两个破折号在提交之间,或者一个破折号,如果使用主要标记来指示它是一个副本)。
最终的目标是把我自己的

H--I--J   <-- dev/br-2

在我自己的回购协议中,在赶上上游之后,在新重定基的G '上的分支。也就是说,在我自己的回购协议中,最后,它应该看起来像这样:

A--B--C--D   <-- rebased master (upstream)
          \
           E'-F'-G'   <-- rebased dev/br-1 (upstream)
                  \
                   H'-I'-J'   <-- rebased dev/br-2

必须复制的提交正好是E-F-G-H-I-J(按此顺序)。
如果你在自己的仓库里有所有的6个提交(当然还有4个A-B-C-D提交),那么-暂时忽略你想要的 * 标签 *-我们所要做的就是说服Git复制,就像摘樱桃一样,有问题的6个提交。
执行此操作的两个选项是:

  • git cherry-pick,这使得原件和副本都很容易找到;或
  • git rebase执行复制,然后为我们移动一(1)个分支名称。

移动一个分支名是不够的,它不是特别有害,我们可以允许Git这样做,但让我们直接用cherry-pick来做。我们将从检查所有内容应该到达的提交开始,创建一个新的分支名:

git switch -c copied <hash-of-D>

或:

git switch -c copied --no-track upstream/master

(假设upstream/master名称提交D)。则:

git cherry-pick <hash-of-D>..<hash-of-J>

或:

git cherry-pick upstream/master..dev/br2

(再次,这里使用的两个 * 名称 * 仅仅是键入原始提交散列ID而不必键入原始提交散列ID的方式).这里的两点语法意味着 * 从第二说明符可达的任何提交,排除从第一说明符可达的所有提交 *,因此这意味着 * 从J一直回到根的提交,减去从D一直返回到根 * 的提交,这意味着E-F-G-H-I-J
此复制的结果将是:

E'-F'-G'-H'-I'-J'  <-- copied (HEAD)
          /
A--B--C--D   <-- master (upstream)
    \
     E--F--G   <-- dev/br-1 (upstream)
            \
             H--I--J   <-- dev/br-2 (myown)

现在我们已经有了想要的 * commits *,我们只需要放置特定的标签,因为这些标签中的一个或多个将指向commit G',所以将上面的标签重新绘制成H'-I'-J'所在的行就很有帮助了:

H'-I'-J'  <-- copied (HEAD)
                  /
           E'-F'-G'
          /
A--B--C--D   <-- master (upstream)
    \
     E--F--G   <-- dev/br-1 (upstream)
            \
             H--I--J   <-- dev/br-2 (myown)

要移动的标签为:

  • dev/br1,也许是;它应该指向G';
  • upstream/dev/br1-但这是upstreamdev/br1的 * 我们的副本 *,所以 * 我们 * 不直接移动它,我们让名为upstream的远程设备移动 * 他们的 *,以便我们的Git更新我们的内存中的名称;
  • origin/dev/br1:这就像upstream/dev/br1一样;
  • dev/br2:此应指向J';以及
  • myown/dev/br2origin/dev/br2,但这又是我们的Git对其他仓库的分支名称的 * 内存 *,所以我们必须说服 * 其他Git仓库 * 移动 * 他们的 * 名称。

要移动我们自己的dev/br1,现在可以简单地使用git branch -f

git branch -f dev/br1 copied~3

例如,由于名称copied选择提交J',后缀~3表示"向后移动三个第一父节点",因此将选择提交G'-f表示 * 强制 *,并导致Git移动dev/br1
要移动upstreamdev/br1并使我们的upstream/dev/br1移动,我们现在需要git push --force-with-lease或类似于upstream,这也假设我们有权限(在任何托管upstream的系统上:Git本身并不"执行"权限,但像GitHub这样的网站会执行,原因很明显)。--force-with-lease告诉我们的Git验证他们的dev/br1是否仍然指向我们期望的位置;如果我们确信这是真的,我们可以使用普通的--force。无论哪种方式,命令都是,或者类似于:

git push --force-with-lease upstream dev/br1
git push --force-with-lease origin dev/br1

它利用了我们强制br1指向G'的事实。
同样的过程也适用于使不同的名称指向H',只是现在我们可以使用名称copied

git branch -f dev/br2 copied
git push --force-with-lease origin dev/br2

一旦我们完成了所有这些,我们就可以切换到dev/br2或者其他什么,并删除额外的分支名称copied。它的存在只是为了让我们有一个很好的简单方法来在所有复制之后 * 查找 * 提交H'

    • 这里的关键是要理解 * 名称 * 在很大程度上是无关紧要的。真正重要的是 * 提交 ,它们由它们的哈希ID标识。诀窍是我们使用 * 名称 * 来 * 找到 * 提交 ,这样名称才是相关的。

备选方案

如果你愿意,你仍然可以用两个git rebase操作来完成这个任务,因为事情相对简单,我们不需要为 * 第一个 * 使用花哨的--onto(但是我们需要为第二个):

git switch dev/br-1
git rebase upstream/master

这是我们的起点,看起来像这样--注意,这次我假设的是远程跟踪名称:

A--B--C--D   <-- upstream/master
    \
     E--F--G   <-- dev/br-1 (HEAD), upstream/dev/br-1
            \
             H--I--J   <-- dev/br-2, origin/dev/br-2

并且将E-F-G复制到新的和改进的E'-F'-G',将它们放置在提交D之后,如由upstream/master命名的:

E'-F'-G'  <-- dev/br-1 (HEAD)
          /
A--B--C--D   <-- upstream/master
    \
     E--F--G   <-- upstream/dev/br-1
            \
             H--I--J   <-- dev/br-2, origin/dev/br-2

创建了三个副本后,git rebasenamedev/br-1从提交G中删除,并使其指向提交G'
现在我们将单独复制H-I-J

git switch dev/br-2
git rebase --onto dev/br-1 upstream/dev/br-1

这里我们需要--onto来告诉Git:

  • 不要向后复制upstream/dev/br-1的提交,也就是说,* don 't * copy commits G及更早的提交,但那不是我们想要放置副本的地方!
  • 在提交G'后放置副本,即新更新的dev/br-1
  • this* 的结果为:
H'-I'-J'  <-- dev/br-2 (HEAD)
                  /
           E'-F'-G'  <-- dev/br-1
          /
A--B--C--D   <-- upstream/master
    \
     E--F--G   <-- upstream/dev/br-1
            \
             H--I--J   <-- origin/dev/br-2

与第一个git rebase一样,Git复制了从当前分支名称中找到的提交(即J及其后),不包括我们声明 not 要复制的提交(即,G和背面),将副本放置在它们所到的任何地方-这次与“什么不要复制”部分分开-然后,在制作了副本之后,git rebasenamedev/br-2拉到指向最终复制的提交(J')。
(本地)仓库的名称现在指向副本,再一次只需要使用git push --force-with-leasegit push --force获取 other Git软件,与两个 other 仓库一起更新 * 它们的 * 分支名称,这样我们自己的Git内存中的远程跟踪名称也会得到更新。
(If你不能直接强制推送到upstreamorigin,你仍然可以发送更新后的提交,例如通过拉取请求,但是其他人必须说服其他仓库移动他们的分支名称来接收新的提交。

lymnna71

lymnna712#

***警告:***这是一个 * 非常 * 长的答案,因为它包括了每个步骤的命令、输出和状态。它是我如何实现目标的日志,即:

========
1.我分叉了一个上游,并在我自己的新分支(dev/br-2)中,从**它的开发分支 *(dev/br-1)和git push中对我自己的repo进行了自己的更改。
1号点的情况:

A---B---C---D  master (upstream)
    \
     E---F---G  dev/br-1 (upstream)
             \
              H---I---J dev/br-2 (myown)

1.现在,上游已经取得了进步,无论是在master还是develop分支。
1.向上游推进其develop分支的方式是通过rebase,即,在rebase之后,它自己的所有更改都被 * 放回***顶部***。
第2点和第3点的情况:

A---B---C---D  master (upstream)
            \
              E'---F'---G'  dev/br-1 (upstream)

1.我的本地repo和我的旧机器一起消失了,我需要从我自己的repo中提取/git-clone来继续我的定制。
1.我想在所有上游变更的基础上对dev/br-2分支(共享一个共同的祖先)中的变更进行重定基。
也就是说,最终的目标是把我自己的
H---I---J dev/br-2
在我自己的repo中,在赶上上游之后,在新重定基的G'之上的分支。也就是说,在我自己的repo中,最后,它应该看起来像这样:

A---B---C---D  rebased master (upstream)
            \
             E'---F'---G'  rebased dev/br-1 (upstream)
                       \
                        H'---I'---J' rebased dev/br-2

========

    • 解决方案:**

用三个git rebase操作来完成。
再次,准备步骤的细节可以在pastebin.com/Df8VbCp2上找到,包括下面做一个完整的故事:

$ cd /tmp

$ rm -rf upstream/ myfork/

$  mkdir upstream

$  cd upstream

# == prepare its `master` and `dev/br-1` branches

$  git init

seq 12 | tee file-a
git commit -am 'add file-a'

$  git checkout -b dev/br-1
Branch 'dev/br-1' set up to track local branch 'master' by rebasing.
Switched to a new branch 'dev/br-1'

sed -i 's/^1/7/' file-a

git commit -am 'update file-a'

seq 5 6 | tee -a file-a
git commit -am 'more updates'
seq 12 | tee file-b
git commit -am 'more updates'

 cd /tmp
 git clone upstream myfork
 # == prepare my own changes based on the `dev/br-1` branch into `dev/br-2`

$  git branch -avv
* master                  2f5eaaf [origin/master] add file-a
  remotes/origin/HEAD     -> origin/master
  remotes/origin/dev/br-1 0006c5e more updates
  remotes/origin/master   2f5eaaf add file-a

$  git checkout --track origin/dev/br-1
Branch 'dev/br-1' set up to track remote branch 'dev/br-1' from 'origin' by rebasing.
Switched to a new branch 'dev/br-1'

$ git checkout -b dev/br-2
Branch 'dev/br-2' set up to track local branch 'dev/br-1' by rebasing.
Switched to a new branch 'dev/br-2'

sed -i '/9/{ N; N; s/^.*$/3\n4/; }' file-a 

$ cat file-a
7
2
3
4
5
6
7
8
3
4
72
5
6

git commit -am 'update file-a in dev/br-2'

 cd /tmp/upstream
 # advance its `master` 

 sed -i 's/^5/55/' file-a
 git commit -am 'update file-a from main'

 # and its`dev/br-1` branches
 git checkout dev/br-1
 git rebase -X theirs master dev/br-1

$ cat file-a
7
2
3
4
55
6
7
8
9
70
71
72
5
6

# Now upstream has advanced, both in its master and its develop branches (via rebase)

从这里继续--从第4点开始,从我自己的repo中提取/git-clone以继续我的定制,然后使用

git rebase upstream/master

git switch dev/br-1
git rebase upstream/master

git switch dev/br-2
git rebase --onto dev/br-1 upstream/dev/br-1

详情:

# I need to pick-up from my own repo to continue with my customization.
 cd /tmp
 mv myfork myfork0
 git clone myfork0 myfork1
 cd myfork1
 git remote -v
 git remote add upstream /tmp/upstream
 git remote -v
 git fetch upstream

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (upstream/master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (HEAD -> master, origin/master, origin/HEAD) add file-a

$ git rebase upstream/master
Successfully rebased and updated refs/heads/master.

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (HEAD -> master, upstream/master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

$ git switch dev/br-1
fatal: 'dev/br-1' matched multiple (2) remote tracking branches
$ git checkout --track origin/dev/br-1
Branch 'dev/br-1' set up to track remote branch 'dev/br-1' from 'origin' by rebasing.
Switched to a new branch 'dev/br-1'

$ git log --all --decorate --oneline --graph
* 7aec18c (upstream/dev/br-1) more updates
* b83c3f8 more updates
* 4cf241f update file-a
* 200959c update file-a from main
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (HEAD -> dev/br-1, origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

$ git rebase upstream/master
Successfully rebased and updated refs/heads/dev/br-1.

$ git log --all --decorate --oneline --graph
* 53d26a9 (HEAD -> dev/br-1) more updates
* 3ad83f2 more updates
* c18eab7 update file-a
| * 7aec18c (upstream/dev/br-1) more updates
| * b83c3f8 more updates
| * 4cf241f update file-a
| * 200959c update file-a from main
|/  
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a


$ git switch dev/br-2
Branch 'dev/br-2' set up to track remote branch 'dev/br-2' from 'origin' by rebasing.
Switched to a new branch 'dev/br-2'

$ git log --all --decorate --oneline --graph
* 53d26a9 (dev/br-1) more updates
* 3ad83f2 more updates
* c18eab7 update file-a
| * 7aec18c (upstream/dev/br-1) more updates
| * b83c3f8 more updates
| * 4cf241f update file-a
| * 200959c update file-a from main
|/  
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (HEAD -> dev/br-2, origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

$ git rebase --onto dev/br-1 upstream/dev/br-1
Auto-merging file-a
CONFLICT (content): Merge conflict in file-a
error: could not apply 85afa56... update file-a
Resolve all conflicts manually, mark them as resolved with
"git add/rm <conflicted_files>", then run "git rebase --continue".
. . .

# fix it, then
git add file-a

$ git rebase --continue
Successfully rebased and updated refs/heads/dev/br-2.

$ git log --all --decorate --oneline --graph
* 76d524a (HEAD -> dev/br-2) update file-a
* 53d26a9 (dev/br-1) more updates
* 3ad83f2 more updates
* c18eab7 update file-a
| * 7aec18c (upstream/dev/br-1) more updates
| * b83c3f8 more updates
| * 4cf241f update file-a
| * 200959c update file-a from main
|/  
* dc45a94 (upstream/master, master) update file-a from main
| * ce2a804 (origin/dev/br-2) update file-a
| * 0006c5e (origin/dev/br-1) more updates
| * cdee8bb more updates
| * 85afa56 update file-a
|/  
* 2f5eaaf (origin/master, origin/HEAD) add file-a

$ cat file-a
7
2
3
4
55
6
7
8
3
4
72
5
6

相关问题