Об’єднайте два сховища Git, не порушуючи історію файлів


226

Мені потрібно об'єднати два сховища Git у абсолютно новий, третій сховище. Я знайшов багато описів, як це зробити за допомогою злиття піддерева (наприклад , відповідь Якуба Нарбського про те, як ви з’єднуєте два сховища Git? ) Та слідуючи цим інструкціям, здебільшого працює, за винятком того, що коли я примушую піддерево об'єднати всі файли зі старих сховищ записуються як нові додані файли. Коли я це бачу, я можу бачити історію фіксації зі старих сховищ git log, але якщо я git log <file>це роблю, то для цього файлу відображається лише одна фіксація - підрівень злиття. Судячи з коментарів до вищезгаданої відповіді, я не один бачу цю проблему, але не знайшов жодного опублікованого рішення для неї.

Чи є спосіб об'єднати сховища та залишити історію окремих файлів недоторканою?


Я не використовую Git, але в Mercurial я спершу зробив перетворення, якщо потрібно, щоб виправити шляхи до файлу репостів, які потрібно об'єднати, а потім змусити потягнути одне репо в ціль, щоб отримати набори змін, а потім зробити злиття різних галузей. Це перевірено і працює;) Можливо, це допомагає знайти рішення і для Git ... порівняно з підходом до підключення до підрівню, я думаю, що крок перетворення відрізняється там, де історія переписується замість просто відображення шляху (якщо я розумію правильно). Після цього забезпечується плавне злиття без спеціального оброблення шляхів до файлів.
Lucero

Я також знайшов це питання корисний stackoverflow.com/questions/1683531 / ...
nacross

Я створив подальше запитання. Можливо, буде цікаво: Об’єднайте два сховища Git та збережіть історію майстра: stackoverflow.com/questions/42161910/…
Dimitri Dewaele

Автоматизоване рішення , яке працювало для мене було stackoverflow.com/a/30781527/239408
xverges

Відповіді:


269

Виявляється, що відповідь набагато простіша, якщо ви просто намагаєтесь склеїти два сховища разом і зробити так, щоб все це було так, а не управляти зовнішньою залежністю. Вам просто потрібно додати пульт віддалених файлів до своїх старих репостів, об'єднати їх з новим головним майстром, перемістити файли та папки у підкаталог, здійснити переміщення та повторити для всіх додаткових репост. Підмодулі, злиття піддерева та фантазійні скасування призначені для вирішення дещо іншої проблеми і не підходять для того, що я намагався зробити.

Ось приклад сценарію Powershell склеювати два сховища разом:

# Assume the current directory is where we want the new repository to be created
# Create the new repository
git init

# Before we do a merge, we have to have an initial commit, so we'll make a dummy commit
git commit --allow-empty -m "Initial dummy commit"

# Add a remote for and fetch the old repo
git remote add -f old_a <OldA repo URL>

# Merge the files from old_a/master into new/master
git merge old_a/master --allow-unrelated-histories

# Move the old_a repo files and folders into a subdirectory so they don't collide with the other repo coming later
mkdir old_a
dir -exclude old_a | %{git mv $_.Name old_a}

# Commit the move
git commit -m "Move old_a files into subdir"

# Do the same thing for old_b
git remote add -f old_b <OldB repo URL>
git merge old_b/master --allow-unrelated-histories
mkdir old_b
dir exclude old_a,old_b | %{git mv $_.Name old_b}
git commit -m "Move old_b files into subdir"

Очевидно, що ви можете замість цього об'єднати old_b у old_a (який стає новим комбінованим репо), якщо ви хочете зробити це - змінити сценарій відповідно до цього.

Якщо ви хочете також запустити гілки функцій, що не працюють, скористайтеся цим:

# Bring over a feature branch from one of the old repos
git checkout -b feature-in-progress
git merge -s recursive -Xsubtree=old_a old_a/feature-in-progress

Це єдина неочевидна частина процесу - це не злиття підрівню, а скоріше аргумент нормального рекурсивного злиття, яке говорить Git, що ми перейменували ціль, і це допомагає Git вирівняти все правильно.

Я написав кілька більш докладне пояснення тут .


16
це рішення за допомогою git mvне працює так добре. коли ви згодом використовуєте a git logна одному з переміщених файлів, ви отримуєте фіксацію лише з переміщення. вся попередня історія втрачена. це тому, що git mvє насправді, git rm; git addале в один крок .
mholm815

15
Це те саме, що будь-яка інша операція переміщення / перейменування в Git: з командного рядка ви можете отримати всю історію, виконавши git log --follow, або всі інструменти GUI роблять це для вас автоматично. Наскільки я знаю, під час злиття підкреслень ви не можете отримати історію для окремих файлів, тому цей метод є кращим.
Ерік Лі

3
@EricLee Коли об'єднується репортаж old_b, я отримую багато конфліктів злиття. Це очікується? Я отримую КОНФЛІКТ (перейменувати / видалити)
Jon

9
Коли я намагаюся "dir -exclude old_a |% {git mv $ _. Ім'я old_a}", я отримую sh.exe ": dir: команда не знайдена та sh.exe": git: команда не знайдена. Використовуючи цей твір: ls -I old_a | xargs -I '{}' git mv '{}' old_a /
Джордж

5
Це 1(номер один) для lsта капітальне «око» xargs. Дякую за цю пораду!
Домінік Віал

149

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

  1. Додайте друге репо як дистанційне:

    cd firstgitrepo/
    git remote add secondrepo username@servername:andsoon
    
  2. Переконайтеся, що ви завантажили всі зобов'язання secondrepo:

    git fetch secondrepo
    
  3. Створіть локальну гілку з другого відділення репо:

    git branch branchfromsecondrepo secondrepo/master
    
  4. Перемістіть усі його файли у підкаталог:

    git checkout branchfromsecondrepo
    mkdir subdir/
    git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} subdir/
    git commit -m "Moved files to subdir/"
    
  5. Об'єднайте другу гілку в головну гілку першого репо:

    git checkout master
    git merge --allow-unrelated-histories branchfromsecondrepo
    

У вашому сховищі буде декілька кореневих комірок, але це не повинно створювати проблем.


1
Крок 2 не працює для мене: fatal: Недійсне ім'я об'єкта: 'secondrepo / master'.
Кіт

@Keith: переконайтеся, що ви додали друге репо як віддалений під назвою "secondrepo", і що в цьому repo є гілка з назвою "master" (ви можете переглянути гілки на віддаленому репо з командою git remote show secondrepo)
Flimm

Мені довелося зробити забір, щоб також його збити. Між 1 та 2 я зробив git fetch
secondrepo

@monkjack: я відредагував свою відповідь, щоб включити крок отримання git. Сміливо самостійно відредагуйте відповідь.
Flimm

4
@MartijnHeemels У старшій версії Git просто опустіть --allow-unrelated-histories. Дивіться історію цього відповіді.
Flimm

8

Минуло кілька років, і є добре обґрунтовані рішення, але я хочу поділитися моїм, оскільки це було дещо інше, тому що я хотів об'єднати 2 віддалені сховища в нове, не видаляючи історію з попередніх сховищ.

  1. Створіть новий сховище в Github.

    введіть тут опис зображення

  2. Завантажте новостворене репо і додайте старий віддалений сховище.

    git clone https://github.com/alexbr9007/Test.git
    cd Test
    git remote add OldRepo https://github.com/alexbr9007/Django-React.git
    git remote -v
    
  3. Вилучити всі файли зі старого репо, щоб створити нову гілку.

    git fetch OldRepo
    git branch -a
    

    введіть тут опис зображення

  4. У головній гілці зробіть злиття, щоб поєднати старе репо з новоствореним.

    git merge remotes/OldRepo/master --allow-unrelated-histories
    

    введіть тут опис зображення

  5. Створіть нову папку, щоб зберігати весь новий створений вміст, доданий із OldRepo, та переміщуйте його файли в цю нову папку.

  6. Нарешті, ви можете завантажити файли з комбінованих репостів і безпечно видалити OldRepo з GitHub.

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


1
Це єдине рішення, яке працювало для мене для збереження історії git. Не забудьте видалити віддалене посилання на старий репо git remote rm OldRepo.
Харубіорі

7

будь ласка, подивіться на використання

git rebase --root --preserve-merges --onto

пов'язати дві історії на початку свого життя.

Якщо у вас є шляхи, які перетинаються, виправте їх

git filter-branch --index-filter

коли ви використовуєте журнал, переконайтеся, що ви "знаходите копії важче"

git log -CC

таким чином ви знайдете будь-які рухи файлів на шляху.


Документація Git рекомендує не видавати
Стівен Тернер

7

Я перетворив рішення від @Flimm у git aliasподібне (додане до мого ~/.gitconfig):

[alias]
 mergeRepo = "!mergeRepo() { \
  [ $# -ne 3 ] && echo \"Three parameters required, <remote URI> <new branch> <new dir>\" && exit 1; \
  git remote add newRepo $1; \
  git fetch newRepo; \
  git branch \"$2\" newRepo/master; \
  git checkout \"$2\"; \
  mkdir -vp \"${GIT_PREFIX}$3\"; \
  git ls-tree -z --name-only HEAD | xargs -0 -I {} git mv {} \"${GIT_PREFIX}$3\"/; \
  git commit -m \"Moved files to '${GIT_PREFIX}$3'\"; \
  git checkout master; git merge --allow-unrelated-histories --no-edit -s recursive -X no-renames \"$2\"; \
  git branch -D \"$2\"; git remote remove newRepo; \
}; \
mergeRepo"

12
Просто цікаво: чи справді ви робите це досить часто, щоб потрібен псевдонім?
Паркер Коутс

1
Ні, я не знаю, але ніколи не пам'ятаю, як це зробити, щоб псевдонім - це лише спосіб, щоб я його запам'ятав.
Фредрік Ерландссон

1
Так ... але спробуйте змінити комп’ютери і
забудьте

1
У чому цінність $GIT_PREFIX?
neowulf33

github.com/git/git/blob/… "GIT_PREFIX" встановлюється як повернуто за допомогою запуску "git rev-parse - show-prefix" з початкового поточного каталогу. Дивіться linkgit: git-rev-parse [1].
Фредрік Ерландссон

3

Ця функція буде клонувати віддалений репо в локальний репо-репорт:

function git-add-repo
{
    repo="$1"
    dir="$(echo "$2" | sed 's/\/$//')"
    path="$(pwd)"

    tmp="$(mktemp -d)"
    remote="$(echo "$tmp" | sed 's/\///g'| sed 's/\./_/g')"

    git clone "$repo" "$tmp"
    cd "$tmp"

    git filter-branch --index-filter '
        git ls-files -s |
        sed "s,\t,&'"$dir"'/," |
        GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
        mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
    ' HEAD

    cd "$path"
    git remote add -f "$remote" "file://$tmp/.git"
    git pull "$remote/master"
    git merge --allow-unrelated-histories -m "Merge repo $repo into master" --edit "$remote/master"
    git remote remove "$remote"
    rm -rf "$tmp"
}

Як використовувати:

cd current/package
git-add-repo https://github.com/example/example dir/to/save

Зауважте. Цей скрипт може переписати коміти, але збереже всіх авторів і дат, це означає, що нові коміти матимуть ще один хеш, і якщо ви спробуєте натиснути зміни на віддалений сервер, він зможе лише натискати клавішу примусового клавіші, а також перезапише коміти на сервер. Тому, будь ласка, зробіть резервні копії перед запуском.

Прибуток!


Я використовую zsh, а не bash, і v2.13.0 git. Незалежно від того, що я намагався, я так і не зміг дійти git filter-branch --index-filterдо роботи. Як правило, я отримую повідомлення про помилку про те, що .new індексний файл не існує. Чи дзвонить той дзвінок?
Патрік Борода

@PatrickBeard Я не знаю zsh, ви можете створити відокремлений файл git-add-repo.shз функцією вгорі, в кінці файлу поставити цей рядок git-add-repo "$@". Після цього ви можете використовувати його з zsh like cd current/git/packageіbash path/to/git-add-repo.sh https://github.com/example/example dir/to/save
Андрій Ізман

Проблему обговорювали тут: stackoverflow.com/questions/7798142/… mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE" виходить з ладу іноді, тому вам доведеться додати if test.
Патрік Борода

1
Я б не використовував цей метод! Я спробував сценарій, наївно і дослівно (я можу винуватити себе лише за цю частину), і він закріпив моє місцеве git repo. Історія виглядала здебільшого вірно, але віджимання git назад до Github призвело до того, що "RPC не вдалося; curl 55 SSL_write () повернув SYSCALL, помилка errno = 32". Я намагався його відремонтувати, але він був непоправно зламаний. Мені довелося реконструювати речі в новому місцевому репо.
Мейсон Фрід

@MasonFreed цей скрипт створює нову історію GIT з поєднанням обох РЕПО, тому він не може бути притиснутий до старого репо, він вимагає , щоб створити нову або поштовх з ключем сили, значить переписати репозиторій на сервері
Андрій Izman

2

Виконайте кроки, щоб вставити одне репо в інше репо, маючи одну єдину історію git, об’єднавши обидві історії git.

  1. Клоніруйте обидва репости, які потрібно об’єднати.

git clone git@github.com: user / parent-repo.git

git clone git@github.com: user / child-repo.git

  1. Перейдіть на дитячу репо

cd child-repo /

  1. запустіть команду нижче, замініть шлях my/new/subdir(3 випадки) на структуру каталогів, де ви хочете мати дочірнє репо.

git filter-branch --prune-empty --tree-filter ', якщо [! -е мій / новий / субдір]; тоді mkdir -p my / new / subdir git ls-tree - тільки ім'я $ GIT_COMMIT | xargs -І файли mv-файлів my / new / subdir fi '

  1. Перейдіть на батьківське репо

cd ../parent-repo/

  1. Додайте дистанційне до батьківського репо, вказуючи шлях до дочірнього репо

git remote додати для дітей ../child-repo/

  1. Отримайте дитину репо

git fetch віддалений від дитини

  1. Об’єднайте історії

git merge - дозволити неспоріднену історію для дітей, віддалену / головну

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

Нижче стаття допомогла мені вкласти одне репо в інше репо, маючи одну єдину історію git шляхом об’єднання обох історій git.

http://ericlathrop.com/2014/01/combining-git-repositories/

Сподіваюсь, це допомагає. Щасливе кодування!


Крок 3 не вдався до мене із синтаксичною помилкою. Напівколони відсутні. Виправленняgit filter-branch --prune-empty --tree-filter ' if [ ! -e my/new/subdir ]; then mkdir -p my/new/subdir; git ls-tree --name-only $GIT_COMMIT | xargs -I files mv files my/new/subdir; fi'
Юрій Л

1

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

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

У випадку, якщо ви хочете вставити aу підкаталог, виконайте наступні дії перед командами вище:

cd a
git filter-repo --to-subdirectory-filter a
cd ..

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

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

Детальніше про це тут .

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