Как понимать и разрешать конфликты в Git

Вот оно, слово, которое ненавидит видеть каждый разработчик: конфликт. 😱 Невозможно обойти случайный конфликт слияния при работе с Git (или другими системами контроля версий).

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

Урегулирование конфликтов часто остается темным, загадочным местом: ситуация, когда что-то сильно ломается и неясно, как из нее выйти (не усугубляя ситуацию).

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

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

Когда вы правильно поймете эти вещи, вы сможете справляться с конфликтами слияния гораздо более расслабленно и уверенно. 😍

Как и когда возникают конфликты

Название уже говорит об этом: «конфликты слияния» могут возникать в процессе интеграции коммитов из другого источника.

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

Все эти действия выполняют своего рода интеграцию - и именно тогда могут возникнуть конфликты слияния.

Но, конечно, эти действия не приводят к конфликту слияния каждый раз (слава Богу!). В идеале вы должны попадать в такие ситуации редко. Но когда именно возникают конфликты?

На самом деле возможности слияния Git - одно из его величайших преимуществ: слияние ветвей в большинстве случаев выполняется без усилий, потому что Git обычно может разбираться во всем самостоятельно.

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

Настоящая классика - это когда одна и та же строка кода была изменена за два коммита в двух разных ветвях. Git не знает, какое изменение вы предпочитаете! 🤔

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

«Башня» Гит рабочего стола GUI , например, имеет хороший способ визуализации таких ситуаций:

Как узнать, когда произошел конфликт

Не волнуйтесь: Git очень четко сообщит вам, когда возник конфликт. 😉  

Во-первых, он немедленно сообщит вам об этой ситуации , например, когда слияние или ребазирование не удалось из-за конфликта:

$ git merge develop Auto-merging index.html CONFLICT (content): Merge conflict in index.html CONFLICT (modify/delete): error.html deleted in HEAD and modified in develop. Version develop of error.html left in tree. Automatic merge failed; fix conflicts and then commit the result.

Как вы можете видеть из приведенного выше примера, когда я пытался выполнить слияние, я создал конфликт слияния, и Git очень четко и быстро сообщает о проблеме:

  • Возник конфликт в файле index.html.
  • Возник еще один конфликт в файле error.html.
  • И наконец, из-за конфликтов операция слияния не удалась.

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

В том маловероятном случае, если вы пропустили эти предупреждающие сообщения при возникновении конфликта, Git дополнительно информирует вас при каждом запуске git status:

$ git status On branch main You have unmerged paths. (fix conflicts and run "git commit") (use "git merge --abort" to abort the merge) Unmerged paths: (use "git add/rm ..." as appropriate to mark resolution) deleted by us: error.html both modified: index.html

Другими словами: не беспокойтесь о том, что не заметите конфликтов слияния. Git позаботится о том, чтобы вы не пропустили их.

Как отменить конфликт в Git и начать заново

Конфликты слияния имеют некоторую срочность. И это правильно: вам придется с ними разобраться, прежде чем вы сможете продолжить свою работу.

Но хотя их игнорирование не является вариантом, «разрешение конфликтов слияния» не обязательно означает, что вы должны их разрешать. Отменить их тоже можно!

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

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

Для этого у большинства команд есть --abortопция, например git merge --abortи git rebase --abort:

$ git merge --abort $ git status On branch main nothing to commit, working tree clean

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

Как на самом деле выглядят конфликты в Git

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

В качестве примера давайте посмотрим на содержимое (в настоящее время конфликтующего) файла index.html в редакторе:

Git was kind enough to mark the problem area in the file, enclosing it in <<<<<<< HEAD and >>>>>>> [other/branch/name]. The content that comes after the first marker originates from our current working branch. Finally, a line with ======= characters separates the two conflicting changes.

How to Solve a Conflict in Git

Our job as developers now is to clean up these lines: after we're finished, the file has to look exactly as we want it to look.

It might be necessary to talk to the teammate who wrote the "other" changes and decide which code is actually correct. Maybe it's ours, maybe it's theirs - or maybe a mixture between the two.

This process - cleaning up the file and making sure it contains what we actually want - doesn't have to involve any magic. You can do this simply by opening your text editor or IDE and starting to making your changes.

Often, however, you'll find that this is not the most efficient way. That's when dedicated tools can save time and effort:

  • Git GUI Tools: Some of the graphical user interfaces for Git can be helpful when solving conflicts. The Tower Git GUI, for example, offers a dedicated "Conflict Wizard" that helps visualize and solve the situation:
  • Dedicated Merge Tools: For more complicated conflicts, it can be great to have a dedicated "Diff & Merge Tool" at hand. You can configure your tool of choice using the "git config" command. (Consult your tool's documentation for detailed instructions.) Then, in case of a conflict, you can invoke it by simply typing git mergetool. As an example, here's a screenshot of "Kaleidoscope" on macOS:

After cleaning up the file - either manually or in a Git GUI or Merge Tool - we have to commit this like any other change:

  • By using git add on the (previously) conflicted file, we inform Git that the conflict has been solved.
  • When all conflicts have been solved and added to the Staging Area, you need to complete the resolution by creating a regular commit.

How to Become More Confident and Productive

Many years ago, when I started using version control, merge conflicts regularly freaked me out: I was afraid that, finally, I had managed to break things for good. 😩

Only when I took the time to truly understand what was going on under the hood was I able to deal with conflicts confidently and efficiently.

The same was true, for example, when dealing with mistakes: only once I learned how to undo mistakes with Git was I able to become more confident and productive in my work.

I highly recommend taking a look at the free "First Aid Kit for Git", a collection of short videos about how to undo and recover from mistakes with Git.

Have fun becoming a better programmer!

About the Author

Tobias Günther is the CEO of Tower, the popular Git desktop client that helps more than 100,000 developers around the world to be more productive with Git.