无需使用签出即可合并、更新和拉取Git分支

我在一个有2个分支的项目上工作,A和B。我通常在分支A上工作,并合并分支B中的东西。

git merge origin/branchB

但是,我也想保留分支B的本地副本,因为我可能偶尔会在没有首先与分支A合并的情况下签出分支。

git checkout branchBgit pullgit checkout branchA

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

209810 次浏览

只有当合并是快进时,您才能这样做。如果不是,那么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移动分支一样,除了它还将其记录在重新启动中,就像您实际进行了合并一样。

拜托,拜托,不要对不是快进的东西这样做,否则你只是将你的分支重置为另一个提交。(要检查,请查看git merge-base <branch> <commit>是否给出了分支的SHA1。)

不,没有。目标分支的签出是必要的,以允许您解决冲突等问题(如果Git无法自动合并它们)。

但是,如果合并是快进的,你不需要签出目标分支,因为你实际上不需要合并任何东西——你所要做的就是更新分支以指向新的头引用。你可以用git branch -f做到这一点:

git branch -f branch-b branch-a

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

-f选项代表--force,这意味着branch-b将被覆盖。

注意:一个更安全的选择是使用#0,它只允许快进

这个方法可以这样使用:

git branch -f branch-b branch-b@{Upstream}

或更短

git branch -f branch-b branch-b@{U}

强制更新分支,而不检查它(例如,如果它们在rebase后发散)

正如Amber所说,快进合并是唯一可以做到这一点的情况。任何其他合并都需要经历整个三方合并,应用补丁,解决冲突处理-这意味着需要有文件。

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

#!/bin/bash
_usage() {echo "Usage: git merge-ff <branch> <committish-to-merge>" 1>&2exit 1}
_merge_ff() {branch="$1"commit="$2"
branch_orig_hash="$(git show-ref -s --verify refs/heads/$branch 2> /dev/null)"if [ $? -ne 0 ]; thenecho "Error: unknown branch $branch" 1>&2_usagefi
commit_orig_hash="$(git rev-parse --verify $commit 2> /dev/null)"if [ $? -ne 0 ]; thenecho "Error: unknown revision $commit" 1>&2_usagefi
if [ "$(git symbolic-ref HEAD)" = "refs/heads/$branch" ]; thengit merge $quiet --ff-only "$commit"elseif [ "$(git merge-base $branch_orig_hash $commit_orig_hash)" != "$branch_orig_hash" ]; thenecho "Error: merging $commit into $branch would not be a fast-forward" 1>&2exit 1fiecho "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"; thenif [ -z $quiet ]; thenecho "Fast forward"git diff --stat "$branch@{1}" "$branch"fielseecho "Error: fast forward using update-ref failed" 1>&2fifi}
while getopts "q" opt; docase $opt inq ) quiet="-q";;* ) ;;esacdoneshift $((OPTIND-1))
case $# in2 ) _merge_ff "$1" "$2";;* ) _usageesac

附注:如果有人看到这个脚本有任何问题,请评论!这是一个写了就忘的工作,但我很乐意改进它。

您可以克隆存储库并在新存储库中进行合并。在同一文件系统上,这将硬链接而不是复制大部分数据。最后将结果拉到原始存储库中。

另一个公认相当野蛮的方法是重新创建分支:

git fetch remotegit branch -f localbranch remote/remotebranch

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

在许多情况下(例如合并),您可以只使用远程分支,而无需更新本地跟踪分支。在reflg中添加消息听起来有点矫枉过正,会阻止它更快。为了更容易恢复,请将以下内容添加到您的git配置中

[core]logallrefupdates=true

然后键入

git reflog show mybranch

查看你们分行的近期历史

简短的答案

只要你正在做快进合并,那么你可以简单地使用

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比仅强制移动分支引用更安全,因为只要您不在refspec中使用+git fetch将自动防止意外的非快进。

冗长的答案

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

但是,在快进合并的情况下,这是可能的,因为根据定义,此类合并永远不会导致冲突。要在不首先签出分支的情况下执行此操作,您可以使用git fetch和refspec。

这是一个更新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 fetch语法

如果您希望fetch命令在更新不是快进时失败,那么您只需使用表单的refspec

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

如果你想允许非快进更新,那么你在refspec的前面添加一个+

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

请注意,您可以使用.将本地repo作为“远程”参数传递:

git fetch . <sourceBranch>:<destinationBranch>

的文件

来自#0解释此语法的留档(强调我的):

<refspec>

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

获取匹配#0的远程ref,如果#1不是空字符串,则使用#0快速转发匹配它的本地ref。如果使用可选的加+,即使它不会导致快进更新,本地ref也会更新。

另见

  1. 不接触工作树的Git签出和合并

  2. 在不更改工作目录的情况下合并

输入git前合并

不需要签出目标,git-forward-merge <source> <destination>将源合并到目标分支中。

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

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

在您的情况下,您可以使用

git fetch origin branchB:branchB

它做你想要的(假设合并是快进的)。如果分支无法更新,因为它需要非快进合并,那么这将通过消息安全地失败。

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

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

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

目前,我的上游服务器访问是通过一个缓慢的VPN,所以我定期连接,git fetch更新所有遥控器,然后断开连接。然后,如果,比如说,远程主机已经改变了,我可以这样做

git fetch . remotes/origin/master:master

安全地使我的本地主机更新,即使我目前有一些其他分支签出。不需要网络访问。

我为我每天在项目中遇到的类似用例编写了一个shell函数。这基本上是保持本地分支与公共分支保持最新的快捷方式,例如在打开PR之前开发等。

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

glmh(“这里的git拉取和合并”)将自动checkout branchBpull最新、重新checkout branchAmerge branchB

没有解决需要保留分支A的本地副本的问题,但是可以通过在检出分支B之前添加一个步骤来轻松修改。就像……

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 pullgit 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

注意:这是没有足够强大,可以将分支名称以外的参数传递给git merge

有效做到这一点的另一种方法是:

git fetchgit branch -d branchBgit branch -t branchB origin/branchB

因为它是小写-d,所以只有当数据仍然存在于某处时才会删除它。它类似于@kkoehne的回答,只是它不强制。由于-t,它将再次设置遥控器。

我有一个与OP略有不同的需求,那就是在合并拉取请求后,在develop(或master)上创建一个新功能分支。这可以不用强制在一行程序中完成,但它不会更新本地develop分支。这只是签出一个新分支并让它基于origin/develop的问题:

git checkout -b new-feature origin/develop
git worktree add [-f] [--detach] [--checkout] [--lock] [-b <new-branch>] <path> [<commit-ish>]

你可以尝试git worktree让两个分支并排开放,这听起来可能是你想要的,但与我在这里看到的其他一些答案非常不同。

通过这种方式,您可以在同一个git repo中跟踪两个单独的分支,因此您只需获取一次即可在两个工作树中获取更新(而不必两次git clone并在每个工作树上进行git拉取)

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

当你想要删除它,你可以清理

git worktree remove [-f] <worktree>

只是为了在不检查我使用的主机的情况下拉动主机

git fetch origin master:master

绝对可以在没有git checkout的情况下进行任何合并,即使是非快进合并。@grego的worktree答案是一个很好的提示。对此进行扩展:

cd local_repogit worktree add _master_wt mastercd _master_wtgit pull origin master:mastergit merge --no-ff -m "merging workbranch" my_work_branchcd ..git worktree remove _master_wt

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

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

# Check if you can fast-forwardif git merge-base --is-ancestor a b; thengit update-ref refs/heads/a refs/heads/bexitfi
# Else, create a "merge" commitcommit="$(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 commitgit update-ref refs/heads/a "$commit"

你可以简单地git pull origin branchB到你的branchA,git会为你做的伎俩。

这个问题中缺少一些东西,如果您没有打算处理它,为什么需要签出本地branchB?在您的示例中,您只想在保留branchA的同时更新本地branchB

我最初假设你是对获取origin/branchB这样做的,你已经可以用git fetch这样做了,所以这个答案实际上是基于这个。在你需要处理它之前不需要拉branchB,当你需要从origin/branchB合并时,你总是可以获取origin

如果您想跟踪分支B在某个时间点的位置,您可以从origin/branchB的最新获取中创建一个标记或另一个分支。

因此,您需要从origin/branchB合并的所有内容:

git fetchgit merge origin/branchB

然后下次你需要在branchB上工作:

git checkout branchBgit pull

此时,您将获得更新的本地副本。虽然有一些方法可以在不签出的情况下做到这一点,但这很少有用,在某些情况下可能是不安全的。有一些现有的答案涵盖了这一点。

详细答案:

git pull做了一个提取+合并。下面的两个命令大致相同,其中<remote>通常是origin(默认),远程跟踪分支以<remote>/开头,后跟远程分支名称:

git fetch [<remote>]git merge @{u}

@{u}表示法是为当前分支配置的远程跟踪分支。如果branchB跟踪origin/branchB,那么branchB中的@{u}与键入origin/branchB相同(有关详细信息,请参阅git rev-parse --help)。

由于您已经与origin/branchB合并,因此缺少的只是git fetch(可以从任何分支运行)来更新该远程跟踪分支。

请注意,如果在拉入本地branchB时创建了任何合并,您应该在从branchB拉入后将branchB合并到branchA(并最终将更改推回orign/branchB,但只要它们快进,它们将保持不变)。

请记住,本地branchB不会更新,直到您切换到它并进行实际拉取,但是只要没有添加到此分支的本地提交,它就会保持快进到远程分支。

对于许多GitFlow用户来说,最有用的命令是:

git fetch origin master:master --update-head-okgit fetch origin dev:dev --update-head-ok

--update-head-ok标志允许在devmaster分支上使用相同的命令。

.gitconfig中的一个方便的别名:

[alias]f=!git fetch origin master:master --update-head-ok && git fetch origin dev:dev --update-head-ok