【问题标题】:Isolating one change from another with git用 git 隔离一个变化
【发布时间】:2011-08-05 17:40:46
【问题描述】:

我一直在我的本地 master 分支上开发一个尚未准备好投入生产的新功能。但是,我刚刚在我的实时应用程序中发现了一个单独的错误,所以我很快在本地修复了它。但是,我想将这个 bug 修复推送到我的远程 master 分支,而不是推送我一直在开发的这个新功能。我该怎么做?

【问题讨论】:

    标签: git version-control github


    【解决方案1】:

    不那么严肃的回答:

    通过时光倒流并为您的开发使用适当的分支模型。停止在生产分支上工作并开始使用功能分支。您描述的情况正是您应该使用分支的原因:能够将开发工作搁置一旁,签出 master,执行错误修复并返回您的开发分支。忽略分支,你就忽略了很多让 Git 很棒的东西。

    更严肃的回答:

    更实际的是,您可以重新排序提交,将本地 master 指向您要推送、推送的那个,然后将您的开发提交作为新分支签出。

    如果你的提交历史是这样的:

    A <-- (master) bug fix, you want to push this
    B <-- you don't want to push this
    C <-- (origin/master)
    

    您可以使用git rebase -i HEAD~2 重新排序最后两个提交,使其看起来像这样(只需在出现的编辑器中切换行的顺序):

    B <-- (master) you don't want to push this
    A <-- bug fix
    C <-- (origin/master)
    

    记下B 的 SHA1,您将暂时将其从主分支中删除。记录 SHA1 后,您可以使用 git update-ref refs/heads/master [SHA1 of A],这会导致

    A <-- (master) bug fix
    C <-- (origin/master)
    

    您现在可以git pushA 合并到origin/master 并将结果发送到origin

    最后,为了恢复您的开发工作(提交 B),创建一个新的开发分支(您应该首先完成)指向您的 B 提交:git branch development [SHA1 of B]

    您的存储库现在将如下所示:

    B <-- (development) you don't want to push this
    A <-- (master,origin/master) bug fix
    C <-- where origin/master *was* before pushing
    

    当您准备好将该开发工作合并到 master 中时,您可以:

    git checkout master
    git merge development
    

    【讨论】:

    • 如何查看我的提交历史?也许那么你可以给我确切的指示 b/c 我对你展示给我的所有东西都很陌生
    • 您可以使用git log --pretty=oneline --decorate=full 来获得类似于我上面使用的伪格式的日志格式。您必须将真正的 SHA1 哈希替换为 ABC,它们只是占位符。我没有向您展示的唯一步骤是变基,这真的很简单。你的编辑器应该包含两行;只需颠倒它们的顺序。
    • @Justin 请注意,当我谈到输入 SHA1 时,您只需写下前 6 个字符左右。不要费心输入完整的 40 个字符。
    • A - (HEAD, refs/remotes/origin/master, refs B - apointer css C - added play image
    • B 和 C 绝对不适合这个新的开发功能,可能是 A 吗?我可能还没有提交开发功能的任何部分。我只是不确定。
    【解决方案2】:

    你得到了一些很好的答案,但有点复杂。我希望能简化一点。以下假设您仅在一次提交中提交了您的修复,最后一次在您的本地 master 分支上。

    git branch new-feature master~1
    git checkout -b bugfix
    git branch -f master origin/master
    git rebase --onto master new-feature
    

    这会将你的新特性的工作放在它自己的名为 new-feature 的分支上,将你的错误修复放在它自己的名为 bugfix 的单独分支上,重置你的主分支以匹配你上次推送的内容,然后变基以删除新特性从您的错误修复分支工作。

    这就是您通常希望您的分支在这一点上看到的方式。我是如何得到它的还不是很重要,因为将来您只需使用 git checkout -b another-new-feature master 创建一个新分支,然后再开始处理另一个新功能或错误修复。

    现在,仅推送您的错误修复,只需执行以下操作:

    git checkout master
    git merge bugfix
    git push
    

    然后要继续开发新功能,请执行以下操作:

    git checkout new-feature
    

    如果您想在新功能分支中包含错误修复,请执行以下操作:

    git rebase master
    

    否则,它将在您进行下一次推送之前被合并。当您准备好推送新功能时,只需执行与 bugfix 分支相同的合并,但使用 new-feature 分支。

    【讨论】:

      【解决方案3】:

      你可以只将你想要提交的文件添加到暂存区,然后将它们提交到你的分支上。然后创建另一个分支并提交您不想提交的文件。然后你只需推送带有修复的分支。

      当您提交时,它只会将文件添加到您的暂存区,以便您可以在一个分支上提交您的一部分工作,而在另一个分支上提交您的另一部分工作。

      如果您想在有未提交的文件时切换分支,您可以将它们添加到暂存区,签出另一个分支,然后将暂存区提交到另一个分支。

      如果你做了一些你想要推送的更改和一些你不想推送到同一个文件的更改,那么它会变得更加复杂。

      【讨论】:

      • 所以如果我创建一个新分支并且只提交我做出更改的文件,然后在该新分支上推送到 github,git 不会推送我在本地所做的任何更改主分支?
      • @Justin 是的,但我宁愿将我的修复程序放在 master 分支中并将其推送到 github 并将临时分支保留在本地。我希望我能正确理解你的问题。
      【解决方案4】:

      Here 是遍历命令的答案。我写了一个类似的问题。

      它的基础是,将你的错误修复移动到一个单独的分支,并将该分支与你的 master 和 topic 合并。与其在第 4 步中修复更改,不如使用git cherry-pick bugFixSHA,而不是从您的功能分支中删除提交。

      【讨论】:

        【解决方案5】:
        1. 交互式变基并将错误修复提交移动到变更集的第一个提交
        2. 创建一个指向错误修复提交的新分支
        3. 将新的 bug 修复分支推送到远程 master

        我今天没有正常的测试机器,所以语法来自记忆。如果有,请有人给我打电话。

        不确定 ascii 艺术是否可行:

          [origin/master]  hash_0 blah
          [new_feature  ]  hash_1 feature progress: more widgets!
                           hash_2 feature progress: less widgets, what was I thinking?
                           hash_3 bug fix: fixed memory management
        

        命令可能如下所示:

        我假设我们正在使用分支新功能。

        git rebase -i origin/master
        

        此时你会得到一个提交列表:

        hash_1 feature progress: more widgets!
        hash_2 feature progress: less widgets, what was I thinking?
        hash_3 bug fix: fixed memory management
        

        您将重新排序提交:

        hash_3 bug fix: fixed memory management
        hash_1 feature progress: more widgets!
        hash_2 feature progress: less widgets, what was I thinking?
        

        如果您在此处遇到错误,那么您的错误修复取决于您的新功能更改。很难就此给出建议。在这种情况下,通常我只是在 origin/master 中重新创建错误修复。

          [origin/master]  hash_0 blah
          [new_feature  ]  hash_3 bug fix: fixed memory management
                           hash_1 feature progress: more widgets!
                           hash_2 feature progress: less widgets, what was I thinking?
        

        我们的顺序应该是正确的,我通常会打开 gitk 并直观地验证它是否符合我的预期。您希望在 origin/master 之后的下一次提交中看到您的错误修复。

        git checkout hash_3
        git branch some_bug_fix
        git push origin some_bug_fix:master
        git checkout new_feature
        git rebase origin/master
        

        最后一次变基可能是多余的。

          [origin/master]  hash_0 blah
                           hash_3 bug fix: fixed memory management
          [new_feature  ]  hash_1 feature progress: more widgets!
                           hash_2 feature progress: less widgets, what was I thinking?
        

        或者,

        1. 检查遥控器
        2. Cherry 选择错误修复提交。

        不过,这会复制您本地分支中的代码,因此您需要将其从本地分支中删除并基于 master 进行 rebase。

        我总是用 gitk 来挑选,帮不了你的命令。

        【讨论】:

        • 能否请您给出命令示例?另外,我已经在中期提交了该功能。
        • @Justin 将功能提交给遥控器?还是本地?
        • 我其实不记得了。查看下面微薄的回答,了解我历史上的最后三个提交。
        【解决方案6】:

        使用 Git 的“交互式添加”功能仅将错误修复添加到“暂存区”。然后,您可以隐藏其余的更改,测试错误修复以确保其有效,然后自行提交。

        以下是一个示例演练。起初它可能看起来很长而且很复杂,但实际上很容易。一旦你这样做了几次,它就会成为第二天性,你可能会发现自己经常使用这种技术。


        在此示例中,bar.cfoo.c 都有更改,但只有 foo.c 具有与错误修复相关的更改。首先,我使用git add -i 以交互方式仅添加错误修复:

        test(master *)$ git add -i
                   staged     unstaged path
          1:    unchanged        +1/-0 bar.c
          2:    unchanged        +1/-1 foo.c
        
        *** Commands ***
          1: status       2: update       3: revert       4: add untracked
          5: patch        6: diff         7: quit         8: help
        

        我选择“p”选项告诉 Git 我希望选择单独的“补丁”添加到“暂存区”:

        What now> p
                   staged     unstaged path
          1:    unchanged        +1/-0 bar.c
          2:    unchanged        +1/-1 foo.c
        

        接下来我输入2 告诉它我想从foo.c 中选择单个补丁:

        Patch update>> 2
                   staged     unstaged path
          1:    unchanged        +1/-0 bar.c
        * 2:    unchanged        +1/-1 foo.c
        

        由于foo.c 是唯一一个有我想要添加到“暂存区”的补丁的文件,现在我已经完成了文件选择,所以我只需在提示符处按 Enter:

        Patch update>>
        

        Next Git 向我显示“foo.c”中的各个补丁,并询问我是否要将它们添加到索引中。在这个例子中,只有一个变化,所以我暂存它:

        diff --git a/foo.c b/foo.c
        index 7bc741e..ec7ddfc 100644
        --- a/foo.c
        +++ b/foo.c
        @@ -1 +1 @@
        -Here is foo.c; it has a bug.
        +Here is foo.c.
        Stage this hunk [y,n,q,a,d,/,e,?]? y
        

        我现在已经上演了作为错误修复一部分的所有内容。于是我退出了“交互添加”模式:

        *** Commands ***
          1: status       2: update       3: revert       4: add untracked
          5: patch        6: diff         7: quit         8: help
        What now> q
        Bye.
        

        注意状态。 foo.c 已将更改添加到“暂存区”,但“bar.c”没有:

        test(master *+)$ git status
        # On branch master
        # Changes to be committed:
        #   (use "git reset HEAD <file>..." to unstage)
        #
        #       modified:   foo.c
        #
        # Changes not staged for commit:
        #   (use "git add <file>..." to update what will be committed)
        #   (use "git checkout -- <file>..." to discard changes in working directory)
        #
        #       modified:   bar.c
        #
        

        接下来我告诉 Git 隐藏我的工作,但要保留我添加到“暂存区”(a.k.a. “索引”):

        test(master *+)$ git stash --keep-index
        Saved working directory and index state WIP on master: ba84dec Adding bar.c with new "bar feature"
        HEAD is now at ba84dec Adding bar.c with new "bar feature"
        test(master +$)$ git status
        # On branch master
        # Changes to be committed:
        #   (use "git reset HEAD <file>..." to unstage)
        #
        #       modified:   foo.c
        #
        

        现在请注意,我的工作树中唯一的东西是错误修复,它已准备好在下一次提交中提交。现在我可以在提交之前对错误修复进行任何我想做的测试。

        当我对我的更改感到满意时,我现在可以提交错误修复:

        test(master +$)$ git commit
        [master 79acd00] Commit bugfix for foo.c
         1 files changed, 1 insertions(+), 1 deletions(-)
        

        现在我将存储区弹出以将我在 bar.c 中所做的其他工作返回到我的工作树中:

        test(master $)$ git stash pop
        # On branch master
        # Changes not staged for commit:
        #   (use "git add <file>..." to update what will be committed)
        #   (use "git checkout -- <file>..." to discard changes in working directory)
        #
        #       modified:   bar.c
        #
        no changes added to commit (use "git add" and/or "git commit -a")
        Dropped refs/stash@{0} (d306d098a272335ed31c14f07cf57e62ffc13151)
        

        我已经完成了。我现在可以推送到远程存储库,它会得到错误修复,而我的其余工作仍在我的工作树中并且不会被推送。

        【讨论】:

        • 啊这太酷了。谢谢!一个问题:如果您已经在暂存区进行了错误修复,而其他工作不在暂存区,为什么还需要隐藏您的工作?存储有什么作用?
        • @Justin:存储让您可以单独测试错误修复,无需任何其他更改...以确保它有效,并且不依赖于任何其他更改.虽然您可以只提交暂存的内容而无需先暂存,但您可能没有暂存整个错误修复。也许您在向“暂存区”添加更改时不小心跳过了一大块。隐藏让您有机会防止此类错误。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-12-14
        • 2021-04-04
        相关资源
        最近更新 更多