В чем разница между `git merge` и`git merge --no-ff`?
С помощью gitk log
Я не мог заметить разницу между ними. Как я могу наблюдать разницу (с помощью команды git или какого-либо инструмента)?
9 ответов
--no-ff
флаг мешает git merge
выполнить "ускоренную перемотку вперед", если обнаружит, что ваш текущий HEAD
является предком коммита, который вы пытаетесь объединить. Быстрая перемотка вперед - это когда вместо создания коммита слияния git просто перемещает указатель вашей ветки, чтобы указать на входящий коммит. Это обычно происходит при выполнении git pull
без каких-либо локальных изменений.
Однако иногда вы хотите предотвратить такое поведение, как правило, потому что вы хотите поддерживать топологию конкретной ветви (например, вы объединяетесь в ветку темы и хотите, чтобы она выглядела так при чтении истории). Для этого вы можете передать --no-ff
флаг и git merge
всегда будет создавать слияние вместо быстрой пересылки.
Точно так же, если вы хотите выполнить git pull
или использовать git merge
чтобы выполнить быструю перемотку, и вы хотите выручить, если она не может перемотать, вы можете использовать --ff-only
флаг. Таким образом, вы можете регулярно делать что-то вроде git pull --ff-only
не задумываясь, а затем, если произойдет ошибка, вы можете вернуться и решить, хотите ли вы объединить или перебазировать.
Графический ответ на этот вопрос
Вот сайт с четким объяснением и графической иллюстрацией использования git merge --no-ff:
Пока я не увидел это, я был полностью потерян с мерзавцем. Использование --no-ff позволяет человеку, просматривающему историю, ясно видеть ветку, над которой вы работали. (эта ссылка указывает на "сетевой" инструмент визуализации github). А вот еще одна отличная ссылка с иллюстрациями. Этот справочник хорошо дополняет первый, с большим акцентом на тех, кто менее знаком с git.
Основная информация для таких новичков, как я
Если вы похожи на меня, а не на Git-гуру, мой ответ здесь описывает обработку удаления файлов из отслеживания git без удаления их из локальной файловой системы, что кажется плохо документированным, но часто встречающимся. Еще одна новая ситуация - получение текущего кода, который все еще удается ускользнуть от меня.
Пример рабочего процесса
Я обновил пакет на своем сайте и должен был вернуться к своим заметкам, чтобы увидеть мой рабочий процесс; Я подумал, что полезно добавить пример к этому ответу.
Мой рабочий процесс команд git:
git checkout -b contact-form
(do your work on "contact-form")
git status
git commit -am "updated form in contact module"
git checkout master
git merge --no-ff contact-form
git branch -d contact-form
git push origin master
Ниже: фактическое использование, включая пояснения.
Примечание: вывод ниже обрезан; мерзавец довольно многословен.
$ git status
# On branch master
# Changed but not updated:
# (use "git add/rm <file>..." to update what will be committed)
# (use "git checkout -- <file>..." to discard changes in working directory)
#
# modified: ecc/Desktop.php
# modified: ecc/Mobile.php
# deleted: ecc/ecc-config.php
# modified: ecc/readme.txt
# modified: ecc/test.php
# deleted: passthru-adapter.igs
# deleted: shop/mickey/index.php
#
# Untracked files:
# (use "git add <file>..." to include in what will be committed)
#
# ecc/upgrade.php
# ecc/webgility-config.php
# ecc/webgility-config.php.bak
# ecc/webgility-magento.php
Обратите внимание на 3 вещи сверху:
1) В выводе вы можете увидеть изменения в обновлении пакета ECC, включая добавление новых файлов.
2) Также обратите внимание, что есть два файла (не в /ecc
папку) Я удалил независимо от этого изменения. Вместо того, чтобы путать эти удаления файлов с ecc
Я сделаю другое cleanup
ветвь позже, чтобы отразить удаление этих файлов.
3) Я не следил за своим рабочим процессом! Я забыл про мерзавца, когда пытался заставить ecc снова работать.
Ниже: а не делать все включено git commit -am "updated ecc package"
Я обычно хотел бы, я только хотел добавить файлы в /ecc
папка. Эти удаленные файлы не были частью моего git add
, но поскольку они уже были отслежены в git, мне нужно удалить их из коммита этой ветки:
$ git checkout -b ecc
$ git add ecc/*
$ git reset HEAD passthru-adapter.igs
$ git reset HEAD shop/mickey/index.php
Unstaged changes after reset:
M passthru-adapter.igs
M shop/mickey/index.php
$ git commit -m "Webgility ecc desktop connector files; integrates with Quickbooks"
$ git checkout master
D passthru-adapter.igs
D shop/mickey/index.php
Switched to branch 'master'
$ git merge --no-ff ecc
$ git branch -d ecc
Deleted branch ecc (was 98269a2).
$ git push origin master
Counting objects: 22, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (14/14), done.
Writing objects: 100% (14/14), 59.00 KiB, done.
Total 14 (delta 10), reused 0 (delta 0)
To git@github.com:me/mywebsite.git
8a0d9ec..333eff5 master -> master
Скрипт для автоматизации вышеперечисленного
Воспользовавшись этим процессом более 10 раз в день, я приступил к написанию пакетных сценариев для выполнения команд, поэтому я сделал почти правильный git_update.sh <branch> <"commit message">
скрипт для выполнения вышеуказанных шагов. Вот источник Gist для этого сценария.
Вместо git commit -am
Я выбираю файлы из "модифицированного" списка, созданного с помощью git status
а затем вставьте те в этом сценарии. Это произошло потому, что я сделал десятки правок, но хотел, чтобы различные имена ветвей помогли сгруппировать изменения.
- Явное слияние без ускоренной перемотки
- Неявное через перебазирование или ускоренное слияние
- Сквош при слиянии
--no-ff
опция гарантирует, что слияние в прямом направлении не произойдет, и что новый объект фиксации всегда будет создан. Это может быть желательно, если вы хотите, чтобы git поддерживал историю ветвей функций. На изображении выше, левая сторона является примером истории мерзавцев после использования git merge --no-ff
и правая сторона является примером использования git merge
где слияние FF было возможно.
РЕДАКТИРОВАТЬ: предыдущая версия этого изображения указала только один родительский элемент для фиксации слияния. Коммиты слияния имеют несколько родительских коммитов, которые git использует для сохранения истории "ветви функций" и исходной ветви. Несколько родительских ссылок выделены зеленым цветом.
Это старый вопрос, и он несколько тонко упоминается в других постах, но объяснение, которое сделало этот щелчок для меня, заключается в том, что для слияния без ускоренной перемотки потребуется отдельная фиксация.
Флаг --no-ff заставляет слияние всегда создавать новый объект фиксации, даже если слияние может быть выполнено с быстрой перемоткой вперед. Это позволяет избежать потери информации об историческом существовании ветки функции и группирует вместе все коммиты, которые вместе добавили функцию.
Другие ответы прекрасно указывают на то, что это приводит к фиксации слияния. Это сохраняет историческую информацию о функциональной ветке, которая полезна, поскольку функциональные ветки регулярно очищаются и удаляются.
Этот ответ может дать контекст, когда использовать или не использовать.
Слияние из функции в основную ветку: использование
Пример работы:
$ git checkout -b NewFeature
[work...work...work]
$ git commit -am "New feature complete!"
$ git checkout main
$ git merge --no-ff NewFeature
$ git push origin main
$ git branch -d NewFeature
Слияние изменений из основной в функциональную ветку: оставить
--no-ff
Пример работы:
$ git checkout -b NewFeature
[work...work...work]
[New changes made for HotFix in the main branch! Lets get them...]
$ git commit -am "New feature in progress"
$ git pull origin main
[shotrcut for "git fetch origin main", "git merge origin main"]
Что такое ускоренная перемотка?
Перемотка вперед — это то, что Git делает, когда вы объединяете или перемещаете ветку непосредственно перед той, которую вы извлекли.
Учитывая следующую настройку ветки:
У вас есть обе ветки, ссылающиеся на один и тот же коммит. У них обоих точно такая же история. Теперь закоммитьте что-нибудь, чтобы показать.
Ветка master по-прежнему ссылается на 7ddac6c, а функция продвинулась на два коммита. Теперь функциональную ветвь можно считать опережающей основную.
Теперь относительно легко увидеть, что произойдет, когда Git перемотает вперед. Он просто обновляет основную ветку, чтобы указать точную фиксацию, которую делает эта функция. В сам репозиторий никаких изменений не вносится, так как коммиты из фичи уже содержат все необходимые изменения.
История вашего репозитория теперь будет выглядеть так:
Когда не происходит перемотка вперед?
Перемотка вперед не происходит, когда изменения были внесены в исходную ветку и новую ветку.
Если бы вы объединили или перебазировали функцию на мастер, Git не смог бы выполнить быструю перемотку вперед, потому что оба дерева разошлись. Учитывая, что коммиты Git неизменяемы, у Git нет возможности получить коммиты из функции в мастер без изменения их родительских ссылок.