将 master 分支中未提交的更改移动到 dev 分支并还原 master

Move uncommitted changes in master branch to dev branch and revert master

我正在尝试开始使用 git 分支,但有些地方我不明白。

最初我的 master 分支上的一个小改动正在发展成为一个大功能,所以我想将它移动到一个名为 dev 的新分支,这样我仍然可以在 master 分支上进行小改动并合并完成后的 dev 分支。

我已经尝试了各种方法,但我正在使用 this one as example 因为我有意外的行为。

因此,在 git stash 中,所有未提交的更改都将移至存储区,主服务器将恢复到其上次提交的状态。 然后创建一个新的"dev"分支并切换到它。 stash apply 保存的存储被应用回这个分支,所有的更改都回来了。 现在,当我 checkout master 时,我看到所有更改的文件再次出现。我预计只有 dev 分支有这些更改,当切换回 master 时,它的状态将是最后一次提交。我不确定这是正常行为还是我的期望是错误的(有更多问题)或者 master 分支是否应该保持最后提交状态?

在大多数情况下,如果您在有未提交的更改时尝试签出,将会收到错误消息。如果两个分支(您所在的分支和您正在签出的分支)不指向同一个提交,就会发生这种情况。要在两个分支之间保留未提交的更改,您需要像您所做的那样使用 git stash

当您创建一个分支时,它指向您当前所在的提交。所以 devmaster 都指向同一个提交。在这种特殊情况下,git 将允许您自由切换分支,因为 checkout 不会更改任何文件。

这对您的用例最有用...您开始进行更改但忘记开始一个新分支。在这种情况下,您可以创建新分支并切换到它而无需使用 git stash.


编辑:

如果更改在 git-stash 之后不存在,那么在提交到另一个分支后它们将不存在。如果你真的想测试这个,没有什么能阻止你这样做:

git checkout dev
git add *
git commit -m 'Test commit'
git checkout master

# look around

git checkout dev
git reset HEAD~

最后一行从您的最后一次提交中弹出,并将文件留在那里作为未提交的更改。只是不要推 test commit.

我通常支持 Philip 的分析。

只是想补充一点:如果您在新创建的分支上并且需要检出回到 master,希望不会在这里找到您的更改,您需要在创建之前在新分支上提交这些更改开关:

# just after your stash apply on the new branch
git commit -a -m"temporary message"
git checkout master

然后如果你想继续在新分支上工作,撤消这个临时提交,工作直到你完成,然后提交 "for real" :

git checkout dev
# the following line will undo the commit but keep your changes in working tree
git reset --soft HEAD^
# work some more
git commit -a -m "real final message"

Git 仅存储您提交的内容。由于您尚未提交这些更改,因此它们不会存储在任何一个分支中。 Stash 是一种创建不属于任何特定分支的提交的特殊方式,包括暂存和未暂存的更改(有多种选项可以修改此行为);但是当你应用或弹出一个存储时,它不会自动在你的分支上创建一个新的提交。

Git 对失去工作非常敏感。当您要求 git 检出 master 时,它注意到您有未提交的更改。它在后台进行了快速检查,以查看从开发人员转移到主人员时是否会丢失任何工作。由于两个分支具有相同的历史记录(因此两个分支之间的文件没有差异),git 知道不会有冲突,因此您未提交的更改不会被覆盖或 "lost"。它只是保存了它们。当您意识到自己在错误的分支上工作时,这是一个方便的功能……只要没有冲突,您就可以尝试切换分支并处理所有未提交的工作。再次注意,在您提交之前,未提交的更改不会存储在任何一个分支中。

您看到的是您的 工作树——您工作的区域——和实际的提交.

如果您不将 Git 视为存储 更改 ,这可能会有所帮助,因为它不会。它存储 快照 。每次提交都是您所有文件的完整、完整的副本——好吧,您提交的所有文件,但这有点多余,所以想想 "all the files"。保存的快照将永远冻结——好吧,只要提交本身存在,就一直存在,但无论如何,默认情况下是永远的。

这些快照以一种特殊的、压缩的、冻结的、仅 Git 的格式保存。除了 Git,它们对任何东西都没有好处,所以要在 git checkout 上使用它们,Git 必须将它们展开为正常形式,在那里它们解冻并且很有用,当然你可以改变它们。那是你的工作树,你将在那里完成你的工作。

所以每个文件都有两个 "active" 副本:提交中冻结的一个,以及您正在处理的一个。当您将事物视为 变化 时,您是在比较这些不同的副本。

Git 在这里添加了一个额外的皱纹,因为每个文件实际上都有一个 third 副本。当 Git 第一次提取提交时,它会将冻结的 Git-only 文件复制到 Git 调用的不同的 index暂存区,或缓存。在这里,文件仍然是 Git-only,但现在它们不再完全冻结。它们更像是泥泞的、随时可以冻结的,具体来说,您可以 替换 任何文件的索引副本为新副本。这就是 git add 所做的。

当您使用 git stash 时,Git 真正做的是进行一次提交,或更准确地说,两次提交。然后它会清除索引和工作树,以便它们与当前或 HEAD 提交匹配。这个隐藏提交的主要特殊之处在于它不在 any 分支上。所以你可以切换到另一个分支并使用 git apply 重新提取它,无论你切换到哪个分支。

切换到另一个分支意味着select该分支的提示提交为当前提交,该分支为当前分支。 Git 会将所有 those 文件从 that 快照复制到索引和工作树中。如果您之前是干净的,那么您将再次处于干净的状态,但现在 HEAD 提交是其他提交。现在您可以再次修改工作树了。

一样,有一个特殊情况,两个分支names标识same提交。如果我们将提交画成一条链,两个分支名称指向最新的提交:

...--o--o--*   <-- master, develop

那么,从某种意义上说,我们是git checkout master还是git checkout develop并不重要。两者都标识相同的最后一次提交。 HEAD 中的活动快照、索引和提交都将具有相同的 内容 。如果您从提交 * 切换到提交 *,Git 不必对索引和工作树做任何事情,因为您实际上并没有在其中切换任何内容!

但这还不是 git checkout 所做的全部!特别是,签出 master 告诉 Git 将名称 HEAD 附加到名称 master,而签出 develop 告诉 Git 附加它改为 develop 。当您进行 new 提交时,这会影响哪个 branch name 得到 updated。如果你是这样设置的:

...--o--o--*   <-- master, develop (HEAD)

然后进行 new 提交,Git 将 移动名称 develop 以指向新提交:

...--o--o--o   <-- master
            \
             *   <-- develop (HEAD)

请注意 HEAD 仍然只是附加到 develop。诀窍是 develop 现在可以识别这个新提交。

Git 从 index 中的任何内容进行新提交,而不是从工作树中的内容进行提交。您使用 git add 告诉 Git:将工作树文件复制到索引文件之上。 这为冻结文件做好准备,将其压缩到特殊 Git-only 格式,使用工作树版本中的任何内容。然后 git commit 只需快速冻结索引副本,就可以制作新提交的所有文件的冻结副本。

因此,对于 这个特殊的 案例,git stash 进行了提交,清理了你的工作树,然后你重新附加了 HEAD相同的提交但不同的分支名称,然后重新应用您的工作树更改。存储和应用是完全没有必要的。但是,如果 developmaster 指向 不同的 提交,您通常需要执行该存储和应用。 (即便如此,在许多情况下,Git 会让您摆脱切换提交的麻烦。请参阅 Checkout another branch when there are uncommitted changes on the current branch 了解所有细节。)