Вливание legacy-истории в дерево: нахождение оптимальной точки ответвления

от автора

По долгу службы мне досталась в наследство некая система, имеющая ~15 лет истории и порядка нескольких десятков инсталляций в разных организациях. Сама по себе системы относительно небольшая (~25K строчек кода, ~1K коммитов), но проблема была в release management:

  • было основное дерево в subversion (изначально в cvs, разумеется), где проводился «основной курс партии» — делались какие-то масштабные изменения, добавлялись новые возможности, исправлялись глобальные ошибки и т.п.
  • конкретные инсталляции делались путем:
    • в лучшем случае — svn checkout, который потом обновлялся через svn update; почти во всех инсталляциях делались локальные доработки «на живую» (как минимум — правились конфигурационные файлы) и эти изменения никуда не коммитились; если при очередном svn update изменения в upstream создавали конфликт — конфликт ресолвился «на месте» тем программистом, который делал update, опять же, без какого-либо трекинга изменений
    • в худшем случае — svn export, который потом, понятно, не обновлялся совсем, оставаясь раз и навсегда (или по крайней мере пока начальство не одумается) на уровне развития даты экспорта; в особо запущенных случаях (из конца 1990-х — начала 2000-х) так делали еще и потому, что просто не было физической возможности сделать checkout — в организации не было доступа в интернет, архив просто приносили на дискетке и разворачивали единожды на месте

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

После недолгого консилиума, продолжать поддержку столь распределенной системы в svn было признано нецелесообразным и решено было мигрировать на git.

Проблема номер один — перетащить мастер-дерево из svn на git — решилась в целом просто стандартными средствами git-svn.

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

  1. получить их форк
  2. понять, откуда он в свое время был форкнут и до какого уровня последний раз перебазировался (если это svn checkout)
  3. создать новый бранч для этого форка
  4. попытаться разделить сделанные изменения на более-менее семантически-связанные кусочки поменьше и закоммитить их все в этот бранч

Основной затык внезапно оказался на шаге 2 — понять, откуда же была форкнута очередная инсталляция. В случае svn checkout можно было хотя бы посмотреть на текущее состояние working copy, в случае же svn export угадывать было нетривиально. Потыкавшись с полуручным археологическим исследованием состояния кода пару раз, мне надоело и решено было автоматизировать поиски. Готового решения не нашлось (git bisect здесь, к сожалению, не годится) и получился следующий скрипт:

#!/bin/sh -ef  if [ $# -ne 2 ]; then 	echo "Usage: $0 <git-repo-dir> <candidate-checkout-dir>" 	exit 1 fi  GIT_REPO="$1" CANDIDATE_DIR=$(cd "$2" && pwd) TAB=$(printf '\t')  cd "$GIT_REPO" COMMITS=$(git log --all --format=format:%H)  # Remember current commit CURRENT_COMMIT=$(git rev-parse HEAD)  for C in $COMMITS; do 	git checkout --quiet $C 	echo -n "$C$TAB" 	diff -urN --exclude=.git --exclude=.svn "$CANDIDATE_DIR" . | wc -l done | sort -t"$TAB" -k2,2n  # Restore current commit git checkout --quiet "$CURRENT_COMMIT" 

Скрипт принимает 2 параметра: (1) путь к git-репозитарию, (2) путь к очередному форку-кандидату, для которого нужно найти место «врезки» в общее дерево развития проекта. Скрипт банально рассчитывает объем diff’а (в строках) между каждым checkout’ом репозитария и кандидатом-на-врезку. С большой вероятностью — коммит, где объем различий минимален — и есть оптимальное место для базирования бранча. Результат работы выглядит примерно так:

 3810315aaa238e32a7106312f9973f1d1f0ea097        651 19b595d87eecc43933ea60d89882319c7ac3f512        835 989cee69664733b773a4a81cc49e2a1a0cdff38a        872 9026dae1154f98018c808b73c7f1c6cd09310dc7        885 802943edf287ad28d5e71a57510400afacb49176        894 c5bd4050fce754e16664e6e1eeb57a4ff3ed06c6        894 dcb70c4a2e9fc0431ceb6154ecd1688189362622        908 ... 

Это значит, что скорее всего задача будет решена как-то так:

$ git branch new-organization 3810315aaa238e32a7106312f9973f1d1f0ea097 $ git checkout new-organization $ cp -r ../new-organization-fork/* . 

… после чего можно уже разбираться с изменениями, пытаться разделять их на части и коммитить (возможно, даже с —date и —author, если получится их выяснить).

Буду рад, если приведенное решение окажется полезным кому-то еще. Комментарии и советы, как сделать лучше, приветствуются.

ссылка на оригинал статьи http://habrahabr.ru/post/191696/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *