【问题标题】:How can I fast-forward a single git commit, programmatically?如何以编程方式快进单个 git 提交?
【发布时间】:2011-02-22 20:32:49
【问题描述】:

我会定期从 git 收到如下所示的消息:

Your branch is behind the tracked remote branch 'local-master/master' 
by 3 commits, and can be fast-forwarded.

我希望能够在 shell 脚本中编写可以执行以下操作的命令:

  1. 如何判断我当前的分支是否可以从它正在跟踪的远程分支快速转发?

  2. 如何知道我的分支“后面”有多少次提交?

  3. 1234563 /p>

(有兴趣的朋友,我正在尝试整理一个高质量的 git/darcs 镜像。)

【问题讨论】:

    标签: git git-pull fast-forward


    【解决方案1】:

    如果当前提交是远程分支头的祖先,则远程分支可以快速转发到本地分支。换句话说,如果远程分支的“单分支历史”包含当前提交(因为如果包含,则可以肯定新提交已提交“到”当前提交)

    所以一个安全的方法来判断远程分支是否可以快进:

    # Convert reference names to commit IDs
    current_commit=$(git rev-parse HEAD)
    remote_commit=$(git rev-parse remote_name/remote_branch_name)
    
    # Call git log so that it prints only commit IDs
    log=$(git log --topo-order --format='%H' $remote_commit | grep $current_commit)
    
    # Check the existence of the current commit in the log
    if [ ! -z "$log" ]
      then echo 'Remote branch can be fast-forwarded!'
    fi
    

    请注意,调用 git log 时没有 --all 参数(将列出所有分支),因此当前提交不可能在“侧分支”上并且仍然打印在输出上。

    当前提交之前的提交数等于 $log 中 $current_commit 之前的行数。

    如果您只想快进一次提交,则取当前提交之前的行(例如,使用 grep -B 1),并将本地分支重置为该提交。

    更新:您可以使用git log commit1..commit2 来确定快进提交的数量:

    if [ ! -z "$log" ]
    then
      # print the number of commits ahead of the current commit
      ff_commits=$(git log --topo-order --format='%H' \
        $current_commit..$remote_commit | wc -l)
      echo "Number of fast-forwarding commits: $ff_commits"
    
      # fast-forward only one commit
      if [ $ff_commits -gt 1 ]
      then
        next_commit=$(git log --topo-order --format='%H' \
          $current_commit..$remote_commit | tail -1)
        git reset --hard $next_commit
      fi
    fi
    

    当然,如果您将第一次调用的结果保存到文件中,您可以通过一次 git log 调用来执行此操作。

    【讨论】:

    • 谢谢;这种方法看起来很有原则。 git-rev-parse 的手册页是我见过的最糟糕的手册之一... +1
    • 嗯,程序的简短描述(“挑选和按摩参数”)真的没有太多信息:D 上面用于将引用名称(HEAD,origin/master)转换为提交 ID。我在代码中添加了一些 cmets,所以现在可能更容易理解了。
    • 感谢您的解释。如果我们用 git merge $next_commit 代替 git reset --hard $next_commit 会怎样?还是我错过了什么?
    • git reset --hard 正是我想要快进一些提交而不是整个分支的东西。谢谢。
    【解决方案2】:

    替代方法

    您提到您正在为 Git 和 Darcs 制作某种镜像。您可以查看 git fast-importgit fast-export 命令,而不是在历史记录中拖动工作树,以查看它们是否提供了更好的方法来管理您需要提取/提供的数据。

    如何判断一个分支是否可以快进到其上游分支

    这有两个部分。首先,您必须知道或确定哪个分支是当前分支的“上游”。然后,一旦您知道如何引用上游,您就可以检查快进的能力。

    为分支寻找上游

    Git 1.7.0 有一种方便的方法来查询一个分支跟踪哪个分支(它的“上游”分支)。 @{upstream} 对象规范语法可用作分支说明符。作为一个简单的名称,它指的是当前签出的分支的上游分支。作为后缀,可用于查找当前未签出的分支的上游分支。

    对于 1.7.0 之前的 Gits,您必须自己解析分支配置选项(branch.name.remotebranch.name.merge)。或者,如果您有一个标准的命名约定,您可以使用它来确定上游分支的名称。

    在这个答案中,我将写 upstream 来引用当前分支上游分支尖端的提交。

    检查快进能力

    当且仅当 A 是 B 的祖先时,提交 A 的分支可以快进到提交 B。

    gyim 展示了一种检查这种情况的方法(列出所有可从 B 访问的提交并在列表中检查 A)。也许检查这种情况的更简单方法是检查 A 是否是 A 和 B 的合并基。

    can_ff() {
        a="$(git rev-parse "$1")" &&
        test "$(git merge-base "$a" "$2")" = "$a"
    }
    if can_ff HEAD local-master/master; then
        echo can ff to local-master/master
    else
        echo CAN NOT ff to local-master/master
    fi
    

    查找“背后提交”的数量

    git rev-list ^HEAD upstream | wc -l
    

    这并不要求 HEAD 可以快进到上游(它只计算 HEAD 落后于上游多远,而不是上游落后于 HEAD 多远)。

    通过一次提交向前推进

    一般来说,可快进的历史记录可能不是线性的。在下面的历史 DAG 中,master 可以快进到 upstream,但 A 和 B 都是来自 master 的“单向提交”通往上游的路。

    ---o---o                      master
           |\
           | A--o--o--o--o--o--o  upstream
            \                 /
             B---o---o---o---o
    

    您可以跟踪一侧,就好像它是一个线性历史一样,但只能跟踪到合并提交的直接祖先。

    修订遍历命令有一个--first-parent 选项,可以很容易地只跟踪导致合并提交的第一个父级的提交。将此与 git reset 结合使用,您可以有效地“向前,一次提交”拖动一个分支。

    git reset --hard "$(git rev-list --first-parent --topo-order --reverse ^HEAD upstream | head -1)"
    

    在对另一个答案的评论中,您表达了对 git reset 的恐惧。如果您担心损坏某些分支,那么您可以使用临时分支或使用分离的 HEAD 作为未命名分支。只要您的工作树是干净的并且您不介意移动分支(或分离的 HEAD),git reset --hard 就不会丢弃任何东西。如果您仍然担心,您应该认真考虑使用 git fast-export,您根本不必接触工作树。

    跟随不同的父母会更困难。您可能必须编写自己的历史漫游器,以便您可以就每次合并的“哪个方向”向它提供建议。

    当您向前移动到距离合并不远的点时,DAG 将如下所示(拓扑与之前相同,只是移动了 master 标签):

    ---o---o--A--o--o--o--o--o    master
           |                  \
           |                   o  upstream
            \                 /
             B---o---o---o---o
    

    此时如果你“向前提交一个提交”,你将移动到合并。这也将“引入”(使 master 可访问)从 B 到合并提交的所有提交。如果您假设“向前提交一次”只会向历史 DAG 添加一次提交,那么这一步将违反该假设。

    您可能需要仔细考虑在这种情况下您真正想要做什么。像这样拖入额外的提交是可以的,或者在处理合并提交之前是否应该有某种机制“返回”到 B 的父级并在该分支上前进?

    【讨论】:

    • 这里有很多很好的信息,尤其是关于git-merge-base,谢谢。其中大部分是我想避免的“陷阱和陷阱”。我认为git-fast-export 与 darcs 开展业务的方式根本不一致,事实上,获得准确信息的唯一方法是一次一步地浏览历史。我希望我使用 repo 的方式将确保我避免这种有问题的情况。 +1
    【解决方案3】:

    这可能不是最优雅的,但它确实有效:

    $ git 获取 $ 混帐状态 | sed -n 2p # 你的分支在 'origin/master' 后面 23 次提交,并且可以快进。 $ git reset origin/master~22 > /dev/null $ 混帐状态 | sed -n 2p # 你的分支在 'origin/master' 后面 22 次提交,并且可以快进。

    【讨论】:

    • 谢谢+1。我有点害怕git reset---我最好非常确定这件事可以快进。有点遗憾git status 的这种行为没有记录在案。
    猜你喜欢
    • 2018-11-10
    • 2020-10-03
    • 2010-11-22
    • 2020-07-23
    • 2018-06-13
    • 1970-01-01
    • 1970-01-01
    • 2014-07-11
    • 2015-08-11
    相关资源
    最近更新 更多