Git: merge – зачем нужна опция –no-ff (no-fast-forward)

Автор: | 26/03/2018

Во время выполнения git merge – можно указать опцию --no-ff, что бы гит сохранил историю коммитов в feature-бранче (или девелоп-бранче, кому как удобнее называть).

Рассмотрим пример.

Создаём каталог:

[simterm]

$ mkdir testrepo

[/simterm]

Создаём в нём репозиторий:

[simterm]

$ cd testrepo/ && git init .
Initialized empty Git repository in /home/setevoy/Temp/testrepo/.git/

[/simterm]

Создаём и добавляем тестовый файл:

[simterm]

$ touch testfile
$ git add testfile
$ git commit -m "testfile added to master"
[master (root-commit) 6bb0bd5] testfile added to master
 1 file changed, 0 insertions(+), 0 deletions(-)
 create mode 100644 testfile

[/simterm]

Проверяем:

[simterm]

$ git log --pretty=oneline --abbrev-commit
6bb0bd5 (HEAD -> master) testfile added to master=

[/simterm]

Создаём новый тестовый бранч testbranch:

[simterm]

$ git checkout -b testbranch
Switched to a new branch 'testbranch'

[/simterm]

Вносим изменения в файл:

[simterm]

$ echo testchanges > testfile

[/simterm]

Коммитим их:

[simterm]

$ git add testfile
$ git commit -m "testchanges"
[testbranch 1901bcb] testchanges
 1 file changed, 1 insertion(+)

[/simterm]

Проверяем:

[simterm]

$ git log --pretty=oneline --abbrev-commit
1901bcb (HEAD -> testbranch) testchanges
6bb0bd5 (master) testfile added to master

[/simterm]

Переключаемся на master:

[simterm]

$ git checkout master
Switched to branch 'master'

[/simterm]

Мерджим без --no-ff:

[simterm]

$ git merge testbranch
Updating 6bb0bd5..1901bcb
Fast-forward
 testfile | 1 +
 1 file changed, 1 insertion(+)

[/simterm]

Вот оно – Fast-forward.

Проверяем историю:

[simterm]

$ git log --pretty=oneline --abbrev-commit
1901bcb (HEAD -> master, testbranch) testchanges
6bb0bd5 testfile added to master

[/simterm]

Т.е. HEAD будет перенесён напрямую с master на master, и вся история коммитов будет иметь линейный вид, как будто все изменения выполнялись в одном бранче.

ОК, возвращаемся к тестовому бранчу:

[simterm]

$ git checkout testbranch
Switched to branch 'testbranch'

[/simterm]

Вносим новые изменения:

[simterm]

$ echo newtestchanges >> testfile

[/simterm]

Коммитим:

[simterm]

$ git add testfile
$ git commit -m "new testchanges"
[testbranch 93e7773] new testchanges
 1 file changed, 1 insertion(+)

[/simterm]

Переключаемся на master:

[simterm]

$ git checkout master
Switched to branch 'master'

[/simterm]

И мержим без Fast Forward – добавляем --no-ff:

[simterm]

$ git merge testbranch --no-ff
Merge made by the 'recursive' strategy.
 testfile | 1 +
 1 file changed, 1 insertion(+)

[/simterm]

Проверяем:

[simterm]

$ git log --pretty=oneline --abbrev-commit
2c4bab0 (HEAD -> master) Merge branch 'testbranch'
93e7773 (testbranch) new testchanges
1901bcb testchanges
6bb0bd5 testfile added to master

[/simterm]

Теперь в мастере есть отдельный объект коммита для new testchanges из testbranch, и отдельный объект коммита для самого слияния.

Наглядно можно посмотреть с помощью опции --graph:

[simterm]

$ git log --graph --oneline --all
*   2c4bab0 (HEAD -> master) Merge branch 'testbranch'
|\  
| * 93e7773 (testbranch) new testchanges
|/  
* 1901bcb testchanges
* 6bb0bd5 testfile added to master

[/simterm]

Но:  git применит fast forward только в том случае, если в master за время работы над testbranch не было изменений.

Переключаемся на testbranch:

[simterm]

$ git checkout testbranch
Switched to branch 'testbranch'

[/simterm]

Вносим изменения:

[simterm]

$ echo "testchanges from testbrahc" >> testfile

[/simterm]

Коммитим изменения:

[simterm]

$ git add testfile 
$ git commit -m "new testchanges from testbrach"
[testbranch 1ee6e60] new testchanges from testbrach
 1 file changed, 1 insertion(+)

[/simterm]

Переключаемся на мастер:

[simterm]

$ git checkout master
Switched to branch 'master'

[/simterm]

Вносим изменеия тут:

[simterm]

$ git commit -m "new testchanges from master"
On branch master
nothing to commit, working tree clean

[/simterm]

Коммитим их:

[simterm]

$ git add testfile
$ git commit -m "new testchanges from master"
On branch master
nothing to commit, working tree clean

[/simterm]

Мерджим testbranch в master без --no-ff:

[simterm]

$ git merge -m "testbrahc merge to master" testbranch
Merge made by the 'recursive' strategy.
 testfile | 1 +
 1 file changed, 1 insertion(+)

Merge made by the ‘recursive’ strategy.

[/simterm]

Проверяем дерево:

[simterm]

$ !671
git log --graph --oneline --all
*   5318384 (HEAD -> master) testbrahc merge to master
|\  
| * 1ee6e60 (testbranch) new testchanges from testbrach
* |   2c4bab0 Merge branch 'testbranch'
|\ \  
| |/  
| * 93e7773 new testchanges
|/  
* 1901bcb testchanges
* 6bb0bd5 testfile added to master

[/simterm]

Теперь изменения бранча testbranch идут в отдельной ветке даже без указания no fast-forward.

Т.е., основная идея в использовании fast forward git-ом – это “не плодить лишних сущностей”, если в этом нет необходимости (верный последователь принципа Бритвы Оккама): если в мастер не было изменений, то при мердже из девелопа все коммиты будут в ветке мастера.

Если же изменения были в мастере, и в девелоп-ветке – то git сохранит две отдельных линии коммитов.

По теме

Fast-Forward Git Merge