эффективное переписывание (rebase -i) большой части истории с помощью git

У меня есть репозиторий git с примерно 3500 коммитами и 30 000 различных файлов в последней версии. Это представляет собой около 3 лет работы нескольких человек, и мы получили разрешение сделать все это открытым исходным кодом. Я очень стараюсь выпустить всю историю, а не только последнюю версию. Для этого мне нужно «вернуться в прошлое» и вставить заголовок лицензии в начало файлов при их создании.На самом деле у меня это работает, но это занимает около 3 дней, когда полностью отсутствует виртуальный диск, и все еще требует небольшого ручного вмешательства. Я знаю, что это может быть намного быстрее, но мой git-fu не совсем подходит для этой задачи.

Вопрос: как я могу сделать то же самое намного быстрее?

Что я делаю в настоящее время (автоматизировано в скрипте, но, пожалуйста, потерпите меня...):

  1. Определите все коммиты, в которых новый файл был добавлен в репозиторий (их всего около 500, fwiw):

    git whatchanged --diff-filter=A --format=oneline
    
  2. Определите переменную окружения GIT_EDITOR как мой собственный скрипт, который заменяет pickна editтолько один раз в первой строке файла (скоро вы поймете, почему). Это ядро ​​операции:

    perl -pi -e 's/pick/edit/ if $. == 1' $1
    
  3. Для каждой фиксации из выходных данных 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 г.: Запись в блогесо всеми кровавыми подробностями.

6
задан jonny0x5 17 June 2012 в 17:01
поделиться