Merge git: зачем и когда их использовать / Хабр

Содержание

зачем и когда их использовать / Хабр

Часто у разработчиков возникает выбор между Merge (слияние) и Rebase (перемещение). В Гугле вы увидите разное мнение, многие советуют не использовать Rebase, так как это может вызвать серьезные проблемы. В статье я объясню, что такое слияние и перемещение, почему вы должны (или не должны) использовать их и как это сделать.

Git Merge и Git Rebase преследуют одну и ту же цель. Они предназначены для интеграции изменений из одной ветки в другую. Хотя конечная цель одинаковая, принципы работы разные.

Некоторые считают, что вы всегда должны использовать Rebase, другие предпочитают Merge. В этом есть свои плюсы и минусы.

Git Merge

Слияние — обычная практика для разработчиков, использующих системы контроля версий. Независимо от того, созданы ли ветки для тестирования, исправления ошибок или по другим причинам, слияние фиксирует изменения в другом месте. Слияние принимает содержимое ветки источника и объединяет их с целевой веткой. В этом процессе изменяется только целевая ветка. История исходных веток остается неизменной.

Плюсы:
  • простота;
  • сохраняет полную историю и хронологический порядок;
  • поддерживает контекст ветки.

Минусы:
  • история коммитов может быть заполнена (загрязнена) множеством коммитов;
  • отладка с использованием git bisect может стать сложнее.

Как это сделать

Слейте ветку master в ветку feature, используя команды checkout и merge.

$ git checkout feature
$ git merge master
(or)
$ git merge master feature

Это создаст новый «Merge commit» в ветке feature, который содержит историю обеих веток.
Git Rebase

Rebase — еще один способ перенести изменения из одной ветки в другую. Rebase сжимает все изменения в один «патч». Затем он интегрирует патч в целевую ветку.

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

Плюсы:

  • Упрощает потенциально сложную историю
  • Упрощение манипуляций с единственным коммитом
  • Избежание слияния коммитов в занятых репозиториях и ветках
  • Очищает промежуточные коммиты, делая их одним коммитом, что полезно для DevOps команд

Минусы:
  • Сжатие фич до нескольких коммитов может скрыть контекст
  • Перемещение публичных репозиториев может быть опасным при работе в команде
  • Появляется больше работы
  • Для восстановления с удаленными ветками требуется принудительный пуш. Это приводит к обновлению всех веток, имеющих одно и то же имя, как локально, так и удаленно, и это ужасно.

Если вы сделаете перемещение неправильно, история изменится, а это может привести к серьезным проблемам, поэтому убедитесь в том, что делаете!

Как это сделать

Переместите ветку feature на главной ветке, используя следующие команды.

$ git checkout feature
$ git rebase master

Это перемещает всю ветку функции в главную ветку. История проекта изменяется, создаются новые коммиты для каждого коммита в основной ветке.
Интерактивное перемещение

Это позволяет изменять коммиты при их перемещении в новую ветку. Это лучше, чем автоматическое перемещение, поскольку обеспечивает полный контроль над историей коммитов. Как правило, используется для очистки истории до слияния ветки feature в master.
$ git checkout feature
$ git rebase -i master

Это откроет редактор, перечислив все коммиты, которые будут перемещены.
pick 22d6d7c Commit message#1
pick 44e8a9b Commit message#2
pick 79f1d2h Commit message#3

Это точно определяет, как будет выглядеть ветка после выполнения перемещения. Упорядочивая объекты, вы можете сделать историю такой, как захотите. Вы можете использовать команды
fixup
, squash, edit, и так далее.



Какой из них использовать?

Так что же лучше? Что рекомендуют эксперты?

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

Принимайте решения на основании компетенции команды в Git. Для вас важна простота или перезаписывание истории, а может быть что-то другое?

Что рекомендую я?

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

Преимущества Rebase:

  • Вы разрабатываете локально: если вы не делились своей работой с кем-либо еще. На данный момент вы должны предпочесть перемещение слиянию, чтобы сохранить свою историю в порядке. Если у вас есть личная вилка репозитория, которая не используется совместно с другими разработчиками, вы можете делать rebase даже после того, как переместились в свою ветку.
  • Ваш код готов к ревью: вы создали пулл реквест. Другие анализируют вашу работу и потенциально стягивают ее к своей вилке для локального ревью. На данный момент вы не должны перемещать свою работу. Вы должны создать коммит «переделать» и обновить ветку. Это помогает отслеживать запросы на пулл реквест и предотвращает случайную поломку истории.
  • Ревью сделано и готово к интеграции в целевую ветку. Поздравляем! Вы собираетесь удалить свою ветку feature. Учитывая, что с этого момента другие разработчики не будут fetch-merging эти изменения, это ваш шанс изменить вашу историю. На этом этапе вы можете переписать историю и сбросить оригинальные коммиты, и эти надоедливые «переделки» и «слияние» сливаются в небольшой набор целенаправленных коммитов. Создание явного слияния для этих коммитов является необязательным, но имеет значение. Он записывает, когда функция достигла master.

Теперь вы знаете хоть и незначительную, но разницу между Merge и Rebase. Уверен, вы примете правильное решение и будете использовать то, что подходит именно вам.

Не забывайте:

code = coffee + developer

Команда Git Merge — SYSOUT

В какой-то момент история репозитория ветвится. Например, в проекте требуется пофиксить баг –  обычно это выполняют в отдельной ветке, а потом сливают в основную ветку master.  Команда git merge как раз и служит для слияния веток.

Пример использования

Допустим, есть основная ветка master. И есть ветка hotfix, в которой мы фиксим баг. Мы его пофиксили и хотим слить изменения в ветку

master. Для этого надо перейти на ветку master – ту ветку, в которую сливают изменения:

git checkout master

И залить изменения из ветки hotfix в текущую ветку master:

git merge hotfix

Это типичный пример использования команды.

Но не всегда слияние происходит гладко. Пока мы делали hotfix, кто-то мог отредактировать тот же файл в том же месте, что и мы. Тогда слияние не удается, возникает конфликт.

Конфликт слияния

Допустим, файл index.html отредактировал кто-то еще в том же месте. Тогда при попытке выполнить команду merge мы получим сообщение:

$ git merge hotfix
Auto-merging index.html
CONFLICT (content): Merge conflict in index.html
Automatic merge failed; fix conflicts and then commit the result.

Что же делать? Если открыть index.html, мы увидим, что Git его пометил примерно так:

<<<<<<< HEAD:index.html
<div>contact : [email protected]</div>
=======
<div>
  please contact us at [email protected]
</div>
>>>>>>> hotfix:index.html

Надо удалить разметку в файле и оставить только один вариант спорной строки. После этого нужно добавить конфликтный файл в индекс:

git add index.html

И сделать снимок:

git commit

На этом все, конфликт будет разрешен.

Если же разрешать конфликты в данный момент некогда, можно отменить текущее слияние

merge (чтобы выполнить его в следующий раз):

git merge abort

Продвинутые сценарии git merge

Параметр –no-commit

Бывают ситуации, когда история снимков (ряд команд commit) в ветке была сделана не очень осмысленно, то есть вы делали снимки состояний, которые не стоит сохранять в истории на всеобщем обозрении. Здесь то и пригодится параметр –no-commit. Он используется нечасто – только в том случае, если хочется уничтожить историю снимков ветки, так чтоб никто их никто никогда не увидел и к ним нельзя было вернуться.

Но сначала вернемся к вышеприведенным командам и рассмотрим, как работает merge без параметра:

git checkout master
git merge hotfix

Первая команда (checkout

) переведет нас на ветку master. Это значит, что наша папка на диске физически заполнятся версиями файлов из ветки master

А команда merge:

  • перенесет в master все изменения ветки hotfix. То есть содержимое текущей папки изменится с учетом залитых изменений
  • и сделает commit (если нет нет конфликтов). То есть сделает снимок полученного состояния и занесет его  в историю. При этом история коммитов ветки hotfix будет сохранена и ее можно будет посмотреть.

Теперь рассмотрим, как работает команда merge с параметром.

Первый шаг такой же – переходим на ветку master, в которую надо залить изменения:

git checkout master

Но в этот раз допустим, что снимки в ветке hotfix  были ненужными и мы хотим, чтоб их не было в истории. В этом случае надо добавить параметр

–no-commit:

git merge --no-commit hotfix

Эта команда обновит текущие папки –  перенесет в них все изменения ветки hotfix, но финального снимка сделано не будет. Мы сможем еще кое-что поменять в файлах и потом выполнить команду commit самостоятельно. При этом в историю попадет только финальный снимок. Будет видно, что ветка hotfix слита с веткой master, и все. А снимки ветки hotfix видны не будут.

Параметр –squash

Эта команда такая же, как предыдущая, разница будет только при просмотре истории. Перейдем на ветку master, в которую мы хотим залить изменения:

git checkout master

После выполнения команды:

git merge --squash hotfix

изменения  появятся в текущих папках ветки master

, аналогично тому, как это было после выполнения предыдущей команды с аргументом –no-commit. При этом финального снимка (commit) так же не произойдет.

Но затем после самостоятельного выполнения команды commit мы увидим в истории этот снимок как самый обычный снимок в ветке master. То, что ветка hotfix сливалась в ветку master, отображено в истории не будет.

Параметр –no-ff

Параметр –no-ff запрещает перемотку.

ff означает fast forward, то есть перемотка.

Во-первых объясню, что такое перемотка. Она случается при слиянии, если в той ветке, от которой мы ответвились, не было снимков после нашего ответвления. Называется это перемоткой, потому что технически никакого нового снимка в этом случае делать не надо, выполняя слияние двух предков. Наш снимок считается последним, и на него просто перематывается текущий указатель. Смотрите пример.

На картинке мы ответвились от С и сделали снимки X и Y. После снимка C в ветке master никто ничего не делал, и при слиянии указатель просто переместится на снимок Y:

master: A - B - C
                 \
hotfix:           X - Y

Еще раз обратите внимание, что в ветке master после ответвления от С снимков не появилось, именно поэтому перемотка и возможна.

С помощью аргумента –no-ff можно запретить перемотку, а именно: сделать при слиянии новый отдельный снимок, хоть в нем и нет технической необходимости. Сделаем это:

git merge --no-ff hotfix

На картинке это выглядит так:

master: A - B - C   -  (M)
                 \     /
hotfix:           X - Y

Будет создан дополнительный снимок M и указатель переместится на него (хотя мог бы просто переместиться на Y, если б выполняли слияние без параметра –no-ff).

Когда именно стоит использовать параметр –no-ff сказать сложно – отчасти это вопрос вкуса. Некоторые любят делать отдельный снимок при любом важном слиянии.

Слияния или мерджи веток. Урок 9

Урок, в котором мы научимся мерджить ветки между собой

Видеоурок

Конспект урока

Краткое содержание урока, основные инструкции для командной строки, полезные ссылки и советы.

Ветка master — еще раз

Мастер — это основная ветка проекта, в которую заливается только рабочий проверенный код. Новый функционал в конце концов оказывается в мастере. В этой ветке находится тот же самый код, что и на боевом сайте

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

Подробнее о разных соглашениях мы поговорим во второй части курса.

Что такое мердж или слияние веток

Это перенос кода из одной ветки в другую. Например, когда мы заканчиваем работу над веткой, например, сделали новый функционал или поправили багу, мы сливаем ее в мастер. В мастере код проверяется еще раз и выкладывается на боевой сервер.

Сливать друг в друга можно любые ветки. Технически, с точки зрения git нет никакой разницы, сливается ветка с новым функционалом в мастер или наоборот. Для нас мастер — это основная ветка разработки, а для git это просто ветка.

Разница логическая

Мы будем говорить, что новая ветка сливается в мастер, когда мы закончили работать над функционалом и хотим выложить его на боевой сайт. Тогда мы сливаем ветку в мастер и выкладываем мастер в продакшен.

Мастер сливается в ветку или мастер подтягивается в ветку, когда мы продолжаем работать над веткой, но хотим периодически подтягивать новые коммиты с сервера — новый код, который написали наши коллеги. Если работа над веткой идет довольно долго, есть смысл подтягивать изменения из мастера хоть каждый день.

Следует четко различать мердж своей ветки в мастер и мердж мастера в свою ветку.

Мердж ветки в мастер

Выполняется после завершения работы над своей веткой при помощи команды git merge. Чтобы вмерджить ветку в мастер, нужно сначала перейти в мастер, а затем выполнить git merge branch_name.


    $ git checkout master
    $ git merge news

При этом возможны разные ситуации

Поговорим о них подробнее

Пока мы работали над веткой, в мастере не появилось новых коммитов

То есть мы создали ветку, поработали над ней, собрались заливать ее в мастер, а за это время новых коммитов там не появилось. Тогда слияние проходит так


    $ git merge news
    Updating f32b91e..33ea897
    Fast-forward
     index.html | 3 ++-
     1 file changed, 2 insertions(+), 1 deletion(-)
     
    $ git push origin master
    $ git branch -d news

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

Не забудьте сразу запушить изменения, чтобы их увидели коллеги, и удалить локальную ветку header, если она больше не нужна.

Теперь другая ситуация.

Пока мы работали над веткой, в мастере появились коммиты от коллег

Сначала переключаемся на мастер


    $ git checkout master 
    Switched to branch 'master'
    Your branch is up-to-date with 'origin/master'.

Почему «is up-to-date»? Потому что мы еще не сделали git pull. Делаем


    $ git pull --rebase origin master 

Мерджим свою ветку в мастер


    $ git merge news-styles 

И не забываем запушить изменения


    $ git push origin master 

Что если сначала не подтягивать мастер, а смерджить свою ветку

Принципиально ничего не изменится, но лучше сначала сделать актуальной ветку мастер, а уже потом заливать свои изменения. А еще лучше держать актуальной свою ветку относительно мастера. Это значит, что стоит почаще подтягивать мастер в свою ветку. Таким образом мы в своей ветке будем работать с актуальным кодом и у нас меньше риска что-нибудь поломать. А если поломаем, то лучше чинить это в своей ветке, а не в мастере.

Как вмерджить мастер в свою ветку

Сначала идем в мастер, подтягиваем изменения с сервера, то есть делаем git pull. Затем переключаемся в свою ветку и делаем git merge master


    $ git checkout master
    $ git pull --rebase origin master
    $ git checkout news-redesign
    $ git merge master

Затем проверяем, что ничего не поломалось и продолжаем работать.

Мердж коммиты

Чем чаще мерджить между собой ветки, тем больше появляется так называемых мердж-коммитов. Такой коммит появляется каждый раз, когда мы подтягиваем мастер в свою ветку или сливаем свою ветку в мастер. Эти коммиты не хранят изменений, они хранят только факт мерджа одной ветки в другую.

Посмотрим список коммитов и найдем мердж-коммит с хэшем 051f754


    $ git log --oneline 
    051f754 Merge branch 'news'
    ...

Посмотрим его содержимое


    $ git show 051f754
    
    commit 051f75475cb1dca3cd08c1c7367a3308671ccf7b
    Merge: 0a3a6a3 2346be5
    Author: Alexandr Shestakov 
    Date:   Sat Feb 8 14:10:39 2020 +0300
    
        Merge branch 'news'

То есть информация только о том, что в мастер была залита ветка news. Сами изменения в файлах, которые мы делали в ветке news, лежат в других коммитах.

Споры о том, есть польза от таких коммитов, ведутся годами и не закончатся, видимо, никогда.

Плюс в том, что у нас есть возможность выбора — git позволяет обходиться без таких коммитов вообще. Тогда история коммитов будет содержать только изменения в коде, а не операции с ветками. Это достигается механизмом ребейза, но это тема, которая выходит за рамки первой курса. Ребейз, его плюсы и минусы, мы подробно рассмотрим во второй части курса.

Также можете почитать мою старую статью — Отличия мерджа от ребейза.
В ней нет видеоурока, но наглядно объясняется на скриншотах.

Мерджи всегда проходят так гладко?

К сожалению, нет

В этом уроке мы намеренно упрощали ситуацию и рассматривали случаи, когда наши коллеги делают изменения в других участках кода. То есть мы с ними даже не пересекались. В реальной жизни так происходит не всегда. Иногда мы правим одни и те же участки кода и тогда при их слиянии git не может понять, чей вариант правильный. Это называется возникновения конфликта.

Подробнее о конфликтах и их разрешении мы поговорим в следующем уроке.

Что могу посоветовать

  • Не забывайте, что мастер — это продакшен, это рабочая версия кода, которую не стоит ломать
  • В мастер не сливается незаконченная работа, только полностью рабочий и проверенный функционал
  • Перед мерджем своей ветки в мастер, подтягивайте изменения в мастер с сервера
  • После мерджа в мастер, не забывайте пушить эти изменения
  • Подтягивайте мастер в свою ветку почаще
  • Мердж-коммиты несут информацию только о факте слияния веток. Договоритесь в команде, насколько они вам нужны
  • Познакомьтесь с механизмом ребейза, он позволяет вести чистую историю без мердж-коммитов

Спасибо за внимание и до встречи!

Все уроки курса

Продолжение следует…

пример merge develop в master

Пример того, как смерджить бранч из девелоп-ветки в мастер, с сохранением всех коммитов и истории.

Переключаемся на мастер, потягиваем последние изменения:

$ git checkout master
$ git pull
Already up to date.

Переключаемся на бранч, который будем мерджить в master, в данном примере это LTHS-380_Update_build_deploy_to_compose:

git checkout LTHS-380_Update_build_deploy_to_compose

M       scripts/api_start.sh

M       terraform/terraform-cn.sh

Switched to branch ‘LTHS-380_Update_build_deploy_to_compose’

Your branch is up to date with ‘origin/LTHS-380_Update_build_deploy_to_compose’.

M после чекаута указывает на то, что файл был modified:

  • M = modified
  • A = added
  • D = deleted
  • R = renamed
  • C = copied
  • U = updated but unmerged

Но тут эти изменения не нужны, коммитить их не надо, продолжаем.

Подтягиваем последние изменения в девелоп-бранче:

git pull

Already up to date.

Тут изменений нет.

Мержим изменения из мастера в девелоп бранч, что бы сохранить все изменения и решить конфликты, если будут, в девелоп-бранче:

git merge master

Already up to date.

Тут изменений в мастере не было, потому up to date, идём дальше.

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

Мерджим в него изменения из девелоп-ветки:

git merge -m «compose migration complete» —no-ff LTHS-380_Update_build_deploy_to_compose

Removing terraform/files/userdata/eureka.sh

Removing terraform/eureka.tf

Removing packer/eureka/template.json

Removing packer/eureka/app.sh

Merge made by the ‘recursive’ strategy.

packer/api/app.sh                   |  1 +

packer/api/docker-compose.yml       | 64 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

packer/api/template.json            |  8 +++++++-

packer/eureka/app.sh                | 11 ————

packer/eureka/template.json         | 35 ————————————

terraform/api.tf                    | 35 ————————————

terraform/eureka.tf                 | 91 ——————————————————————————————-

terraform/files/userdata/api.sh     | 37 ++++———————————

terraform/files/userdata/eureka.sh  |  8 ———

terraform/security_group_api_elb.tf | 74 +————————————————————————-

terraform/variables.tf              |  3 +++

11 files changed, 80 insertions(+), 287 deletions(-)

create mode 100644 packer/api/docker-compose.yml

delete mode 100644 packer/eureka/app.sh

delete mode 100644 packer/eureka/template.json

delete mode 100644 terraform/eureka.tf

delete mode 100644 terraform/files/userdata/eureka.sh

Опция --no-ff, или no fast-forward описана в посте Git: merge – зачем нужна опция –no-ff, no-fast-forward.

Теперь осталось запушить изменения в удалённый репозиторий:

git push

Counting objects: 1, done.

Writing objects: 100% (1/1), 228 bytes | 228.00 KiB/s, done.

Total 1 (delta 0), reused 0 (delta 0)

To ssh://bitbucket.domain.tld:7999/server-api-infrastructure.git

391c558..1e23693  master -> master

Проверяем:

Теперь соберём всё вместе:

  1. git checkout master // переключаемся на мастер, в который будем делать мердж
  2. git pull // подягиваем последние изменения
  3. git checkout develop // переключаемся на бранч, который будем мерджить в мастер
  4. git pull // подтягиваем последние изменения
  5. git merge master // мерджим мастер в девелоп-бранч, решаем конфиликты, если есть, в девелоп бранче
  6. git checkout master // переключаемся на мастер
  7. git merge -m "merge message" develop // мерджим изменения из девелопа в мастер, можно добавить --no-ff
  8. git push // пушим мастер в репозиторий

Если работа над задачей, для которой создавался бранч, закончена — то можно добавить git тег и удалить бранч.

В таком случае процесс будет таким:

  1. git checkout master // переключаемся на мастер, в который будем делать мердж
  2. git pull // подягиваем последние изменения
  3. git checkout develop // переключаемся на бранч, который будем мерджить в мастер
  4. git pull // подтягиваем последние изменения
  5. git merge master // мерджим мастер в девелоп-бранч, решаем конфиликты, если есть, в девелоп бранче
  6. git checkout master // переключаемся на мастер
  7. git merge -m "merge message" develop // мерджим изменения из девелопа в мастер, можно добавить --no-ff
  8. git tag -a v0.1 -m "tag message" // добавляем тег на текущее состояние репозитория
  9. git push origin v0.1 // пушим тег
  10. git branch -d develop // удаляем develop бранч, при необходимости — восстанавливаем из тегов — git checkout -b develop v1.0
  11. git push // пушим мастер в репозиторий

Готово.


Изучаем git. merge vs rebase для начинающих

август 21 , 2016

Про git merge и git rebase написаны тысячи статей. Зачем же нужна еще одна?
Разбираясь в свое время с git rebase, я не нашел ни одной статьи, описывающей этот инструмент с точки зрения начинающих пользоваться гитом. Я видел множество схем ребейза, но так и не мог понять, для чего же все-таки нужен rebase, чем он так отличается от merge. Когда стоит использовать rebase и что будет плохого (и будет ли), если его не применять.
Я не люблю абстрактные схемы. Поэтому покажу простой пример из повседневной жизни двух коллег-программистов. Наглядно, с картинками и подробностями, мы увидим, как работает merge и rebase. Для давно работающих с гитом в статье не будет ничего нового. Тем же, кто только начинает знакомиться с rebase, надеюсь, статья будет полезной.

Представим ситуацию: двое коллег, назовем их Вася и Петя, работают над одним проектом. Пусть это будет простой сайт. Файл index.html с разметкой из меню, содержимого и подвала. И стили для страницы style.css. Вася создает первый коммит и дальше работает над подвалом сайта. Петя занимается верхним меню и содержимым главной страницы — контентом.

В проекте есть основная ветка master, в нее периодически коммитят и Вася, и Петя. Для работы над стилями подвала Вася создает новую ветку new-footer, делая свои коммиты в нее и время от времени подтягивая в эту ветку мастер. После завершения работы над new-footer Вася сольет ее в основную ветку.

В плане слияния веток git merge отличается от git rebase только тем, как выглядит история коммитов, насколько она красива и понятна. И сейчас мы посмотрим поэтапно, как происходит работа в случае merge и rebase и как выглядит история коммитов и веток.

Начинает проект Вася, он создает первый коммит и пушит его. Дальше начинается уже совместная работа с Петей. Пока наша ветка выглядит так (скриншоты взяты из phpStorm, инструмента git log)

Дальше Вася добавляет подвал прямым коммитом в мастер.

Вася хочет поделиться подвалом со своим коллегой Петей и пытается запушить мастер. Но Петя уже успел запушить со своей стороны в мастер коммит, касающийся меню. Как известно, git не даст запушить ветку до тех пор, пока Вася не подтянет свежий коммит от Пети. Что может быть проще? Вася делает
git pull origin master
и видит в истории это

Что это за хрень? У нас фактически всего лишь 3 коммита: initial, один от Васи и один от Пети. Но в истории мы видим уже 4. Дополнительный коммит ‘merge branch master …’ не несет никакой смысловой нагрузки. Плюс непонятное ответвление, тоже никому не нужное. Мы всего лишь подтянули коммит Пети в той же ветке, мы не сливали одну ветку в другую (с нашей точки зрения). А представьте, какую кучу хлама будет содержать ветка после десятка таких git pull. Как это исправить?

Вернемся к тому моменту, когда мы собрались делать git pull. И сделаем его чуть иначе
git pull —rebase origin master.
Теперь история коммитов выглядит так

Отлично! То, что нужно. Коммит от Пети следует сразу за initial, коммит от Васи самый свежий. Можно пушить ветку на сервер.
git push origin master

Просто удивительно, сколько разработчиков пренебрегают, забывают или просто не знают об этой возможности. Если все в команде договорятся делать git pull —rebase, то основные долгоживущие ветки будут выглядеть гораздо чище. Но это мы отвлеклись, продолжим.

После этого Вася приступает к работе над оформлением подвала и благоразумно заводит новую ветку
git checkout -b new-footer
Дальше Вася делает в новой ветке первый коммит

Но за это время шустрый Петя уже успел закоммитить в мастер правки по тексту на главной странице. Вася ответственный разработчик и он помнит, что время от времени нужно подтягивать в свою ветку свежий мастер, дабы убедиться, что его работа не ломает работу Пети (и наоборот). Вася переключается на мастер, стягивает этот свежий коммит (конечно, через git pull —rebase) и переходит обратно в свою ветку, чтобы эти изменения влить в new-footer.
git checkout master
git pull —rebase origin master
git checkout new-footer

Задача Васи — влить ветку мастер в свою ветку new-footer. Вася делает
git merge master
и видит это

Что-то не очень. Подтянули один коммит и опять получили лишний ‘merge branch’. Ладно, некоторые программисты не видят в этом ничего плохого и утверждают, что хотят знать, когда в текущую ветку вливался мастер. И когда мастер заливался во второй раз. И в третий. Ну вы поняли. Будет очень здорово, если в комментариях мне пояснят — зачем?

Но оставим пока так и работаем дальше. Вася делает еще один коммит в новом подвале

Затем он тестирует свою ветку, убеждается, что все хорошо, и сливает ее в мастер.
git checkout master
git merge —no-ff new-footer

Заметьте, я делаю git merge с опцией —no-ff, чтобы в мастере были видны коммиты, сделанные в другой ветке. Чаще всего встречается именно такая практика, именно ее я и придерживаюсь. В итоге наша история выглядит так

Напомню, в ветке new-footer всего лишь 2 коммита от Васи. Здесь же мы видим лишний merge и лишнюю связь, которая ни о чем не говорит. Легко представить тот бардак, который будет твориться при нескольких мерджах в долгоживущих ветках.

git rebase дает нам возможность сохранить историю коммитов чистой и понятной. Давайте все исправим и вернемся к тому моменту, когда мы собрались мерджить мастер в нашу ветку

Теперь делаем так
git rebase master и видим следующую картину

Намного лучше! Мы просто передвинули указатель HEAD так, как будто new-footer ответвилась от мастера только что. Вася делает второй коммит

И наконец Вася сливает ветку new-footer в мастер и удаляет ее.
git checkout master
git merge —no-ff new-footer
git branch -d new-footer

Отличная картинка. Мы видим 2 коммита Васи и видим, что они сделаны в отдельной ветке. А если Вася подписывал бы начало своих коммитов названием ветки, а не своим именем, то история выглядела бы еще лучше :-)

Обратим внимание на опцию —no-ff. git merge по умолчанию пытается просто передвинуть указатель, не делая мерджа. Если мы сделаем просто git merge new-footer, то итоговая история будет такой

Я лично предпочитаю первый вариант, но здесь уже зависит от того, какие договоренности приняты в Вашем проекте.

P.S. Есть негласное правило. Без опаски пользуйтесь git rebase смело до тех пор, пока Вы работаете в своей ветке один. Как только к ветке присоединяется ваш коллега, подтягивайте изменения из основной ветки через merge. Это связано с тем, что rebase перезаписывает историю коммитов, и пушить на сервер вам придется с опцией —force. Но это уже другая история.

P.P.S. К сожалению, кроме незнания/неумения, есть еще одна банальная причина, по которой программисты не пользуются rebase. Многие из нас работают с гитом исключительно средствами любимой IDE. Так вот часто случается, что git pull в IDE по умолчанию не ставит опцию —rebase. Нужно для этого тыкать отдельный чекбокс. И git merge находится ближе в меню, чем git rebase. И все.

Я до сих пор вспоминаю добрым словом своего тимлида с одной из прошлых работ, который настоятельно рекомендовал по максимуму работать с гитом в терминале.

Привыкая работать в командной строке, Вы гораздо больше узнаете о возможностях используемого инструмента. Я не призываю к фанатизму и не предлагаю отказываться от графического интерфейса. Есть операции, которые удобнее делать в IDE, например, просмотр истории или разрешение конфликтов при слиянии. Но уверяю, что 90-95% повседневной работы с гитом быстрее и удобнее выполнять в терминале. И тогда сами собой пропадут вопросы «что такое git pull —rebase и как его делать в phpStorm»

Для тех, кто подумал, что я умный, советую статью Как я перестал бояться и полюбил git

И небольшой опрос напоследок

Все статьи о git

Анонсы статей, обсуждения интернет-магазинов, vue, фронтенда, php, гита.
Истории из жизни айти и обсуждение кода.

Как Вам статья? Оцените!

Понравилось? Поделись с другими 🙂

Please enable JavaScript to view the comments powered by Disqus.

зачем и когда их использовать

Git Merge и Git Rebase преследуют одну и ту же цель. Они предназначены для интеграции изменений из одной ветки в другую. Хотя конечная цель одинаковая, принципы работы разные.

Некоторые считают, что вы всегда должны использовать Rebase, другие предпочитают Merge. В этом есть свои плюсы и минусы.

Git Merge

Слияние — обычная практика для разработчиков, использующих системы контроля версий. Независимо от того, созданы ли ветки для тестирования, исправления ошибок или по другим причинам, слияние фиксирует изменения в другом месте. Слияние принимает содержимое ветки источника и объединяет их с целевой веткой. В этом процессе изменяется только целевая ветка. История исходных веток остается неизменной.

Плюсы:

  • простота;
  • сохраняет полную историю и хронологический порядок;
  • поддерживает контекст ветки.

Минусы:

  • история коммитов может быть заполнена (загрязнена) множеством коммитов;
  • отладка с использованием git bisect может стать сложнее.

Как это сделать

Слейте ветку master в ветку feature, используя команды checkout и merge.

Это создаст новый «Merge commit» в ветке feature, который содержит историю обеих веток.

Git Rebase

Rebase — еще один способ перенести изменения из одной ветки в другую. Rebase сжимает все изменения в один «патч». Затем он интегрирует патч в целевую ветку.

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

Плюсы:

  • Упрощает потенциально сложную историю
  • Упрощение манипуляций с единственным коммитом
  • Избежание слияния коммитов в занятых репозиториях и ветках
  • Очищает промежуточные коммиты, делая их одним коммитом, что полезно для DevOps команд

Минусы:

  • Сжатие фич до нескольких коммитов может скрыть контекст
  • Перемещение публичных репозиториев может быть опасным при работе в команде
  • Появляется больше работы
  • Для восстановления с удаленными ветками требуется принудительный пуш. Это приводит к обновлению всех веток, имеющих одно и то же имя, как локально, так и удаленно, и это ужасно.

Если вы сделаете перемещение неправильно, история изменится, а это может привести к серьезным проблемам, поэтому убедитесь в том, что делаете!

Как это сделать

Переместите ветку feature на главной ветке, используя следующие команды.

Это перемещает всю ветку функции в главную ветку. История проекта изменяется, создаются новые коммиты для каждого коммита в основной ветке.

Интерактивное перемещение

Это позволяет изменять коммиты при их перемещении в новую ветку. Это лучше, чем автоматическое перемещение, поскольку обеспечивает полный контроль над историей коммитов. Как правило, используется для очистки истории до слияния ветки feature в master.

Это откроет редактор, перечислив все коммиты, которые будут перемещены.

Это точно определяет, как будет выглядеть ветка после выполнения перемещения. Упорядочивая объекты, вы можете сделать историю такой, как захотите. Вы можете использовать команды fixup, squash, edit, и так далее.

Какой из них использовать?

Так что же лучше? Что рекомендуют эксперты?

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

Принимайте решения на основании компетенции команды в Git. Для вас важна простота или перезаписывание истории, а может быть что-то другое?

Что рекомендую я?

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

Преимущества Rebase:

  • Вы разрабатываете локально: если вы не делились своей работой с кем-либо еще. На данный момент вы должны предпочесть перемещение слиянию, чтобы сохранить свою историю в порядке. Если у вас есть личная вилка репозитория, которая не используется совместно с другими разработчиками, вы можете делать rebase даже после того, как переместились в свою ветку.
  • Ваш код готов к ревью: вы создали пулл реквест. Другие анализируют вашу работу и потенциально стягивают ее к своей вилке для локального ревью. На данный момент вы не должны перемещать свою работу. Вы должны создать коммит «переделать» и обновить ветку. Это помогает отслеживать запросы на пулл реквест и предотвращает случайную поломку истории.
  • Ревью сделано и готово к интеграции в целевую ветку. Поздравляем! Вы собираетесь удалить свою ветку feature. Учитывая, что с этого момента другие разработчики не будут fetch-merging эти изменения, это ваш шанс изменить вашу историю. На этом этапе вы можете переписать историю и сбросить оригинальные коммиты, и эти надоедливые «переделки» и «слияние» сливаются в небольшой набор целенаправленных коммитов. Создание явного слияния для этих коммитов является необязательным, но имеет значение. Он записывает, когда функция достигла master.
Теперь вы знаете хоть и незначительную, но разницу между Merge и Rebase. Уверен, вы примете правильное решение и будете использовать то, что подходит именно вам.Не забывайте:

Перевод статьи An Introduction to Git Merge and Git Rebase: What They Do and When to Use Them от Digital Skynet 🙂

Безболезненное разрешение Merge конфликтов в Git / Хабр

Предлагаю читателям «Хабрахабра» перевод публикации «Painless Merge Conflict Resolution in Git»
из блога blog.wuwon.id.au.

В моей повседневной работе, часто приходится иметь дело со множеством git ветвей (branch). Это могут быть ветви промежуточных релизов, ветви с устаревшим API находящиеся на поддержке для некоторых клиентов, или ветви с экспериментальными свойствами. Лёгкость создания ветвей в модели Git так и соблазняет разработчиков создавать все больше и больше ветвей, и как правило бремя от большого количества ветвей становится очень ощутимым, когда приходится все эти ветви поддерживать и периодически делать слияния (merge) с другими ветвями.


Слияния очень важны для поддержания кода в актуальном состоянии, и как правило ошибка сделанная при слиянии может привести к большей головной боли, нежели ошибка сделанная при простом коммите. К сожалению ошибки слияния далеко не редкость, потому что во-первых слияния имеют несколько родительских ветвей. Даже при анализе истории слияния ветвей, бывает очень трудно понять, какие же изменения были сделаны для разрешения конфликта. Во-вторых, отмена неудачного слияния может превратиться в большую головную боль. В-третьих, большая часть конфликтов слияния происходит при работе с чужим кодом, потому что само понятие ветвей подразумевает множество пользователей, т.е. далеко не всегда слияние производит тот же человек который работал с той или иной веткой. В сухом остатке, сделать ошибку при слиянии очень легко, её трудно исправить и трудно найти. Таким образом время потраченное на изучение и понимание процесса слияния ветвей, окупится с лихвой.

Удивительно, но я обнаружил, что многие доступные инструменты и интерфейсы предназначенные для выполнения слияний, не достаточно хорошо оснащены для эффективного выполнения этого процесса. Часто программист просто надеется что команда git merge сделает за него всю работу. Но когда все-таки происходит конфликт, то обычно стратегия слияния заключается в беглом просмотре кода вокруг строки конфликта, и интуитивном угадывании что именно данный кусок кода предпочтительней другого.

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


Голубые Розы (Roses are Blue)

Давайте предположим что вашей команде поручили писать поэмы в отведённом для этих целей репозитории. (Какой кошмар!) А вам доверили самое главное — делать слияния последних фиксов из ветки master в ветку beta. Итак, вы переключаетесь в ветку beta и выполняете следующую команду:

$ git merge master
Auto-merging roses.txt
CONFLICT (content): Merge conflict in roses.txt
Automatic merge failed; fix conflicts and then commit the result.

Ого, это конфликт. Вы решаете просмотреть файл на который ссылается git:

$ cat roses.txt
<<<<<<< HEAD
roses are #ff0000
violets are #0000ff
all my base
are belong to you
=======
Roses are red,
Violets are blue,
All of my base
Are belong to you.
>>>>>>> master

(Listing 1)

Замечательно! Весь файл, как показывает Listing 1, находится в конфликтном состоянии. Какой же вариант файла является более корректным? Оба варианта выглядят корректно. Верхний вариант написан в хакер-стиле с элементами цветовой кодировки в стиле HTML и с использованием только строчных букв. Нижний вариант выглядит более натурально, с использованием пунктуации и заглавных букв.

Если бы это был ваш проект, вы бы могли просто выбрать один вариант и покончить с этим слиянием. Но проблема в том, что это не ваша поэма, вы никогда не читали эту поэму раньше, не были ответственны за написание или редактирование, и вы отлично понимаете что в случае не верного решения чья-то тяжёлая работа может кануть в небытие. Однако вас всё же назначили ответственным по слиянию этих веток. Что же вам делать?


Назад к Базе (Back to Base)

Хитрость заключается в том, что Listing 1 не даёт вам полную информацию, необходимую для совершения корректного слияния. На самом деле, в процессе слияния участвуют четыре важных части информации (состояния), три из которых просто необходимы для успешного разрешения конфликта. В случае Listing 1, Git предоставил вам только два состояния.

Следующая диаграмма иллюстрирует эти четыре состояния:

Состояния (B) и © относятся к текущим положениям (head) веток master и beta соответственно, эти два состояния как раз таки и отражены в Listing 1. Состояние (D) это результат слияния, то что вы хотите получить/сгенерировать в конечном итоге (в большинстве случаев Git автоматически генерирует состояние (D)). Состояние (А) на самом верху, представляет собой базу (основу) слияния веток master и beta. База слияния (A) это последний общий предок веток master и beta, и пока предположим что это база слияния уникальна. Как мы увидим позже состояние (A) играет ключевую роль в разрешении конфликтов. На диаграмме я также отразил дельты 1 и 2, которые представляют изменения между состояниями (A)-(B), и (A)-© соответственно. Зная состояния (A), (B) и © дельты 1 и 2 могут быть легко получены (вычислены). Обратите внимание, что дельты 1 и 2 могут состоять из более чем одного коммита. Но для наших целей будем считать что все дельты монолитны.

Чтобы понять, как получить состояние (D), вы должны понимать что же операция слияния пытается сделать. Состояние (D) должно представлять собой сочетание изменений, внесённых в ветку master и beta соответственно. Т.е. другими словами сочетание дельт 1 и 2. Идея проста на поверхности и большую часть времени не требует вмешательства со стороны человека, за исключением особых случаев когда дельты затрагивают наслаиваемые (пересекающиеся) части файла. В такой ситуации вам требуется помочь машине сгенерировать результат (D), путём сравнения дельт 1 и 2.


Определение Отличий (Identifying the Differences)

Для того чтобы найти изменения внесённые в каждую ветку, необходимо знать как выглядит база слияния, состояние (A). Самый простой механизм получения информации о базе слияния, это установка опции merge.conflictstyle в значение diff3

$ git config merge.conflictstyle diff3

После включения этой опции, попробуйте заново сделать слияние (git reset —hard; git merge master) и проинспектируйте конфликтующий файл ещё раз:

$ cat roses.txt
<<<<<<< HEAD
roses are #ff0000
violets are #0000ff
all my base
are belong to you
|||||||
roses are red
violets are blue
all my base
are belong to you
=======
Roses are red,
Violets are blue,
All of my base
Are belong to you.
>>>>>>> master

(Listing 2)

Теперь мы видим третий фрагмент посередине, который и является базой слияния или состояние (A). Изменения видны как на ладони: в ветке beta (HEAD) человеческие названия цветов были заменены на HTML коды, а в ветку master добавили капитализацию и пунктуацию. Основываясь на этих знаниях, мы теперь знаем что результат должен включать в себя капитализацию, пунктуацию и HTML коды цветов.

В принципе на этом можно было бы и закончить, потому что результат достигнут. Но есть решение и получше.


Графическое Слияние (GUI Merging)

Хотя и простое текстовое представление конфликта слияния делает свою работу в простых случаях, на практике конфликты могут быть более радикальными и сложными. В таких случаях могут помочь графические инструменты. Мой выбор пал на простой инструмент написанный на Python под названием meld, но может подойти любой другой графический инструмент, способный представить слияние в трёх-колоночном виде.

Для использования графического инструмента (он должен быть установлен), после того как git пожаловался что есть конфликт, введите следующую команду:

$ git mergetool

Последует вопрос какой программой для слияния вы хотели бы воспользоваться, просто введите meld и нажмите Enter. Вот как окно программы может выглядеть (подразумевается опция merge.conflictstyle не была включена):

Несмотря на то что информация представлена бок о бок, она не отображает нужные фрагменты которые были в Listing 2. Мы не видим здесь фрагмента базы слияния (состояния (A)), что мы видим это файл roses.txt.LOCAL.2760.txt в левой колонке и файл roses.txt.REMOTE.2760.txt в правой колонке и файл посередине это неудачное слияние. Т.е. по сути нам представили состояния (B), © и несостоявшееся состояние (D), но состояние (A) отсутствует…

Правда отсутствует? Давайте проверим, в старом добром терминале:

$ ls -1
roses.txt
roses.txt.BACKUP.2760.txt
roses.txt.BASE.2760.txt
roses.txt.LOCAL.2760.txt
roses.txt.REMOTE.2760.txt

Видим интересующий нас файл: roses.txt.BASE.2760.txt. Это и есть файл базы слияния. Теперь нам осталось всего лишь найти изменения внесённые в ветки master и beta, по отношению к базе. Мы можем сделать это двумя отдельными вызовами meld:

$ meld roses.txt.LOCAL.2760.txt roses.txt.BASE.2760 &
$ meld roses.txt.BASE.2760 roses.txt.REMOTE.2760.txt &

(Кто-то может подметить что было бы более разумно, поменять порядок аргументов в первом вызове, для того чтобы файл базы находился в левой колонке в обоих случаях, но именно такой порядок сохраняет подобие трёх-колоночного вида, при котором база остаётся по середине.) Результат выполнения — два окна как показано ниже:

При чтении первого окна справа налево и второго окна слева направо, становится ясно как день, какие изменения произошли в каждой ветке. Так как meld любезно подсветил все изменения, теперь практически не возможно пропустить даже мелко заметные правки (Кто-нибудь заметил добавление предлога «of» при просмотре текстового представления разрешения конфликта Listing 1 или даже Listing 2?)

Вооружившись этими знаниями, мы теперь можем вернуться к трёх-колоночному представлению и сделать изменения. Моя стратегия ручного слияния это взять весь текст из ветки с более весомыми изменениями (в данном случае master/REMOTE т.е. beta), и поверх него производить пошаговые правки, т.е. вносить изменения сделанные в другой ветке (master). Вот что получилось:


А теперь всё вместе (All Together Now)

Надеюсь, вы найдёте этот трёх-окошечный метод разрешения конфликтов, таким же полезным каким нахожу его я. Но согласитесь что запускать новые вызовы meld вручную каждый раз при разрешении конфликтов, не очень то и удобно. Выход, это настроить git таким образом чтобы все три окна открывались автоматически при вызове команды git mergetool. Для этого можно создать выполняемый скрипт, который должен находится в переменной окружения PATH (например $HOME/bin/gitmerge), со следующим содержимым:

#!/bin/sh
meld $2 $1 &
sleep 0.5
meld $1 $3 &
sleep 0.5
meld $2 $4 $3

И добавьте следующее в ваш ~/.gitconfig файл:

[merge]
    tool = mymeld
[mergetool "mymeld"]
    cmd = $HOME/bin/gitmerge $BASE $LOCAL $REMOTE $MERGED

Теперь, когда вы в следующий раз будете запускать команду git mergetool для разрешения конфликта, откроются все три окна:

Окно дифа между BASE и LOCAL
Окно дифа между BASE и REMOTE
Окно трёх-колоночного вида

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


Бонус от переводчика

Для тех кто пользуется tmux и n?vim, предлагаю следующий скрипт gitmerge:

#!/bin/sh
sn=gitmerge

tmux new-session -d -s "$sn" -n "diff3" "nvim -d $2 $4 $3"
tmux split-window -t "$sn:1" -v "nvim -d $2 $1"
tmux split-window -t "$sn:1" -h "nvim -d $1 $3"

Примечание: если вы не используете эту опцию в своем ~/.tmux.conf, то вам надо поменять в двух последних строках "$sn:1" на "$sn:0"

Соответственно добавьте следующее в ваш ~/.gitconfig

[mergetool "gitmerge"]
    cmd = $HOME/bin/gitmerge \"$BASE\" \"$LOCAL\" \"$REMOTE\" \"$MERGED\"
[merge]
    tool = gitmerge

Воркфлоу разрешения конфликта будет выглядеть так:

Пока игнорируем вопрос (Was the merge successful [y/n]?) и переключаемся в сессию под названием gitmerge (сочетание TMUXPREFIX + s):

Видим наше трёх-оконное представление на одном экране. Цифрами обозначены сплиты (panes) tmux’a, буквами соответствующие состояния. Делаем правки для разрешения конфликта, т.е. редактируем состояние (D) и сохраняем. После этого возвращаемся обратно в исходную сессию tmux’a и подтверждаем что слияние произошло успешно.


git rebase master

Лично я предпочитаю и считаю более правильным делать сначала rebase master в ветке beta, и только после этого переключаться в master и делать git merge beta. В принципе воркфлоу не сильно отличается, за исключением трёх-оконного вида.

Переключаемся в сессию gitmerge

Обратите внимание, что состояния (B) и © поменялись местами:

Рекомендую всем поиграться с примером репозитария хотя бы один раз, сделать разрешение конфликта по вышеописанной схеме. Лично я больше не гадаю а что же выбрать «Accept theirs» или «Accept yours».

Git Merge | Учебник Atlassian Git

Слияние — это способ Git снова собрать разветвленную историю. Команда git merge позволяет вам взять независимые линии разработки, созданные git branch , и интегрировать их в одну ветку.

Обратите внимание, что все представленные ниже команды объединяются в текущую ветку. Текущая ветка будет обновлена, чтобы отразить слияние, но целевая ветка не будет затронута полностью.Опять же, это означает, что git merge часто используется вместе с git checkout для выбора текущей ветви и git branch -d для удаления устаревшей целевой ветви.

Как это работает

Git merge объединит несколько последовательностей коммитов в единую историю. В наиболее частых случаях использования git merge используется для объединения двух веток. Следующие примеры в этом документе будут сосредоточены на этом шаблоне слияния ветвей.В этих сценариях git merge принимает два указателя фиксации, обычно это подсказки веток, и находит общую базовую фиксацию между ними. Как только Git найдет общую базовую фиксацию, он создаст новую «фиксацию слияния», которая объединяет изменения каждой поставленной в очередь последовательности фиксации слияния.

Допустим, у нас есть новая функция ветвления, основанная на ветке master . Теперь мы хотим объединить эту функциональную ветку с master .

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

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

Подготовка к слиянию

Перед выполнением слияния необходимо выполнить несколько подготовительных шагов, чтобы обеспечить плавное слияние.

Подтвердите принимающую ветвь

Выполните git status , чтобы убедиться, что HEAD указывает на правильную ветвь приема слияния. При необходимости выполните git checkout , чтобы переключиться на принимающую ветвь. В нашем случае мы выполним git checkout master.

Получить последние удаленные коммиты

Убедитесь, что принимающая ветвь и ветвь слияния обновлены с учетом последних удаленных изменений. Выполните git fetch , чтобы получить последние удаленные коммиты.После завершения выборки убедитесь, что в ветке master есть последние обновления, выполнив git pull.

Слияние

После того, как были выполнены ранее описанные шаги «подготовки к слиянию», слияние можно инициировать, выполнив git merge , где — это имя ветви, которая будет объединена с принимающей ветвью.

Быстрое слияние вперед

Слияние с ускоренной перемоткой вперед может происходить, когда существует линейный путь от текущей вершины ветви к целевой ветви.Вместо «фактического» слияния ветвей все, что Git должен сделать для интеграции историй, — это переместить (то есть «перемотать вперед») конец текущей ветки вверх до конечной точки целевой ветки. Это эффективно объединяет истории, поскольку все коммиты, доступные из целевой ветви, теперь доступны через текущую. Например, быстрое слияние некоторых функций с master будет выглядеть примерно так:

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


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

Наш первый пример демонстрирует быстрое слияние. Приведенный ниже код создает новую ветку, добавляет к ней две фиксации, а затем интегрирует ее в основную строку с помощью быстрого слияния.

  # Начать новую функцию git checkout -b new-feature master # Отредактировать некоторые файлы git add git commit -m "Запустить функцию" # Отредактировать некоторые файлы git add git commit -m "Завершить функцию" # Слить с новым -feature branch git checkout master git merge new-feature git branch -d new-feature  

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

Также обратите внимание, что Git не должен жаловаться на git branch -d , так как новая функция теперь доступна из основной ветки.

В случае, если вам требуется фиксация слияния во время слияния с ускоренной перемоткой вперед для целей ведения записей, вы можете выполнить git merge с параметром --no-ff .

  git слияние --no-ff  

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

3-стороннее слияние

Следующий пример очень похож, но требует трехстороннего слияния, потому что master выполняется, пока функция выполняется. Это распространенный сценарий для крупных функций или когда несколько разработчиков работают над проектом одновременно.

  Запустить новую функцию git checkout -b new-feature master # Отредактировать некоторые файлы git add git commit -m "Запустить функцию" # Отредактировать некоторые файлы git add git commit -m "Завершить функцию" # Разработать основную ветку git checkout master # Отредактируйте некоторые файлы git add git commit -m "Внесите сверхстабильные изменения в master" # Слияние в ветке новой функции git merge new-feature git branch -d new-feature  

Обратите внимание, что для Git невозможно выполнить быстрое слияние, поскольку нет способа переместить master в new-feature без возврата.

Для большинства рабочих процессов новая функция будет гораздо более крупной функцией, на разработку которой потребовалось много времени, поэтому тем временем на master будут появляться новые коммиты. Если ваша функциональная ветка на самом деле была такой же маленькой, как в приведенном выше примере, вам, вероятно, было бы лучше перенастроить ее на master и выполнить быстрое слияние. Это предотвращает засорение истории проекта лишними коммитами слияния.

Разрешение конфликта

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

Важной частью процесса слияния Git является то, что он использует знакомый рабочий процесс редактирования / этапа / фиксации для разрешения конфликтов слияния. Когда вы сталкиваетесь с конфликтом слияния, выполнение команды git status показывает, какие файлы необходимо разрешить. Например, если обе ветви изменили один и тот же раздел hello.py , вы увидите что-то вроде следующего:

  На главном узле ветки Несвязанные пути: (используйте "git add / rm... "в зависимости от разрешения) оба изменены: hello.py  

Как представлены конфликты

Когда Git обнаруживает конфликт во время слияния, он редактирует содержимое затронутых файлов с помощью визуальных индикаторов, которые отмечают обе стороны конфликтующего содержимого. Эти визуальные маркеры: >>>>>>. Полезно поискать в проекте эти индикаторы во время слияния, чтобы найти, где необходимо разрешить конфликты.

  вот некоторый контент, не затронутый конфликтом >>>>>>> feature branch;  

Обычно содержимое перед маркером ======= является принимающей ветвью, а часть после — объединяющей ветвью.

После того, как вы определили конфликтующие разделы, вы можете войти и исправить слияние по своему вкусу. Когда вы будете готовы завершить слияние, все, что вам нужно сделать, это запустить git add для конфликтующих файлов, чтобы сообщить Git, что они разрешены. Затем вы запускаете обычный git commit , чтобы сгенерировать фиксацию слияния. Это тот же процесс, что и при фиксации обычного снимка, что означает, что нормальным разработчикам легко управлять своими собственными слияниями.

Обратите внимание, что конфликты слияния возникают только в случае трехстороннего слияния.При ускоренном слиянии невозможно получить конфликтующие изменения.

Сводка

Этот документ представляет собой обзор команды git merge . Слияние — важный процесс при работе с Git. Мы обсудили внутреннюю механику слияния и различия между слиянием с ускоренной перемоткой вперед и трехсторонним истинным слиянием. Вот некоторые ключевые моменты:

  1. Слияние Git объединяет последовательности коммитов в единую историю коммитов.
  2. Существует два основных способа слияния Git: ускоренная перемотка вперед и трехсторонняя
  3. Git может автоматически объединять коммиты, если в обеих последовательностях фиксации нет конфликтующих изменений.

В этом документе интегрированы и упомянуты другие команды Git, например: git branch , git pull и git fetch . Посетите соответствующие отдельные страницы для получения дополнительной информации.

.

О методах слияния на GitHub

Документы GitHub
  • Все продукты
  • GitHub.com
    • Начиная
      • Быстрый старт
        • Настроить Git
        • Создать репо
        • Форк репо
        • Быть социальным
      • Изучение GitHub
        • Продукты GitHub
        • Изучение выпусков раннего доступа с предварительным просмотром функций
        • Типы аккаунтов GitHub
        • Часто задаваемые вопросы об изменениях в планах GitHub
        • GitHub CLI
        • GitHub Desktop
        • GitHub для мобильных устройств
        • Разрешения на доступ на GitHub
        • Глоссарий GitHub
        • Шпаргалка по Git
        • Учебные ресурсы Git и GitHub
      • Регистрация на GitHub
        • Регистрация новой учетной записи GitHub
        • Подтверждение адреса электронной почты
        • Настройка пробной версии GitHub Enterprise Cloud
        • Настройка пробной версии GitHub Enterprise Server
      • Изучение проектов на GitHub
        • Поиск способов внести свой вклад в открытый исходный код на GitHub
        • Сохранение репозиториев со звездочками
        • Следуя за людьми
      • Использование GitHub
        • Поддерживаемые браузеры
        • Устранение проблем с подключением
        • Горячие клавиши
.

Варианты и примеры стратегии слияния Git

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

стратегии слияния Git

Слияние происходит при объединении двух ветвей.Git возьмет два (или более) указателя на фиксацию и попытается найти между ними общую базовую фиксацию. В Git есть несколько различных методов поиска базовой фиксации, эти методы называются «стратегиями слияния». Как только Git найдет общую базовую фиксацию, он создаст новую «фиксацию слияния», которая объединяет изменения указанных коммитов слияния. Технически фиксация слияния — это обычная фиксация, у которой просто есть две родительские фиксации.

git merge автоматически выберет стратегию слияния, если явно не указано иное.Команды git merge и git pull могут быть переданы с опцией -s (стратегия). К опции -s можно добавить имя желаемой стратегии слияния. Если не указано явно, Git выберет наиболее подходящую стратегию слияния на основе предоставленных веток. Ниже приводится список доступных стратегий слияния.

Рекурсивный

  git merge -s рекурсивная ветка 2 ветка 3  

Оперирует двумя головками.Рекурсивный — это стратегия слияния по умолчанию при извлечении или слиянии одной ветки. Кроме того, он может обнаруживать и обрабатывать слияния, включающие переименование, но в настоящее время не может использовать обнаруженные копии. Это стратегия слияния по умолчанию при извлечении или слиянии одной ветки.

Решимость

  git merge -s resolve branch2 branch3  

Это может разрешить только две головы, используя трехсторонний алгоритм слияния. Он пытается тщательно выявлять неоднозначности перекрестного слияния и считается в целом безопасным и быстрым.

Осьминог

  git merge -s octopus branch2 branch3 branch4 branchN  

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

Наши

  git merge -s наша ветка2 ветка3 ветка N  

Наша стратегия работает с несколькими N филиалами.Результат слияния вывода всегда равен результату текущей ветви HEAD . Термин «наш» подразумевает предпочтение, фактически игнорирующее все изменения из всех других ветвей. Он предназначен для использования для объединения истории похожих ветвей функций.

Поддерево

  git merge -s subtree branchA branchB  

Это расширение рекурсивной стратегии. При объединении A и B, если B является дочерним поддеревом A, B сначала обновляется, чтобы отразить древовидную структуру A. Это обновление также выполняется для общего дерева предков, которое совместно используется между A и B.

типов стратегий слияния Git

Явное слияние

Явное слияние — это тип слияния по умолчанию. «Явная» часть заключается в том, что они создают новую фиксацию слияния. Это изменяет историю фиксации и явно показывает, где было выполнено слияние. Содержимое фиксации слияния также явно проявляется в том факте, что оно показывает, какие коммиты были родительскими фиксации слияния. Некоторые команды избегают явных слияний, потому что, возможно, коммиты слияния добавляют «шума» в историю проекта.

неявное слияние через перебазирование или быстрое слияние

В то время как явное слияние создает фиксацию слияния, неявное слияние — нет. Неявное слияние берет серию коммитов из указанной заголовка ветки и применяет их к вершине целевой ветки. Неявные слияния запускаются событиями перебазирования или слияниями с ускоренной перемоткой вперед. Неявное слияние — это произвольный выбор коммитов из указанной ветви.

Сквош при слиянии, как правило, без явного слияния

Другой тип неявного слияния — это сквош.Сквош можно выполнить во время интерактивной перебазировки. Слияние сквоша берет коммиты из целевой ветки и объединяет или сжимает их в один коммит. Затем эта фиксация добавляется к HEAD базовой ветки слияния. Сквош обычно используется для сохранения «чистой истории» во время слияния. Целевая ветвь слияния может иметь подробную историю частых коммитов. При сжатии и слиянии история фиксации целевых ветвей становится единственной сжатой «фиксацией ветки». Этот метод полезен с рабочими процессами git , которые используют ветки функций.

Параметры стратегии рекурсивного слияния Git

«Рекурсивная» стратегия, представленная выше, имеет собственное подмножество дополнительных опций работы.

Не следует путать со стратегией слияния Ours. Этот параметр конфликтует, чтобы автоматически разрешить его чисто, отдав предпочтение «нашей» версии. Изменения «их» автоматически учитываются, если они не противоречат друг другу.

Противоположность «нашей» стратегии. вариант «их» отдает предпочтение иностранному дереву слияния в разрешении конфликтов.

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

Эта опция позволяет указать явный алгоритм сравнения. Дифференциальные алгоритмы используются совместно с командой git diff .

  ignore- * 

ignore-space-change
ignore-all-space
ignore-space-at-eol
ignore-cr-at-eol

Набор параметров, нацеленных на пробельные символы.Любая строка, которая соответствует подмножеству переданной опции, будет проигнорирована.

Этот параметр запускает извлечение и регистрацию для всех деревьев git-деревьев при разрешении трехстороннего слияния. Эта опция предназначена для использования с объединяющимися ветвями с разными состояниями checkin / checkout .

Отключает опцию перенормировки. Это отменяет конфигурационную переменную merge.renormalize .

Эта опция игнорирует переименованные файлы во время слияния.

Это поведение по умолчанию. Рекурсивное слияние учитывает переименования файлов. Параметр n может использоваться для прохождения порога схожести переименования. Значение по умолчанию n равно 100%.

Эта опция заимствована из стратегии «поддерево». Если стратегия работает с двумя деревьями и изменяет способ их сопоставления на общем предке, этот параметр вместо этого работает с метаданными пути дерева, чтобы они совпадали.

Наша политика слияния Git

Atlassian настоятельно предпочитает использование явных слияний.Причина очень проста: явное слияние обеспечивает отличную прослеживаемость и контекст для сливаемых функций. Абсолютно приветствуется перебазирование локальной истории перед публикацией функциональной ветки для обзора, но это никак не меняет политику. Это увеличивает его.

.

git-merge (1)

merge.conflictStyle

Укажите стиль, в котором конфликтующие блоки записываются в файлы рабочего дерева при слиянии. По умолчанию используется «объединить», что показывает маркер конфликта <<<<<<< , изменения, внесенные одной стороной, маркер ======= , изменения, внесенные другой стороной, а затем маркер >>>>>>> .Альтернативный стиль, «diff3», добавляет ||||||| маркер и исходный текст перед маркером ======= .

merge.defaultToUpstream

Если слияние вызывается без аргумента фиксации, слияние восходящего потока ветви, настроенные для текущей ветви, используя их последние наблюдаемые значения хранятся в их ветвях удаленного отслеживания. Ценности ветви .<текущая ветка> .merge , которая называет филиалы на удаленном компьютере, названные ветвью . <текущая ветка> .remote запрашиваются, а затем они отображаются через удаленный . .fetch в соответствующие ветки удаленного отслеживания, а советы эти ветви отслеживания объединяются.

merge.ff

По умолчанию Git не создает дополнительную фиксацию слияния при слиянии. коммит, который является потомком текущего коммита.Вместо этого подсказка текущей ветки пересылается вперед. Если установлено значение false , эта переменная сообщает Git создать дополнительный коммит слияния в таком случай (эквивалент параметра --no-ff из команды линия). Если установлено значение только , будут выполняться только такие слияния с быстрой перемоткой вперед. разрешено (эквивалентно предоставлению опции --ff-only из командная строка).

слить.verifySignatures

Если true, это эквивалентно команде --verify-signatures. вариант линии. Подробнее см. Git-merge (1).

merge.branchdesc

В дополнение к именам веток заполните сообщение журнала с помощью текст описания ветки, связанный с ними. По умолчанию к ложному.

слить.журнал

В дополнение к именам веток, заполните сообщение журнала с помощью максимальное указанное количество однострочных описаний из фактические коммиты, которые объединяются. По умолчанию false, и истина является синонимом 20.

merge.suppressDest

Добавив глобус, соответствующий именам интеграции переходит к этой многозначной переменной конфигурации, сообщение слияния по умолчанию, вычисленное для слияния с этими ветви интеграции будут опускать "в <название ветки>" из его название.

Элемент с пустым значением может использоваться для очистки списка глобусов, накопленных из предыдущих записей конфигурации. Если переменная merge.suppressDest не определена, значение по умолчанию master используется для обратной совместимости.

merge.renameLimit

Количество файлов, которые следует учитывать при обнаружении переименования во время слияния; если не указано, по умолчанию используется значение разницаrenameLimit. Этот параметр не действует, если обнаружение переименования выключен.

merge.renames

Обнаруживает ли Git переименование. Если установлено значение "false", переименовать обнаружение выключен. Если установлено значение «true», включено базовое определение переименования. По умолчанию используется значение diff.renames.

merge.directoryRenames

Обнаруживает ли Git переименование каталогов, влияя на то, что происходит в время слияния с новыми файлами, добавленными в каталог на одной стороне история, когда этот каталог был переименован на другой стороне история.Если для merge.directoryRenames установлено значение "false", каталог обнаружение переименования отключено, что означает, что такие новые файлы будут остались в старом каталоге. Если установлено значение "true", каталог включено обнаружение переименования, что означает, что такие новые файлы будут переехал в новый каталог. Если установлено значение «конфликт», конфликт будет сообщено для таких путей. Если merge.renames ложно, merge.directoryRenames игнорируется и рассматривается как false.По умолчанию к «конфликту».

merge.renormalize

Сообщите Git, что каноническое представление файлов в репозиторий со временем изменился (например, более ранняя фиксация записи текстовые файлы с окончанием строки CRLF, но последние используют строку LF концовки). В таком репозитории Git может конвертировать данные записаны в коммитах в каноническую форму перед выполнением объединить, чтобы уменьшить ненужные конфликты.Чтобы получить больше информации, см. раздел «Объединение веток с разной регистрацией / оплатой атрибуты »в gitattributes (5).

merge.stat

Следует ли печатать diffstat между ORIG_HEAD и результатом слияния в конце слияния. Верно по умолчанию.

merge.autoStash

При значении true автоматически создавать временную запись в тайнике. до начала операции и применять после операции заканчивается.Это означает, что вы можете запустить слияние на грязном рабочем дереве. Однако используйте осторожно: последнее приложение тайника после успешное слияние может привести к нетривиальным конфликтам. Эта опция может быть отменена с помощью --no-autostash и --autostash вариантов git-merge (1). По умолчанию - false.

merge.tool

Управляет тем, какой инструмент слияния используется git-mergetool (1).В приведенном ниже списке показаны действительные встроенные значения. Любое другое значение рассматривается как настраиваемый инструмент слияния и требует что определена соответствующая переменная mergetool. .cmd.

merge.guitool

Управляет тем, какой инструмент слияния используется git-mergetool (1), когда -g / - указан флаг gui. В списке ниже показаны действительные встроенные значения. Любое другое значение рассматривается как настраиваемый инструмент слияния и требует, чтобы соответствующий mergetool.Переменная .cmd определена.

  • gvimdiff

  • gvimdiff2

  • gvimdiff3

  • nvimdiff

  • nvimdiff2

  • vimdiff2

  • vimdiff3

слить.многословие

Управляет объемом вывода, отображаемым рекурсивным слиянием стратегия. Уровень 0 ничего не выводит, кроме последней ошибки сообщение, если обнаружены конфликты. Только выходы уровня 1 конфликты, 2 выхода, конфликты и изменения файлов. Уровень 5 и выше выводит отладочную информацию. По умолчанию - уровень 2. Может быть переопределено переменной среды GIT_MERGE_VERBOSITY .

слияние. <драйвер> .name

Определяет удобочитаемое имя для настраиваемого низкоуровневого драйвер слияния. Подробнее см. Gitattributes (5).

слияние. <драйвер> .driver

Определяет команду, реализующую настраиваемый низкоуровневый драйвер слияния. Подробнее см. Gitattributes (5).

слить.<драйвер> .recursive

Называет низкоуровневый драйвер слияния, который будет использоваться, когда выполнение внутреннего слияния между общими предками. Подробнее см. Gitattributes (5).

ветка. <имя> .mergeOptions

Устанавливает параметры по умолчанию для слияния с веткой . Синтаксис и поддерживаемые параметры такие же, как у git merge , но вариант значения, содержащие символы пробела, в настоящее время не поддерживаются.

.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *