合并,更新和拉取Git分支,而无需使用签出


627

我在一个有两个分支A和B的项目上工作。我通常在分支A上工作,并合并来自分支B的内容。对于合并,我通常会这样做:

git merge origin/branchB

但是,我也想保留分支B的本地副本,因为有时我可能会在不先与我的分支A合并的情况下签出该分支。为此,我将执行以下操作:

git checkout branchB
git pull
git checkout branchA

有没有一种方法可以在一个命令中完成上述操作,而不必来回切换分支?我应该git update-ref为此使用吗?怎么样?



1
雅各布对第一个相关问题的回答解释了为什么这通常是不可能的。另一个(后验)解释是,您不能在裸仓库中合并,因此很明显它需要工作树。
卡斯卡贝尔

3
@Eric:通常的原因是,对于大型存储库,检出很耗时,并且即使您返回相同的版本,它们也会更新时间戳,因此make认为需要重新构建所有内容。
卡斯卡贝尔

我链接的第二个问题是询问一个不寻常的情况-合并可能是快速进行的,但OP希望使用该--no-ff选项进行合并,因此无论如何都会记录合并提交。如果您对此感兴趣,在那里的答案显示了您可以如何做-不像我在此处发布的答案那么强大,但可以肯定地将两者的优势结合在一起。
卡斯卡贝尔

Answers:


971

简短答案

只要您要进行快速合并,就可以使用

git fetch <remote> <sourceBranch>:<destinationBranch>

例子:

# Merge local branch foo into local branch master,
# without having to checkout master first.
# Here `.` means to use the local repository as the "remote":
git fetch . foo:master

# Merge remote branch origin/foo into local branch foo,
# without having to checkout foo first:
git fetch origin foo:foo

尽管Amber的答案也可以在快进情况下使用,但git fetch以这种方式使用它比强制移动分支引用要安全一些,因为git fetch只要您不在分支机构中使用,它就会自动防止意外的非快进+。 refspec。

长答案

您不能将分支B合并到分支A中,除非先签出A,否则会导致非快进合并。这是因为需要工作副本来解决任何潜在的冲突。

但是,在快速合并的情况下,这是可能的,因为按照定义,此类合并绝不会导致冲突。为此,无需先签出分支,就可以使用git fetchrefspec。

这是一个示例,master如果您feature签出了另一个分支,则会进行更新(不允许进行非快进更改):

git fetch upstream master:master

这种用例非常普遍,以至于您可能想要在git配置文件中为其命名一个别名,如下所示:

[alias]
    sync = !sh -c 'git checkout --quiet HEAD; git fetch upstream master:master; git checkout --quiet -'

该别名的作用如下:

  1. git checkout HEAD:这会使您的工作副本进入分离状态。如果您想在master签出时进行更新,这很有用。我认为有必要这样做,因为否则分支的引用master将不会移动,但是我不记得这是否真的很重要。

  2. git fetch upstream master:master:这会将您的本地人快速master移到与相同的地方upstream/master

  3. git checkout -签出您先前签出的分支(-在这种情况下就是这样做)。

git fetchfor(非)快进合并的语法

如果您希望该fetch命令在更新不快速的情况下失败,那么您只需使用以下形式的refspec

git fetch <remote> <remoteBranch>:<localBranch>

如果要允许非快进更新,则+在refspec的前面添加一个:

git fetch <remote> +<remoteBranch>:<localBranch>

请注意,您可以使用以下命令将本地存储库作为“远程”参数传递.

git fetch . <sourceBranch>:<destinationBranch>

文档资料

git fetch解释该语法文档中(重点是我的):

<refspec>

<refspec>参数的格式是可选的加号+,其后是源ref <src>,然后是冒号:,然后是目标ref <dst>

<src>获取匹配的远程引用,如果<dst>不是空字符串,则使用对其进行快速转发<src>。如果使用了可选的加号+,则本地引用会更新,即使它不会导致快速转发更新也是如此。

也可以看看

  1. Git检出并合并而不触及工作树

  2. 合并而不更改工作目录


3
git checkout --quiet HEADgit checkout --quiet --detach作为GIT中的1.7.5。
拉法

6
我发现我必须做:git fetch . origin/foo:foo将我的本地foo更新为我的本地origin / foo
weston

3
长答案中是否包含“ git checkout HEAD --quiet”和“ git checkout --quiet-”部分,而不是简短答案?我想这是因为即使您可以进行git pull,也可以在您退出master时运行该脚本?
肖恩

8
为什么在这里“获取”命令以进行“合并” ...这根本没有意义;如果'pull'是'fetch',然后是'merge',则必须有一个更合乎逻辑的'merge --ff-only'等价物,它会在本地从'origin / branch'更新'branch',因为'fetch'具有已经运行了。
Ed Randall

1
谢谢你的git checkout -把戏!就像一样简单cd -
骏马

84

不,那里没有。为了使您能够解决冲突,必须对目标分支进行检出(如果Git无法自动合并它们)。

但是,如果合并是快速进行的合并,则无需签出目标分支,因为您实际上不需要合并任何内容-您要做的就是更新分支以指向目标分支。新头号 您可以使用git branch -f

git branch -f branch-b branch-a

将更新branch-b为指向的头部branch-a

-f选项代表--force,这意味着使用它时必须小心。

除非您完全确定合并将是快速进行的,否则不要使用它。


46
请非常小心,不要这样做,除非您完全确定合并将是快速的!不想在以后意识到您放错了提交。
卡斯卡贝尔

@FuadSaud不完全是。git reset仅适用于当前已签出的分支。
2014年

9
相同的结果(快进)通过git fetch upstream branch-b:branch-b从此答案中得出)获得。
奥利弗

6
要扩展@Oliver的注释,您还可以执行git fetch <remote> B:A,其中B和A是完全不同的分支,但是B可以快速合并到A中。您还可以使用.远程别名将本地存储库作为“远程”传递:git fetch . B:A

8
OP的问题非常清楚地表明合并确实是快速进行的。branch -f就像您指出的那样,这很危险。所以不要使用它!使用fetch origin branchB:branchB,如果合并不快进,则会安全失败。
Bennett McElwee

30

正如Amber所说,快进合并是可以想象的唯一情况。可以想象,任何其他合并都需要经历整个三方合并,应用补丁程序,解决冲突问题-这意味着周围需要文件。

我碰巧有个脚本可以用于此目的:进行快速合并而无需触及工作树(除非您要合并到HEAD中)。这有点长,因为它至少有点健壮-它检查以确保合并将是快进的,然后在不检出分支的情况下执行合并,但产生的结果与您产生的结果相同-您会看到diff --stat更改摘要,并且reflog中的条目完全类似于快速前进合并,而不是使用时获得的“重置” branch -f。如果命名它git-merge-ff并将其放在bin目录中,则可以将其称为git命令:git merge-ff

#!/bin/bash

_usage() {
    echo "Usage: git merge-ff <branch> <committish-to-merge>" 1>&2
    exit 1
}

_merge_ff() {
    branch="$1"
    commit="$2"

    branch_orig_hash="$(git show-ref -s --verify refs/heads/$branch 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown branch $branch" 1>&2
        _usage
    fi

    commit_orig_hash="$(git rev-parse --verify $commit 2> /dev/null)"
    if [ $? -ne 0 ]; then
        echo "Error: unknown revision $commit" 1>&2
        _usage
    fi

    if [ "$(git symbolic-ref HEAD)" = "refs/heads/$branch" ]; then
        git merge $quiet --ff-only "$commit"
    else
        if [ "$(git merge-base $branch_orig_hash $commit_orig_hash)" != "$branch_orig_hash" ]; then
            echo "Error: merging $commit into $branch would not be a fast-forward" 1>&2
            exit 1
        fi
        echo "Updating ${branch_orig_hash:0:7}..${commit_orig_hash:0:7}"
        if git update-ref -m "merge $commit: Fast forward" "refs/heads/$branch" "$commit_orig_hash" "$branch_orig_hash"; then
            if [ -z $quiet ]; then
                echo "Fast forward"
                git diff --stat "$branch@{1}" "$branch"
            fi
        else
            echo "Error: fast forward using update-ref failed" 1>&2
        fi
    fi
}

while getopts "q" opt; do
    case $opt in
        q ) quiet="-q";;
        * ) ;;
    esac
done
shift $((OPTIND-1))

case $# in
    2 ) _merge_ff "$1" "$2";;
    * ) _usage
esac

PS:如果有人看到该脚本有任何问题,请发表评论!这项工作是一劳永逸的,但我很乐意加以改进。


您可能对stackoverflow.com/a/5148202/717355感兴趣以进行比较
Philip Oakley

@PhilipOakley快速浏览一下,它的核心与我的完全一样。但是我的并不是硬编码到一对分支上的,它具有更多的错误处理功能,它可以模拟git-merge的输出,并且在您实际上在分支上调用它时,它的含义是正确的。
卡斯卡贝尔2012年

我的文件夹中确实有您的文件夹;-)我只是忘记了它,而是四处看看,看到了那个脚本,它提示了我的回忆!我的副本确实具有此链接,并# or git branch -f localbranch remote/remotebranch让我想起了来源和选项。给您对另一个链接+1的评论。
菲利普·奥克利

3
+1,也可以默认"$branch@{u}"为committish合并,以获得上游分支(从kernel.org/pub/software/scm/git/docs/gitrevisions.html
OriP的

谢谢!您是否有机会在答案中举一个简单的例子?
nmr 2013年

20

仅当合并是快进时,才可以执行此操作。如果不是,那么git需要将文件检出,以便可以合并它们!

这样做只是为了快进

git fetch <branch that would be pulled for branchB>
git update-ref -m "merge <commit>: Fast forward" refs/heads/<branch> <commit>

<commit>获取的提交在哪里,您想要快速转发到的提交。这基本上就像git branch -f用来移动分支,除了它还会将其记录在reflog中,就像您实际上进行了合并一样。

拜托,拜托,不要为非快进操作执行此操作,否则您将只是将分支重置为其他提交。(要检查,请查看是否git merge-base <branch> <commit>给出分支的SHA1。)


4
如果无法快速前进,是否有办法使其失败?
gman 2012年

2
您可以使用@gman git merge-base --is-ancestor <A> <B>。“ B”是需要合并为“ A”的事物。示例是A = master和B = develop,以确保将development快进到master。注意:如果不可行,则以0存在;如果不是,则以1存在。
eddiemoya 2014年

1
没有在git-scm中记录,但是在kernal.org上。 kernel.org/pub/software/scm/git/docs/git-merge-base.html
eddiemoya 2014年

快速总结了我正在谈论的内容(实际上并没有对其进行测试,但应该可以使您大为受益)。gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ----作为一个补充说明,我手头很方便,我有一个脚本可以检查ff,如果没有的话,则可以让您先重新设置所需的分支-然后ff合并-所有都没有检查任何东西。
eddiemoya 2014年

12

您可以使用

git fetch origin branchB:branchB

哪个做您想要的(假设合并是快进的)。如果由于需要非快进合并而无法更新分支,则此操作会安全地失败并显示一条消息。

这种形式的获取还有一些更有用的选项:

git fetch <remote> <sourceBranch>:<destinationBranch>

请注意,它<remote> 可以是本地存储库,也<sourceBranch>可以是跟踪分支。因此,即使没有签出,您也可以更新本地分支,而无需访问网络

目前,我的上游服务器访问是通过慢速VPN进行的,因此我会定期连接,git fetch更新所有远程服务器,然后断开连接。然后,例如,如果远程主服务器已更改,我可以

git fetch . remotes/origin/master:master

即使我目前有其他分支结帐,也可以安全地更新我的本地管理员。无需网络访问。


11

另一种公认的蛮横方式是重新创建分支:

git fetch remote
git branch -f localbranch remote/remotebranch

这会丢弃本地过时的分支,并重新创建一个具有相同名称的分支,因此请谨慎使用...


现在,我刚刚看到原始答案已经提到了branch -f ...不过,对于最初描述的用例,在reflog中进行合并没有任何好处。
kkoehne 2011年

7

您可以克隆存储库并在新存储库中进行合并。在同一文件系统上,这将硬链接而不是复制大多数数据。通过将结果拉入原始回购来完成。


4

输入git-forward-merge

无需签出目的地,git-forward-merge <source> <destination>将源合并到目的地分支中。

https://github.com/schuyler1d/git-forward-merge

仅适用于自动合并,如果有冲突,则需要使用常规合并。


2
我认为这比git fetch <remote> <source>:<destination>更好,因为快速前进是合并而不是获取的操作,并且更容易编写。不好的是,它不在默认的git中。
Binarian

4

在许多情况下(例如合并),您可以仅使用远程分支,而不必更新本地跟踪分支。在reflog中添加消息听起来像是过分杀戮,并且会阻止它更快。为了更容易恢复,请将以下内容添加到您的git配置中

[core]
    logallrefupdates=true

然后输入

git reflog show mybranch

查看分支机构的最近历史记录


我认为该节一定[core]不是[user]吗?(默认情况下,用于带工作区的
仓库

3

我为每天在项目中遇到的类似用例编写了一个shell函数。这基本上是一种捷径,用于使本地分支与常见分支(例如在打开PR之前进行开发)保持最新。

即使您不想使用checkout,也可以发布此代码,以防其他人不介意该约束。

glmh(“ git pull and merge here”)将自动checkout branchBpull最新,重新checkout branchAmerge branchB

不能满足保留branchA本地副本的需要,但是可以通过在签出branchB之前添加一个步骤来轻松地对其进行修改。就像是...

git branch ${branchA}-no-branchB ${branchA}

对于简单的快进合并,这会跳到提交消息提示。

对于非快速合并,这会将您的分支置于冲突解决状态(您可能需要干预)。

要进行设置,请添加到.bashrc.zshrc,等等:

glmh() {
    branchB=$1
    [ $# -eq 0 ] && { branchB="develop" }
    branchA="$(git branch | grep '*' | sed 's/* //g')"
    git checkout ${branchB} && git pull
    git checkout ${branchA} && git merge ${branchB} 
}

用法:

# No argument given, will assume "develop"
> glmh

# Pass an argument to pull and merge a specific branch
> glmh your-other-branch

注意:这不够健壮,无法将分支名称之外的args移交给git merge


2

有效执行此操作的另一种方法是:

git fetch
git branch -d branchB
git branch -t branchB origin/branchB

因为它是小写字母-d,所以只有在数据仍然存在的地方,它才会删除它。它不强制,类似于@kkoehne的答案。由于-t它会再次设置遥控器。

我与OP的需求略有不同,OP是在合并拉取请求之后创建一个新的功能分支develop(或master)。可以单线完成,而不用用力,但是它不会更新本地develop分支。只需签出一个新分支并基于它即可origin/develop

git checkout -b new-feature origin/develop


1

如果没有,完全可以进行任何合并,甚至是非快进合并git checkoutworktree@grego 的答案是一个很好的提示。对此进行扩展:

cd local_repo
git worktree add _master_wt master
cd _master_wt
git pull origin master:master
git merge --no-ff -m "merging workbranch" my_work_branch
cd ..
git worktree remove _master_wt

现在,您已将本地工作分支合并到本地master分支,而无需切换结帐。


1

如果要与要合并的分支之一保持同一棵树(即不是真正的“合并”),则可以这样做。

# Check if you can fast-forward
if git merge-base --is-ancestor a b; then
    git update-ref refs/heads/a refs/heads/b
    exit
fi

# Else, create a "merge" commit
commit="$(git commit-tree -p a -p b -m "merge b into a" "$(git show -s --pretty=format:%T b)")"
# And update the branch to point to that commit
git update-ref refs/heads/a "$commit"

1
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

您可以尝试git worktree并排打开两个分支,这听起来像您想要的,但与我在这里看到的其他一些答案有很大不同。

这样,您可以在同一个git repo中跟踪两个独立的分支,因此您只需获取一次即可在两个工作树中获取更新(而不是必须git克隆两次并git pull一次)

Worktree将为您的代码创建一个新的工作目录,您可以在其中同时签出一个不同的分支,而不用交换分支。

当您要删除它时,可以使用

git worktree remove [-f] <worktree>
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.