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

By | 03/26/2018
 

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

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

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

mkdir testrepo

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

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

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

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

Проверяем:

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

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

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

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

echo testchanges > testfile

Коммитим их:

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

Проверяем:

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

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

git checkout master
Switched to branch 'master'

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

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

Вот оно – Fast-forward.

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

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

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

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

git checkout testbranch
Switched to branch 'testbranch'

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

echo newtestchanges >> testfile

Коммитим:

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

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

git checkout master
Switched to branch 'master'

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

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

Проверяем:

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

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

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

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

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

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

git checkout testbranch
Switched to branch 'testbranch'

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

echo "testchanges from testbrahc" >> testfile

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

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

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

git checkout master
Switched to branch 'master'

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

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

Коммитим их:

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

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

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.

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

!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

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

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

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

По теме

Fast-Forward Git Merge