将代码从主分支移动到现有分支



我有main上的代码myscript.py,我正在尝试将其移动到现有的分支branchA

% git branch
branchA
* main 

myscript.pymain,如何将其移动到branchA,使其不再出现在main上?

代码(或者更准确地说,文件)驻留在提交中。

分支(或更准确地说,分支名称)选择提交。 更准确地说,分支名称保存一个特定提交的原始哈希 ID。 分支中的所有其他提交由于该特定提交而位于分支中。

任何提交一旦完成,就无法更改。 但是,存储在分支名称中的原始哈希 ID始终可以更改。 事实上,这就是分支名称的全部意义:存储不断变化的哈希 ID。

当我们进行新的提交时,Git :

  • 打包新的源快照;
  • 添加一些元数据,或有关我们现在进行的新提交的信息;
  • 将所有这些写成一个提交,获得一个新的、唯一的哈希ID;和
  • 提交的哈希 ID写入分支名称

因为 Git 在第二步中添加的一段元数据是当前提交的哈希 ID,存储在我们运行git commit时的分支名称中,这使得新的提交链接返回到曾经是当前提交的内容。 如果我们从带有哈希 ID 的提交链开始,其中我们用单个大写字母表示原始哈希 ID 以保持我们的个人理智,我们可能会有这样的东西:

... <-F <-G <-H

在这里H代表当前(到目前为止,最后一个!)提交的哈希ID。 分支名称main存储此原始哈希 ID,因此名称main指向提交H

... <-F <-G <-H   <-- main

同时存储在提交H中的元数据包含早期提交G的原始哈希ID,所以我们说H指向G。 那是从H里出来的箭。 当然,提交G是一个提交,所以它有一个存储的哈希ID,在这种情况下指向更早的提交F,依此类推。

现在,再一次,任何现有的提交都无法更改。 我们对 Git 的正常用途是添加新的提交,如下所示:

...--G--H   <-- main, somebranch

请注意,我们有两个名称——mainsomebranch——*都指向提交H

我们运行git checkout maingit switch main,编辑代码,运行git add,并运行git commit。 Git 打包一个新的快照并进行一个新的提交,我们称之为I. 因为我们已经签出main,所以 Git 将新的哈希 ID 写入名称main. 为了记住我们已经签出了哪一个,让我们将名称HEAD附加到main,并绘制新的提交:

...--G--H   <-- somebranch

I   <-- main (HEAD)

注意main是如何移动的,而somebranch没有。 如果我们现在git checkout somebranchgit switch somebranch,我们得到:

...--G--H   <-- somebranch (HEAD)

I   <-- main

来自提交的文件I消失,取而代之的是,我们有来自提交H的文件。 Git 删除了那些与提交I一起使用的那些——它们安全地存储在I快照中——并用H中的文件替换了它们。

现在,我们可以解决一种回答您问题的方法:

myscript.pymain上,如何将其移动到branchA使其不再出现在main上?

我们应该画出你所拥有的。 我不确定你的branchA选择什么提交,也不确定你的main选择什么提交,所以我不得不猜测:你自己的绘图会更好,或者你可以运行git log --all --decorate --oneline --graph或任何其他花哨的命令从 Pretty Git 分支图。 但是,假设我们有:

G--H   <-- branchA
/
...--F

I   <-- main (HEAD)

让我们进一步假设所有文件都已提交(因为如果没有,您有更多选择)。

您可以简单地运行:

git rm myscript.py
git commit

要在缺少该文件的main上进行提交,请执行以下操作:

G--H   <-- branchA
/
...--F

I--J   <-- main (HEAD)

提交J不再包含该文件。 现在,您可以git switch branchA切换到现有的分支branchA并提交H

G--H   <-- branchA (HEAD)
/
...--F

I--J   <-- main

您现在可以看到提交H中的所有文件,这当然意味着您看不到myscript.py。 但是我们知道它永久保存在提交I中,所以我们需要告诉 Git:从这个现有提交中获取这个保存的文件

有多个命令可以执行此操作;我通常推荐的是git restore,具有--source-SW选项:

git restore --source main~1 -SW -- myscript.py

这有点啰嗦,并且与旧命令执行相同的操作:

git checkout main~1 -- myscript.py

也就是说,它使用带有后缀~1的名称main首先查找提交J(main),然后后退一次(~1)以提交I。 然后,它会在该提交中找到名为myscript.py的文件,并将该文件复制到两个位置:

  • -W将文件复制到工作树中,您可以在其中查看和编辑它。
  • -S将文件复制到 Git 的暂存区域,现在可以提交该文件了。

git checkout命令没有-S-W标志:它总是复制到两个位置。

现在您已经有了这个文件并且它已经git add-ed,您只需运行git commit进行新的提交,该提交将更新当前 (HEAD) 分支名称:

G--H--K   <-- branchA (HEAD)
/
...--F

I--J   <-- main

提交K与现有的提交H完全相同,只是它添加了这个新文件。

请注意,这些分支中的这些提交历史记录。 通过提交向上提交F位于两个分支中。 Git 找到这些提交的方法是使用分支名称查找最后一个提交,然后向后工作。

还有更多选择

如果您尚未提交该文件,则它当前仅在您的工作树中,也可能在 Git 的暂存区域中。 请参阅约什梅兰达的答案,但请注意,git switch -c尝试创建一个分支;您需要git switch,它使用现有分支

在这里,我们可以指望这样一个事实,即由名称branchA标识的提交中没有名为myscript.py的文件。 这在实践中变得非常复杂,尽管在这种情况下很简单。 有关所有血腥的详细信息,请参阅当当前分支上有未提交的更改时签出另一个分支。

以上所有内容都是关于添加更多提交。 在某些情况下,我们有一些提交,出于某种原因我们不喜欢它们。 在这种情况下,我们可以在限制范围内告诉 Git停止使用这些提交

考虑以下情况:

G--H   <-- branchA
/
...--F

I   <-- main (HEAD)

我们决定我们不喜欢提交I. 我们所做的是最初不要管它,并使用它来将文件myscript.py复制到我们添加到branchA的新提交中:

G--H--J   <-- branchA (HEAD)
/
...--F

I   <-- main

使用相同的git restore技术(尽管这次我们使用--source main,而不是--source main~1,因为我们没有main上进行新的提交)。

但是,然后,我们git switch回到main并运行:

git reset --hard HEAD~1

或:

git reset --hard main~1

~1后缀的作用与以前相同:找到提交,然后后退一跳。 这将定位提交F。 我们可以运行git log并使用鼠标剪切并粘贴提交F的原始哈希 ID:

git reset --hard <hash>

这里。 接下来,git reset

  • 分支名称移动到我们选择的提交;
  • 重置 Git 的索引/暂存区;和
  • 使您的工作树匹配(由于--hard)。

这给我们留下了:

G--H--J   <-- branchA
/
...--F   <-- main (HEAD)

I   ???

请注意,如何不再有任何名称查找提交I。 如果你记住了它的哈希ID,或者把它写在纸上,或者其他东西,你仍然可以这样找到它。 Git 还提供了其他方法来恢复它,默认情况下至少 30 天。 但大多数情况下,它似乎已经消失了,好像它从未存在过。

所以现在看起来main上的最后一次提交是提交F,而不是提交I。 只要没有其他人提交I——你从未将其发送到其他 Git 存储库——"摆脱"这样的提交是安全的。 如果你确实把它发送到某个地方,它可能会从那里回来,因为 Git 真的很喜欢添加提交,并且会像病毒一样传播它们,只要有一半的机会。 因此,一旦你发出了提交,"倒带"或"删除"提交通常是不明智的(通常使用git push)。

如果您尚未提交文件,只需移动新分支并继续:

git switch -c branchA

如果已提交文件,则可以在将其移动到新分支之前将其还原到某个提交:

git restore -s <commit> myscript.py
git switch -c branchA

最新更新