Git в условиях экстремальной атомарности веток

Преимущество мелких веток

Как устроены ваши ветки в git? Как они выглядят, какого размера?

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

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

К примеру, вам нужно было написать подсистему к вашему проекту и gui к этой подсистеме. Уже видно, что это будет две ветки — ветка для самой подсистемы, ветка для gui к этой подсистеме:

cqjaqm5voffdfqpufgoqgp2ora8.png

От master-ветки отпочкована subsystem, на ее базе будет строиться subsystem_ui.

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

xfljlifa4zsxel0izwcjij5_c0o.png

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

Проблема мелких веток

Несложно сообразить, что при таком подходе нередки ситуации, когда в итоге можно получить длинные цепочки веток, зависящих друг от друга:

nkveeje7p0ssy5kurtlw8tw_oey.png

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

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

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

Как упростить себе жизнь

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

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

import os

# CONFIG
repo = 'mah_project'

branches = [
    ['master', 'inventory_mechanics', 'ingame_market'],
    ['master', 'remove_legacy_classes', 'new_item_types_project_migration'],
    ['inventory_mechanics', 'configs_refactor', 'new_item_types_project_migration'],
    ['inventory_mechanics', 'new_item_types', 'new_item_types_project_migration'],
]

# CODE
def ex(command):
    print(command)
    err = os.system(command)
    if err != 0:
        print('UNSUCCESS. ABORT')
        exit()    

os.chdir(repo)
ex('git pull')

for way in branches:
    for i in range(len(way)-1):
        src = way[i]
        dst = way[i+1]
        print(f'\n{src} -> {dst}')

        ex(f'git checkout {dst}')
        ex('git pull')
        ex(f'git merge origin/{src}')
        ex('git push')

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

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

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

py chain_merge.py

и на минуточку заняться чем-то более приятным или осмысленным.

© Habrahabr.ru