Git в условиях экстремальной атомарности веток
Преимущество мелких веток
Как устроены ваши ветки в git? Как они выглядят, какого размера?
Мои ветки стремятся к тому, чтобы быть максимально мелкими и атомарными. В противовес подходу, когда ты начинал ветку, как реализацию конкретной фичи, потом накатил в ее рамках рефакторинг всего проекта, по пути починил обнаруженный тобой назойливый баг, а закончил тем, что название ветки совершенно не отображает того, что там действительно сделано. Да и ты сам не сможешь в одном предложении описать суть сделанного в ветке. И потом тестировщики вешаются от того, что не ясно, что именно тут тестировать. А ревьювер не очень-то спешит смотреть твой PR/MR, поскольку не особо хочется спускаться в этот филиал ада.
Я строго слежу, чтобы мои ветки были лаконичными и делали одну конкретную работу. А бывает, что даже разбиваю ветку на две или несколько, когда осознаю, что ветка начинает растекаться в разные стороны.
К примеру, вам нужно было написать подсистему к вашему проекту и gui к этой подсистеме. Уже видно, что это будет две ветки — ветка для самой подсистемы, ветка для gui к этой подсистеме:
От master
-ветки отпочкована subsystem
, на ее базе будет строиться subsystem_ui
.
В процессе написания subsystem
вы успели написать порядочное количество core-классов, которые по факту не относятся к вашей подсистеме, а являются классами общего назначения, которые могли бы пригодиться всем программистам на проекте в целом. Это хороший повод расколоть ветку надвое и вынести эти классы за рамки subsystem
:
Здорово, четко, понятно. Но, вероятно, некоторые из вас уже стали напрягаться, ведь начинает проглядываться проблема, связанная с таким подходом.
Проблема мелких веток
Несложно сообразить, что при таком подходе нередки ситуации, когда в итоге можно получить длинные цепочки веток, зависящих друг от друга:
Да, у меня это вполне рабочий кейс, когда на работе происходит особенно интенсивная разработка. Тестировщики в мыле, ревьюверы не успевают разгребать завалы, и ваше дерево из веток только растет.
За чем в таком режиме работы нужно следить в первую очередь, так это за тем, чтобы все мои хитросплетенные ветки оставались актуальными и не протухали. Ваше утро начинается с кофе, мое утро начинается с подмерживания 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
и на минуточку заняться чем-то более приятным или осмысленным.