17

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

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

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

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

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

  3. 如何仅通过一次提交快进,例如,我的本地分支将从“落后 3 次提交”变为“落后于 2 次提交”?

(对于那些感兴趣的人,我正在尝试组装一个高质量的 git/darcs 镜像。)

4

3 回答 3

11

替代方法

您提到您正在为 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

查找“背后提交”的数量</h2>
git rev-list ^HEAD upstream | wc -l

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

通过一次提交向前推进

一般来说,可快进的历史可能不是线性的。在下面的历史 DAG 中,master可以快进到upstream ,但是 A 和 B 都是从masterupstream的“一个提交” 。

---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 将如下所示(拓扑与之前相同,只是移动了标签):

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

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

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

于 2010-05-29T06:22:30.340 回答
10

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

所以一个确定远程分支是否可以快进的安全方法:

# 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 参数(将列出所有分支),因此当前提交不可能在“侧分支”上并且仍然打印在输出上。

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

如果您只想快进一次提交,则取当前提交之前的行(例如,使用 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 调用来执行此操作。

于 2010-05-28T22:53:48.097 回答
8

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

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