Git 变基没有远程的本地分支

Git rebase a local branch that doesn't have a remote

我创建了一个本地分支 (git checkout -b),我在其中进行了一些肮脏的提交("WIP" 等)。我从来没有推过遥控器。我现在想 rename/squash 在推送之前进行一些提交:

$ git rebase -i
There is no tracking information for the current branch.
Please specify which branch you want to rebase against.
See git-rebase(1) for details

    git rebase <branch>

If you wish to set tracking information for this branch you can do so with:

    git branch --set-upstream-to=origin/<branch> feature/position-preview

然后我按照建议做:

$ git branch --set-upstream-to=origin/feature/my-feature feature/my-feature
error: the requested upstream branch 'origin/position-preview' does not exist
hint:
hint: If you are planning on basing your work on an upstream
hint: branch that already exists at the remote, you may need to
hint: run "git fetch" to retrieve it.
hint:
hint: If you are planning to push out a new local branch that
hint: will track its remote counterpart, you may want to use
hint: "git push -u" to set the upstream config as you push.

问题是,我不想 push -u 原样,因为这会导致糟糕的历史。

如何重新定位我的新本地分支?

问题是,默认情况下,不带参数的变基建议从上次推送的提交变基。然而,我们可以将它推向正确的方向:

git rebase -i <hash>

选择 <hash> 提交 就在 之前 您想要在历史记录中看到的提交。这些哈希值可以通过 git log.

获得

Git 的文档是 much derision 的主题......有充分的理由:

git rebase [-i | --interactive] [options] [--exec <cmd>] [--onto <newbase>] [<upstream> [<branch>]]

此处的参数标记为 upstream,命令的有用 (?) 输出指的是 git branch --set-upstream-toorigin/<branch>,这意味着您的本地分支必须有一个像 origin/my-feature 这样的远程分支才能工作。但事实并非如此。

git rebase 所做的是 复制 一系列提交,每次复制就像你 运行 git cherry-pick一个特定的承诺。通过在 commit-graph 内的某个线性提交链中复制 every 提交,您可以获得复制 branch-of-development.1[=170 的效果=] 我认为以图形形式表达更有意义。例如,假设您有这个序列:

... <- o <- A <- B    <-- master
         \
           C <- D <- E   <-- my-feature

其中每个字母代表某个特定的提交——那些丑陋的 40 字符 SHA-1 数字中的一个——my-feature 的提交历史与 [=25] 的 commit-history 连接在一起=] 在提交时 o。如果您在 master 上创建一个新分支 my-feature,然后返回到分支 master,进行两次提交,然后转到 [=24],就会得到这种图表=] 并进行了三次提交(每次提交的实际顺序并不重要:我只需要你在 master 上进行两次提交,然后我将它们绘制在 now 作为 first-of-those A 和 second-one B,以及三个 my-feature).

现在,正如开发过程中发生的那样,您可能会认为根据 master 的当前提示 C-through-E 更有意义,即,让 C 的 parent 提交 B.

实现此目的的一种非常手动的方法是:

$ git checkout master
$ git checkout -b new-my-feature
$ git cherry-pick <id of commit C>
$ git cherry-pick <id of commit D>
$ git cherry-pick <id of commit E>

这会创建一个新分支,new-my-feature,并复制三个提交,2,结果如下:

                C' - D' - E'   <-- new-my-feature
              /
... o - A - B   <-- master
      \
        C - D - E    <-- my-feature

git 构造新提交 C' 的方式是将 C 的树(关联的源快照)与其 parent 提交 [=26] 的树进行比较=],将相同的更改应用于 B 的树,并进行新提交 re-uses C 的提交消息。换句话说,提交 C'C 的 "copy"。 D 中的 D'E 中的 E' 也是如此。

无论如何,如果你拍了这第三张照片,然后删除my-feature分支标签并将new-my-feature重命名为my-feature,你得到的是:

                C' - D' - E'   <-- my-feature
              /
... o - A - B   <-- master

my-feature 上的原始三个提交是 "lost"(它们通过 "reflog" 条目保留了 30 天,但它们通常是不可见的,以避免混乱你的看法)。

git rebase 所做的是自动执行此过程:它找到要复制的提交序列,将序列复制到其他提交上,然后移动分支标签。

使用所有 not-terribly-well-documented 选项,您可以指定的是分支名称——默认是您当前的分支——以及通常有点间接地 停止复制。 "target" 提交,rebase 调用 --onto <newbase>,默认为您为 rebase 调用 <upstream>.

提供的参数(如果有)

它的工作方式依赖于理解其他相当重要的东西,这会影响 two-dot 表示法。

让我们再次回到原始图表,在我们做任何 cherry-picking 和 branch-label-moving 之前,即我们在 rebase 之前拥有的:

... - o - A - B    <-- master
        \
          C - D - E   <-- my-feature

现在让我们问一下git master..my-feature中有哪些修改:

$ git rev-list master..my-feature
e59f6c2d348d465e3147b11098126d3965686098
8413a79e67177d026d2d8e1ac66451b80bb25d62
1f9e0a53489aaca7859722e037a47e93858cbc42

(这些是我编造的,但实际上使用此设置您实际上只会得到三个 SHA-1)。

我认为解释这一点的一个好方法是 color-filling / 突出显示(当然我不能在本文中这样做,所以你必须想象它)。

假设您从 my-feature 的提示开始,即提交 E。将此提交涂成绿色:它是 "to be used"。然后,跟随其 parent 指针返回提交 D。也将此提交涂成绿色。跟随它到 C 并涂上绿色;跟随它到 o 并将那个也涂成绿色,然后继续到 o 的 parent(s) 一直到整个图表。

然后,从master的提示开始,即提交B。将此提交涂成红色:"stop"。然后,跟随它的 parent 提交 A,并将这个涂成红色。按照那个到 o,然后用红色油漆覆盖绿色油漆:"stop"。一路跟随o的parent(s),用红色油漆覆盖他们所有的绿色油漆。

(如果你愿意,你可以先做红色,然后做绿色,只要你不要用绿色覆盖任何红色。)

结果——git rev-list 应该列出的提交——正是那些(仍然)绿色的提交,这正是我们想要变基的三个。即使我们从提交 B 开始 "red list",master.

的提示也是如此

所有这一切的简短版本是,为了变基,我们想要的——至少现在——是 git rebasemaster 视为 "upstream". rebase 的 "upstream" 参数提供了 "red paint" 参数,master..my-feature 的左半部分 revision-specifier,an --onto 参数:我们想要复制提交,以便它们出现在 master 的 tip-most 提交之后。

实际上,您可以使用git branch --set-upstream-tomaster(在您自己的私有存储库中)设置为my-feature的"upstream"。上游不一定是像 origin/branch 这样的 remote-tracking 分支。您自己的本地分支机构在这里可以正常工作。 (但请记住,如果你确实开始共享这个分支——通过将它推送到远程 git 存储库,比如 origin——你可能想在那个时候开始使用 remote-tracking 分支作为"upstream" 从那以后,提醒自己你正在分享,并开始一个更典型的 rebase 流程。你可以在任何时候通过做一个新的 git branch --set-upstream-to 来改变 "upstream"。 )


1单词 "branch" 是 git 文档存在问题的另一个示例。它至少有两个不同的含义。在作为脚注的段落的一部分的图表中,master 是 "branch"(我喜欢称它为 "branch label" 或 "branch name")。同样,my-feature 是一个分支。在这种情况下,这两个都简单地标记了一个特定的提交——分别为 BE。但是,每个提交都有自己指向其 parent 提交的指针:B 指向 AE 指向 D,以及很快。遵循这些 parent-pointing 箭头产生的提交链也是 "branch".

停止将这样的链称为 "branch" 的确切点取决于您的观点:my-feature 可能会在提交 o 时停止,而 master可能会继续返回 o。 Git 在这里并没有多大帮助,尽管无论如何都没有 always-right 答案:它确实取决于您想要包含的内容。

2有一种更简单的方法,因为 git cherry-pick 可以将多个提交作为参数:

$ git cherry-pick <id> <id> <id>

例如,或:

$ git cherry-pick my-feature~3..my-feature

它使用 the gitrevisions documentation 中描述的说明符得到 git 以找到三个 ID。