Pull to refresh

Comments 62

Спасибо) Это добавляет аргументов в пользу перехода на DCVS.
Не совсем понятно, почему не сделать для каждой выделенной библиотеки отдельный репозиторий, а не таскать изменения туда-сюда между ветками?
Это было альтернативное экспериментальное решение. То, что вы предлагаете, это классические run-time библиотеки.
Почему runtime? svn:externals прописываете и получаете рабочую схему — испытал на себе неоднократно.
да,git это позволяет, буквально сегодня делал
Т.е. он и примет 8, и заблокирует 9 правки на схеме?
он спросит что делать если не сможет очевидным образом разрешить коллизию через diff3
Понятно, значит он разрешит оба мержа, жаль, конечно, мог бы и догадаться, что на левой ветке 5 правка уже есть.
нет, он не сможет наложить повторно одну и ту же правку, diff так не работает
строго говоря он начнёт вас спрашивать ещё на этапе 3-8
И про 8 тоже спросит? А чего там спрашивать?
ну если я правильно понял схему то 3 патчит 4,4 меняется на 6, 6 патчит 7 и 7 меняется на 8, а значит патч 3 уже в комплекте
если же на однои из этапов патч 3->4 полностью откатывается то спрашивать не будет, если не полностью то будет попытка решить это самостоятельно, и если не выйдет будет вопрос
Не совсем так, на схеме изображены три ветки одного и того же проекта, время идет снизу вверх. Стрелки — это merge, т.е. перенос какой-то правки или их группы с одной ветки на другую. Вот история этой схемы:

1. От ствола ответвляются правая и левая ветки.
2. На стволе производятся изменения.
3. На правой ветке производятся изменения.
4. Изменения 3 переносятся на ствол.
5. На правой ветке еще производятся изменения.
6. Изменения 5 переносятся на ствол.
7. Изменения правок 2 и 6 переносятся на левую ветку.
8. Изменения 3 с правой ветки переносятся на левую.
9. Изменения 5 с правой ветки переносятся на левую.
а, да, я не правильно понял схему, тогда 3-8 не спросит, а 5-9 уведомит что изменение уже наложено
Но это сделает уже непосредственно diff, а не Git, так ведь?
именно так, а дальше уже вопрос в том как настроен git
если 5-я правка уже есть, он ее пропустит если при наложении патча ничего не меняется
лишнего коммита не будет
вообще в git совершенно другая схема работы с ветками, там ветки делаются в больших количествах и есть удобные инструменты для сведения веток
вы не мержили переименование файлов с изменением. вот это весело
Работаю с Mercurial и суть такова:

Попытка сделать 3 -> 8

Работаю с Mercurial и суть такова:

Попытка слить 2 и 6 в 7 одновременно лишена смысла, т.к. изменение 2 уже содержится внутри 6.
Аналогично с 3 -> 8, и 5 -> 9: Mercurial не даст сделать такое слияние. Я сделал небольшой тестовый репозиторий для Вас и вот что получается:

>hg merge -r3 -r8
abort: can't merge with ancestor

>hg merge -r5 -r9
abort: can't merge with ancestor

> Попытка слить 2 и 6 в 7 одновременно лишена смысла, т.к. изменение 2 уже содержится внутри 6.

Как это? Изменения 2 и 6 — это два совершенно разных изменения, в одном мы делаем что-то одно, а в другом — что-то другое.
Смотрите, допустим этот проект вначале состоит из одного файла содержащего три строки, все это на всех трех ветках:
А     А     А
Б     Б     Б
В     В     В

2. На стволе добавляется строка
А     А     А
Б     Б     Б
В     В     В
      Г

3. На правой ветке строка 1 изменена
А     А     АА
Б     Б     Б
В     В     В
      Г

4. Переносим изменение 3 на ствол
А     АА    АА
Б     Б     Б
В     В     В
      Г

5. Строка 2 на правой ветке изменена
А     АА    АА
Б     Б     ББ
В     В     В
      Г

6. Переносим изменение 5 на ствол
А     АА    АА
Б     ББ    ББ
В     В     В
      Г

7. Переносим изменения 2 и 6 на левую ветку
А     АА    АА
ББ    ББ    ББ
В     В     В
Г     Г

8. Переносим изменение 3 с правой ветки на левую
АА    АА    АА
ББ    ББ    ББ
В     В     В
Г     Г


9. Пытаемся перенести изменение 5 (ББ) с правой ветки на левую - получаем конфликт
Видите в чем суть, на шаге 7 в моей схеме все три ветки разные, в вашей схеме вы каждым мержем переносите все возможние изменения с одной ветки на другую, я же говорю о выборочном переносе только некоторых изменений.
выборочно переносить изменения — это ад, в котором потом нельзя разобраться
Если есть программная платформа, на основе которой строятся все проекты, то изменения уже делятся на два класса — нужные только в конкретном проекте и полезные для платформы в целом. Вы ответвляете проект, вносите ив него и первые, и вторые изменения, а потом только вторые переносите обратно на ветку платформы. Разве это не типичный сценарий использования?
Пока писал длинные ответ, Вы в целом описали свою проблематику.

Могу дать такой совет, как это должно было бы быть сделано в распределённой системе (Mercurial в частности).

Первый класс изменений ведётся в ветке 1.
Второй класс изменений (под проект Х) ведётся в ветке 2.
Если появляется проект Y, то под него заводится ветка 3.

Все слияния (переносы изменений) делать в направлении 1 -> 2 и 1 -> 3.
Никаких потребностей в выборочных переносах при этом появиться не должно.
Ревизия при слиянии в другую ветку (merge) тянет за собой всех предков — и именно это предотвратит конфликт на шаге 9 в изначальном примере.
Да, вы правы, теперь мы так и делаем, попытка «множественного наследования» оказалась неудачной, думаю она в принципе не реализуема на современных системах, т.к. в ней нет потребности.
Предвижу следующий вопрос с Вашей стороны:
«А что если в проекте Y будут сделаны изменения (добавлена одна функция), которая может потом пригодится в проекте X — для этого ведь понадобится передать частичное изменение (без всех предков) из Y -> X».

Отвечаю: поскольку, частичная передача изменений без ревизий-предков противоречит всей коцепции веток и слияний, делать следует так:

Для всех общих функций в проектах X и Y заводится четвёртая ветка под названием «4(XY) — общее для проектов X и Y» и слияния просиходят по следующей схеме:

1(Платформа) -> 4(XY) -> 2(X)
и
1(Платформа) -> 4(XY) -> 3(Y).

Ещё вопрос: «А что если полезная функция была УЖЕ внесена в 3(Y) и теперь мы хотим перенести её в ветку для проекта X?»

Да, можно отщепить эту функцию старым дедовским способом, взяв отдельный diff из ветки 3(Y) и наложив его на ветку 2(X). Но это плохое решение. А если эту функцию потом надо будет немного усовершенствовать причём для двух проектов сразу? Придётся возиться в обоих ветках, либо опять гонять diff вручную.

Именно для из этих соображений, я предлагаю Вам ветку 4(XY). Выносить всё общее для двух проектов нужно туда (по аналогии с глобально-общей-для всех проектов веткой 1).

Итак, я всё ещё не ответил на вопрос: как средствами Mercurial правильно перенести функцию УЖЕ написанную в ветке 3(Y) куда-то в более подходящее ей место (4XY или 1), если она потребовалась в нескольких проектах одновременно? И не отвечу, ибо не знаю прямо сейчас :) Обычно я заранее планирую что и где должно лежать и пользуюсь строгой последовательностью слияний от общего к частному.

У меня пока одна идея: использовать функциональность распределённых систем под названием Rebase. Подробности опускаю, т.к. не сам не пробовал, только читал в доках.
Да, понятно, но мы с вами по-моему несколько все усложнили)
Я могу перефразировать вопрос так: отслеживает ли система откуда и какие именно атомарные изменения пришли. если они прошли через несколько разных веток?
Я так понимаю по нашей дискуссии, что и для mercurial, ответ все-таки нет.
10 минут назад не готов был ответить, но сейчас уже знаю ответ: в git это называется cherry-pick, в Mercurial — Transplant — перенос частичных изменений и история их перемещений средствами системы контроля версий!
Выборочный перенос изменений в git'е называется cherry-pick, реализован очень просто и без проблем взаимодествует с полными мерджами. То есть вы можете черри-пикать отдельные коммиты из ветки в ствол, а потом слить всю ветку целиком без всяких конфликтов.
Наверняка в mercurial есть аналогичный инструмент.
Хороший совет, действительно, cherry-pick!

Погуглил, в Mercruial есть аналогичное расширение под названием Transplant.
нет
типичный сценарий — это появились изменения полезные для платформы — сделали их в транке и смержили в ветки
Можно, но неудобно, отлаживать на ветке а потом руками переносить на ствол. откатывать на ветке, и снова мержить со ствола.
мы тестируем прямо в ветке
не знал что с этим могут проблемы
Тестируете на ветке, потом руками переносите на ствол, коммитите на стволе и мержите снова на ветку?
тестируем на ветке, коммитим в ветку и в конце сливаем ветку в trunk
Внимательно прочитал Ваш пример и вы абсолютно правы: я переношу ВСЕ возможные изменения с одной ветки на другую, а Вы выборочно.

Ваш вопрос: можно ли в Mercurial переносить изменения по вашей схеме и не получить конфликт на шаге 9?

Я никогда не пробовал переносить изменения выборочно, как это описали Вы.
В частности, меня удивил пункт «7. Переносим изменения 2 и 6 на левую ветку», в результате которого на левой ветке в первой строке осталась одна буква «А». Чтобы добавить вторую букву A вы делаете дополнительный перенос изменения 3.

Вопрос концептуальный: зачем вам понадобился такой контроль версий? Возможно, это из-за специфики работы с SVN…

Если всё-таки необходимо переносить изменения вот так атомарно, то первое что приходит на ум: чтобы сделать выборочный перенос, нужно брать diff от версии N и N-1 (то самое выборочное изменение), сохранять его в файл и накладывать на другую ветку (patch) — но контроль версий тут уже не причём, с этим справится любая diff/patch программа и, естественно, в итоге на пункте 9 будет конфликт.

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

Но повторюсь ещё раз, скорее всего, Вам надо задуматься о самом подходе к веткам — почему Вы хотите копировать изменения выборочно?

Вернусь к Вашему примеру:
7. Переносим изменения 2 и 6 на левую ветку
А     АА    АА
ББ    ББ    ББ
В     В     В
Г     Г


В левой ветке в первой строке одна буква A.
В левой ветке во второй строке две буквы ББ, которые (если проследить цепочку) пришли из изменения «5. Строка 2 на правой ветке изменена»:
А     АА    АА
Б     Б     ББ


Изменение 5 в свою очередь было сделано поверх изменения «3. На правой ветке строка 1 изменена»:
А     А     АА


Для меня, как для разработчика, возникает подозрение: если изменение 5 было сделано поверх 3 — скорее всего 5 зависит от 3? ББ во второй строке не скомпилируется без АА в первой?

А если так, то переносить ББ отдельно от АА — в чём смысл этих атомарных переносов?

Если же ББ никак не зависит от АА и в будущем есть потребность переносить ББ, не перенося AA — их следует сделать в двух разных ветках! Эту будут ревизии без связи предок-потомок и их можно будет атомарно как угодно переносить между ветками, не получив в итоге на шаге 9 никаких конфликтов (ибо память у Mercurial основана на ревизиях, которые однако переносятся между ветками вместе со всеми своими предками). Могу сделать пример и показать.

Таким образом, Ваша схема с атомарными переносами могла бы быть реализована бОльшим числом веток (каждое атомарное изменение производится в своей ветке, а потом комбинируй их как хочешь!) — но концептуально, это лишнее.

Боюсь я немного запутанно описал свои мысли.

Попробуйте прочитать вот эти две более стройные статьи с картинками про ветки и различные use case'ы:

stevelosh.com/blog/2009/08/a-guide-to-branching-in-mercurial/
nvie.com/posts/a-successful-git-branching-model/
Спасибо за подробный ответ.
Попробую объяснить зачем мы используем атомарные мержи)

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

По-моему, это самый очевидный и прямой путь, как по-другому это можно организовать?
вот и переносите изменения из ветки в ствол и из ствола в другие ветки. а гонять изменения между всеми ветками — вы быстро запутаетесь. а в случае неатомарных коммитов, которые неизбежны, как бы вы всё не перепроверяли, придётся ещё и следить чтобы перемёрживались правильные их группы.

Теперь так и делаем
Вы ошиблись, merge tracking в Subversion появился начиная с версии 1.5.0.
Я так понимаю, эти проблемы происходят из того, что при мерже веток в свне остаются обе ветки (и ствол, и бранч). Хотя на самом деле необходимо в этом месте «закрыть» одну из веток (слияние же!). При последующих расхождениях будет создана новая ветвь, которую тоже можно смержить. Т.е. в свн все выглядит так:
| |
|\
| |
| |
А по идее должно быть так:
|
|\
| |
| |
DVCS (например, меркуриал) предлагают более подходящую модель. Ветки могут появляться где угодно и когда угодно, но при слиянии их результатом будет 1 ветка, а не 2. Естественно, влить заново изменения из уже смерженной ветки не выйдет, поскольку они УЖЕ включены в результирующую ветку и являются частью ее истории. Это все равно что просить систему сделать один и тот же коммит 2 раза подряд.
Простите, не понял — почему все-таки не svn externals?
А как оно тут поможет?
Выносим ядро и библиотеки в отдельные репозитарии. Начиная новый проект, подключаем ядро и нужные модули externals'ами. В процессе работы, если в модуль вносятся общеполезные правки, они коммитятся, оказываясь в соответствующем репозитарии и становясь доступными всем проектам.
Ну, а специфичные для проекта правки делаем в тех файлах, которые специфичны для проекта.
Да, это решение, правда повторно используемый код должен быть локализован в отдельной папке, а так не вижу в нем никаких недостатков.
если ядро и библиотеки по отдельным репам распихать, то апы будут ооочень небыстрыми.
Не без этого:( У любого решения есть цена.
а мне вот больше интересно как сделать так, чтобы можно было в своей ветке организовывать свою структуру директорий да так, чтобы можно было без проблем мержиться из ствола и заливать обратно.

сейчас я поместил каждый модуль в отдельную репу и копирую её в проект целиком, не прописывая как сабмодуль. это позволяет получать обновления из ствола модуля, но коммитить их и локальные правки в проект. всё бы хорошо, да вот развёртывать такую структуру с нуля вручную гемор, ибо при клонировании репы для модулей не клонируются. а елси прописывать их как субмодули, то они не будут комититься в проект
Это не про SVN?
Как выходите из положения? Пишете скрипт развертывания/деплоя?
это в гите, конечно, в свн-е так нельзя, ибо разбрасывает свои директории по всем папкам %-)

пока никак ^^' я экспериментирую… но видимо придётся сделать скрипт, который будет собирать файлик .gitrepos и использовать его для инициализации репозиториев при апдейте
Интересно было бы прочесть об этом, когда эксперименты будут завершены.
на всякий случай напомню: SVN с 1.7 в рабочей копии служебную папку .svn держит только в корне
Да, решение с экстерналами накладывает некое ограничение на файловую структуру.
Я прекрасно ветвлюсь и мержусь в Git, вообще без проблем.

Вплоть до того, что могу внести в свой репозиторий чужой с дико другой историей в виде отдельной ветки, аккуратно смержить и отослать автору )
По поводу cherry-pick вы я думаю всё-таки не правы. В случае если вы сделаете cherry-pick 5-ой правки как девятой, git просто обнаружит идентичную уже объединённую ревизию и ничего не сделает. Мне часто кажется, что там внутри какая-то чистая незамутнённая магия, так что просто попробуйте и скорее всего будете счастливы :)
не совсем так, он обнаружит что «patch already applied» и не создаст коммита
потому что при cherry-pick вы просто пытаетесь наложить патч из указанного коммита на текущую ветку
Sign up to leave a comment.

Articles