There are merge conflicts gitlab что делать
Безболезненное разрешение 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:
Замечательно! Весь файл, как показывает 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
Теперь мы видим третий фрагмент посередине, который и является базой слияния или состояние (A). Изменения видны как на ладони: в ветке beta (HEAD) человеческие названия цветов были заменены на HTML коды, а в ветку master добавили капитализацию и пунктуацию. Основываясь на этих знаниях, мы теперь знаем что результат должен включать в себя капитализацию, пунктуацию и HTML коды цветов.
В принципе на этом можно было бы и закончить, потому что результат достигнут. Но есть решение и получше.
Графическое Слияние (GUI Merging)
Хотя и простое текстовое представление конфликта слияния делает свою работу в простых случаях, на практике конфликты могут быть более радикальными и сложными. В таких случаях могут помочь графические инструменты. Мой выбор пал на простой инструмент написанный на Python под названием meld, но может подойти любой другой графический инструмент, способный представить слияние в трёх-колоночном виде.
Для использования графического инструмента (он должен быть установлен), после того как git пожаловался что есть конфликт, введите следующую команду:
Последует вопрос какой программой для слияния вы хотели бы воспользоваться, просто введите meld и нажмите Enter. Вот как окно программы может выглядеть (подразумевается опция merge.conflictstyle не была включена):
Несмотря на то что информация представлена бок о бок, она не отображает нужные фрагменты которые были в Listing 2. Мы не видим здесь фрагмента базы слияния (состояния (A)), что мы видим это файл roses.txt.LOCAL.2760.txt в левой колонке и файл roses.txt.REMOTE.2760.txt в правой колонке и файл посередине это неудачное слияние. Т.е. по сути нам представили состояния (B), © и несостоявшееся состояние (D), но состояние (A) отсутствует.
Правда отсутствует? Давайте проверим, в старом добром терминале:
Видим интересующий нас файл: roses.txt.BASE.2760.txt. Это и есть файл базы слияния. Теперь нам осталось всего лишь найти изменения внесённые в ветки master и beta, по отношению к базе. Мы можем сделать это двумя отдельными вызовами meld:
(Кто-то может подметить что было бы более разумно, поменять порядок аргументов в первом вызове, для того чтобы файл базы находился в левой колонке в обоих случаях, но именно такой порядок сохраняет подобие трёх-колоночного вида, при котором база остаётся по середине.) Результат выполнения — два окна как показано ниже:
При чтении первого окна справа налево и второго окна слева направо, становится ясно как день, какие изменения произошли в каждой ветке. Так как meld любезно подсветил все изменения, теперь практически не возможно пропустить даже мелко заметные правки (Кто-нибудь заметил добавление предлога «of» при просмотре текстового представления разрешения конфликта Listing 1 или даже Listing 2?)
Вооружившись этими знаниями, мы теперь можем вернуться к трёх-колоночному представлению и сделать изменения. Моя стратегия ручного слияния это взять весь текст из ветки с более весомыми изменениями (в данном случае master/REMOTE т.е. beta), и поверх него производить пошаговые правки, т.е. вносить изменения сделанные в другой ветке (master). Вот что получилось:
А теперь всё вместе (All Together Now)
И добавьте следующее в ваш
Теперь, когда вы в следующий раз будете запускать команду git mergetool для разрешения конфликта, откроются все три окна:
После того как вы привыкните к такому разрешению конфликтов с использованием трёх вышеупомянутых окон, вы скорее всего обнаружите, что процесс стал более методичным и механическим. В большинстве случаев, вам даже не придётся читать и понимать куски кода из каждой ветки, для того чтобы понять какой же вариант применить для слияния. Вам больше не понадобится догадываться, потому что вы будете гораздо более уверенным в корректности вашего комита. Из-за этой уверенности, появится чувство что разрешение конфликтов превратилось в увлекательное занятие.
Бонус от переводчика
Для тех кто пользуется tmux и n?vim, предлагаю следующий скрипт gitmerge:
Примечание: если вы не используете эту опцию в своем
/.tmux.conf, то вам надо поменять в двух последних строках «$sn:1» на «$sn:0»
Соответственно добавьте следующее в ваш
Воркфлоу разрешения конфликта будет выглядеть так:
Пока игнорируем вопрос (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».
GitLabMerge request conflict resolution (FREE)
Merge conflicts occur when two branches have different changes that cannot be merged automatically.
Git can merge changes between branches in most cases, but occasionally Git requires your assistance to resolve the conflicts manually. Typically, this is necessary when people change the same parts of the same files.
GitLab prevents merge requests from being merged until all conflicts are resolved. Conflicts can be resolved locally, or in many cases in GitLab (see conflicts available for resolution for information on when this is available).
NOTE: GitLab resolves conflicts by creating a merge commit in the source branch that is not automatically merged into the target branch. The merge commit can be reviewed and tested before the changes are merged. This prevents unintended changes entering the target branch without review or breaking the build.
Resolve conflicts: interactive mode
Clicking Resolve Conflicts displays a list of files with conflicts, with conflict sections highlighted:
Resolve conflicts: inline editor
Some merge conflicts are more complex, requiring you to manually modify a file to resolve them. Use the merge conflict resolution editor to resolve complex conflicts in the GitLab interface. Click Edit inline to open the editor. After you’re sure about your changes, click Commit to source branch.
Conflicts available for resolution
GitLab allows resolving conflicts in a file where all of the below are true:
If any file in your merge request containing conflicts can’t meet all of these criteria, you can’t resolve the merge conflict in the UI.
Additionally, GitLab does not detect conflicts in renames away from a path. For example, this does not create a conflict:
Instead, both files are present in the branch after the merge request is merged.
About merge conflicts
In this article
Merge conflicts happen when you merge branches that have competing commits, and Git needs your help to decide which changes to incorporate in the final merge.
Git can often resolve differences between branches and merge them automatically. Usually, the changes are on different lines, or even in different files, which makes the merge simple for computers to understand. However, sometimes there are competing changes that Git can’t resolve without your help. Often, merge conflicts happen when people make different changes to the same line of the same file, or when one person edits a file and another person deletes the same file.
You must resolve all merge conflicts before you can merge a pull request on GitHub. If you have a merge conflict between the compare branch and base branch in your pull request, you can view a list of the files with conflicting changes above the Merge pull request button. The Merge pull request button is deactivated until you’ve resolved all conflicts between the compare branch and base branch.
Resolving merge conflicts
To resolve a merge conflict, you must manually edit the conflicted file to select the changes that you want to keep in the final merge. There are a couple of different ways to resolve a merge conflict:
If you have a merge conflict on the command line, you cannot push your local changes to GitHub until you resolve the merge conflict locally on your computer. If you try merging branches on the command line that have a merge conflict, you’ll get an error message. For more information, see «Resolving a merge conflict using the command line.»
Merge conflicts
Merge conflicts happen when the two branches in a merge request (the source and target) each have different changes, and you must decide which change to accept. In a merge request, Git compares the two versions of the files line by line. In most cases, GitLab can merge changes together. However, if two branches both change the same lines, GitLab blocks the merge, and you must choose which change you want to keep.
Conflicts you can resolve in the user interface
If any file in your merge request contains conflicts, but can’t meet all of these criteria, you must resolve the conflict manually.
Conflicts GitLab can’t detect
When these branches merge, both example1.txt and example3.txt are present.
Methods of resolving conflicts
Resolve conflicts in interactive mode
Find the merge conflicts message, and select Resolve conflicts. GitLab shows a list of files with merge conflicts. The conflicts are highlighted:
Resolve conflicts in the inline editor
Resolve conflicts from the command line
While most conflicts can be resolved through the GitLab user interface, some are too complex. Complex conflicts are best fixed locally, from the command line, to give you the most control over each change:
Open the terminal and check out your feature branch. For example, my-feature-branch :
Rebase your branch against the target branch (here, main ) so Git prompts you with the conflicts:
Resolving Merge Conflicts from the GitLab UI
Merge conflicts can be very annoying for both merge request authors and reviewers. As an author, I just want my merge request to be merged. But the reviewer might not be in the same time zone as me and by the time they review my changes, I have a merge conflict. I then need to fix it and pass the merge request back to them, which is a lot of busy work for something that could be fairly trivial to fix.
Similarly, as a reviewer, I want merge requests to be accepted when they’re ready. I don’t want to spend my time checking out the author’s branch, fixing the conflicts, and pushing back; and I don’t particularly like waiting around for them to fix it. As an author or a reviewer, I just want to be able to have the merge request accepted, and move on to the next thing.
In GitLab 8.11, we introduced the ability to resolve merge conflicts in the UI. This post describes the background for the feature, how it works, and what we’re planning to do next with it.
What is a merge conflict?
A merge conflict is when a merge can’t be performed cleanly between two versions of the same file.
Wait, what’s a merge?
Note: for this post, we will just concentrate on conflicts within a file. However, renames and deletions can also cause conflicts, and we plan on supporting those in the future.
Why are conflicts a problem?
They’re really annoying!
When there’s a conflict, a merge request can’t be merged without manual intervention.
If you can’t resolve merge conflicts within GitLab, that means that any merge request with a conflict needs to be checked out locally, resolved locally, pushed back, and merged. That’s a hassle and can’t be done without having some Git tools installed locally. At GitLab, we want everyone to be able to collaborate on all digital content, and that means not having to install special tools whenever possible.
Types of conflict resolution
There are several different ways we might want to resolve a conflict:
Just pick one version, and use that. This is often the case with generated files. One example is the schema.rb file in a Rails app. Conflicts on the schema version line are common, but we (almost) always want the latest version.
Keep the lines from both versions. A great example of this is the GitLab CE CHANGELOG file, which is a frequent source of merge conflicts. We’re working on tooling to help with this, but that’s specific to GitLab rather than applicable to every project.
Write our own resolution. For instance, if we started with the sentence:
There are two versions of GitLab: CE and EE
I might think that those acronyms should be spelled out:
There are two versions of GitLab: Community Edition (CE) and Enterprise Edition (EE)
And you might think that the sentence needs some closing punctuation:
There are two versions of GitLab: CE and EE.
Because conflicts are based on lines, there’s no way to automatically pick both of those changes. We can do so manually, though:
There are two versions of GitLab: Community Edition (CE) and Enterprise Edition (EE).
At present, the conflict resolution support in GitLab is only really useful for resolving the first type of conflict. We plan to allow using an editor in the future so more conflicts can be resolved.
How do we resolve them?
When a merge request can have its conflicts resolved within GitLab, it will have a link within the merge box to ‘resolve these conflicts’:
Clicking that link will show the files with conflicts, with conflict sections highlighted as ‘our changes’ (the changes in the merge request’s source branch) and ‘their changes’ (the changes in the merge request’s target branch):
Here’s an example conflict in a schema.rb that I resolved on the GitLab CE project:
How does that work?
The current implementation, at a high level, works like this:
Why can’t some conflicts be resolved in GitLab?
The implementation above produces a number of constraints (for the most up-to-date list, please see the conflict resolution documentation):
Because all conflicts must be resolved at once, if any of the conflicts for a merge request can’t be resolved in the GitLab UI, then the conflicts must be resolved manually.
What’s next?
There are plenty of places for improving our current implementation, and we’d love to hear of ones we haven’t thought of. Here are three obvious ones already in our issue tracker.
An editor!
The most obvious improvement is to allow editing the conflict resolution. This is closer to the experience on the command line, with the conflict markers present in the file. It will also cover most of the problem cases listed above with the current approach.
Binary files!
In addition to this, as binary files are typically not manually mergeable, we could just show both versions, and ask which one to use. This will work best for images, but can support all binary files.
Renames!
Incompatible renames are detected as conflicts by the git command line tool, but not by the library we use at GitLab. We can detect rename conflicts ourselves, it’s just more work.