我可以 git 一次提交到父分支 + 变基吗?

Can I git commit to parent branch + rebase in one go?

我有一个分支 develop,并从中分支出一个名为 next 的大功能。

next 中,有一个连续的过程来编辑最初来自 develop 的文件,以及专门在 next 中创建的新文件。

TL;DR: 您可以跳过下一节直接进入问题。


(a) next 中从 develop 继承的文件的更改始终向后兼容,并且始终(也)在那里提交。

(b)同时,develop本身也在进行开发,这些变化在next中应该也能看到。因此,next 时不时地重新基于 develop

提交回 develop 会导致合并冲突,因为 (b),而变基会导致合并冲突,因为 (a)。后者可以通过 -X theirs 解决,因为冲突已在第一步中解决。

但这效率不高。


有没有办法自动:

  1. 将原本来自父分支的文件提交到父分支(例如develop
  2. 将父分支上不存在的文件提交到当前分支(例如next
  3. 将当前分支重新设置为父分支的基础以使更改冒泡?

可能的复杂解

我可以想象一些 'simple'(r) git-foo 是可能的,因为这个愿望不是唯一的。但如果没有,一些 bash 脚本可能是可能的。例如,下面的命令:

git ls-tree -r develop --name-only

将显示确实在 develop 中的文件列表。不在该列表中的文件将仅在 next.

我想如果一切都失败了,我可以创建一个 shellscript 来:

  1. 首先将 develop 上存在的所有文件提交到 develop
  2. 然后将所有其他文件提交到 next
  3. develop
  4. 上重新设置 next

但这并不像听起来那么简单。

这将需要分多个步骤完成。提交 next,然后变基 and/or 合并以将所有内容返回到 develop。 Git 没有能力一次完成所有这些

一如既往,我首先建议您先绘制(已知的)"before" 和(期望的)"after" 提交 graph。好吧,也许是第二个:第一个,决定是否要允许这些操作带有 "dirty work tree" and/or "dirty index"(一个与当前不匹配的, HEAD, 提交).

不过,在这种情况下,您可能希望使用关于每个快照 tree 对象的二级标签来扩充图表,尤其是举例说明您希望如何处理(现有的)文件) 有问题的提交,vs 在整个过程开始时可能位于索引 and/or 工作树中的文件。这可能有助于决定如何(根据 Git 机制和可编写脚本的命令)继续进行。

一旦你绘制了这些,请记住,Git 是一大箱工具,你可以随心所欲地使用。这些工具包括以下功能:

  • A new 提交作为其父提交,任何提交是 HEAD,以及索引 at 中的任何内容那一刻作为它的树。但是,另请参阅 git commit -a,它将从工作树中获取索引中路径为 already 的文件的新文件版本,或者从索引路径中删除丢失的文件来自工作树。

    当然,仅在索引或工作树中的文件很容易被删除。提交现有索引使其与任何提交一样永久(见下文)。所以一般来说,如果你有未提交的工作,要做的第一件事就是提交它。新提交将旧提交作为其父提交,新提交现在位于 HEAD 中——或者,通常在 HEAD 引用的分支名称中(以便 git rev-parse HEAD 找到新 ID,但这样做的 意思是 git rev-parse refs/heads/next 或其他)。

  • Branch names 只是指向提示提交。然后每个提交都指向它的直接父级(或者对于合并提交,父级,复数)。以命名的 tip 提交结尾的提交链也称为分支(参见 What exactly do we mean by "branch"?)。如果方便的话,您可以使用分离的 HEAD 来创建没有名称 yet 的新提交:您只需要将它们的 ID 保存在某处(使用 git rev-parse HEAD 一旦它们被创建,将 ID 复制到 shell 变量中,例如) s 以便您可以命名它们。您必须在两周的默认修剪期限内为它们指定永久名称(通常是分支名称),以防止它们被垃圾收集收割。

  • git stash,如果你选择使用它,通过将当前索引保存为提交,然后将当前工作树保存为另一个提交,然后可选择保存未跟踪,甚至未跟踪和忽略的文件作为 third 提交。三个提交中的 None 在 any 分支上;相反,特殊名称 refs/stash 指向这两个或三个提交之一(然后用于查找其他提交,以及 HEADgit stash 两个或三个都)。这些提交 参与 git rebase。如果您需要干净的工作树 and/or 索引,则无需使用 git stash 及其并发症。如果没有,您可能想要它,或者做类似的事情。

  • 运行 git rebase --onto <em>target</em> <em>limit</em> 将复制由 <em>limit</em>..HEAD 选择的提交(但通常会丢弃合并和预先挑选的提交,即,这确实使用了 --cherry-pick --right-only --no-merges <em>limit</em>...HEAD,尽管具体情况有所不同也由 rebase 的 type,包括 -m-i-k-p 参数(如果给定)。 target 默认为作为 limit 参数给出的提交(rebase 文档调用 <upstream> ).通常这是适当和充分的;在您的情况下,这可能是正确的,也可能不是正确的,具体取决于您添加提交的方式和时间。

我以前是怎么做到的

综上所述,我自己以前处理过这样的事情,我将描述我更喜欢这样做的方式。这是否适合您完全是另一个问题。

你有一个 "base branch",在你的情况下 develop,我假设有一个更大的小组正在处理。您还有一个功能或主题分支(您称之为 next,但我通常会给出一个更具体的名称,project-mac or whatever(尽管我本人从未在麻省理工学院工作过......))。我做的是我的分支

  1. project/0(或project-0,我一直不一致):我第一次尝试。这基本上是 git checkout -b project/0 develop,然后修改并提交一段时间。

  2. project/1(或project-1):一些经验教训,我"rebase"在当前的主分支上,通过挑选或重写或其他方式。通常在 0 和 1 之间,没有足够的可挽救代码来使用 git rebase.

  3. 这样的工具
  4. 现在有些部分已经足够扎实了,我将自己提交到主分支(并根据工作等,让他们审查或通过测试或其他)。现在我 project/2 通过在 Git 中做:

    git checkout -b project/2 project/1
    git rebase develop
    

    这会自动抛出已经被干净地挑选到 develop 中的提交(由我作为第 3 步的第一部分),并给我合并 "uncleanly cherry-picked" 或 "uncleanly cherry-picked" 部分的冲突否则与我解决的其他工作冲突。在棘手的情况下,如果我需要记住我做了什么以及我为什么这样做,我可以从那里轻松 git log project/1git show 特定提交。如果我擅长提交消息,它们可以作为必要的提醒。

  5. 冲洗并重复恶心。

请注意,这些分支中的 none 实际上会变基,除了在初始步骤中从旧版本创建新的编号版本时。这意味着它们可以很容易地在多个克隆之间共享,这在必须在不同操作系统上测试某些东西或分布在多个人之间时非常有用。

我知道没有内置方法可以做到这一点,尽管我认为这是很受欢迎的。所以我进行了一些 bash 黑客攻击并想出了这个。

注意只有当您在进行更改之前刚重新设置基础时,这才在没有合并冲突的情况下有效。


为了区分哪些文件应该提交给父 (develop) 分支,哪些文件应该提交给特性 (next),这些是最重要的命令:

  • 列出所有更改、删除、(重命名)和新文件:
    • git ls-files -mdo --exclude-standard
  • 列出存在于父分支(develop)的所有文件:
    • git ls-tree -r develop --name-only
  • 与这些列表相交 以找出父分支中存在哪些更改的文件:
    • sort <(git ls-tree -r develop --name-only | sort) <(git ls-files -mdo --exclude-standard | sort) | uniq -d

所以我们要做的是:

  • Stage 文件我们要提交到父分支 (develop):
    sort <(git ls-tree -r develop --name-only | sort) <(git ls-files -mdo --exclude-standard | sort) | uniq -d | xargs git add
    注意现在是手动添加重命名文件的时候了,因为此时只进行文件删除。
  • Stash (1) 所有东西! 我们想稍后将它们提交到这个分支 (next)。但是保留暂存文件
    git stash --keep-index -u
  • Stash (2) staged 文件。我们想将它们提交到父分支 (develop)。
    git stash
  • 结帐父分支(develop)
    git checkout develop
  • Pop stash(2)
    git stash pop
  • 提交弹出的存储
    git commit -am "<commit message>"
  • 再次检查 功能分支 (next)。
    git checkout next
  • 弹出 剩余的存储空间(1)。
    git stash pop
  • 阶段所有(新)文件
    git add -A
    git commit -m "<commit message>"
  • Rebase 这个特性在它的父级上被禁止 git rebase develop

完成! developnext 现已更新为最新代码。


如果您承诺不重命名文件,您可以安全地将其放入脚本中以自动执行此操作。

mergedowncommit.sh:

#!/usr/bin/env bash
# By Redsandro (http://www.Redsandro.com/)
# 2017-03-03

if [ -z "" ]; then
    echo "No commit message supplied."
    echo "Usage: mergedowncommit.sh <parent> <feature> <commit message>"
    exit 1
fi

sort <(git ls-tree -r "" --name-only | sort) <(git ls-files -mdo --exclude-standard | sort) | uniq -d | xargs git add
git stash --keep-index -u
git stash
git checkout ""
git stash pop
git commit -am ""
git checkout ""
git stash pop
git add -A
git commit -m ""
git rebase ""

if [ $? -eq 0 ]; then
    echo "Looks like it worked. :)"
else
    echo "Looks like some files were not freshly rebased prior to running this script."
    echo "Please manually solve merge conflicts, and run \"git rebase --continue\"."
fi

用作:

mergedowncommit.sh <parent> <feature> <commit message>

例如:

mergedowncommit.sh develop next "This is my commit message"

您可以根据自己的工作流程进行调整。也许添加您最喜欢的 difftool 来解决合并冲突。我可以想象你至少有一个 develop 也在工作并且它包含一个带有版本信息的文件。


注意:使用风险自负。