在推送之前将多个提交合并为一个


130

这个问题不仅与如何完成这项任务有关,而且与Git的实践是好还是坏有关。

考虑到本地,我在master分支上做最多的工作,但是我创建了一个主题分支,我将其称为“ topical_xFeature”。在“ topical_xFeature”上工作并来回切换以在master分支上进行其他工作的过程中,事实证明,我在“ topical_xFeature”分支上进行了多个提交,但是在每次提交之间,我没有做任何事情推。

首先,您会考虑这种不良做法吗?在每次推送中每个分支坚持一次提交会更明智吗?在什么情况下,在进行一次推送之前在分支上进行多次提交会很好?

第二,如何最好地完成对topical_xFeature分支的多次提交到master分支进行推送?不用担心它,只是在多个提交被推送的情况下进行推送,这是很麻烦的事情吗?或者以某种方式将提交合并到一个提交然后进行推送是否更令人讨厌?再次,该怎么做?

Answers:


139

对于您的第一个问题,不,一次推送多个提交没有错。很多时候,您可能希望将工作分解为几个小的逻辑提交,但只有在感觉整个系列准备就绪时才将它们推高。或者,您可能会在断开连接时在本地进行几次提交,然后在再次连接后将它们全部推送。没有理由将自己限制为每次推送一次提交。

我通常发现,最好将每个提交都提交一个单一的,逻辑的,连贯的更改,其中包括它需要工作的所有内容(因此,它不会使您的代码处于崩溃状态)。如果您有两次提交,但是如果仅应用第一个提交,那么它们将导致代码被破坏,将第二个提交压缩为第一个提交可能是一个好主意。但是,如果您有两个提交,每个提交都进行了合理的更改,则将它们作为单独的提交推送就可以了。

如果确实要将多个提交压缩在一起,可以使用git rebase -i。如果您在分支机构topical_xFeature,则可以运行git rebase -i master。这将打开一个编辑器窗口,其中列出了一堆以前缀的提交pick。您可以将除第一个以外的所有内容更改为squash,这将告诉Git保留所有这些更改,但将其压入第一次提交。完成之后,签出master并合并到功能分支中:

git checkout topical_xFeature
git rebase -i master
git checkout master
git merge topical_xFeature

另外,如果您只想将所有内容都topical_xFeature压入master,则可以执行以下操作:

git checkout master
git merge --squash topical_xFeature
git commit

您选择哪一个取决于您。通常,我不担心会有多个较小的提交,但是有时候您不想打扰额外的次要提交,因此只需将它们压缩为一个即可。


1
与--squash合并后,无法使用删除主题分支git branch -d topic。为什么git无法识别所有更改都已合并?
巴尔基2012年

7
@balki因为Git根据修补程序是否出现在给定分支的历史记录中来检测它们是否合并。挤压提交会改变它们;它们变成了一个新的提交,而那个新提交恰好与其他提交做了相同的事情,但是Git不能告诉它,它只能判断提交是否具有相同的提交ID(SHA-1)是否相同。 。因此,一旦压缩它,就需要告诉git删除旧分支,git branch -D topic并强制删除它。
布莱恩·坎贝尔

66

通常,这是在推送代码之前将多个Commit合并为一个提交的方式。

为此,我建议您使用GIT提供的“ 壁球 ”概念。

请遵循以下步骤。

1)git rebase -i master(代替master也可以使用特定的提交)

打开rebase交互式编辑器,它将在其中显示所有提交。基本上,您需要确定要合并到单个提交中的提交。

想象一下这些是您的提交,并在编辑器中显示了类似的内容。

pick f7f3f6d changed my name a bit    
pick 310154e updated README formatting and added blame   
pick a5f4a0d added cat-file  

重要的是要注意,这些提交的列出顺序与使用log命令通常看到的相反。意味着,较早的提交将首先显示。

2)将“ pick”更改为“ squash”以进行最后一次提交的更改。如下所示。这样做,您的最后2个提交将与第一个提交合并。

pick f7f3f6d changed my name a bit         
squash 310154e updated README formatting and added blame   
squash a5f4a0d added cat-file

如果您要合并很多提交,也可以使用简写形式:

p f7f3f6d changed my name a bit         
s 310154e updated README formatting and added blame   
s a5f4a0d added cat-file

要使用“ i”进行编辑,它将使编辑器可以插入。请记住,最上(最旧)的提交不能被压缩,因为没有先前的提交可以与之结合。因此必须将其选中或“ p”。使用“ Esc”退出插入模式。

3)现在,使用以下命令保存编辑器:wq

保存该内容时,您将拥有一个提交,该提交将介绍所有前三个提交的更改。

希望这会帮助你。


5
也许这对于其他人是显而易见的,但是当您说“ git rebase -i”时,您还需要指定您从哪个提交开始。当我尝试遵循此示例时,这是我没有意识到的。因此,在此示例中,它将是“ git rebase -i xxxxx”,其中xxxxx是按时间顺序在f7f3f6d之前的提交。一旦我弄清楚了,一切都按上述方法进行了工作。
nukeguy

@nukeguy很有趣,我没有指定特定提交的任何问题。它只是默认为那里。
JCrooks

也许像@nukeguy一样,git rebase -i HEAD~2对我来说是一个有用的起点。那么这个答案很有帮助。然后,我的git status节目“您的分支和'origin / feature / xyz'已经分开,分别具有1和1个不同的提交。” 所以我需要git push origin feature/xyz --force-with-leasestackoverflow.com/a/59309553/470749freecodecamp.org/forum/t/...
瑞恩

11

首先:没有什么可以告诉您每个推送每个分支只有一个提交:推送是一种发布机制,允许您在远程回购上发布本地历史记录(即提交的集合)。

其次git merge --no-ff topical_xFeature在推动之前,a 将记录在master上作为一次提交您的主题工作master
(这样一来,您topical_xFeature就可以进行进一步的开发,可以master在下一次合并时将其记录为一个新的提交--no-ff。
如果要摆脱topical_xFeature的目标git merge --squash是正确的选择,则如Brian Campbell所述,是正确的选择的答案。)


我认为,这--squash不是--no-ff您想要的。--no-ff将创建合并提交,但也将保留所有提交topical_xFeature
布莱恩·坎贝尔

@Brian:我同意并赞成你的回答,但是我首先想到了--no-ff选项,因为我想保留topical_feature分支,只记录master分支上的一个提交。
VonC

8

切换到master分支,并确保您是最新的。

git checkout master

git fetch 这可能是必要的(取决于您的git配置),以接收源/主服务器的更新

git pull

将功能分支合并到主分支。

git merge feature_branch

将master分支重置为原始状态。

git reset origin/master

Git现在将所有更改视为未分段的更改。我们可以将这些更改作为一次提交添加。新增。还将添加未跟踪的文件。

git add --all

git commit

参考:https : //makandracards.com/makandra/527-squash-several-git-commits-into-a-single-commit


3
这个答案很容易理解,而且很容易形象化。
jokab

6
  1. 首先选择您想要所有提交的提交。

    git reflog
    5976f2b HEAD@{0}: commit: Fix conflicts
    80e85a1 HEAD@{1}: commit: Add feature
    b860ddb HEAD@{2}: commit: Add something
    
  2. 重置为您选择的头像(我已选择HEAD@{2}

    git reset b860ddb --soft
    
  3. git status (只是要确定)

  4. 添加您的新提交

    git commit -m "Add new commit"
    

注意:HEAD@{0}HEAD@{1}现在合并为1个提交,也可以对多个提交进行此操作。

git reflog 再次应显示:

git reflog
5976f2b HEAD@{0}: commit: Add new commit
b860ddb HEAD@{1}: commit: Add something

0

一种将多个提交自动化为一个的工具

如Kondal Kolipaka所说。使用“ git rebase -i”

“ git rebase”的逻辑

使用“ git rebase -i”时,git在当前.git / rebase-merge目录中生成git-rebase-todo文件,然后调用git编辑器以使用户编辑git-rebase-todo文件进行处理。因此该工具需要满足:

  1. 将git编辑器修改为我们提供的工具;
  2. 该工具处理git-rebase-todo文件。

修改默认的git编辑器

git config core.editor #show current default git editor
git config --local --replace-all  core.editor NEW_EDITOR # set the local branch using NEW_EDITOR as git editor

因此,该工具需要更改git编辑器并处理git-rebase-todo文件。下面使用python的工具:

#!/usr/bin/env python3
#encoding: UTF-8

import os
import sys

def change_editor(current_file):
    os.system("git config --local --replace-all  core.editor " + current_file) # Set current_file as git editor
    os.system("git rebase -i") # execute the "git rebase -i" and will invoke the python file later with git-rebase-todo file as argument
    os.system("git config --local --replace-all core.editor vim") # after work reset the git editor to default

def rebase_commits(todo_file):
    with open(todo_file, "r+") as f:
        contents = f.read() # read git-rebase-todo's content
        contents = contents.split("\n")
        first_commit = True
        f.truncate()
        f.seek(0)
        for content in contents:
            if content.startswith("pick"):
                if first_commit:
                    first_commit = False
                else:
                    content = content.replace("pick", "squash") # replace the pick to squash except for the first pick
            f.write(content + "\n")

def main(args):
    if len(args) == 2:
        rebase_commits(args[1]) # process the git-rebase-todo
    else:
        change_editor(os.path.abspath(args[0])) # set git editor

if __name__ == "__main__":
    main(sys.argv)

参考:https : //liwugang.github.io/2019/12/30/git_commits_zh.html


4
请调低您的网站促销。另请参阅如何成为垃圾邮件发送者。
人间
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.