如何将本地未提交的更改合并到另一个Git分支中?

703

我怎样在Git中做到以下操作?

我的当前分支是branch1,我已经进行了一些本地更改。然而,我现在意识到我实际上想要将这些更改应用于branch2。有没有办法将这些更改应用/合并到branch2中,使它们成为branch2上的本地更改,而不必在branch1上提交它们?


3
在stackoverflow上有一个很好的Git教程,在这里。它是stackoverflow上所有git问题的中心。 - Decio Lira
由于StackOverflow的神奇,此链接存在于右侧的“相关”问题集中,但我认为它值得一个评论链接:请参见将现有的未提交工作移动到Git的新分支中 - torek
10个回答

991

由于您的文件尚未在 branch1 中提交:

git stash
git checkout branch2
git stash pop
或者
git stash
git checkout branch2
git stash list       # to check the various stash made in different branch
git stash apply x    # to select the right one
上面是更长、更明确的版本,源自rbento回答
git stash
git stash branch branch2

这里使用了:

git stash branch <branchname> [<stash>]

  • 创建并检出一个名为<branchname>的新分支,该分支从最初创建<stash>时的提交开始
  • <stash>中记录的更改应用于新的工作树和索引。

如果成功,并且<stash>是如下格式的引用stash@{<revision>},那么它会删除<stash>

如果在运行git stash push的分支上发生了足够多的更改,导致git stash apply由于冲突而失败,则此功能很有用。
由于stash条目应用于运行git stash时的HEAD所在的提交之上,因此它可以恢复最初存储的状态,而没有冲突。


benjohn的评论所述(请参见git stash手册页面):

要同时隐藏当前未跟踪(新添加的)文件,请添加参数-u,因此:

git stash -u

2
欢迎你。更多的stash使用示例请参考http://unethicalblogger.com/posts/2008/11/git_protip_stash_goods_yo_git_stash。 - VonC
2
如果您正在寻找解决TFS中相同问题的方法,等效的解决方案是将更改搁置,然后使用TFS Power Tools使用/migrate开关将其取消搁置到正确的分支。 - xr280xr
1
这对我有用。但是,我还必须创建一个本地分支才能使“stash pop”工作。如果您遇到类似的情况,请查看https://dev59.com/w3I-5IYBdhLWcg3wlpUM。 - mimoralea
27
要同时隐藏当前未跟踪的(新添加的)文件,请添加参数“-u”,即:git stash -u - Benjohn
2
@Benjohn 很好的观点。我已经将您的评论包含在答案中以增加可见性。 - VonC
显示剩余12条评论

89

暂存、临时提交和变基可能都过于复杂。如果你还没有将改变的文件加入到索引中,那么你可能只需要切换到其他的分支。

git checkout branch2

只要你在branch1和branch2之间编辑的文件没有不同,这样做就可以。它会让你保留工作中的更改并切换到branch2。如果它们不同,那么你可以使用-m选项指定要将本地更改与切换分支引入的更改合并。

git checkout -m branch2

如果你已经将更改添加到索引中,那么你需要先使用重置撤消这些更改。(这将保留你的工作副本,只会删除暂存的更改。)

git reset

3
我认为这个藏品“更简单”一些易于理解,但你的方法更好地考虑了不同分支之间的工作目录。+1 - VonC
6
一个简单的传统检出似乎更适合当前的问题。检出更轻量级,它只会更新需要更改的文件。也许使用“贮藏”方法更容易理解,或者只是因为在这种用例中不够显然,检出是“安全”的。 - CB Bailey
如果在某些情况下checkout -m不是“安全的”(可能会导致合并冲突),那么存储是否提供任何优势(例如,您可以取消弹出的存储)? - Craig McQueen
1
@craigMcQueen 你不能取消一个已经弹出的stash,但是当你弹出它时,stash会抱怨冲突。你可以解决冲突然后提交,但在这种情况下原始的stash仍然在堆栈中! :) - Shaun
在合并冲突的情况下,文件不是会备份为“.orig”吗? - jocull

17

更简单的替代方案如下:

将更改暂存到一个stash中。

  1. git stash

创建并切换到一个新分支,然后一步将stash应用到该分支上。

  1. git stash branch 新分支名称

接着,只需将更改添加(add)和提交(commit)到这个新分支即可。


12

警告:不适合git新手。

在我的工作流程中,这个问题出现的频率足以让我尝试编写一个新的git命令。通常使用git stash流程是正确的方法,但有点笨拙。我通常首先创建一个新的提交,因为如果我一直在查看更改,所有信息都会在我的脑海中清晰,最好立即开始对我发现的内容(通常是在功能分支上工作时发现的属于主分支的修复程序)进行git commit

如果您经常遇到这样的情况,拥有与当前工作目录并排的另一个工作目录始终检出了master分支也很有帮助。

所以我是这样做到的:

  1. 立即用好的提交消息git commit提交更改。
  2. git reset HEAD~1撤消当前分支的提交。
  3. (可选) 继续在该功能上工作。

稍后(异步)或立即在另一个终端窗口中:

  1. cd my-project-master 这是另一个共享相同的.git的工作目录。
  2. git reflog 查找刚刚创建的修复程序。
  3. git cherry-pick SHA1 提交的哈希值。

可选(仍然异步)您可以重新基于分支以获取错误修复(通常在准备提交PR并已经清理了功能分支和工作目录时):

  1. cd my-project 这是我正在使用的主要WD。
  2. git rebase master 获取错误修复。

这样我就可以持续地开发该功能,无需担心使用 git stash 或在执行 git checkout 前清空我的工作目录(然后再次检查功能分支),并且所有的错误修复都会进入 master,而不是隐藏在我的功能分支中。

在我看来,当你正在处理一些大型功能时,使用 git stashgit checkout 真是让人头疼。


有趣且有效的替代方案,点赞+1。 - VonC
你是从mercurial来的吗?my-project-master.git共享让人感觉像是这样。为什么不使用git checkout -b bugfixABC; git commit -a; git reset HEAD^ --hard,然后稍后(异步地)在master上,git cherry-pick <SHA1 of the commit(s) in bugfixABC>呢?(甚至可以为了避免找到SHA1而直接使用git rebase --onto master feature bugfixABC,从你当前所在的任何分支开始。这意味着你可以在上面的git reset之后直接在feature上执行此操作。) - Gauthier
然而,OP听起来似乎还没有准备好提交更改,这种情况下只需使用checkout -m即可。 - Gauthier
@chakrit:这让我非常烦恼,以至于我花时间编写了脚本:https://dev59.com/rXRB5IYBdhLWcg3wroyB#75363041。但是,在您的情况下,如果您有第二个“master”工作树,则可能会拒绝在功能工作树中检出“master”。 - Fritz

2

如果你想处理提交的更改,你应该查看git-rebase,但正如VonC在评论中指出的那样,由于你所说的是本地更改,因此使用git-stash肯定是一个好的方法。


我不理解这个解决方案:它会从branch1重写branch2的提交历史记录...为什么在branch2中获取来自branch1的所有提交更改,而我们只想在branch2中获取branch1的本地未提交更改呢?... - VonC
@VonC:同意,在这种情况下,rebase会将自上次分支合并以来所有已提交的更改都合并到branch1中。一开始我没有理解这个问题中“未提交”的参数。rebase不是好的答案。 - claf
@claferri:哎呀,我开始头疼了;) 我本来想给你的回答点个踩的,但是因为我自己也发表了一个类似的回答,所以存在“利益冲突”。有了你更新的帖子,现在我完全不需要踩了。谢谢:) - VonC
@VonC:下次如果我的回答像这个一样错误,随意给我点个踩吧 ;) - claf

1
以下是我遵循的步骤:
  • git clone {link}
  • cd {repo folder}

您可以使用以下命令检查状态和所在分支:

  • git status
  • git branch
  • git branch -a

注意:如果在转移到新分支之前在本地 repo 中进行更改,则以下步骤仍应有效。

如果 "git branch" 显示 master,并且您想要创建并移动到另一个分支:

  • git checkout -b {branch name}

再次使用 "git branch" 检查分支,现在应该显示您在新分支中。

现在添加、提交和推送:

  • git add .
  • git commit -m "added new branch"
  • git push origin {branch name}

以上步骤对我来说在两种情况下都适用,即在转移到新本地分支之前或在转移到新分支后进行更改。希望这能帮助遇到类似情况的人。


1
你可以使用git stash。另外,你可以从这个链接学习https://git-scm.com/docs/git-stash
在我看来,最简单的方法是
git stash
git checkout branch2
git stash pop

请点赞:)。那能帮助我向Stack Overflow社区提问。

0

我发现这个答案很有用。

然而,由于该线程已关闭且无法发表评论,我对该答案有疑问。

当我应用git checkout other_branch时,我遇到了以下错误

error: pathspec 'other_branch' did not match any file(s) known to git

所以,我不是应用命令,而是使用了“which”来解决我的问题。

git branch other_branch
git checkout other_branch

0

如果未提交的更改是未跟踪和已跟踪更改的混合

什么是未跟踪的更改?

当您创建了一个新文件时。例如,在文件资源管理器中,VSCode会在文件旁边显示一个 U

什么是已跟踪的更改?

当您对之前提交到存储库中的文件进行更改时。

快速解释流程

所以想象一下,您当前在分支 A 上,但您只想将对现有文件的更改提交到分支 A,而新创建的文件(未跟踪)应该提交到一个新的分支 B。可以使用一些技巧来使用暂存功能,逐步解释如下。

.git/config 添加脚本

.git 文件夹内有一个 config 文件。打开它,您会看到类似于以下内容:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[remote "origin"]
    url = https://github.com/...
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

将配置文件更改为:

[core]
    repositoryformatversion = 0
    filemode = true
    bare = false
    logallrefupdates = true
    ignorecase = true
    precomposeunicode = true
[alias]
    stash-untracked = "!f() {    \
                git stash;               \
                git stash -u;            \
                git stash pop stash@{1}; \
            }; f"
[remote "origin"]
    url = https://github.com/...
    fetch = +refs/heads/*:refs/remotes/origin/*
[branch "main"]
    remote = origin
    merge = refs/heads/main

现在当你在A分支上时,你将能够使用以下命令。

git stash-untracked

如果您正在使用诸如VSCode之类的编辑器,您将会看到新文件消失了(现在已被隐藏)

在仍然处于A分支的情况下,将更改提交到现有文件:

git add .
git commit -m "committing tracked changes to current branch"

下一步是创建一个新的分支B(使用checkout -b命令可以立即访问它)。
git checkout -b newBranchName

使用 stash pop 命令,存储的更改将被添加到当前分支。

git stash pop

现在唯一剩下的就是将更改提交到新分支B上并进行提交

git add .
git commit -m "created new file"

0

警告:不适合 Git 初学者。

chakrit's answer类似,我经常遇到这种情况:在一个功能分支上工作,发现一个 bug 并想修复它。但是修复应该在main分支上进行,而不是my-feature。要将更改内容放入main中的整个序列是7个或更多个git命令,这真的很烦人且容易出错。

由于我找不到脚本来完成此操作,所以我自己编写了它。只需将其放在$PATH的某个位置(例如/usr/local/bin/$HOME/.local/bin或其他位置),然后您可以执行以下操作:

用法

# currently working on branch `my-feature`
$ git add some-file       # use git add -p if you want only some changes
$ git commit-branch main --rebase -m 'Fixed some nasty bug in some-file'

然后它将打印一些进度消息:

Committing your staged changes to branch 'main'.
+ git checkout --quiet HEAD~0
+ git commit --quiet -m 'Fixed some nasty bug in some-file'
++ git rev-parse HEAD
+ commit_hash=82513091473646a09d541893b8bd60a0f98b765d
+ git stash push --quiet
+ git checkout --quiet main
+ git cherry-pick --quiet 82513091473646a09d541893b8bd60a0f98b765d
[main 1c5d96e] Fixed some nasty bug in some-file
 Date: Mon Feb 6 15:04:03 2023 +0100
 1 file changed, 2 insertions(+)
+ git checkout --quiet my-feature
+ git rebase --quiet main
+ git stash pop --quiet
+ set +x
Success.

脚本

这是文件git-commit-branch的源代码。请不要忘记在将其放置在$PATH后执行chmod +x。该脚本也可以在github上找到:https://github.com/fritzw/git-utils。欢迎提出改进意见。

它的工作方式如下:

  1. 进入分离 HEAD 状态
  2. 使用暂存的更改创建临时提交
  3. 隐藏所有其他更改(包括未跟踪的文件)
  4. 切换到目标分支
  5. 在目标分支上挑选临时提交
  6. 切换回原始分支
  7. [可选] 在目标分支上对原始分支进行变基,以便在原始分支中包含 bugfix
  8. 从隐藏处恢复所有其他更改

如果任何命令失败,它将简单地停止并打印一些信息以帮助您恢复情况。如果您想了解更多详细信息,请查看脚本末尾的注释和 git 命令序列。

#!/usr/bin/env bash

set -o errexit
set -o nounset

usage() {
    echo "Usage: git commit-branch <target-branch> [--rebase|-r] [ <git-commit-options>... ]"
    echo ""
    echo "Commits your staged changes to <target-branch>, discarding them from your current branch."
    echo "Use --rebase or -r to rebase your current branch on the new commit in <target-branch>,"
    echo "and thus include the changes in your current branch as well."
    echo ""
    echo "Example usage working on branch my-feature:"
    echo "  git add some-file"
    echo "  git commit-branch main --rebase -m 'Fixed a bug in some-file'"
}

if [[ $# -lt 1 ]]; then
    usage
    exit 1
fi
target_branch="$1"; shift # Remove first argument
if ! git rev-parse --verify "$target_branch" >/dev/null; then
    echo "fatal: '$target_branch' is not a branch in this git repository."
    usage
    exit 1
fi

rebase_command=''
if [[ $# -gt 0 ]] && [[ "$1" == "-r" || "$1" == "--rebase" ]]; then
    rebase_command="git rebase --quiet $target_branch"
    shift # Remove -r/--rebase argument
fi

current_branch="$(git branch --show-current)"
if ! [[ "$current_branch" ]]; then
    echo "fatal: Unable to determine current branch. You must be on a branch to use git commit-branch."
    exit 1
fi

commit_hash='not-committed-yet'
print_error_message() {
    set +x
    echo
    echo "Something went wrong in the last command. :-("
    echo "Your unstaged changes and untracked files should be in the last stash."
    echo "Your previously staged changes should be in the following commit: $commit_hash"
    echo "Please check which commands were executed and try to undo them manually."
    echo
}

echo "Committing your staged changes to branch '$target_branch'."
trap 'print_error_message' ERR # Print some hopefully helpful info if something fails
set -x # Print all executed commands
git checkout --quiet 'HEAD~0' # Go into 'detached HEAD' state to avoid changing current branch
git commit --quiet "$@" # Create temporary commit
commit_hash="$(git rev-parse HEAD)" # Note temporary commit ID
git stash push --include-untracked --quiet # Save all other changes from working tree
git checkout --quiet "$target_branch" # Move to target branch
git cherry-pick --quiet "$commit_hash" # Apply changes from temporary commit to target branch
git checkout --quiet "$current_branch" # Switch back to current branch
$rebase_command # Execute git rebase if --rebase flag is present
git stash pop --quiet # Re-apply untracked changes to working tree
set +x # Stop printing executed commands
echo "Success."
if ! [[ "$rebase_command" ]]; then
    echo ""
    echo "If you want to include those changes in your current branch, you can run:"
    echo "  git stash; git rebase $target_branch; git stash pop"
    echo "or"
    echo "  git stash; git merge $target_branch; git stash pop"
    echo ""
fi

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