Git rebase不会删除特性分支中的文件

c3frrgcw  于 5个月前  发布在  Git
关注(0)|答案(1)|浏览(88)

我在同一天基于dev分支创建了两个分支b1和b2,两个分支都有一个名为f1.txt的文件。
1天后,我在分支b1中执行了一次提交(c1)删除文件f1.txt,并将更改推送到远程分支b1并将其合并到dev
一个月后,我做了一个分支b2,提交了一个**(c2),做了(git pull origin/dev --rebase)。我主要做(git pull --rebase
文件
f1.txt仍然在分支b2中。
如果
分支b2**中的文件被删除,如何更新本地分支?

qqrboqgw

qqrboqgw1#

首先你必须完全理解rebase

Rebase不适用于 files。Rebase适用于 commits。具体来说,git rebase意味着:

  • 我有一些承诺。
  • 关于这些提交的很多东西都是好的。
  • 但是这些提交的一些事情并不是那么好。
  • 我知道这是不可能的 * 改变 * 一个 * 现有的提交 *,但如果你,Git,将是如此善良,我希望你采取这些现有的提交和应用他们,一个接一个,从我喜欢的地方开始,并给我选择停止和 * 改变周围的文件和/或更改提交消息你做新的副本。

也就是说,我们采用一些现有的提交序列:

K--L   <-- my-feature (HEAD)
         /
...--G--H--I--J   <-- main

字符串
而且,因为我们不喜欢Kparent commitH的事实-这两个提交在图中的位置是错误的,换句话说-我们运行:

git rebase main


Git会接收KL的提交,并将它们 copynew 提交。新的提交将:

  • 做与KL相同的事情,但是
  • J之后

这样我们得到:

K--L   <-- my-feature
         /
...--G--H--I--J   <-- main
               \
                K'-L'  <-- new-and-improved-my-feature


注意这两个分支在这一点上是如何共存的。
现在我们利用这个事实,(虽然不是Git)通过使用 * 分支名称 * 来 * 查找 * 提交。(Git使用hash ID。)提交K'L'看起来很像KL,人类看到L'可能会错误地认为他们看到的是L。(Git 不会,因为L'将有一个完全不同的随机哈希ID。
所以现在,我们让Git稍微交换一下名称!然后我们让Git检查它构建的新分支:

K--L   <-- old-and-lousy-my-feature
         /
...--G--H--I--J   <-- main
               \
                K'-L'  <-- my-feature (HEAD)


然后 * 删除旧分支上的名称 *:

K--L   [abandoned
         /
...--G--H--I--J   <-- main
               \
                K'-L'  <-- my-feature (HEAD)


我们再也找不到提交K-L了,因为 * 我们 * 使用了名称。(Git仍然可以找到它们,只要Git可以找到哈希ID。Git会将这些哈希ID保存一段时间,或者永远保存在GitHub上。)
简单地说,这就是rebase,尽管我们可以使用git rebase -i来实现。这里Git将提供一个包含pick命令的指令表,我们可以修改它来读取editreword或其他命令。这改变了Git执行copy-of-commits的方式。在我们继续之前,我们应该提到的是,实际的 * 复制 * 是通过git cherry-pick发生的。1 cherry-pick“copies a commit”的方式,粗略地说,是将提交的快照与提交的父提交的快照进行比较。无论这个提交做了什么 * 更改 *,这些也是副本会做的更改。
因此,在我们上面的例子中,如果你显示commit K,你会看到 changegit diff输出)-如果你在换基之前这样做,或者如果你保存它的哈希ID,这样你就可以在换基之后把它拿出来-将匹配你在换基之后显示commit K'时看到的 change
1对于所有的交互式git rebase操作都是如此,对于 modern Git中的大多数git rebases也是如此,但是在2.26版本之前的Git中,很多rebase操作使用了不同的后端。结果 * 通常 * 是相同的,无论如何,你应该通常认为rebase是“自动化的樱桃采摘”。

接下来你必须完全理解git pull

pull命令基本上是一个方便的命令:它只是为你运行两个 * 其他 * Git命令。这使得它相对容易理解,除了要真正理解它,你必须理解其他两个命令!所以让我们看看它们是什么:

  1. git pull运行git fetch
    git fetch命令的意思是 * 在需要的时候,访问其他Git仓库并从那里收集一些提交 *。这可能会变得很复杂,所以我们暂时不讨论这个问题。git fetch收集的新提交(如果有的话)会被记录在隐藏的.git文件夹中的一个特殊文件中:.git/FETCH_HEAD。根据您的Git版本以及您调用git pull的方式,此git fetch也可以更新origin/dev等内容。但在所有情况下,这 * 不会影响您当前的任何工作 *。
    新的提交,填充到你的Git仓库中,只是新的提交。你实际上还没有 * 使用 * 它们中的任何一个。通常,我们获取新的提交是因为我们 * 想要使用它们 *。所以.
    1.运行完git fetch后,git pull运行第二个Git命令。
    第二个命令由你决定。你可以选择让Git运行git merge--这是默认的,如果你没有设置其他任何东西的话--或者你可以选择让Git运行git rebase
    当然,第二个命令的目的是利用你在第一步中获取的新提交。这就是一切变得非常棘手的地方。
    我们一会儿再回到git fetch,因为它关系很大。现在让我们看看你提供的信息。

你说的

我在同一天基于dev分支创建了两个分支b1和b2,两个分支都有一个名为f1.txt的文件。
这不是很正确的描述。让我们更正一下。

由于Git实际上是关于 commits 的--不是文件,不是分支,而是 commits--让我们画出你到目前为止描述的情况。你有一个仓库,里面有一些提交。每个提交都包含每个文件的完整快照,加上一些元数据。(比如谁提交了提交,什么时候提交的,以及提交的 parent 提交的原始哈希ID)。我们将绘制这组提交和分支名称,就像这样:

...--G--H   <-- dev, b1, b2


也就是说,所有三个分支名称都选择了某个提交。每个提交都有一个唯一的哈希ID--一个由字母和数字组成的丑陋的字符串,对于这个特定的提交来说是唯一的--而一个 * 分支名称 * 则提供了一种方式让人类告诉Git:* 只要给我最新的提交,不管它的哈希ID是什么。* Git通过将最新提交的哈希ID存储在分支名称中来实现这一点。所以这里的三个分支名称,devb1b2都存储提交H的哈希ID。提交H在其快照中存储包括文件f1.txt在内的完整文件集;提交H在其元数据中存储早期(父)提交G的哈希ID。
由于所有三个 * 分支名称 * 都持有 * 相同的哈希ID*,所以所有三个分支都选择提交H。这一个提交持有文件f1.txt,并且它将永远这样做(任何提交的任何部分都不会改变)。
1天后,我在分支b1中提交(c1)删除文件f1.txt
让我们绘制提交C1(我甚至在这里称之为C1):

C1   <-- b1
         /
...--G--H   <-- dev, b2


提交C1有一个新的快照:在C1的快照中,文件f1.txt不存在。这是每个文件的完整快照,所以当我们比较H中的快照和C1中的快照时,我们看到的一个 * 变化 * 是“删除文件f1.txt“。
请注意,C1并不 * 存储更改 。它只存储快照和元数据。我们看到的“更改”是比较HC1的结果,通过以下分支名称devb2提交H,以及以下分支名称b2提交C1获得。
并将更改推送到远程分支b1,然后将其合并到dev
我想这意味着:
我运行了git push origin b1 * 或 * 我运行了git push -u origin b1 *。到目前为止一切顺利,但现在,我们有一个小问题。当你使用git push时,你正在做的事情与git fetch相反。你让你的Git调用其他一些Git软件,使用不同但相关的Git存储库-Git称之为 clone。您可能从他们的存储库克隆了您的存储库,但“clone-ness”是对称的:如果Fred 2是Fred 1的克隆,那么Fred 1是Fred 2的克隆。
这里的问题是每个克隆都有 * 它自己的分支 *。这就是为什么你说“remote branchb1":这是一个名为origin的分支。所以现在 * 他们 * 有一个b1。然后你说 * 并将其合并到dev *,但是这里的 it 是什么?你是如何进行合并的?
假设你有自己的dev and your own b1`,你可以运行:

git checkout dev; git merge b1


或者,考虑到你用x1c 0d1x github标记了整个事情,也许你的意思是在:

git push -u origin b1


你在 * GitHub上使用了一个绿色的点击按钮 *,导致合并发生在 * GitHub上的GitHub存储库 * 中。也就是说,你在 * 他们的 * 存储库中进行了合并,而不会影响 * 你的 * 存储库。
我不知道哪一个可能是正确的,但是如果我们假设你现在在GitHub上使用了这个按钮,并且在本地没有做任何事情,那么你的 local 仓库现在有了这个:

C1   <-- b1, origin/b1
         /
...--G--H   <-- dev, b2, origin/dev


请注意,我已经添加了origin/名称。这些是Git记住Git在GitHub上的GitHub存储库中设置的Git * 想法 * 的方式。Git只能在某些情况下更新这些名称。最常见的情况是使用git fetch时,但即使在这里也会变得有点复杂。
如果你在你的仓库中进行了合并,假设你允许git merge进行 * 快进 *(这是它的默认设置),那么你会得到以下结果:

C1   <-- b1, dev, origin/b1
         /
...--G--H   <-- b2, origin/dev


让我们回到您自己的命令,正如您所描述的:
一个月后,我在一个分支b2工作,
如果我们从上面的图开始,不对它进行任何更新--这是我们在这一点上所能做的--那么b2仍然指向commit H,其中仍然有文件f1.txt
提交(c2
假设你在这次提交中没有接触f1.txt,所以在新提交中它有 * 相同 * 的f1.txt

C1   <-- b1, [dev?], origin/b1
         /
...--G--H   <-- [dev?], origin/dev
         \
          C2   <-- b2

请注意,我不知道 yourdev选择了哪个提交,也不知道 their(origin's)dev是否仍然选择了提交H
并执行了(git pull origin/dev --rebase)。
我们现在必须进入git fetch的一些血淋淋的细节。当你运行git pull时,无论有没有--rebase选项,git pull都会将 * 大部分 * 参数传递给git fetch。然而,--rebase选项是git pull的一个选项,告诉它 * 运行第二个命令 *,所以它不会将该值传递给git fetch
让我们把这个也加入到mix中:
我主要做(git pull --rebase
所以这两个中的一个会运行:

git fetch origin/dev

其中一个会运行:

git fetch

git fetch的血腥细节

当你运行git fetch时,* 没有额外的参数 *,如下所示:

git fetch

(as例如,你会得到git pull --rebase),你的Git将:
1.使用存储在名称origin下的URL调用另一个位于origin的Git;
1.让他们列出 * 所有 * 他们的分支名称,并为每个分支名称,找出任何新的名称和/或提交,他们有,你没有;
1.把所有这些新的名字和提交都带过来,塞进你的仓库里;
1.根据第3步中的内容更新 * 所有 * origin/*名称。
他们 * 删除 * 的任何分支名称都不会在第2步中显示,因此不会在第3步中更新,因此您的Git不会删除您自己存储库中对应的origin/名称。也就是说,假设他们 * 有 * 一个分支temporary,并且您运行git fetch。此时您会得到一个origin/temporary。稍后,他们 * 删除 * 他们的temporary。你运行git fetch,你的Git没有看到temporary。但这只是意味着你的Git没有更新你的origin/temporary。所以它从早期一直存在,即使它的存在已经没有意义了。通常这并不重要,这只是意味着“陈旧”的名字会累积起来。
所以git fetch本身意味着 * 获取所有内容并更新我所有的远程跟踪名称,然后停止 *。这通常是 * 我想要的 *,所以这是我通常使用的-我自己几乎从来没有使用过git pull。我喜欢查看更新的内容,查看新的分支名称(如果有的话),也许看看新提交。
但是,如果你喜欢git pull,记住git pull origin dev意味着 * 运行git fetch origin dev *。记住git pull origin/dev意味着 * 运行git fetch origin/dev
后者不起作用!你说这就是你运行的,如果这就是你运行的,那就解释了为什么它不工作。git fetch后面的第一个非选项词是 * 远程名称 *。你唯一拥有的远程是(可能是3)origin。没有origin/dev远程,因此git fetch origin/dev将失败。

这可能是一个错字,也许你运行了git pull origin dev。这将运行git fetch origin dev。运行这个告诉你的Git:* 调用origin上的另一个Git,并列出它们的部分或全部分支名称,但我只对拼写为dev的那个感兴趣。* 他们会列出他们的名字,你的Git会看到他们是否有dev。如果有,他们会发现他们的dev最近的提交是哪个提交。

如果这个提交对你来说是新的--也就是说,如果他们有一个dev,并且它有新的提交--你的Git会把这些提交带过来。在任何情况下,无论他们是否有新的提交,你的Git都会把他们最近的-dev-commit哈希ID写入.git/FETCH_HEAD。如果你的Git版本至少是1.8.2,你的Git现在也会更新你的origin/dev,但是如果你的Git比这更旧,你的Git将无法更新你的origin/dev
一旦git fetch完成,它将返回“成功”,如果:

  • 它能够查看遥控器,
  • 它能够连接到Git;以及
  • 它能够找到正确的哈希ID,并在需要时带来任何新的提交。

否则git fetch告诉git pull它失败了,你的pull在这一点上停止。所以git fetch origin/dev会失败,并在这一点上停止你的变基。
如果这是一个错字,你实际上运行了git pull origin dev --rebase(而不是git pull origin/dev --rebase),* 那么 * 你的git fetch可能工作了(GitHub的计算机几乎总是正常工作),你可能得到了一个.git/FETCH_HEAD,其中包含正确的哈希ID。

  • 在这种情况下 *,您的Git将使用.git/FETCH_HEAD中的哈希ID运行git rebase *hash-ID*。这将如上所示使用 theirdev commit的哈希ID执行git rebase

2如果你想删除这些“过时”的远程跟踪名称,可以使用git fetch --prunegit remote prune origin。我通常希望我的Git软件每次都能自动执行此操作;要让你的Git执行此操作,请使用git config --global fetch.prune true。只是要注意,这会让你的Git在意识到远程跟踪名称消失后立即删除它们!
3你将为每个其他Git仓库设置一个 remote,你需要定期与之对话。当你使用git clone进行初始克隆时,它会设置一个remote(“我克隆的另一个Git仓库”)并称之为origin。这是几乎每个仓库中几乎每个人都拥有的一个remote。

再次回到问题

文件f1.txt仍然在分支b2中。
我们现在需要知道的是:

  • 你是如何进行合并的,在哪个仓库中?
  • 你展示的git rebase是正确的,在这种情况下它失败了,或者你给我们看了一个错字,你运行的是正确的,你展示的是错误的?
  • 如果一个rebase运行,什么哈希ID存储在他们的dev?什么是在那里的提交(s)?

您可以运行:

git fetch origin
git log --all --decorate --oneline --graph

得到一个Git绘制的提交图沿着,上面有你和他们的名字,这会告诉我们更多。

相关问题