如何将git pull <REMOTE> <BRANCH> 分解为fetch + merge的步骤?

4
注意:提出这个问题的目的是为了更好地理解git,而不是解决任何特定问题。换句话说,“达到相同目的”的替代方法(即绕开标题中的问题)与本题无关。
该命令为:
git pull

...应该等价于此序列

git fetch
git merge

你是否能够将 git pull <REMOTE> <BRANCH>(即使用明确参数的远程和要拉取的分支)分解为类似于fetch后跟merge的序列?

我想这个命令中的fetch部分会很简单。

git fetch <REMOTE> <BRANCH>

...但是,如果这样做,我无法找到正确的git merge ...命令。


我尝试了一些“显而易见”的方法。例如,如果我运行git branch -r,输出会在分支列表中列出<REMOTE>/<BRANCH>,因此我尝试使用git merge -m 'some message' <REMOTE>/<BRANCH>,但是git回复说Already up-to-date.,并且git-log显示HEAD保持在尝试git merge之前的相同提交上。为了确认这一点,我在git fetch ...git merge ...之间加上了git log ...调用,就像这样:

git log --all --oneline --graph --decorate -10
git fetch <REMOTE> <BRANCH>
git merge -m 'some message' <REMOTE>/<BRANCH>
git log --all --oneline --graph --decorate -10

两个调用 git log ... 生成的输出完全相同,它们都显示本地 <BRANCH> 领先于 <REMOTE>/<BRANCH>
以下模拟示例以 /bin/sh 脚本的形式重现了上述结果。(该脚本在 Ubuntu Linux 上测试通过;你的情况可能有所不同。)
#!/bin/sh

BASEDIR=/tmp/gittest
REMOTENAME=remrepo
REMOTEURL="$BASEDIR/$REMOTENAME"
BRANCHNAME=test
BRANCHNAME=master

rm -rf $REMOTEURL
mkdir -p $REMOTEURL

rm -rf $BASEDIR/clone1 $BASEDIR/clone2

git init --bare -q $REMOTEURL/.git
git clone -q -o $REMOTENAME $REMOTEURL $BASEDIR/clone1
git clone -q -o $REMOTENAME $REMOTEURL $BASEDIR/clone2

pushd $BASEDIR/clone1 >/dev/null
git checkout -qb $BRANCHNAME
echo $RANDOM >> random1.txt
git add .
git commit -qam "$(date -Ins)"
git push -q $REMOTENAME $BRANCHNAME

pushd $BASEDIR/clone2 >/dev/null
git pull -q $REMOTENAME
git checkout -q $BRANCHNAME
echo $RANDOM >> random2.txt
git add .
git commit -qam "$(date -Ins)"
git push -q $REMOTENAME $BRANCHNAME

echo
echo 'git log --all --oneline --decorate --graph :'
git log --all --oneline --decorate --graph
echo

pushd >/dev/null
git checkout -q $BRANCHNAME
echo $RANDOM >> random1.txt
git commit -qam "$(date -Ins)"

echo 'git log --all --oneline --decorate --graph :'
git log --all --oneline --decorate --graph
echo

git fetch -q $REMOTENAME $BRANCHNAME
git merge -m "$(date -Ins)" $REMOTENAME/$BRANCHNAME

echo
echo 'git log --all --oneline --decorate --graph :'
git log --all --oneline --decorate --graph

git pull -q --no-edit $REMOTENAME $BRANCHNAME

echo
echo 'git log --all --oneline --decorate --graph :'
git log --all --oneline --decorate --graph

如果您运行它,输出将类似于以下内容:
warning: You appear to have cloned an empty repository.
warning: You appear to have cloned an empty repository.

git log --all --oneline --decorate --graph :
* 2326793 (HEAD, remrepo/master, master) 2013-03-19T10:56:42,838038000-0400
* 34ea848 2013-03-19T10:56:42,360743000-0400

git log --all --oneline --decorate --graph :
* 81cb43f (HEAD, master) 2013-03-19T10:56:43,057198000-0400
* 34ea848 (remrepo/master) 2013-03-19T10:56:42,360743000-0400

Already up-to-date.

git log --all --oneline --decorate --graph :
* 81cb43f (HEAD, master) 2013-03-19T10:56:43,057198000-0400
* 34ea848 (remrepo/master) 2013-03-19T10:56:42,360743000-0400

git log --all --oneline --decorate --graph :
*   e60b993 (HEAD, master) Merge branch 'master' of /tmp/gittest/remrepo
|\
| * 2326793 2013-03-19T10:56:42,838038000-0400
* | 81cb43f 2013-03-19T10:56:43,057198000-0400
|/
* 34ea848 (remrepo/master) 2013-03-19T10:56:42,360743000-0400

从上面的输出中可以看出:

  1. 最后的fetch + merge序列对git log...的输出没有影响。
  2. git merge命令的输出是Already up-to-date.,即使这不是事实(远程和本地版本库各自分别有一个提交)。
  3. 在这个“合并”之后,本地分支(master)比跟踪分支(remrepo/master)多了一个提交。
  4. 与fetch + merge序列相比,pull做了正确的事情(即更新跟踪分支并执行合并),尽管两组命令收到了完全相同的信息。

1
你可以尝试使用 GIT_TRACE=2 git pull 命令,并查看调试输出。 - twalberg
3个回答

1
作为git pull的手册所述:
将远程仓库的更改合并到当前分支中。在默认模式下,git pull是git fetch后跟git merge FETCH_HEAD的简写。
更精确地说,git pull会使用给定的参数运行git fetch,并调用git merge将检索到的分支头合并到当前分支。使用--rebase时,它会运行git rebase而不是git merge。
基于此(并假设您未使用--rebase),git pull命令应该几乎等同于:
# Fetch the info about the branch from the remote
git fetch <REMOTE> <BRANCH>:<REMOTE>/<BRANCH>

# Switches your working copy to the branch that you would like the
# changes to be merged into
git checkout <LOCAL_BRANCH_NAME>

# Merge the changes from <REMOTE>/<REMOTE_BRANCH_NAME> into your
# currently checked out branch which should <LOCAL_BRANCH_NAME>
# after the previous checkout command
git merge <REMOTE>/<REMOTE_BRANCH_NAME>

通常情况下,从远程获取所有引用信息不会有任何问题(除非您有特定的要求)。如果您同意此操作,则可以在第一次git fetch时不使用任何refspec。

git fetch <REMOTE>

如果您使用了--rebase选项,则git merge命令将被以下git rebase命令替换:
git rebase <REMOTE>/<REMOTE_BRANCH_NAME>

PS:与git merge不同,git rebase也可以接受一个可选的目标分支参数。如果您没有指定,则会使用当前检出的分支。

感谢kostix指出了git fetchlocal-branch:remote-branch语法。


正如我在文章中写的那样,当我执行 git merge <REMOTE>/<REMOTE_BRANCH_NAME> 时,没有任何反应(除了 "Already up-to-date." 的输出)。 - kjo
是的,绝对没错,当前分支(带星号的)是 LOCAL_BRANCH_NAME。(顺便说一下,在这种情况下,本地和远程分支具有相同的“基本名称”。) - kjo
我添加了一个脚本,可以在玩具示例中重现我所描述的内容。 - kjo
@kjo,我认为问题在于你的示例中使用了git fetch <remote> <branch>:仅由分支名称组成的refspec被解释为远程分支的名称,它被获取并将其最新提交的SHA-1名称写入.git/FETCH_HEAD文件--由于refspec缺少“:destination”部分(用于更新本地分支),所以没有任何更新。因此,基本上你的git fetch只是一个试运行。请重新阅读git-fetch手册 - kostix
@kjo - 我认为kostix是正确的。只需将git fetch命令替换为git fetch $REMOTENAME $BRANCHNAME:$REMOTENAME/$BRANCHNAME,然后跟随一个git merge,我看到与git pull输出类似的结果。另一种选择是我已经提到的,在fetch命令中删除branchname,仅运行git fetch $REMOTENAME也可以工作。 - Tuxdude
显示剩余6条评论

1
我认为问题在于你的玩具例子使用了git fetch <repository> <branch>,其中只包含一个分支名称的refspec被解释为远程分支的名称,被获取并且它的末尾提交的SHA-1名称被写入到.git/FETCH_HEAD文件中;因为缺少":destination"部分(用于更新本地分支),所以没有更新本地分支。所以基本上你的git fetch只是模拟运行。
请重新阅读git-fetch手册

我感到困惑的是,git pull <REMOTE> <BRANCH> 看起来似乎做了正确的事情,而不需要指定 :destination 部分... 这非常令人困惑。 - kjo

0

简短的回答是...

git pull <remote> <branch>

具有相同的功能:

git fetch <remote>
git merge <remote>/<remote_branch>

如果你收到的回复是“一切都是最新的”,那么你要么没有运行git fetch <remote>,要么你确实是最新的。


编辑: 阅读了您在另一个答案上的一些评论后,听起来您已经运行了git merge <remote>/<branch>命令,但并没有得到更新,即使您已经验证它们处于不同的提交状态。请尝试以下步骤(可能会有重复,但还是试一试)。
git log -1 <local_branch>
git log -1 <remote>/<remote_branch>

如果这两个不相同,那么尝试以下操作。
(如果它们相同,那么你已经是最新的了!)

git checkout <local_branch>
git merge <remote>/<remote_branch>

如果它仍然显示您已经是最新的,那么也许您本地有需要推送的提交,这可能是为什么您看到不同的提交的原因。
git push <remote> <local_branch>:<remote_branch>

使用分支:分支的格式只是一种非常具体地说明你要推送什么的方法。它表示将推送:

现在再次检查这些日志...

git log -1 <local_branch>
git log -1 <remote>/<remote_branch>

如果你仍然看到了不同的提交 - 那么你确实遇到了一个边缘情况。尝试复制你在 / 上得到的提交哈希值。然后将其直接合并到你的本地分支中。

git merge <SHA1>

我已经添加了一个脚本,以玩具示例重现了我所描述的内容。 - kjo
你的脚本似乎没有使用我上面指出的命令。git fetch <remote> - 不要指定分支 - 通常没有必要这么具体地获取,它只是更新您的机器,以便了解远程存储库中的内容。 - eddiemoya

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接