Объединение нескольких коммитов в одну перед отправкой


134

Этот вопрос касается не только того, как выполнить эту задачу, но и того, является ли это хорошей или плохой практикой с Git.

Учтите, что локально я больше всего работаю над основной веткой, но я создал тематическую ветку, которую назову «topical_xFeature». В процессе работы над «topical_xFeature» и переключения туда и обратно для выполнения другой работы в основной ветке выясняется, что я сделал более одной фиксации в ветке «topical_xFeature», но между каждой фиксацией я не делал От себя.

Во-первых , считаете ли вы это плохой практикой? Разве не было бы разумнее придерживаться одной фиксации на ветку за нажатие? В каких случаях было бы хорошо иметь несколько коммитов в ветке до того, как будет сделан push?

Во-вторых , как мне лучше всего выполнить перенос нескольких коммитов из ветки topical_xFeature в главную ветку для push-уведомлений? Разве неприятно не беспокоиться об этом и просто делать push, когда несколько коммитов отправляются, или менее неприятно как-то объединить коммиты в один, а затем нажать? Опять же, как это сделать?

Ответы:


142

Отвечая на ваш первый вопрос: нет, нет ничего плохого в одновременном выполнении нескольких коммитов. Часто вы можете захотеть разбить свою работу на несколько небольших логических коммитов, но подталкивайте их только тогда, когда вы чувствуете, что вся серия готова. Или вы можете делать несколько локальных коммитов при отключении, и вы нажимаете их все, когда снова подключаетесь. Нет причин ограничивать себя одной фиксацией на пуш.

Как правило, я считаю, что для каждой фиксации рекомендуется сохранять одно, логичное, связное изменение, которое включает в себя все необходимое для работы (чтобы не оставлять ваш код в неисправном состоянии). Если у вас есть две фиксации, но они могут привести к нарушению кода, если вы примените только первую, может быть хорошей идеей сжать вторую фиксацию на первую. Но если у вас есть два коммита, каждый из которых вносит разумные изменения, то продвижение их как отдельных коммитов вполне нормально.

Если вы хотите объединить несколько коммитов, вы можете использовать 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 не может определить, что все изменения объединены?
balki

7
@balki Потому что Git определяет, объединяются ли исправления, в зависимости от того, появляются ли они в истории данной ветки. Сжатие коммитов изменяет их; они становятся новой фиксацией, и хотя эта новая фиксация делает то же самое, что и другие, Git не может этого сказать, он может определить, совпадают ли фиксации, только если у них одинаковый идентификатор фиксации (SHA-1) , Итак, как только вы его раздавите, вам нужно сказать git, чтобы он удалил старую ветку, git branch -D topicчтобы принудительно удалить ее.
Брайан Кэмпбелл,

67

Это способ, которым я обычно следую, чтобы объединить несколько коммитов в один коммит, прежде чем вставлять код.

Для этого я предлагаю вам использовать концепцию « сквоша », предоставленную 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-leaseувидеть stackoverflow.com/a/59309553/470749 и freecodecamp.org/forum/t/…
Райан

11

Во-первых : ничто не говорит вам о том, что нужно иметь только одну фиксацию для каждой ветки на push: push - это механизм публикации, позволяющий публиковать локальную историю (то есть коллекцию коммитов) в удаленном репо.

Во-вторых : a git merge --no-ff topical_xFeatureбудет записывать в master как отдельную фиксацию вашу работу по теме, прежде чем нажимать master.
(Таким образом, вы сохраните topical_xFeatureдля дальнейшего развития, что вы можете записать masterкак один новый коммит при следующем слиянии --no-ff.
Если topical_xFeatureцель git merge --squash- избавиться от него, то это правильный вариант, как подробно описано в Брайане Кэмпбелле «S ответ .)


Я думаю, что это --squashне --no-ffто, что вам нужно. --no-ffсоздаст коммит слияния, но также оставит все коммиты из topical_xFeature.
Брайан Кэмпбелл

@Brian: Я согласен и поддержал ваш ответ, но сначала я подумал о опции --no-ff, потому что я хотел сохранить topical_featureветвь и просто записать одну фиксацию в masterветке.
VonC

8

Переключитесь на главную ветку и убедитесь, что у вас установлена ​​последняя версия.

git checkout master

git fetch это может быть необходимо (в зависимости от вашей конфигурации git) для получения обновлений на origin / master

git pull

Объедините функциональную ветку с основной веткой.

git merge feature_branch

Сбросьте главную ветвь в исходное состояние.

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

Инструмент для автоматизации нескольких коммитов в один

как говорит Кондал Колипака . Использование "git rebase -i"

Логика "git rebase"

При использовании «git rebase -i» git создает файл git-rebase-todo в текущем каталоге .git / rebase-merge, а затем вызывает редактор 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_commit_en.html


4
Пожалуйста, уменьшите продвижение вашего веб-сайта. См. Также Как не быть спамером.
Tripleee
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.