git-subtree не сохраняет историю, поэтому я не могу отправить изменения поддерева, как я могу это исправить / избежать этой проблемы в будущем? - PullRequest
16 голосов
/ 23 апреля 2011

Я использую расширение git-subtree (https://github.com/apenwarr/git-subtree) для управления подпроектами в нашем основном проекте. Он делает именно то, что мне нужно, за исключением того, что он терпит неудачу, когда я пытаюсь выделить изменения сделано для подпроекта из нашего основного проекта.

например. раньше я сделал

git subtree add -P Some/Sub/Dir --squash git@gitserver:lib.git master

чтобы ввести код библиотеки в Some / Sub / Dir в нашем основном проекте. Все здесь прошло отлично, поэтому я внес свои изменения в наш главный главный проект голого репозитория. Затем я решаю внести изменения в мою локальную версию библиотеки в Some / Sub / Dir, зафиксировать ее, а затем разделить, чтобы вернуть обратно в репозиторий lib.git

git subtree split -P Some/Sub/Dir -b some_branch

все работает как положено. Больше не нужна локальная копия репо, я удалил ее.

После клонирования новой копии репо из нашего центрального репо я сделал некоторые изменения в lib в Some / Sub / Dir и решил, что хочу разделить эти изменения и перенести их обратно в репозиторий lib.git. Я пытаюсь использовать ту же команду разделения поддерева, что и раньше, однако на этот раз я получаю следующий вывод:

1/      3 (0)
2/      3 (1)
3/      3 (1)
fatal: bad object d76a03f0ec7e20724bcfa253e6a03683211a7bb1

d76a03f0ec7e20724bcfa253e6a03683211a7bb1 появляется, когда я добавил поддерево:

commit 43b3eb7d69d5eb64241eddb12e5bd74fd0215083
Author: Ian Bond <ibond@onezero.com>
Date:   Fri Apr 22 15:06:50 2011 -0400

    Squashed 'Subtree/librepoLib/' content from commit d76a03f

    git-subtree-dir: Subtree/librepoLib
    git-subtree-split: d76a03f0ec7e20724bcfa253e6a03683211a7bb1

, который фактически ссылается на коммит в репозитории lib.git.


То, что я смог собрать вместе (и я git noob, так что я могу ошибаться, пропуская что-то или используя неправильную терминологию здесь), это то, что 'git subtree add --squash' принесет всю историю из удаленного репозитория lib.git в текущий репо, раздавить его в отдельный коммит, затем добавить этот коммит в рабочую ветку. История коммитов lib.git сохраняется в текущем репо, однако они висят коммиты, поскольку на них не ссылаются, кроме как через текст коммита сквоша. Пока эти висячие коммиты остаются, git-поддерево может использовать их для выполнения разбиений, однако, поскольку push или pull не содержат висячих объектов (или если я запускаю gc и полностью обрезаю висячие объекты), эти висячие коммиты теряются и У git-subtree больше нет необходимой информации для выполнения разбиения.

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


Мои вопросы:

1) Что я могу сделать, чтобы справиться с существующей ситуацией, когда у меня теперь есть поддеревья, которые я хочу объединить с их исходным репо, но больше не имею никакой истории, которая связывает их вместе. Моя текущая мысль - сделать что-то вроде:

git subtree split -P Some/Sub/Dir 43b3eb7^.. --ignore-joins -b splitBranch

, чтобы разделить всю историю начиная с 'git subtree add' и объединить ее с репозиторием происхождения (который, к счастью, не изменился с момента добавления). Это лучший путь? Любые рекомендации, как мне выполнить слияние?

2) Могу ли я что-нибудь сделать, чтобы git-поддерево заработало должным образом? Я полагаю, что если я опущу параметр --squash в 'git subtree add', тогда все будет работать, однако это приведет к тому, что в мою репо будет вставлена ​​куча несвязанной истории. Есть ли способ сохранить необходимые коммиты (желательно без сохранения всей истории библиотеки)?

Ответы [ 2 ]

14 голосов
/ 23 апреля 2011

Цель git subtree split - создать несколько новых коммитов (представляющих «локальные» изменения, изначально внесенные в локальный каталог поддерева) поверх оригинальной истории поддерева. Поскольку он напрямую включает исходную историю поддерева (как родительский коммит первого переписанного локального коммита, который касается поддерева), операция разделения не может быть выполнена без присутствия самой оригинальной истории поддерева.

Подумайте, что вы будете делать с историей, которую генерирует git subtree split. Возможно, вам захочется отправить его в репозиторий, где вы сможете объединить его с остальной «восходящей» историей. Чтобы эта операция слияния имела смысл, история разделения должна основываться на самой исходной истории 1 .

Вероятно, наиболее надежный способ обеспечить пользователям доступ к исходной истории поддерева - это опубликовать URL-адрес для верхнего хранилища поддерева в вашей документации и попросить их определить для него удаленный (вполне нормально иметь «несвязанные» пульты). в одном репозитории). Э.Г.

Если вам нужно работать с «upstream» из Some/Sub/Dir (чтобы извлекать внешние изменения или выталкивать локальные изменения), пожалуйста, определите и обновите пульт дистанционного управления для хранилища библиотеки перед использованием git subtree:

git remote add lib git@host:the-lib-repository &&
git fetch lib

Вам нужно будет сделать что-то подобное, даже если вы не используете --squash, так как пользователям нужно будет знать, где получить новые восходящие коммиты (и где (в конечном итоге) нажать новые сгенерированные сплит коммиты).

Использование --squash дает вам «чистую» историю в вашем основном проекте и означает, что только те пользователи, которым необходимо иметь дело с «восходящим потоком» поддерева, фактически должны иметь его объекты в своих хранилищах.


Кажется, вы хорошо понимаете модель объекта. Вы правы, что история, в которую входит git subtree add --squash, станет висящей 2 , но git subtree split все еще может использовать ее до тех пор, пока она не будет удалена.

(со ссылкой на ваш сценарий воспроизведения)
Вы можете успешно разделить ваш repoMainClone только потому, что локальные клоны автоматически жестко связывают (или копируют) все файлы в .git/objects/ (таким образом, получая доступ к repoMain копиям висящего (или почти повисшего) 2 ) объекты из repoLib) вместо использования обычного транспорта «пакетный протокол» (который будет ограничивать переданные объекты только теми, которые необходимы для переданных ссылок; т. Е. Пропускать что-либо из repoLib). Ваш repoMainPull фактически эквивалентен клонированию file://"$(pwd)"/repoMain repoMainCloneFile (URL-адрес file:// заставляет локальных клонов использовать переносы на основе пакетов вместо простого связывания / копирования всего).


1 На самом деле, вы можете напрямую объединять несвязанные истории, но вы теряете способность выполнять трехсторонние объединения (так как общего предка не существует). Это было бы настоящей жертвой.

Ваш предложенный git subtree split -P Some/Sub/Dir 43b3eb7^.. --ignore-joins … (где 43b3eb7 - синтетический коммит, полученный в результате git subtree add --squash …), сгенерирует несвязанную историю (за исключением того, что это должно быть 43b3eb7.., поскольку 43b3eb7^ означает «первый родитель 43b3eb7» и 43b3eb7 не имеет родителей). Я не уверен, что git subtree split был спроектирован для таких диапазонов Документация для git subtree split просто говорит <commit>, но никогда не упоминает ее цель. Чтение кода показывает, что по умолчанию используется HEAD, что может указывать на то, что он предназначен для отдельного коммита, указывающего «подсказку» истории, которую следует обработать для расщепления. Кроме того, при включении вывода отладки отображается сообщение incorrect order:, которое может указывать на то, что использование аргумента диапазона приводит к тому, что операция разбиения оказывается в непредвиденной ситуации (она ожидает обработки всех родительских элементов коммита до обработки самого коммита, но диапазон гарантирует, что 43b3eb7 (который является родителем фиксации слияния поддерева) никогда не обрабатывается). Я думаю, что вы можете просто использовать --ignore-splits и выйти из диапазона, если вы хотите создать «несвязанную» историю и попытаться использовать ее каким-то образом: git subtree split -P Some/Sub/Dir --ignore-joins ….

2 Они на самом деле не висят сразу после того, как git subtree add --squash будетпотому что они все еще ссылаются на FETCH_HEAD. Однако, когда несвязанная выборка будет сделана, они станут действительно висящими.

1 голос
/ 08 марта 2012

@ Ответ Криса Джонсена очень верный, он объясняет, почему разделение работает в клоне, а не в ситуации вытягивания.

Для обходного пути, представленного в вопросе и объясненного в сноске 2 к ответу @Chris Johnsen, яможет подтвердить, что git subtree split -P Some/Sub/Dir -b splitBranch --ignore-joins и git subtree split -P Some/Sub/Dir -b splitBranch 43b3eb7.. на самом деле производят один и тот же коммит и одну и ту же ветку, которые могут отражать изменения, сделанные в локальном репо, но не могут быть перенесены в исходное репо repoLib, потому что у них нет общего аксессора, хотя git diff показывает, что d76a03f0ec7e2 и 43b3eb7d69d одинаковы.

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

Исходный сценарий воспроизведения не может работать гладко в Linux, вот новый: http://pastebin.com/3NAQKEz9

...