Об'єднайте сховище git у підкаталозі


83

Я хотів би об’єднати віддалене сховище git у своєму робочому сховищі git як його підкаталог. Я хотів би, щоб результуюче сховище містило об’єднану історію двох сховищ, а також щоб кожен файл об’єднаного сховища зберігав свою історію, як це було у віддаленому сховищі. Я спробував використати стратегію піддерева, як згадано в розділі Як використовувати стратегію злиття піддерев , але після дотримання цієї процедури, хоча отримане сховище дійсно містить об’єднану історію двох сховищ, окремі файли, що надходять із віддаленого, не зберегли свою історію (`git log 'на будь-якому з них просто показує повідомлення" Об'єднана гілка ... ").

Крім того, я не хочу використовувати підмодулі, оскільки я не хочу, щоб два комбіновані сховища git більше були окремими.

Чи можна об'єднати віддалене сховище git в інше як підкаталог з окремими файлами, що надходять із віддаленого сховища, зберігаючи свою історію?

Велике спасибі за будь-яку допомогу.

EDIT: Зараз я випробовую рішення, яке використовує git filter-branch для переписування об’єднаної історії сховища. Здається, це працює, але мені потрібно ще трохи перевірити. Я повернусь, щоб повідомити про свої висновки.

РЕДАГУВАТИ 2: Сподіваючись, я зрозумію більше, я даю точні команди, які використовував у стратегії піддерева git, що призводить до очевидної втрати історії файлів віддаленого сховища. Нехай A - це репозиторій git, в якому я зараз працюю, а B - репозиторій git, який я хотів би включити в A як його підкаталог. Він зробив наступне:

git remote add -f B <url-of-B>
git merge -s ours --no-commit B/master
git read-tree --prefix=subdir/Iwant/to/put/B/in/ -u B/master
git commit -m "Merge B as subdirectory in subdir/Iwant/to/put/B/in."

Після цих команд і переходу в каталог subdir / Iwant / to / put / B / in, я бачу всі файли B, але git logна будь-якому з них відображається лише повідомлення коміту "Merge B as subdirectory in subdir / Iwant / to / put / B / in ". Їх історія файлів, як у B, втрачена.

Що , здається , на роботу (так як я новачок на мерзотник я можу помилятися) , полягає в наступному:

git remote add -f B <url-of-B>
git checkout -b B_branch B/master  # make a local branch following B's master
git filter-branch --index-filter \ 
   'git ls-files -s | sed "s-\t\"*-&subdir/Iwant/to/put/B/in/-" |
        GIT_INDEX_FILE=$GIT_INDEX_FILE.new \
                git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"' HEAD 
git checkout master
git merge B_branch

Наведена вище команда для фільтра-гілки взята з git help filter-branch, в якій я лише змінив шлях до піддирекції.


Що gitkговорить про історію? Раніше я успішно використовував злиття піддерев git. Можливо, ви можете розкрити свої точні команди? Я не впевнений, що git-filter-branch є правильним підходом. Я можу рекомендувати спробувати git-fast-export та git-fast-import для синтезу нової історії.
Сет Робертсон,

Після виконання процедури піддерева gitkвідображаються два репо, об’єднані на підказках та не пов’язані між собою у початкових комітах. (Це допомогло б, якщо я опублікую скріншоти перегляду історії gitk? Чи можу я?) На жаль, окремі файли віддаленого сховища не зберегли свою історію, якщо я це роблю в терміналі git log <file-from-remote-repo>. Я розглядаю git-fast-exportі git-fast-import; Я дуже новачок у git. Я відредагую своє запитання, щоб точно показати, які команди я використовував з піддеревом git. Велике спасибі за вашу відповідь.
christosc

@christosc: Ваш другий метод працював красиво і дуже просто, дякую! Мені просто довелося змінити subdir / Iwant / на / put / B / in / і зробити його однолінійним (оскільки msysgit у Windows, здається, не підтримує повернення рядків у командах з): git filter-branch --index-filter 'git ls-файли -s | sed "s- \ t \" * - & subdir / Iwant / to / put / B / in / - "| GIT_INDEX_FILE = $ GIT_INDEX_FILE.new git update-index --index-info && mv" $ GIT_INDEX_FILE.new "" $ GIT_INDEX_FILE "'ГОЛОВА
жахливий

@ user1121352 Радий, що допомог вам.
christosc

Я зазвичай дотримуюся цієї відповіді: stackoverflow.com/a/1684694/207791
Віктор Сергієнко

Відповіді:


37

Отримавши більш повне пояснення того, що відбувається, я думаю, я це розумію, і в будь-якому випадку внизу у мене є обхідний шлях. Зокрема, я вважаю, що те, що відбувається, полягає в тому, що виявлення перейменувань обдурюється об’єднанням піддерев із --prefix. Ось мій тестовий приклад:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA
cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB
cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
git read-tree --prefix=bdir -u B/master
git commit -m "subtree merge B into bdir"
cd bdir
echo BBB>>B
git commit -a -m BBB

Ми робимо каталоги git a і b з декількома комітами в кожному. Ми робимо злиття піддерева, а потім робимо остаточне комітування в новому піддереві.

Запуск gitk(у z / a) показує, що історія дійсно з’являється, ми можемо це побачити. Запуск git logпоказує, що історія дійсно з’являється. Однак перегляд конкретного файлу має проблему: git log bdir/B

Ну, ми можемо зіграти фокус. Ми можемо переглянути історію попереднього перейменування конкретного файлу за допомогою --follow. git log --follow -- B. Це добре, але не чудово, оскільки не вдається пов’язати історію попереднього злиття з післязлиттям.

Я спробував пограти з -M та -C, але мені не вдалося змусити його слідувати за одним конкретним файлом.

Отже, рішення, на мою думку, полягає в тому, щоб повідомити git про перейменування, яке відбуватиметься в рамках об’єднання піддерев. На жаль, git-read-tree досить вередує щодо злиття піддерев, тому нам доводиться працювати через тимчасовий каталог, але це може зникнути до того, як ми вчинимо. Згодом ми можемо побачити всю історію.

Спочатку створіть сховище "А" і зробіть кілька комітів:

mkdir -p z/a z/b
cd z/a
git init
echo A>A
git add A
git commit -m A
echo AA>>A
git commit -a -m AA

По-друге, створіть сховище "В" і зробіть кілька комітів:

cd ../b
git init
echo B>B
git add B
git commit -m B
echo BB>>B
git commit -a -m BB

І фокус, щоб зробити цю роботу : змусіть Git розпізнати перейменування, створивши підкаталог і перемістивши вміст до нього.

mkdir bdir
git mv B bdir
git commit -a -m bdir-rename

Поверніться до сховища "А" та отримайте та об'єднайте вміст "В":

cd ../a
git remote add -f B ../b
git merge -s ours --no-commit B/master
# According to Alex Brown and pjvandehaar, newer versions of git need --allow-unrelated-histories
# git merge -s ours --allow-unrelated-histories --no-commit B/master
git read-tree --prefix= -u B/master
git commit -m "subtree merge B into bdir"

Щоб показати, що вони тепер об’єднані:

cd bdir
echo BBB>>B
git commit -a -m BBB

Щоб довести, що повна історія зберігається у зв’язаному ланцюгу:

git log --follow B

Ми отримуємо історію після цього, але проблема полягає в тому, що якщо ви насправді тримаєте старе репозиторій "b" і час від часу зливаєте його (скажімо, це фактично третя сторона, що підтримується окремо), у вас виникають проблеми, оскільки ця третя сторона не зробив перейменування. Ви повинні спробувати об’єднати нові зміни у вашу версію b із перейменуванням, і я боюся, що це не буде гладко. Але якщо b відходить, ви виграєте.


Дійсно, це працює @Seth! І мені не довелося вдаватися до переписування історії, як до фільтру-гілки, що створює дещо оманливу історію (наприклад, під час перегляду git log --stat). Також я не помітив --followперемикання в документації git log; здається дуже зручним при перейменуванні. Щиро дякую за Вашу настільки детальну та інформативну відповідь!
christosc

2
Ця відповідь була б набагато кориснішою, якби приклад коду був розбитий на читабельні рядки, а не на одну лінію, відокремлену крапкою з комою. ;)
jwadsack

Я хотів би об'єднати "b" у "a" із збереженням його повної історії. Як я міг це зробити?
смарагдьє

3
Дивіться stackoverflow.com/questions/37937984/… щодо виправлення помилок
Алекс Браун

1
Як уже згадувалося @AlexBrown, на нові версії gitцього виробляє fatal: refusing to merge unrelated historiesі тому ви повинні працювати git merge -s ours --allow-unrelated-histories --no-commit B/masterзамість цього.
pjvandehaar

61

git-subtree- це сценарій, розроблений саме для цього випадку злиття декількох сховищ в одне, зберігаючи історію (та / або розділяючи історію піддерев, хоча це, здається, не має значення для цього питання). Він розповсюджується як частина дерева git з випуску 1.7.11 .

Щоб об'єднати сховище <repo>при ревізії <rev>як підкаталог <prefix>, використовуйте git subtree addнаступне:

git subtree add -P <prefix> <repo> <rev>

git-subtree реалізує стратегію злиття піддерев у більш зручній манері.

Недоліком є те , що в об'єднаному історії ці файли без префікса (не в підкаталозі). Скажімо, ви об'єднали сховище aв b. У результаті git log a/f1покажуть вам усі зміни (якщо такі є), крім змін у об’єднаній історії. Ви можете зробити:

git log --follow -- f1

але це не відображатиме інших змін, що виникли тоді в об’єднаній історії.

Іншими словами, якщо ви не змінюєте aфайли в репозиторії b, вам потрібно вказати --followі без префіксу шлях. Якщо ви зміните їх в обох сховищах, у вас буде 2 команди, жодна з яких не відображає всіх змін.

Більше про це тут .


Приємно! Це саме те, що мені потрібно було в одному рядку. Дякую, майбутнє!
iameli

Це ідеальне рішення для об’єднання іншого сховища в моє сховище в піднапрямку.
eitch

1
Зверніть увагу, що це не буде працювати з існуючими підкаталогами на <prefix>. Наприклад, щоб об’єднати підкаталог, який був переміщений вручну у власне сховище, і ви хочете об’єднати його назад.
Річард Кіфер,

6

Я хотів

  1. зберігати лінійну історію без явного злиття, і
  2. зробіть так, щоб файли об’єднаного сховища завжди існували в підкаталозі, і як побічний ефект робіть git log -- fileроботу без --follow.

Крок 1 : Перепишіть історію у вихідному сховищі, щоб було схоже, що всі файли завжди існували під підкаталогом.

Створіть тимчасову гілку для переписаної історії.

git checkout -b tmp_subdir

Потім використовуйте, git filter-branchяк описано в Як я можу переписати історію, щоб усі файли, крім тих, які я вже перемістив, знаходились у підкаталозі? :

git filter-branch --prune-empty --tree-filter '
if [ ! -e foo/bar ]; then
    mkdir -p foo/bar
    git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files foo/bar
fi'

Крок 2 : Перейдіть до цільового сховища. Додайте вихідне сховище як віддалене до цільового сховища та отримайте його вміст.

git remote add sourcerepo .../path/to/sourcerepo
git fetch sourcerepo

Крок 3 : Використовуйте merge --ontoдля додавання комітів перезаписаного джерельного сховища поверх цільового сховища.

git rebase --preserve-merges --onto master --root sourcerepo/tmp_subdir

Ви можете перевірити журнал, щоб побачити, що це справді дало вам те, що ви хотіли.

git log --stat

Крок 4 : Після перебазування ви знаходитесь у стані "від'єднаної ГОЛОВИ". Ви можете швидко перемотати майстра до нового керівника.

git checkout -b tmp_merged
git checkout master
git merge tmp_merged
git branch -d tmp_merged

Крок 5 : Нарешті очищення: Вилучіть тимчасовий пульт.

git remote rm sourcerepo

git rebaseсхоже, не дозволяє вказані параметри разом: "помилка: не вдається поєднати інтерактивні параметри (--interactive, --exec, --rebase-merges, --preserve-merges, --keep-empty, --root + - -onto) з опціями am (--committer-date-is-author-date) "
Сем

Цікаво! Спробуйте скинути --committer-date-is-author-date. Перевірка несумісних параметрів була додана нещодавно у git v2.19.0 ( github.com/git/git/commit/… ). З опису це звучить так, ніби --committer-date-is-author-dateраніше все одно мовчки ігнорували.
hfs

Замість того, щоб використовувати стару filter-branchкоманду, використовуйте git filter-repo --to-subdirectory-filter <dir>, це швидше і простіше.
Віллем,

5

Якщо ви дійсно хочете з’єднати речі, знайдіть щеплення. Ви також повинні використовувати git rebase --preserve-merges --onto. Існує також можливість зберегти дату автора для інформації про учасника.


@adymitruk Дякую, за відповідь. Я справді новачок у git, тому я розгляну рішення, яке ви пропонуєте. Я спробував, git filter-branchі, здається, це працює, але, можливо, твій краще. Я спробую це.
christosc

@adymitruk Чи можу я використовувати rebase з двома сховищами, які не пов’язані між собою як гілки? Я маю на увазі два сховища, які я хочу об’єднати, не мають спільних початкових
комітів

Дякую @adymitruk. Я не був впевнений, чи можна перебазувати дані за допомогою двох не пов’язаних сховищ. Це, безумовно, буде корисним ...
christosc

Але не бійтеся фільтру-гілки. Це рятувало нас багато разів. Просто зробіть іншу гілку попередньою, і ви завжди можете повернутися назад. Це, або скористайтеся переробкою.
Адам Димітрук

Я бачу ... У будь-якому випадку мені краще прочитати документи з цих концепцій та команд git. Маючи лише невеликий досвід роботи з VCS, а саме svn, я наче переповнений git. Його сила, хоч, здається, того варта.
christosc

4

Я виявив, що таке рішення є для мене працездатним. Спочатку я переходжу до проекту B, створюю нову гілку, в якій вже всі файли будуть переміщені в нову підкаталог. Потім я підштовхую цю нову гілку до початку. Далі я переходжу до проекту A, додаю та отримую пульт дистанційного керування B, потім перевіряю переміщену гілку, повертаюся до master і зливаю:

# in local copy of project B
git checkout -b prepare_move
mkdir subdir
git mv <files_to_move> subdir/
git commit -m 'move files to subdir'
git push origin prepare_move

# in local copy of project A
git remote add -f B_origin <remote-url>
git checkout -b from_B B_origin/prepare_move
git checkout master
git merge from_B

Якщо я перейду до підкаталогу subdir, я зможу користуватися git log --followі все ще матиму історію.

Я не фахівець у git, тому не можу коментувати, чи це особливо гарне рішення, чи воно має застереження, але поки що, здається, все добре.


Люди , здається, upvoting цього підходу тут: stackoverflow.com/questions/1683531 / ...
nacross

3

Ви пробували додати додаткове сховище як підмодуль git? Це не буде об’єднувати історію із вміщуючим сховищем, насправді, це буде незалежним сховищем.

Я згадую це, бо ви цього ще не зробили.


1
Дякую за відповідь Abizern. Насправді я хочу, щоб дві історії сховищ були об'єднані в одну; Я не хочу, щоб вони більше були окремими, тому я не згадав про підмодулі.
christosc

0

Скажімо, ви хочете об’єднати сховище aв b(я припускаю, що вони розташовані поруч один з одним):

cd a
git filter-repo --to-subdirectory-filter a
cd ..
cd b
git remote add a ../a
git fetch a
git merge --allow-unrelated-histories a/master
git remote remove a

Для цього вам потрібно git-filter-repoвстановити ( filter-branchне рекомендується ).

Приклад об’єднання 2 великих сховищ, розміщення одного з них у підкаталозі: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

Більше про це тут .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.