ChatGPT解决这个技术问题 Extra ChatGPT

在不使用结帐的情况下合并、更新和拉取 Git 分支

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

git merge origin/branchB

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

git checkout branchB
git pull
git checkout branchA

有没有办法在一个命令中完成上述操作,而不必来回切换分支?我应该为此使用 git update-ref 吗?如何?

Jakub's answer 第一个链接问题解释了为什么这通常是不可能的。另一个(后验)解释是你不能合并到一个裸仓库中,所以很明显它需要工作树。
@Eric:常见的原因是大型仓库的结帐很耗时,并且即使您返回相同的版本,它们也会更新时间戳,因此 make 认为一切都需要重建。
我链接的第二个问题是询问一个不寻常的情况 - 可能快进的合并,但 OP 想要使用 --no-ff 选项进行合并,这会导致无论如何都会记录合并提交.如果您对此感兴趣,my answer 将展示您如何做到这一点 - 不像我在此处发布的答案那么强大,但两者的优势当然可以结合起来。

C
Community

简短的回答

只要您进行快进合并,您就可以简单地使用

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's answer 也适用于快进的情况,但以这种方式使用 git fetch 比仅强制移动分支引用更安全一些,因为只要 git fetch 会自动防止意外的非快进您没有在 refspec 中使用 +

长答案

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

但是,在快进合并的情况下,这是可能的,因为根据定义,这种合并永远不会导致冲突。要在不先检查分支的情况下执行此操作,您可以将 git fetch 与 refspec 一起使用。

如果您已签出另一个分支 feature,则以下是更新 master(禁止非快进更改)的示例:

git fetch upstream master:master

这个用例很常见,你可能想在你的 git 配置文件中为它创建一个别名,就像这个:

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

这个别名的作用如下:

git checkout HEAD:这会将您的工作副本置于分离头状态。如果您想在碰巧签出 master 时更新它,这很有用。我认为这是必要的,因为否则 master 的分支引用不会移动,但我不记得这是否真的是我的头脑。 git fetch upstream master:master:这会将您的本地 master 快速转发到与 upstream/master 相同的位置。 git checkout - 签出你之前签出的分支(这就是 - 在这种情况下所做的)。

用于(非)快进合并的 git fetch 语法

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

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

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

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

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

git fetch . <sourceBranch>:<destinationBranch>

文档

git fetch documentation that explains this syntax(强调我的):

参数的格式是可选的加号 +,后跟源 ref ,后跟冒号 :,后跟目标 ref 。获取匹配 的远程 ref,如果 不是空字符串,则匹配它的本地 ref 使用 快进。如果使用可选的加号 +,则更新本地 ref,即使它不会导致快进更新。

也可以看看

Git结帐和合并而不接触工作树合并而不更改工作目录


从 Git 1.7.5 开始,git checkout --quiet HEADgit checkout --quiet --detach
我发现我必须这样做:git fetch . origin/foo:foo 将我的本地 foo 更新为我的本地 origin/foo
“git checkout HEAD --quiet”和“git checkout --quiet -”部分包含在长答案中,而不是短答案中是否有原因?我想这是因为脚本可能会在您签出 master 时运行,即使您可以只执行 git pull?
git fetch upstream master:master 太棒了!因为我不必先签出,而且在我的存储库中签出是一项昂贵的操作。
为什么“获取”命令在这里执行“合并”......这根本没有意义;如果 'pull' 是 'fetch' 后跟 'merge',则必须有一个更合乎逻辑的 'merge --ff-only' 等效项,它会在本地从 'origin/branch' 更新 'branch',假设 'fetch' 有已经运行了。
C
Community

不,那里没有。必须检查目标分支才能解决冲突等问题(如果 Git 无法自动合并它们)。

但是,如果合并是快进的,则不需要检查目标分支,因为您实际上不需要合并任何东西 - 您所要做的就是更新分支以指向新的首席裁判。您可以使用 git branch -f 执行此操作:

git branch -f branch-b branch-a

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

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

除非您绝对确定合并将快进,否则不要使用它。


除非您绝对确定合并将是快进,否则请非常小心不要这样做!不想以后意识到你放错了提交。
git fetch upstream branch-b:branch-b(采用 from this answer)实现了相同的结果(快进)。
要扩展@Oliver 的评论,您还可以执行 git fetch <remote> B:A,其中 B 和 A 是完全不同的分支,但 B 可以快进合并到 A。您也可以使用 {2 将本地存储库作为“远程”传递} 作为远程别名:git fetch . B:A
OP 的问题清楚地表明,合并确实是快进的。正如您所指出的,无论如何,branch -f 可能很危险。 所以不要使用它! 使用 fetch origin branchB:branchB,如果合并不是快进的,它将安全失败。
我不同意将 --force 用于日常工作的建议。
C
Cascabel

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

我碰巧有一个脚本用于此目的:在不接触工作树的情况下进行快进合并(除非您要合并到 HEAD 中)。它有点长,因为它至少有点健壮——它检查以确保合并是快进的,然后在不检查分支的情况下执行它,但产生的结果与你的结果相同——你会看到diff --stat 更改摘要,引用日志中的条目与快进合并完全一样,而不是使用 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 感兴趣以进行比较
@PhilipOakley 快速浏览一下,它的核心与我的完全相同。但是我的并没有硬编码到一对分支,它有更多的错误处理,它模拟了 git-merge 的输出,如果你在分支上调用它,它就会按照你的意思做。
我的 \bin 目录中确实有你的 ;-) 我只是忘记了它,正在环顾四周,看到那个脚本,它促使我回忆!我的副本确实有此链接和 # or git branch -f localbranch remote/remotebranch,以提醒我来源和选项。给您对其他链接的评论 +1。
+1,也可以默认为 "$branch@{u}" 作为提交合并以获取上游分支(来自 kernel.org/pub/software/scm/git/docs/gitrevisions.html
感谢您的精彩剧本(8 年后仍然相关)!我做了 2 个小改进:使用 git show-ref --abbrev ... 而不是硬编码 0:7,因为现代版本的 Git 默认使用更长的 SHA-1 前缀,因此它使输出更加一致;如果实际上根本没有更改,还要检查 $branch_orig_hash != $commit_orig_hash 以避免误导 reflog 条目和差异输出。
C
Cascabel

只有当合并是快进时,您才能执行此操作。如果不是,那么 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。)


如果不能快进,有没有办法让它失败?
@gman 你可以使用 git merge-base --is-ancestor <A> <B> 。 “B”是需要合并到“A”中的东西。示例是 A=master 和 B=develop,确保 develop 可以快速转发到 master。注意:如果它不是 ff-able 则以 0 存在,如果是则以 1 存在。
git-scm 中没有记录,但它在 kernal.org 上。 kernel.org/pub/software/scm/git/docs/git-merge-base.html
快速总结一下我在说什么(实际上并没有测试它,但应该能让你大部分时间到达那里)。 gist.github.com/eddiemoya/ad4285b2d8a6bdabf432 ---- 作为旁注,我有大部分方便,我有一个检查 ff 的脚本,如果没有,您可以先重新设置所需的分支 - 然后 ff 合并 - 所有这些都不需要检查任何内容。
B
Bennett McElwee

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

git fetch origin branchB:branchB

可以满足您的要求(假设合并是快进的)。如果由于需要非快进合并而无法更新分支,则此操作将安全地失败并显示一条消息。

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

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

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

目前,我的上游服务器访问是通过慢速 VPN 进行的,因此我会定期连接 git fetch 以更新所有遥控器,然后断开连接。那么,如果远程主控发生了变化,我可以做

git fetch . remotes/origin/master:master

即使我目前有其他一些分支已签出,也可以安全地使我的本地 master 保持最新状态。无需网络访问。


k
kkoehne

另一种公认的非常粗暴的方法是重新创建分支:

git fetch remote
git branch -f localbranch remote/remotebranch

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


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

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


l
lkraider

输入 git-forward-merge:

不需要结帐目的地, git-forward-merge 将源合并到目标分支。

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

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


我认为这比 git fetch : 更好,因为快进是合并而不是获取的操作,并且更容易编写。但不好的是,它不在默认的 git 中。
T
Thomas Guyot-Sionnest

我在这个问题中遗漏了一些东西,如果您不打算处理它,为什么需要签出本地 branchB?在您的示例中,您只想在保持在 branchA 的同时更新本地 branchB

我最初假设您这样做是为了 fetch origin/branchB,您已经可以使用 git fetch 进行此操作,所以这个答案确实基于此。在您需要处理它之前,无需拉取 branchB,当您需要从 origin/branchB 合并时,您始终可以获取 origin

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

因此,您需要从 origin/branchB 合并:

git fetch
git merge origin/branchB

那么下次您需要处理 branchB 时:

git checkout branchB
git 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 不会更新,但是只要没有本地提交添加到此分支,它就会保持快速转发到远程分支。


不,OP 要求在本地签出 branchA 的同时将上游 origin/branchB 合并到他的本地 branchB
@HolgerBöhnke 你是对的,我误读了这一点,但同时我不能发送太多的 OP 问题。如果没有打算处理它,他为什么要检查 branchB?我觉得它遗漏了一些东西......他不需要签出本地 branchB 即可与 origin/branchB 合并,这就是我认为的问题所在。无论如何,我会澄清我的答案,谢谢。
你说的对。如果您不使用 branchB,您不妨检查一下。分支会自动跟踪为 origin/branchB,一旦您需要它,您将创建一个本地分支并检查它。如果您将分支视为指向 git 提交图中的特定提交的 可移动指针,它对理解有很大帮助。
e
eckes

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

[core]
    logallrefupdates=true

然后输入

git reflog show mybranch

查看您分支的最近历史记录


我认为该部分必须是 [core] 而不是 [user]? (默认情况下,对于带有工作区(即非裸)的存储库,它是打开的。
a
andruso

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

git fetch origin master:master --update-head-ok
git 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

r
rkd

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

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

glmh(“git pull and merge here”)会自动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 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


解析 git branch 的输出可能是个坏主意,它可以通过 config/etc 进行修改,它是供人类使用的。请改用 git symbolic-ref --short HEAD。前两行也可以合并:branchB=${1:-develop}
2
2 revs

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

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

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

我的需求与 OP 略有不同,即在合并拉取请求后从 develop(或 master)创建一个新功能分支。这可以在没有强制力的情况下在单行中完成,但它不会更新本地 develop 分支。只需签出一个新分支并使其基于 origin/develop

git checkout -b new-feature origin/develop

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

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

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

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

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

git worktree remove [-f] <worktree>

S
Sodhi saab

只是为了拉主人而不检查我使用的主人

git fetch origin master:master


z
zanerock

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

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 分支,而无需切换结帐。


git worktree add 不是有效地进行结帐吗?但您不需要克隆存储库。
我想这取决于您所说的结帐是什么意思...它确实会加载分支历史记录,但这对于一般情况下 AFAIK 是不可避免的。但是,它不会取代现有的结帐/分支。问的问题与标题略有不同,所以......
p
phkb

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

# 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"

p
paulomc

您只需将 git pull origin branchB 放入您的 branchA 中,git 就会为您解决问题。