如何在Git中执行以下操作?

我当前的分支是branch1,我已经做了一些本地更改。然而,我现在意识到我实际上是想将这些更改应用到branch2。是否有一种方法可以应用/合并这些更改,使它们成为branch2上的本地更改,而无需在branch1上提交它们?


当前回答

警告:不适合git新手。

类似于chakrit的回答,我经常遇到这种情况:在一个特性分支上工作时,我发现了一个bug,想要修复它。但是修复属于主分支,而不是我的特性。将更改转化为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。请随意提出改进建议。

其工作原理如下:

进入分离HEAD状态 使用阶段性更改创建临时提交 隐藏所有其他更改(包括未跟踪的文件) 切换到目标分支 选择目标分支上的临时提交 切换回原来的分支 [可选]在目标分支上重构原始分支,以便在原始分支中包含bug修复 从stash恢复所有其他更改

如果任何命令失败,它将简单地停止在那里,并打印一些信息来帮助您恢复情况。如果您想了解更多细节,请查看脚本末尾的注释和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

其他回答

相对于公认的答案,一个简短的替代方案是:

临时将更改移动到存储区。

git 存储

创建和切换到一个新的分支,然后弹出隐藏到它在仅仅一个步骤。

Git存储分支new_branch_name

然后只需将更改添加并提交到这个新分支。

警告:不适合git新手。

类似于chakrit的回答,我经常遇到这种情况:在一个特性分支上工作时,我发现了一个bug,想要修复它。但是修复属于主分支,而不是我的特性。将更改转化为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。请随意提出改进建议。

其工作原理如下:

进入分离HEAD状态 使用阶段性更改创建临时提交 隐藏所有其他更改(包括未跟踪的文件) 切换到目标分支 选择目标分支上的临时提交 切换回原来的分支 [可选]在目标分支上重构原始分支,以便在原始分支中包含bug修复 从stash恢复所有其他更改

如果任何命令失败,它将简单地停止在那里,并打印一些信息来帮助您恢复情况。如果您想了解更多细节,请查看脚本末尾的注释和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

存储,临时提交和重基可能都是多余的。如果您还没有将更改后的文件添加到索引中,那么您可以只签出另一个分支。

git checkout branch2

只要你编辑的文件在branch1和branch2之间没有不同,这就可以工作。它将把您留在branch2上,并保留您的工作更改。如果它们是不同的,那么您可以指定您希望合并您的本地更改与通过切换分支引入的更改,并使用-m选项进行签出。

git checkout -m branch2

如果您已经向索引添加了更改,那么您将希望首先通过重置来撤销这些更改。(这将保留您的工作副本,它将删除阶段性更改。)

git reset

如果它是关于提交的更改,你应该看看git-rebase,但正如VonC在评论中指出的,当你谈论本地更改时,git-stash肯定是做到这一点的好方法。

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

什么是未跟踪的更改?

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

什么是被跟踪的变更?

当您更改之前(在以前的提交中)提交给repo的文件时。

程序快速讲解

因此,假设您在分支A上,但是您只想将对现有文件的更改提交到分支A,而新创建的文件(未跟踪的)应该提交到新的分支b。可以使用一些技巧来使用存储,并逐步解释。

添加脚本到.git/config

在.git文件夹中有一个配置文件。打开它,你会看到这样的东西:

[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(签出-b你立即访问它)

git checkout -b newBranchName

当使用隐藏弹出隐藏的变化被添加到您的当前分支。

git stash pop

剩下的唯一事情就是在新的分支B上执行并提交更改

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