{"id":24070367,"url":"https://github.com/vbauerster/painlessmergeconflict","last_synced_at":"2025-02-26T21:16:01.190Z","repository":{"id":81724408,"uuid":"83907699","full_name":"vbauerster/PainlessMergeConflict","owner":"vbauerster","description":"перевод статьи","archived":false,"fork":false,"pushed_at":"2017-03-05T11:35:57.000Z","size":712,"stargazers_count":3,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"master","last_synced_at":"2025-01-09T15:46:43.870Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"http://blog.wuwon.id.au/2010/09/painless-merge-conflict-resolution-in.html","language":null,"has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/vbauerster.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2017-03-04T16:31:41.000Z","updated_at":"2019-05-23T07:07:27.000Z","dependencies_parsed_at":"2023-03-11T14:30:22.601Z","dependency_job_id":null,"html_url":"https://github.com/vbauerster/PainlessMergeConflict","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbauerster%2FPainlessMergeConflict","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbauerster%2FPainlessMergeConflict/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbauerster%2FPainlessMergeConflict/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/vbauerster%2FPainlessMergeConflict/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/vbauerster","download_url":"https://codeload.github.com/vbauerster/PainlessMergeConflict/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":240934436,"owners_count":19880992,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2025-01-09T15:38:45.535Z","updated_at":"2025-02-26T21:16:01.174Z","avatar_url":"https://github.com/vbauerster.png","language":null,"funding_links":[],"categories":[],"sub_categories":[],"readme":"# Безболезненное разрешение Merge конфликтов в Git.\n\nВ моей повседневной работе, часто приходится иметь дело со множеством git ветвей\n(branch). Это могут быть ветви промежуточных релизов, ветви с устаревшим API\nнаходящиеся на поддержке для некоторых клиентов, или ветви с экспериментальными\nсвойствами. Лёгкость создания ветвей в модели Git так и соблазняет разработчиков\nсоздавать все больше и больше ветвей, и как правило бремя от большого количества\nветвей становится очень ощутимым, когда приходится все эти ветви поддерживать и\nпериодически делать слияния (merge) с другими ветвями.\n\nСлияния очень важны для поддержания кода в актуальном состоянии, и как правило\nошибка сделанная при слиянии может привести к большей головной боли, нежели\nошибка сделанная при простом коммите. К сожалению ошибки слияния далеко не\nредкость, потому что во-первых слияния имеют несколько родительских ветвей. Даже\nпри анализе истории слияния ветвей, бывает очень трудно понять, какие же\nизменения были сделаны для разрешения конфликта. Во-вторых, [отмена неудачного\nслияния](http://www.kernel.org/pub/software/scm/git/docs/howto/revert-a-faulty-merge.txt)\nможет превратиться в большую головную боль. В-третьих, большая часть конфликтов\nслияния происходит при работе с чужим кодом, потому что само понятие ветвей\nподразумевает множество пользователей, т.е. далеко не всегда слияние производит\nтот же человек который работал с той или иной веткой. В сухом остатке, сделать\nошибку при слиянии очень легко, её трудно исправить и трудно найти. Таким\nобразом время потраченное на изучение и понимание процесса слияния ветвей,\nокупится с лихвой.\n\nУдивительно, но я обнаружил, что многие доступные инструменты и интерфейсы\nпредназначенные для выполнения слияний, не достаточно хорошо оснащены для\nэффективного выполнения этого процесса. Часто программист просто надеется что\nкоманда git merge сделает за него всю работу. Но когда все-таки происходит\nконфликт, то обычно стратегия слияния заключается в беглом просмотре кода вокруг\nстроки конфликта, и интуитивном угадывании что именно данный кусок кода\nпредпочтительней другого.\n\nВ данной статье я надеюсь продемонстрировать что процесс разрешения конфликтов\nможет быть пошагово точным, при котором отпадает необходимость что-либо там\nугадывать.\n\n## Голубые Розы (Roses are Blue)\n\nДавайте предположим что вашей команде поручили писать поэмы в отведённом для\nэтих целей репозитории. (Какой кошмар!) А вам доверили самое главное - делать\nслияния последних фиксов из ветки master в ветку beta. Итак, вы переключаетесь в\nветку beta и выполняете следующую команду:\n\n```\n$ git merge master\nAuto-merging roses.txt\nCONFLICT (content): Merge conflict in roses.txt\nAutomatic merge failed; fix conflicts and then commit the result.\n```\n\nОго, это конфликт. Вы решаете просмотреть файл на который ссылается git:\n\n```\n$ cat roses.txt\n\u003c\u003c\u003c\u003c\u003c\u003c\u003c HEAD\nroses are #ff0000\nviolets are #0000ff\nall my base\nare belong to you\n=======\nRoses are red,\nViolets are blue,\nAll of my base\nAre belong to you.\n\u003e\u003e\u003e\u003e\u003e\u003e\u003e master\n\n(Listing 1)\n```\n\nЗамечательно! Весь файл, как показывает Listing 1, находится в конфликтном\nсостоянии. Какой же вариант файла является более корректным? Оба варианта\nвыглядят корректно. Верхний вариант написан в хакер-стиле с элементами цветовой\nкодировки в стиле HTML и с использованием только строчных букв. Нижний вариант\nвыглядит более натурально, с использованием пунктуации и заглавных букв.\n\nЕсли бы это был ваш проект, вы бы могли просто выбрать один вариант и покончить\nс этим слиянием. Но проблема в том, что это не ваша поэма, вы никогда не читали\nэту поэму раньше, не были ответственны за написание или редактирование, и вы\nотлично понимаете что в случае не верного решения чья-то тяжёлая работа может\nкануть в небытие. Однако вас всё же назначили ответственным по слиянию этих\nветок. Что же вам делать?\n\n## Назад к Базе (Back to Base)\n\nХитрость заключается в том, что Listing 1 не даёт вам полную информацию,\nнеобходимую для совершения корректного слияния. На самом деле, в процессе\nслияния участвуют четыре важных части информации (состояния), три из которых\nпросто необходимы для успешного разрешения конфликта. В случае Listing 1, Git\nпредоставил вам только два состояния.\n\nСледующая диаграмма иллюстрирует эти четыре состояния:\n\n![four states](http://1.bp.blogspot.com/_DzZflO6z3uM/TIzDapgYAlI/AAAAAAAAAM4/qtesWfDEdbY/s320/PainlessMerginginGit.png)\n\nСостояния (B) и (C) относятся к текущим положениям (head) веток master и beta\nсоответственно, эти два состояния как раз таки и отражены в Listing 1. Состояние\n(D) это результат слияния, то что вы хотите получить/сгенерировать в конечном\nитоге (в большинстве случаев Git автоматически генерирует состояние (D)).\nСостояние (А) на самом верху, представляет собой базу (основу) слияния веток\nmaster и beta. База слияния (A) это последний общий предок веток master и beta,\nи пока предположим что это база слияния уникальна. Как мы увидим позже состояние\n(A) играет ключевую роль в разрешении конфликтов. На диаграмме я также отразил\nдельты 1 и 2, которые представляют изменения между состояниями (A)-(B), и\n(A)-(C) соответственно. Зная состояния (A), (B) и (C) дельты 1 и 2 могут быть\nлегко получены (вычислены). Обратите внимание, что дельты 1 и 2 могут состоять\nиз более чем одного коммита. Но для наших целей будем считать что все дельты\nмонолитны.\n\nЧтобы понять, как получить состояние (D), вы должны понимать что же операция\nслияния пытается сделать. Состояние (D) должно представлять собой сочетание\nизменений, внесённых в ветку master и beta соответственно. Т.е. другими словами\nсочетание дельт 1 и 2. Идея проста на поверхности и большую часть времени не\nтребует вмешательства со стороны человека, за исключением особых случаев когда\nдельты затрагивают наслаиваемые (пересекающиеся) части файла. В такой ситуации\nвам требуется помочь машине сгенерировать результат (D), путём сравнения дельт 1\nи 2.\n\n## Определение Отличий (Identifying the Differences)\n\nДля того чтобы найти изменения внесённые в каждую ветку, необходимо знать как\nвыглядит база слияния, состояние (A). Самый простой механизм получения\nинформации о базе слияния, это установка опции merge.conflictstyle в значение diff3\n\n```\n$ git config merge.conflictstyle diff3\n```\n\nПосле включения этой опции, попробуйте заново сделать слияние (git reset --hard;\ngit merge master) и проинспектируйте конфликтующий файл ещё раз:\n\n```\n$ cat roses.txt\n\u003c\u003c\u003c\u003c\u003c\u003c\u003c HEAD\nroses are #ff0000\nviolets are #0000ff\nall my base\nare belong to you\n|||||||\nroses are red\nviolets are blue\nall my base\nare belong to you\n=======\nRoses are red,\nViolets are blue,\nAll of my base\nAre belong to you.\n\u003e\u003e\u003e\u003e\u003e\u003e\u003e master\n\n(Listing 2)\n```\n\nТеперь мы видим третий фрагмент посередине, который и является базой слияния или\nсостояние (A). Изменения видны как на ладони: в ветке beta (HEAD) человеческие\nназвания цветов были заменены на HTML коды, а в ветку master добавили\nкапитализацию и пунктуацию. Основываясь на этих знаниях, мы теперь знаем что\nрезультат должен включать в себя капитализацию, пунктуацию и HTML коды цветов.\n\nВ принципе на этом можно было бы и закончить, потому что результат достигнут. Но есть\nрешение и получше.\n\n## Графическое Слияние (GUI Merging)\n\nХотя и простое текстовое представление конфликта слияния делает свою работу в\nпростых случаях, на практике конфликты могут быть более радикальными и сложными.\nВ таких случаях могут помочь графические инструменты. Мой выбор пал на простой\nинструмент написанный на Python под названием\n[meld](http://meld.sourceforge.net), но может подойти любой другой графический\nинструмент, способный представить слияние в трёх-колоночном виде.\n\nДля использования графического инструмента (он должен быть установлен), после\nтого как git пожаловался что есть конфликт, введите следующую команду:\n\n```\n$ git mergetool\n```\n\nПоследует вопрос какой программой для слияния вы хотели бы воспользоваться,\nпросто введите meld и нажмите Enter. Вот как окно программы может выглядеть\n(подразумевается опция merge.conflictstyle не была включена):\n\n![](http://3.bp.blogspot.com/_DzZflO6z3uM/TIzA-p3O6_I/AAAAAAAAAMY/ZX4FcjJ2pao/s640/meld1.png)\n\nНесмотря на то что информация представлена бок о бок, она не отображает\nнужные фрагменты которые были в Listing 2. Мы не видим здесь фрагмента базы\nслияния (состояния (A)), что мы видим это файл roses.txt.LOCAL.2760.txt в левой\nколонке и файл roses.txt.REMOTE.2760.txt в правой колонке и файл посередине это\nнеудачное слияние. Т.е. по сути нам представили состояния (B), (C) и\nнесостоявшееся состояние (D), но состояние (A) отсутствует...\n\nПравда отсутствует? Давайте проверим, в старом добром терминале:\n\n```\n$ ls -1\nroses.txt\nroses.txt.BACKUP.2760.txt\nroses.txt.BASE.2760.txt\nroses.txt.LOCAL.2760.txt\nroses.txt.REMOTE.2760.txt\n```\n\nВидим интересующий нас файл: roses.txt.BASE.2760.txt. Это и есть файл базы\nслияния. Теперь нам осталось всего лишь найти изменения внесённые в ветки master\nи beta, по отношению к базе. Мы можем сделать это двумя отдельными вызовами\nmeld:\n\n```\n$ meld roses.txt.LOCAL.2760.txt roses.txt.BASE.2760 \u0026\n$ meld roses.txt.BASE.2760 roses.txt.REMOTE.2760.txt \u0026\n```\n\n(Кто-то может подметить что было бы более разумно, поменять порядок аргументов в\nпервом вызове, для того чтобы файл базы находился в левой колонке в обоих\nслучаях, но именно такой порядок сохраняет подобие трёх-колоночного вида, при\nкотором база остаётся по середине.) Результат выполнения - два окна как показано\nниже:\n\n![](http://1.bp.blogspot.com/_DzZflO6z3uM/TIzB0vIY27I/AAAAAAAAAMg/sE3ozltiIlA/s400/meld2.png)![](http://4.bp.blogspot.com/_DzZflO6z3uM/TIzCBmifzNI/AAAAAAAAAMo/xh-L4QEYuEk/s400/meld3.png)\n\nПри чтении первого окна справа налево и второго окна слева направо, становится\nясно как день, какие изменения произошли в каждой ветке. Так как meld любезно\nподсветил все изменения, теперь практически не возможно пропустить даже мелко\nзаметные правки (Кто-нибудь заметил добавление предлога \"of\" при просмотре\nтекстового представления разрешения конфликта Listing 1 или даже Listing 2?)\n\nВооружившись этими знаниями, мы теперь можем вернуться к трёх-колоночному\nпредставлению и сделать изменения. Моя стратегия ручного слияния\nэто взять весь текст из ветки с более весомыми изменениями (в данном случае\nmaster/REMOTE т.е. beta), и поверх него производить пошаговые правки, т.е. вносить\nизменения сделанные в другой ветке (master). Вот что получилось:\n\n![](http://4.bp.blogspot.com/_DzZflO6z3uM/TIzCHHultVI/AAAAAAAAAMw/dduPVP_LA6o/s640/meld4.png)\n\n## А теперь всё вместе (All Together Now)\n\nНадеюсь, вы найдёте этот трёх-окошечный метод разрешения конфликтов, таким же\nполезным каким нахожу его я. Но согласитесь что запускать новые вызовы meld\nвручную каждый раз при разрешении конфликтов, не очень то и удобно. Выход, это\nнастроить git таким образом чтобы все три окна открывались автоматически при\nвызове команды git mergetool. Для этого можно создать выполняемый скрипт,\nкоторый должен находится в переменной окружения PATH (например\n$HOME/bin/gitmerge), со следующим содержимым:\n\n```\n#!/bin/sh\nmeld $2 $1 \u0026\nsleep 0.5\nmeld $1 $3 \u0026\nsleep 0.5\nmeld $2 $4 $3\n```\n\nИ добавьте следующее в ваш ~/.gitconfig файл:\n\n```\n[merge]\n    tool = mymeld\n[mergetool \"mymeld\"]\n    cmd = $HOME/bin/gitmerge $BASE $LOCAL $REMOTE $MERGED\n```\n\nТеперь, когда вы в следующий раз будете запускать команду git mergetool для\nразрешения конфликта, откроются все три окна:\n\n```\nОкно дифа между BASE и LOCAL\nОкно дифа между BASE и REMOTE\nОкно трёх-колоночного вида\n```\n\nПосле того как вы привыкните к такому разрешению конфликтов с использованием\nтрёх вышеупомянутых окон, вы скорее всего обнаружите, что процесс стал более\nметодичным и механическим. В большинстве случаев, вам даже не придётся читать и\nпонимать куски кода из каждой ветки, для того чтобы понять какой же вариант\nприменить для слияния. Вам больше не понадобится догадываться, потому что вы\nбудете гораздо более уверенным в корректности вашего комита. Из-за этой\nуверенности, появится чувство что разрешение конфликтов превратилось в\nувлекательное занятие.\n\n### Бонус от переводчика\n\nДля тех кто пользуется tmux и n?vim, предлагаю следующий скрипт [gitmerge](https://github.com/vbauerster/dotfiles/blob/master/bin/gitmerge):\n\n```\n#!/bin/sh\nsn=gitmerge\n\ntmux new-session -d -s \"$sn\" -n \"diff3\" \"nvim -d $2 $4 $3\"\ntmux split-window -t \"$sn:1\" -v \"nvim -d $2 $1\"\ntmux split-window -t \"$sn:1\" -h \"nvim -d $1 $3\"\n```\n\nСоответственно добавьте следующее в ваш [~/.gitconfig](https://github.com/vbauerster/dotfiles/blob/100cd6e3045a8d78a6a82f0b88dd51da1d827713/gitconfig#L41-L44)\n\n```\n[mergetool \"gitmerge\"]\n\tcmd = $HOME/bin/gitmerge \\\"$BASE\\\" \\\"$LOCAL\\\" \\\"$REMOTE\\\" \\\"$MERGED\\\"\n[merge]\n\ttool = gitmerge\n```\n\nВоркфлоу разрешения конфликта будет выглядеть так:\n\n![git merge master workflow](diff3/git_merge_master_-_workflow.png)\n\nПока игнорируем вопрос (Was the merge successful [y/n]?) и переключаемся в\nсессию под названием gitmerge (сочетание TMUXPREFIX + s):\n\n![sessiow switch](diff3/session_switch.png)\n\nВидим наше трёх-оконное представление на одном экране. Цифрами обозначены сплиты\n(panes) tmux'a, буквами соответствующие состояния. Делаем правки для разрешения\nконфликта, т.е. редактируем состояние (D) и сохраняем. После этого возвращаемся\nобратно в исходную сессию tmux'a и подтверждаем что слияние произошло успешно.\n\n![git merge master](diff3/git_merge_master_-_diff3.png)\n\n### git rebase master\n\nЛично я предпочитаю и считаю более правильным делать сначала rebase master в ветке beta, и только после этого переключаться в master и делать git merge beta. В принципе воркфлоу не сильно отличается, за исключением трёх-оконного вида.\n\n![git merge master workflow](diff3/git_rebase_master_-_workflow.png)\n\nПереключаемся в сессию gitmerge\n\n![sessiow switch](diff3/session_switch.png)\n\nОбратите внимание, что состояния (B) и (C) поменялись местами:\n\n![git merge master](diff3/git_rebase_master_-_diff3.png)\n\nРекомендую всем поиграться с [примером\nрепозитария](https://github.com/vbauerster/PainlessMergeConflict/blob/master/mergeconflict.tgz)\nхотя бы один раз, сделать разрешение конфликта по вышеописанной схеме. Лично я\nбольше не гадаю а что же выбрать \"Accept theirs\" или \"Accept yours\" :)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvbauerster%2Fpainlessmergeconflict","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fvbauerster%2Fpainlessmergeconflict","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fvbauerster%2Fpainlessmergeconflict/lists"}