您如何将已提交的更改作为未决更改从分支移动到 master?

How do you move committed changes from a branch to master as pending changes?

我正在实施一项新功能,为此我创建了一个新分支,并对其进行了一些提交,然后进行了推送。我现在希望以未决/未提交更改的形式继续在 master 中处理此功能。待定,因为在我提交之前它需要更多的工作。我该怎么做?

当我最终提交时,master 的修订历史应该只指示一次提交,就好像我从未创建另一个分支或进行过中间提交一样。我该怎么做?

手动方法是创建第二个git 工作区,在一个工作区打开master,在另一个工作区打开branch,然后复制粘贴每个修改过的文件。有自动执行此操作的方法吗?

您是否考虑过合并到 master,然后使用您喜欢的 diff 工具来比较提交历史记录?

合并到 master 将是一次提交。但是只要你不 push 对远程的更改(例如 'origin'),你总是可以使用 git reset HEAD~2 重置合并以放弃合并提交。

一个附加说明,'HEAD~2' 是提交历史记录中要重置回的提交次数的示例。如果您的合并提交使您 'x' 提交数量领先于您的远程,您可以将“2”替换为提交数量 git status 报告您领先于您的远程。

首先,我将给出命令,然后给出它们的实际作用以及为什么这样可以达到您想要的效果。我在下面假设功能分支只是称为 branch——如果它有其他名称,请将给定的名称更改为 git merge

git status               # and make sure everything is committed on your branch
                         # do any extra commits here if required

git checkout master      # change the current branch/commit, index, and work-tree
git merge --squash --no-commit branch

(在 git merge 中,--squash 意味着 --no-commit 无论如何,所以你可以在这里省略第二个标志参数。为了说明,我把它包含得更多。此外,--squash表示 在索引中执行合并操作 work-tree,但不进行合并提交。)

根据您的工具,您可能想要执行 git reset(默认为 --mixed 重置并使用 HEAD 作为提交)来复制 HEAD提交回索引,以便普通 git diff 显示所有内容,而 git status 显示所有内容 not staged for commit.

重要的是要注意,这里并不存在 待定更改 这样的事情。 Git 根本不存储 更改 。 Git 存储的是 提交 ,每个提交都是所有文件的完整快照。当您询问 Git 发生了什么变化 时,您必须告诉 Git 关于 两个 提交,或者至少两个 trees-full-of-files。 Git 然后根据这两个输入计算出快照 A 和 B 的不同之处。然后 Git 告诉您这些不同之处——但 A 和 B 仍然是完整快照。在某些方面,none 这很重要 — 您可以在需要时查看更改,并在需要时查看快照。但是,如果您的心智模型与 Git 的实际情况相符,您需要解决的谜团就会更少:您自己的工作会更容易。这一点最重要的方法是记住:为了查看变化或差异,您必须提供两个输入。 有时,其中一个输入是隐含的。有时,两者都是隐含的!

最后一个是简单 git diffgit diff --cached / git diff --staged 的情况:Git 将索引与 work-tree(普通 git diff),或者 HEAD 提交到索引(--staged--cached)。该选项(如果有)确定两个输入。也就是说,Git 从这个列表中取出三个东西中的两个:

  • HEAD提交,这是一个真正的提交,包含特殊Git-only、frozen/read-only形式的所有文件;
  • 索引,本质上是提议的下一次提交,并包含所有文件的副本,也是Git-only形式但没有完全冻结;
  • work-tree,其中包含您的普通有用格式的文件。

手头有两个这样的东西,Git 比较它们并告诉您有什么不同。 git status 命令 运行s 作为其工作的一部分,这两个 git diffs 的 both(始终设置某些选项,例如 --name-status--find-renames=50%) 并总结结果:git diff --staged 的输出是 changes staged for commitgit diff 的输出是 更改未暂存提交

您也可以手动 运行 git diff HEAD。这选择 HEAD 和您的 work-tree 作为两个输入:您在参数中明确命名了其中一个 HEAD,另一个是隐含的。将冻结的 HEAD 内容与未冻结的 normal-format、work-tree 内容进行比较,并向您展示差异。同样,Git 正在比较完整快照: 第二个 是实时快照 work-tree,由比较过程拍摄。

(旁白:在我看来,你想做的事情可能是错误的做法。你通常可以通过尽早和经常提交来简化自己的工作。做差异是个好主意,但是您可以从 base-commit 到 tip-commit 执行它们,即跨越提交的跨度,以评估整体结果。然后您可以使用交互式 rebase,或者——我喜欢的方法——额外的分支,来重建较小的提交会变成更明智的一系列提交。但这在很大程度上是个人偏好。听起来你的 Xcode 工具可以比现在更好地工作;你使用它的方式不是 错了,实在太局限了。)

这是如何工作的以及为什么工作

我开始写更长的 post 但它失控了,所以我只想说:请参阅我的其他一些 Whosebug 答案,了解 Git 如何从索引中提交,不是 work-tree,以及这些提交如何更新附加了 HEAD 的分支名称。

Git的合并本身也是一个很大的话题。但是,我们可以说合并使用 index 来实现其合并结果, work-tree 起次要作用,主要是为了存在合并冲突的情况。

真正的合并——也就是说,git merge 执行完整的 three-way 合并,最后将进行 merge commit[= 类型的新提交153=]—使用 two parents 进行新提交。这两个 parents 是计算合并结果的三个输入中的两个。第三个输入由其他两个隐含;这是 merge bas。 Git 使用提交图自行找到合并基础。 Git 然后将合并基础与 --ours--theirs 分支提示进行比较,就好像使用两个 git diff --find-renames 命令:一个从基础到我们的,一个从基础到他们的。

忽略添加、删除或重命名文件等问题,比较这三个提交会生成一个列表,其中列出了谁更改了哪些文件。如果 我们 更改了一些他们没有更改的文件,或者 他们 更改了一些我们没有更改的文件,合并这些文件很容易:Git 可以拿我们的文件,或者他们的文件。如果我们都没有更改文件,Git 可以采用文件的三个版本中的任何一个(合并基础、我们的或他们的)。

对于困难的情况——我们都更改了一些文件——Git 从合并基础开始,然后 组合 我们的更改和他们的更改并应用合并的merge-base 副本的更改。如果我们的更改和他们的更改触及不同的 ,则组合很简单,并且 Git 声明结果成功(即使它在现实中没有意义)。如果我们和他们都更改了 相同的 行,Git 声明合并冲突。

合并冲突案例是最混乱的。这里,Git 将 所有三个输入文件 留在索引中,并将冲突合并写入 work-tree。作为人类 运行ning git merge,您的工作是根据您的喜好提出正​​确的合并。您可以使用文件的三个索引副本,或 work-tree 副本,或所有这些。您自己解决合并,然后使用 git add 将正确的结果写入索引,将三个未合并的副本替换为一个合并的副本。这解决了这个冲突;解决所有冲突后,即可完成合并。

如果你 运行 git merge--no-commit,Git 将尽可能多地自行合并——这可能是全部——然后停止,就像合并冲突一样。如果没有冲突,这会使索引和 work-tree 都处于 ready-to-commit 状态。 (如果有冲突,--no-commit 选项无效:Git 已经要停止了,留下了烂摊子,现在仍然要停下来,留下了烂摊子让你修复。)

不过,在所有这些情况下,Git 都会在 .git 目录中留下一个文件,告诉 Git 下一个 commit——无论是来自 git merge --continue 还是 git commit——应该有 two parents。这使得新提交成为 merge commit,它将两个分支联系在一起:

             I--J   [master pointed to J when we started]
            /    \
...--F--G--H      M   <-- master (HEAD)
            \    /
             K--L   <-- branch

这不是你想要的。所以,我们使用 git merge --squash,它告诉 Git:照常进行合并工作,但不是进行 merge 提交,而是安排下一次提交是 普通 提交。 也就是说,如果 Git 进行新提交 M,它看起来像这样:

             I--J--M   <-- master (HEAD)
            /
...--F--G--H
            \
             K--L   <-- branch

没有特别好的理由,--squash 禁止提交,即使合并进行得很顺利。所以在 git merge --squash 之后,索引和 work-tree 都更新了,但是你可以对 work-tree 进行更多的更改,使用 git add 将更新的文件复制到索引中,并且只完成后,使用 git commit 进行新提交。

(对于更简单的合并,其中 Git 默认执行 fast-forward,--squash 也会抑制 fast-forward 模式,就好像 git merge --no-ff。所以 --squash,没有 --no-ff 也没有 --no-commit,就足够了。)

最后的 git reset,如果使用和需要,只需将 HEAD 提交复制回索引。也就是说,您可能希望您的工具将 HEAD 提交与 work-tree 提交进行比较,就好像您在 运行 宁 git diff HEAD 一样。如果该工具将索引与 work-tree 进行比较,则在 --squash 操作更新后,您可以通过 de-updating 索引匹配 HEAD 来获得相同的效果。

我相信有很多有效的方法可以做到这一点。我想描述一个简短的使用 补丁 从功能分支转移您的代码更改。

cd $the-root-of-your-project
git checkout feature-branch
git format-patch <the-commit-since-you-began-the-feature>

这将为每次提交创建一个 补丁程序 文件。 (提示:您需要在最后一个命令中输入的提交是您要传输的第一个 之前 的提交。请参阅 sincerevision range https://git-scm.com/docs/git-format-patch 了解详情。)

其次,我建议你 cat 将所有 *.patch 文件合并。

第三,将那个累积的patch文件的变化放到master的索引中:

git checkout master
patch -p1 < accumulated.patch
git diff
git add .
...

patch 是来自 bash 并且 -p1 是去除 git-specific 路径前缀所必需的补丁文件关闭。