У меня есть репозиторий git с примерно 3500 коммитами и 30 000 различных файлов в последней версии. Это представляет собой около 3 лет работы нескольких человек, и мы получили разрешение сделать все это открытым исходным кодом. Я очень стараюсь выпустить всю историю, а не только последнюю версию. Для этого мне нужно «вернуться в прошлое» и вставить заголовок лицензии в начало файлов при их создании.На самом деле у меня это работает, но это занимает около 3 дней, когда полностью отсутствует виртуальный диск, и все еще требует небольшого ручного вмешательства. Я знаю, что это может быть намного быстрее, но мой git-fu не совсем подходит для этой задачи.
Вопрос: как я могу сделать то же самое намного быстрее?
Что я делаю в настоящее время (автоматизировано в скрипте, но, пожалуйста, потерпите меня...):
Определите все коммиты, в которых новый файл был добавлен в репозиторий (их всего около 500, fwiw):
git whatchanged --diff-filter=A --format=oneline
Определите переменную окружения GIT_EDITOR как мой собственный скрипт, который заменяет pick
на edit
только один раз в первой строке файла (скоро вы поймете, почему). Это ядро операции:
perl -pi -e 's/pick/edit/ if $. == 1' $1
Для каждой фиксации из выходных данных git whatchanged
выше вызовите интерактивную перезагрузку, начинающуюся непосредственно перед фиксацией, которая добавила файл:
git rebase -i decafbad001badc0da0000~1
Мой пользовательский GIT_EDITOR (тот самый однострочник perl) изменяет pick
на edit
, и мы попадаем в оболочку, чтобы внести изменения в новый файл. Другой простой скрипт header-inserter
ищет известный уникальный шаблон в заголовке, который я пытаюсь вставить (только в известных типах файлов (*.[chS] для меня)). Если его там нет, он вставляет его, и git add
— это файл.Этот наивный метод не знает, какие файлы были фактически добавлены во время текущей фиксации, но в конечном итоге он делает правильные вещи и является идемпотентным (безопасным для многократного запуска с одним и тем же файлом), и в любом случае весь этот процесс не является узким местом. .
На данный момент мы довольны тем, что обновили текущую фиксацию, и вызываем:
git commit --amend
git rebase --continue
Rebase --continue— затратная часть. Поскольку мы вызываем
git rebase -i
один раз для каждой ревизии в выводе whatchanged
, это много перебазирования. Почти все время, в течение которого выполняется этот скрипт, тратится на наблюдение за увеличением счетчика «Перебазирование (2345/2733)».
Это не просто медленно. Периодически возникают конфликты, которые необходимо решать. Это может произойти по крайней мере в следующих случаях (но, вероятно, и в других): (1) когда «новый» файл на самом деле является копией существующего файла с некоторыми изменениями, внесенными в его самые первые строки (например, #include
заявления). Это настоящий конфликт, но в большинстве случаев он может быть разрешен автоматически (да, есть сценарий, который с этим справляется). (2) при удалении файла. Это тривиально разрешимо, просто подтвердив, что мы хотим удалить его с помощью git rm
. (3) есть некоторые места, где кажется, что diff
просто ведет себя плохо, например, где изменение представляет собой только добавление пустой строки. Другие более легитимные конфликты требуют ручного вмешательства, но в целом они не являются самым большим узким местом. Самое большое узкое место — это просто сидеть и смотреть на «Перебазирование (xxxx/yyyy)».
Прямо сейчас отдельные перебазирования инициируются от более новых коммитов к более старым коммитам, т. е. начиная с верхней части вывода git whatchanged
. Это означает, что самая первая перебазировка влияет на вчерашние коммиты, и что в конечном итоге мы будем перебазировать коммиты трехлетней давности. Переход от «нового» к «старому» кажется нелогичным,но до сих пор я не уверен, что это имеет значение, если мы не изменим более одного pick
на edit
при вызове rebase. Я боюсь делать это, потому что конфликты действительно возникают, и я не хочу иметь дело с приливной волной конфликтных рябей из-за попытки перебазировать все за один раз. Может кто знает как этого избежать? Я не смог придумать ни одного.
Я начал изучать внутреннюю работу объектов git 1! Кажется, должен быть гораздо более эффективный способ обхода графа объектов и просто внесения изменений, которые я хочу сделать.
Обратите внимание, что этот репозиторий взят из репозитория SVN, где мы фактически не использовали теги или ветки (я уже git filter-branch
удалил их), поэтому у нас есть удобство прямого -линейная история. Никаких веток или слияний git.
Я уверен, что упустил кое-какую важную информацию, но этот пост уже кажется слишком длинным. Я сделаю все возможное, чтобы предоставить больше информации по запросу. В конце концов мне может понадобиться просто опубликовать мои различные сценарии, что вполне возможно. Моя цель - выяснить, как таким образом переписать историю в репозиторий git; не обсуждать другие жизнеспособные методы лицензирования и выпуска кода.
Спасибо!
Обновление от 17 июня 2012 г.: Запись в блогесо всеми кровавыми подробностями.