如何以编程方式快进单个git提交?

时间:2010-05-23 04:18:01

标签: git git-pull fast-forward

我定期从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次提交”?< / p>

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

3 个答案:

答案 0 :(得分:10)

替代方法

你提到你正在为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 可以快进到上游,但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将如下所示(拓扑与以前相同,只有标签已移动):

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

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

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

答案 1 :(得分:9)

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

这是一种确定远程分支是否可以快速转发的安全方法:

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

当前提交之前的提交数等于$ 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调用来执行此操作。

答案 2 :(得分:7)

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

$ git fetch
$ git status | sed -n 2p
# Your branch is behind 'origin/master' by 23 commits, and can be fast-forwarded.
$ git reset origin/master~22 > /dev/null
$ git status | sed -n 2p
# Your branch is behind 'origin/master' by 22 commits, and can be fast-forwarded.