Как вы объединяете два репозитория Git?

Рассмотрим следующий сценарий:

Я разработал небольшой экспериментальный проект A в своем собственном репозитории Git. Сейчас он созрел, и я бы хотел, чтобы A стал частью более крупного проекта B, у которого есть собственный большой репозиторий. Теперь я хотел бы добавить A в качестве подкаталога B.

Как мне слить А в Б, не теряя истории ни с какой стороны?

15.09.2009 08:31:54
Если вы просто пытаетесь объединить два хранилища в одно без необходимости хранить оба хранилища, взгляните на этот вопрос: stackoverflow.com/questions/13040958/…
Flimm 6.01.2014 16:06:51
Для слияния git-репо с пользовательским каталогом с сохранением всех комитов используйте stackoverflow.com/a/43340714/1772410
Andrey Izman 11.04.2017 11:14:31
22 ОТВЕТА
РЕШЕНИЕ

Одна ветвь другого репозитория может быть легко помещена в подкаталог, сохраняя свою историю. Например:

git subtree add --prefix=rails git://github.com/rails/rails.git master

Это будет выглядеть как единый коммит, в котором все файлы главной ветки Rails будут добавлены в каталог "rails". Однако заголовок коммита содержит ссылку на старое древо истории:

Добавить 'rails /' из коммита <rev>

Где <rev>хеш коммита SHA-1. Вы все еще можете увидеть историю, виноваты некоторые изменения.

git log <rev>
git blame <rev> -- README.md

Обратите внимание, что отсюда не видно префикса каталога, поскольку это старая ветка, оставшаяся без изменений. Вы должны рассматривать это как обычный коммит перемещения файла: вам понадобится дополнительный прыжок при достижении этого.

# finishes with all files added at once commit
git log rails/README.md

# then continue from original tree
git log <rev> -- README.md

Есть более сложные решения, такие как ручная работа или переписывание истории, как описано в других ответах.

Команда git-subtree является частью официального git-contrib, некоторые менеджеры пакетов устанавливают ее по умолчанию (OS X Homebrew). Но вам может потребоваться установить его самостоятельно в дополнение к git.

429
7.05.2018 10:44:36
Вот инструкции по установке Git SubTree (по состоянию на июнь 2013 года): stackoverflow.com/a/11613541/694469 (и я заменил git co v1.7.11.3 на ... v1.8.3).
KajMagnus 7.06.2013 14:31:23
Спасибо за заголовки о нижеследующем ответе. По состоянию на git 1.8.4 «поддерево» до сих пор не включено (по крайней мере, в Ubuntu 12.04 git ppa (ppa: git-core / ppa))
Matt Klein 30.09.2013 02:10:36
Я могу подтвердить, что после этого git log rails/somefileне будет отображаться история коммитов этого файла, кроме коммит слияния. Как предложил @artfulrobot, проверьте ответ Грега Хьюгилла . И вам может понадобиться использовать git filter-branchрепо, который вы хотите включить.
Jifeng Zhang 8.10.2013 12:07:12
Или прочитайте книгу Эрика Ли «Слияние двух репозиториев Git в один репозиторий без потери истории файлов» saintgimp.org/2013/01/22/…
Jifeng Zhang 8.10.2013 12:17:08
Как уже говорили другие, git subtreeвозможно, не делать то, что вы думаете! Смотрите здесь для более полного решения.
Paul Draper 1.02.2014 08:13:18

Подмодульный подход хорош, если вы хотите поддерживать проект отдельно. Однако, если вы действительно хотите объединить оба проекта в одном репозитории, то у вас есть немного больше работы.

Первым делом можно использовать, git filter-branchчтобы переписать имена всего во втором репозитории, чтобы они находились в подкаталоге, где вы хотели бы, чтобы они заканчивались. Поэтому вместо того foo.c, bar.htmlвы бы projb/foo.cи projb/bar.html.

Затем вы должны быть в состоянии сделать что-то вроде следующего:

git remote add projb [wherever]
git pull projb

git pullБудет делать git fetchсопровождаемый git merge. Не должно быть никаких конфликтов, если репозиторий, к которому вы обращаетесь, еще не имеет projb/каталога.

Далее поиск показывает , что нечто подобное было сделано для слияния gitkв git. Джунио С. Хамано пишет об этом здесь: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

194
15.09.2009 08:38:49
объединение поддеревьев было бы лучшим решением и не требовало бы переписывания истории включенного проекта
Jakub Narębski 15.09.2009 09:18:03
Я хотел бы знать, как использовать git filter-branchдля достижения этой цели. На странице руководства говорится об обратном: сделать subdir / стать корнем, но не наоборот.
artfulrobot 1.06.2012 15:11:54
этот ответ был бы
Anentropic 21.01.2013 14:49:57
Я нашел, как использовать фильтр-ответвление здесь: stackoverflow.com/questions/4042816/…
David Minor 10.05.2013 22:18:55
Посмотрите этот ответ для реализации плана Грега.
Paul Draper 1.02.2014 08:11:02

Вот два возможных решения:

подмодули

Либо скопируйте репозиторий A в отдельный каталог в более крупном проекте B, либо (возможно, лучше) скопируйте репозиторий A в подкаталог в проекте B. Затем используйте подмодуль git, чтобы сделать этот репозиторий подмодулем репозитория B.

Это хорошее решение для слабо связанных хранилищ, где развитие в хранилище А продолжается, и основная часть развития является отдельной автономной разработки в области А. Смотрите также SubmoduleSupport и GitSubmoduleTutorial страниц на Git Wiki.

Поддерево слияния

Вы можете объединить хранилище A в подкаталог проекта B, используя стратегию объединения поддеревьев . Это описано в « Слиянии поддеревьев и вас » Маркуса Принца.

git remote add -f Bproject /path/to/B
git merge -s ours --allow-unrelated-histories --no-commit Bproject/master
git read-tree --prefix=dir-B/ -u Bproject/master
git commit -m "Merge B project as our subdirectory"
git pull -s subtree Bproject master

(Опция --allow-unrelated-historiesнеобходима для Git> = 2.9.0.)

Или вы можете использовать инструмент поддерева git ( репозиторий в GitHub ) от apenwarr (Avery Pennarun), о котором было объявлено, например, в его блоге . Новая альтернатива подмодулям Git: поддерево git .


Я думаю, что в вашем случае (A должен быть частью более крупного проекта B) правильное решение было бы использовать объединение поддеревьев .

612
3.08.2018 12:17:50
Это работает и, кажется, сохраняет историю, но не так, чтобы вы могли использовать ее для сравнения файлов или деления пополам слияния. Я пропускаю шаг?
jettero 7.05.2012 12:44:51
это является неполным . Да, вы получаете множество коммитов, но они больше не ссылаются на правильные пути. git log dir-B/somefileне покажет ничего, кроме одного слияния. См . Ответ Грега Хьюгилла на этот важный вопрос.
artfulrobot 1.06.2012 14:52:14
ВАЖНО: git pull --no-rebase -s поддерево Bproject master Если вы этого не сделаете, и у вас есть параметр pull, настроенный для автоматической перебазировки, вы получите «Не удалось проанализировать объект». См. Osdir.com/ml/git/2009-07/msg01576.html
Eric Bowman - abstracto - 16.09.2012 14:35:17
Этот ответ может вводить в заблуждение, поскольку в качестве объединенного поддерева у него есть B, когда в вопросе это было A. Результат копирования и вставки?
vfclists 20.09.2012 11:32:58
Если вы пытаетесь просто склеить два репозитория, подмодули и слияния поддеревьев - это неправильный инструмент, поскольку они не сохраняют всю историю файлов (как отметили другие комментаторы). См. Stackoverflow.com/questions/13040958/… .
Eric Lee 23.01.2013 00:06:13

Если оба репозитория имеют одинаковые типы файлов (например, два репозитория Rails для разных проектов), вы можете извлечь данные из вторичного репозитория в ваш текущий репозиторий:

git fetch git://repository.url/repo.git master:branch_name

а затем объединить его с текущим хранилищем:

git merge --allow-unrelated-histories branch_name

Если ваша версия Git меньше 2.9, удалите --allow-unrelated-histories.

После этого могут возникнуть конфликты. Вы можете решить их, например, с git mergetool. kdiff3может использоваться исключительно с клавиатурой, поэтому 5 конфликтных файлов занимают при чтении кода всего несколько минут.

Не забудьте завершить слияние:

git commit
48
19.01.2018 13:05:55

Аналогично @Smar, но использует пути файловой системы, заданные в PRIMARY и SECONDARY:

PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master

Затем вы вручную сливаете.

(взято из поста Анара Манафова )

5
12.08.2011 15:15:48

У меня была похожая проблема, но в моем случае мы разработали одну версию кодовой базы в репо A, а затем клонировали ее в новый репо B для новой версии продукта. После исправления некоторых ошибок в репо А нам нужно было оформить изменения в репо Б. Закончилось следующим:

  1. Добавление удаленного к репо B, которое указывало на репо A (git remote add ...)
  2. Извлечение текущей ветки (мы не использовали мастер для исправления ошибок) (git pull remoteForRepoA bugFixBranch)
  3. Толкая слияния в github

Работал лакомством :)

6
18.11.2011 19:36:17

Я знаю, что это долго после факта, но я не был доволен другими ответами, которые я нашел здесь, поэтому я написал это:

me=$(basename $0)

TMP=$(mktemp -d /tmp/$me.XXXXXXXX)
echo 
echo "building new repo in $TMP"
echo
sleep 1

set -e

cd $TMP
mkdir new-repo
cd new-repo
    git init
    cd ..

x=0
while [ -n "$1" ]; do
    repo="$1"; shift
    git clone "$repo"
    dirname=$(basename $repo | sed -e 's/\s/-/g')
    if [[ $dirname =~ ^git:.*\.git$ ]]; then
        dirname=$(echo $dirname | sed s/.git$//)
    fi

    cd $dirname
        git remote rm origin
        git filter-branch --tree-filter \
            "(mkdir -p $dirname; find . -maxdepth 1 ! -name . ! -name .git ! -name $dirname -exec mv {} $dirname/ \;)"
        cd ..

    cd new-repo
        git pull --no-commit ../$dirname
        [ $x -gt 0 ] && git commit -m "merge made by $me"
        cd ..

    x=$(( x + 1 ))
done
7
7.05.2012 13:38:36
Это было именно то, что я искал. Спасибо! Тем не менее, мне пришлось изменить строку 22 на:if [[ $dirname =~ ^.*\.git$ ]]; then
heyman 9.01.2013 15:02:24
^. * blarg $ расточительно жадный RE. Лучше сказать .blarg $ и пропустить передний якорь.
jettero 27.01.2013 17:57:06

Если вы хотите слить project-aв project-b:

cd path/to/project-b
git remote add project-a path/to/project-a
git fetch project-a --tags
git merge --allow-unrelated-histories project-a/master # or whichever branch you want to merge
git remote remove project-a

Взято из: git, объединить разные репозитории?

Этот метод работал очень хорошо для меня, он короче и, на мой взгляд, намного чище.

Примечание:--allow-unrelated-histories параметр существует только с Git> = 2.9. Смотрите Git - Документация по git merge / --allow-unrelated-history

Обновление : добавлено --tagsв соответствии с предложением @jstadler для сохранения тегов.

1879
2.12.2018 18:06:11
Это сделало бизнес для меня. Впервые сработал как шарм с одним конфликтом в файле .gitignore! Это прекрасно сохранило историю коммитов. Большим плюсом по сравнению с другими подходами - в дополнение к простоте - является то, что при этом не требуется постоянной ссылки на объединенное репо. Однако следует обратить внимание на одну вещь - если вы такой же разработчик для iOS, как я, - это быть очень осторожным, чтобы поместить проектный файл целевого репо в рабочее пространство.
Max MacLeod 26.06.2015 08:31:28
Спасибо. Работал на меня. Мне нужно было переместить объединенный каталог в подпапку, поэтому после выполнения описанных выше шагов я просто использовалgit mv source-dir/ dest/new-source-dir
Sid 1.02.2016 20:33:01
git mergeШаг терпит неудачу здесь с fatal: refusing to merge unrelated histories; --allow-unrelated-historiesисправляет это, как описано в документации .
ssc 22.07.2016 13:14:29
--allow-unrelated-historiesбыл введен в git 2.9 . В более ранних версиях это было поведение по умолчанию.
Douglas Royds 7.12.2016 23:31:23
Короче: git fetch /path/to/project-a master; git merge --allow-unrelated-histories FETCH_HEAD.
jthill 9.01.2017 16:49:08

Если вы пытаетесь просто склеить два репозитория, подмодули и слияния поддеревьев - это неправильный инструмент, поскольку они не сохраняют всю историю файлов (как люди отмечали в других ответах). Смотрите этот ответ здесь для простого и правильного способа сделать это.

7
23.05.2017 12:34:51
Ваше решение хорошо работает только для нового хранилища, но как насчет того, чтобы объединить хранилище в другом с конфликтами файлов?
Andrey Izman 11.04.2017 05:22:07

Если вы хотите объединить три или более проектов в один коммит, выполните шаги, описанные в других ответах ( remote add -f, merge). Затем (мягкая) сбросить индекс на старую голову (где слияние не произошло). Добавьте все файлы ( git add -A) и зафиксируйте их (сообщение «Объединение проектов A, B, C и D в один проект). Теперь это идентификатор фиксации master.

Теперь создайте .git/info/graftsсо следующим содержанием:

<commit-id of master> <list of commit ids of all parents>

Беги git filter-branch -- head^..head head^2..head head^3..head. Если у вас более трех веток, просто добавьте столько, head^n..headсколько у вас есть ветвей. Чтобы обновить теги, добавьте --tag-name-filter cat. Не всегда добавляйте это, потому что это может вызвать переписывание некоторых коммитов. Для получения дополнительной информации см. Справочную страницу фильтра-ветки , ищите «трансплантаты».

Теперь с вашим последним коммитом связаны правильные родители.

4
9.05.2013 23:22:01
Подождите, почему вы хотите объединить три проекта в один коммит?
Steve Bennett 17.07.2013 23:59:52
Я начал с репозитория, клиента-репозитория и разработчика моделей как отдельных проектов git. Это было сложно для коллег, поэтому я присоединился к ним в одном проекте Git. Чтобы иметь возможность, что «корень» нового проекта происходит от трех других проектов, я хотел иметь один коммит слияния.
koppor 18.07.2013 15:03:25

git-subtree это хорошо, но это, вероятно, не тот, который вы хотите.

Например, если projectAкаталог, созданный в B, после git subtree,

git log projectA

перечисляет только один коммит: слияние. Коммиты из объединенного проекта предназначены для разных путей, поэтому они не отображаются.

Ответ Грега Хьюгилла наиболее близок, хотя на самом деле он не говорит, как переписать пути.


Решение на удивление простое.

(1) В А

PREFIX=projectA #adjust this

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

Примечание: это переписывает историю, поэтому, если вы намереваетесь продолжать использовать этот репозиторий A, вы можете сначала скопировать (скопировать) его одноразовую копию.

(2) Затем в Б, запустить

git pull path/to/A

Вуаля! У вас есть projectAкаталог в B. Если вы запустите git log projectA, вы увидите все коммиты из A.


В моем случае я хотел две подкаталоги, projectAи projectB. В этом случае я сделал шаг (1) для B, а также.

74
1.08.2017 15:06:06
Похоже, вы скопировали свой ответ из stackoverflow.com/a/618113/586086 ?
Andrew Mao 1.04.2014 21:47:43
@ AndrewMao, я так думаю ... Я не могу вспомнить. Я использовал этот скрипт совсем немного.
Paul Draper 1.04.2014 22:38:46
Я бы добавил, что \ t не работает в OS X, и вы должны войти в <tab>
Muneeb Ali 24.12.2014 21:56:07
"$GIT_INDEX_FILE"должен быть заключен в кавычки (дважды), иначе ваш метод потерпит неудачу, если, например, путь содержит пробелы.
Rob W 17.01.2015 14:43:37
Если вам интересно, чтобы вставить <tab> в osx, вам нужноCtrl-V <tab>
casey 19.07.2015 09:51:27

Я продолжал терять историю при использовании слияния, поэтому в итоге я использовал rebase, поскольку в моем случае два репозитория достаточно различны, чтобы не заканчиваться слиянием при каждом коммите:

git clone git@gitorious/projA.git projA
git clone git@gitorious/projB.git projB

cd projB
git remote add projA ../projA/
git fetch projA 
git rebase projA/master HEAD

=> разрешать конфликты, а затем продолжать столько раз, сколько необходимо ...

git rebase --continue

Это приводит к тому, что один проект имеет все коммиты от projA, за которыми следуют коммиты от projB

25
27.02.2014 03:09:17

Я собрал много информации здесь о Stack OverFlow и т. Д., И мне удалось собрать сценарий, который решает проблему для меня.

Предостережение заключается в том, что он учитывает только ветвь 'разработка' каждого репозитория и объединяет ее в отдельный каталог в совершенно новом репозитории.

Теги и другие ветки игнорируются - это может быть не то, что вы хотите.

Скрипт даже обрабатывает функциональные ветви и теги - переименовывая их в новом проекте, чтобы вы знали, откуда они пришли.

#!/bin/bash
#
################################################################################
## Script to merge multiple git repositories into a new repository
## - The new repository will contain a folder for every merged repository
## - The script adds remotes for every project and then merges in every branch
##   and tag. These are renamed to have the origin project name as a prefix
##
## Usage: mergeGitRepositories.sh <new_project> <my_repo_urls.lst>
## - where <new_project> is the name of the new project to create
## - and <my_repo_urls.lst> is a file contaning the URLs to the respositories
##   which are to be merged on separate lines.
##
## Author: Robert von Burg
##            eitch@eitchnet.ch
##
## Version: 0.3.2
## Created: 2018-02-05
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
IFS=$'\n'

# Detect proper usage
if [ "$#" -ne "2" ] ; then
  echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
  exit 1
fi


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
TIMESTAMP="$(date +%s)"
LOG_FILE="${ROOT_DIR}/${PROJECT_NAME}_merge.${TIMESTAMP}.log"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1
  echo -e "$1"
  exit 1
}

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  if [[ ! -f ".git/MERGE_HEAD" ]] ; then
    echo -e "INFO:   No commit required."
    echo -e "INFO:   No commit required." >>${LOG_FILE} 2>&1
  else
    echo -e "INFO:   Committing ${sub_project}..."
    echo -e "INFO:   Committing ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


# Make sure the REPO_URL_FILE exists
if [ ! -e "${REPO_URL_FILE}" ] ; then
  echo -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"
  exit 1
fi


# Make sure the required directories don't exist
if [ -e "${PROJECT_PATH}" ] ; then
  echo -e "ERROR: Project ${PROJECT_NAME} already exists!"
  exit 1
fi


# create the new project
echo -e "INFO: Logging to ${LOG_FILE}"
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..."
echo -e "INFO: Creating new git repository ${PROJECT_NAME}..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
cd ${ROOT_DIR}
mkdir ${PROJECT_NAME}
cd ${PROJECT_NAME}
git init
echo "Initial Commit" > initial_commit
# Since this is a new repository we need to have at least one commit
# thus were we create temporary file, but we delete it again.
# Deleting it guarantees we don't have conflicts later when merging
git add initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
git rm --quiet initial_commit
git commit --quiet -m "[Project] Initial Master Repo Commit"
echo


# Merge all projects into the branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ "${url:0:1}" == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO: Project ${sub_project}"
  echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1
  echo -e "----------------------------------------------------"
  echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1

  # Fetch the project
  echo -e "INFO:   Fetching ${sub_project}..."
  echo -e "INFO:   Fetching ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote add "${sub_project}" "${url}"
  if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; then
    failed "Failed to fetch project ${sub_project}"
  fi

  # add remote branches
  echo -e "INFO:   Creating local branches for ${sub_project}..."
  echo -e "INFO:   Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read branch ; do
    branch_ref=$(echo $branch | tr " " "\t" | cut -f 1)
    branch_name=$(echo $branch | tr " " "\t" | cut -f 2 | cut -d / -f 3-)

    echo -e "INFO:   Creating branch ${branch_name}..."
    echo -e "INFO:   Creating branch ${branch_name}..." >>${LOG_FILE} 2>&1

    # create and checkout new merge branch off of master
    if ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fi
    if ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
    if ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi

    # Merge the project
    echo -e "INFO:   Merging ${sub_project}..."
    echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
    if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; then
      failed "Failed to merge branch 'remotes/${sub_project}/${branch_name}' from ${sub_project}"
    fi

    # And now see if we need to commit (maybe there was a merge)
    commit_merge "${sub_project}/${branch_name}"

    # relocate projects files into own directory
    if [ "$(ls)" == "${sub_project}" ] ; then
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level."
      echo -e "WARN:   Not moving files in branch ${branch_name} of ${sub_project} as already only one root level." >>${LOG_FILE} 2>&1
    else
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
      echo -e "INFO:   Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..." >>${LOG_FILE} 2>&1
      mkdir ${sub_project}
      for f in $(ls -a) ; do
        if  [[ "$f" == "${sub_project}" ]] ||
            [[ "$f" == "." ]] ||
            [[ "$f" == ".." ]] ; then
          continue
        fi
        git mv -k "$f" "${sub_project}/"
      done

      # commit the moving
      if ! git commit --quiet -m  "[Project] Move ${sub_project} files into sub directory" ; then
        failed "Failed to commit moving of ${sub_project} files into sub directory"
      fi
    fi
    echo
  done < <(git ls-remote --heads ${sub_project})


  # checkout master of sub probject
  if ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "sub_project ${sub_project} is missing master branch!"
  fi

  # copy remote tags
  echo -e "INFO:   Copying tags for ${sub_project}..."
  echo -e "INFO:   Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1
  while read tag ; do
    tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
    tag_name_unfixed=$(echo $tag | tr " " "\t" | cut -f 2 | cut -d / -f 3)

    # hack for broken tag names where they are like 1.2.0^{} instead of just 1.2.0
    tag_name="${tag_name_unfixed%%^*}"

    tag_new_name="${sub_project}/${tag_name}"
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..."
    echo -e "INFO:     Copying tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}..." >>${LOG_FILE} 2>&1
    if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; then
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}"
      echo -e "WARN:     Could not copy tag ${tag_name_unfixed} to ${tag_new_name} for ref ${tag_ref}" >>${LOG_FILE} 2>&1
    fi
  done < <(git ls-remote --tags --refs ${sub_project})

  # Remove the remote to the old project
  echo -e "INFO:   Removing remote ${sub_project}..."
  echo -e "INFO:   Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1
  git remote rm ${sub_project}

  echo
done


# Now merge all project master branches into new master
git checkout --quiet master
echo -e "INFO: Merging projects master branches into new repository..."
echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1
echo -e "===================================================="
echo -e "====================================================" >>${LOG_FILE} 2>&1
for url in $(cat ${REPO_URL_FILE}) ; do

  if [[ ${url:0:1} == '#' ]] ; then
    continue
  fi

  # extract the name of this project
  export sub_project=${url##*/}
  sub_project=${sub_project%*.git}

  echo -e "INFO:   Merging ${sub_project}..."
  echo -e "INFO:   Merging ${sub_project}..." >>${LOG_FILE} 2>&1
  if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

  # And now see if we need to commit (maybe there was a merge)
  commit_merge "${sub_project}/master"

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo -e "INFO: Done." >>${LOG_FILE} 2>&1
echo

exit 0

Вы также можете получить его на http://paste.ubuntu.com/11732805

Сначала создайте файл с URL-адресом для каждого хранилища, например:

git@github.com:eitchnet/ch.eitchnet.parent.git
git@github.com:eitchnet/ch.eitchnet.utils.git
git@github.com:eitchnet/ch.eitchnet.privilege.git

Затем вызовите скрипт с указанием имени проекта и пути к скрипту:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

Сам сценарий имеет много комментариев, которые должны объяснить, что он делает.

8
21.01.2019 09:31:34
Вместо того, чтобы направлять читателей к ответу, пожалуйста, опубликуйте ответ здесь (иначе отредактируйте то, что вы сказали в этом комментарии в этот ответ).
josliber♦ 11.06.2015 14:12:42
Конечно, просто подумал, что лучше не повторяться ... =)
eitch 13.06.2015 15:16:15
Если вы думаете, что этот вопрос идентичен другому, вы можете пометить его как дубликат, используя ссылку «flag» под самим вопросом и указав другой вопрос. Если это не повторяющийся вопрос, но вы думаете, что для решения обеих проблем можно использовать один и тот же ответ, просто опубликуйте один и тот же ответ на обе проблемы (как вы уже сделали). Спасибо за помощь!
josliber♦ 13.06.2015 15:29:10
Удивительно! Не работало в командной строке Windows bash, но оно работало безупречно из Vagrant box, работающего под управлением Ubuntu. Какая экономия времени!
xverges 1.06.2017 16:42:42

Чтобы объединить А в Б:

1) В проекте А

git fast-export --all --date-order > /tmp/ProjectAExport

2) В проекте Б

git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport

В этой ветке делайте все необходимые операции и фиксируйте их.

C) Затем вернемся к мастеру и классическому слиянию двух ветвей:

git checkout master
git merge projectA
4
28.07.2015 16:47:33

Данная команда - лучшее возможное решение, которое я предлагаю.

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
1
24.11.2015 05:47:32

Я пытался сделать то же самое в течение нескольких дней, я использую git 2.7.2. Поддерево не сохраняет историю.

Вы можете использовать этот метод, если вы не будете использовать старый проект снова.

Я бы посоветовал вам сначала ветку B и работать в ветке.

Вот шаги без ветвления:

cd B

# You are going to merge A into B, so first move all of B's files into a sub dir
mkdir B

# Move all files to B, till there is nothing in the dir but .git and B
git mv <files> B

git add .

git commit -m "Moving content of project B in preparation for merge from A"


# Now merge A into B
git remote add -f A <A repo url>

git merge A/<branch>

mkdir A

# move all the files into subdir A, excluding .git
git mv <files> A

git commit -m "Moved A into subdir"


# Move B's files back to root    
git mv B/* ./

rm -rf B

git commit -m "Reset B to original state"

git push

Если вы теперь войдете в любой из файлов в subdir A, вы получите полную историю

git log --follow A/<file>

Этот пост помог мне сделать это:

http://saintgimp.org/2013/01/22/merging-two-git-repositories-into-one-repository-without-losing-file-history/

9
10.03.2016 12:51:08

В моем случае у меня был my-pluginрепозиторий и main-projectрепозиторий, и я хотел сделать вид, что my-pluginон всегда разрабатывался в pluginsподкаталоге main-project.

По сути, я переписал историю my-pluginрепозитория так, чтобы казалось, что вся разработка происходила в plugins/my-pluginподкаталоге. Затем я добавил историю развития my-pluginв main-projectисторию и объединил два дерева. Поскольку plugins/my-pluginв main-projectхранилище еще не было каталога , это было тривиальное слияние без конфликтов. Получившийся репозиторий содержал всю историю обоих исходных проектов и имел два корня.

TL; DR

$ cp -R my-plugin my-plugin-dirty
$ cd my-plugin-dirty
$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all
$ cd ../main-project
$ git checkout master
$ git remote add --fetch my-plugin ../my-plugin-dirty
$ git merge my-plugin/master --allow-unrelated-histories
$ cd ..
$ rm -rf my-plugin-dirty

Длинная версия

Сначала создайте копию my-pluginхранилища, потому что мы собираемся переписать историю этого хранилища.

Теперь перейдите к корню my-pluginхранилища, проверьте свою основную ветку (возможно master) и выполните следующую команду. Конечно, вы должны заменить my-pluginи pluginsкакими бы ни были ваши настоящие имена.

$ git filter-branch -f --tree-filter "zsh -c 'setopt extended_glob && setopt glob_dots && mkdir -p plugins/my-plugin && (mv ^(.git|plugins) plugins/my-plugin || true)'" -- --all

Теперь для объяснения. git filter-branch --tree-filter (...) HEADзапускает (...)команду при каждом доступном коммите HEAD. Обратите внимание, что это работает непосредственно с данными, хранящимися для каждого коммита, поэтому нам не нужно беспокоиться о понятиях «рабочий каталог», «индекс», «подготовка» и так далее.

Если вы запустите filter-branchкоманду, которая потерпит неудачу, она оставит после себя некоторые файлы в .gitкаталоге, и при следующей попытке filter-branchона будет жаловаться на это, если вы не укажете эту -fопцию filter-branch.

Что касается самой команды, мне не особо повезло, когда я сделал bashто, что хотел, поэтому вместо этого я использую команду zsh -cmake zsh. Сначала я установил extended_globопцию, которая включает ^(...)синтаксис в mvкоманде, а также glob_dotsопцию, которая позволяет мне выбирать точечные файлы (такие как .gitignore) с помощью glob ( ^(...)).

Далее я использую mkdir -pкоманду для создания одновременно pluginsи plugins/my-pluginодновременно.

Наконец, я использую zshфункцию «негативный глобус», ^(.git|plugins)чтобы сопоставить все файлы в корневом каталоге репозитория, кроме .gitи только что созданной my-pluginпапки. (Исключение здесь .gitможет и не понадобиться, но попытка переместить каталог в себя является ошибкой.)

В моем репозитории начальная фиксация не включала никаких файлов, поэтому mvкоманда вернула ошибку при первоначальной фиксации (поскольку ничего не было доступно для перемещения). Поэтому я добавил || trueтак, что git filter-branchбы не прерывать.

--allВариант говорит filter-branchпереписать историю для всех ветвей в хранилище, а дополнительный --надо сказать , gitчтобы интерпретировать его как часть списка опций для филиалов переписывать, а в качестве опции к filter-branchсебе.

Теперь перейдите к своему main-projectхранилищу и выберите ветку, в которую вы хотите объединиться. Добавьте свою локальную копию my-pluginхранилища (с измененной историей) в качестве удаленного main-projectс:

$ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

Теперь у вас будет два несвязанных дерева в истории коммитов, которые вы можете визуально визуализировать, используя:

$ git log --color --graph --decorate --all

Чтобы объединить их, используйте:

$ git merge my-plugin/master --allow-unrelated-histories

Обратите внимание, что в Git до 2.9.0 эта --allow-unrelated-historiesопция не существует. Если вы используете одну из этих версий, просто опустите опцию: --allow-unrelated-historiesпредупреждающее сообщение об ошибке также было добавлено в 2.9.0.

Вы не должны иметь никаких конфликтов слияния. Если вы это сделаете, это, вероятно, означает, что либо filter-branchкоманда работала некорректно, либо в ней уже был plugins/my-pluginкаталог main-project.

Обязательно введите пояснительное сообщение о коммите для всех будущих участников, задающихся вопросом, что за хакерство собиралось сделать хранилище с двумя корнями.

Вы можете визуализировать новый граф коммитов, который должен иметь два корневых коммита, используя приведенную выше git logкоманду. Обратите внимание, что будет объединена только masterветка . Это означает, что если у вас есть важная работа над другими my-pluginветвями, которые вы хотите объединить в main-projectдерево, вы должны воздерживаться от удаления my-pluginудаленного, пока вы не выполните эти слияния. Если вы этого не сделаете, то коммиты из этих веток все еще будут в main-projectрепозитории, но некоторые будут недоступны и подвержены возможной сборке мусора. (Кроме того, вам придется ссылаться на них по SHA, потому что удаление удаленного удаляет его ветви удаленного отслеживания.)

По желанию, после того, как вы объединили все, что хотите сохранить my-plugin, вы можете удалить my-pluginпульт, используя:

$ git remote remove my-plugin

Теперь вы можете безопасно удалить копию my-pluginрепозитория, историю которого вы изменили. В моем случае я также добавил уведомление об устаревании в реальный my-pluginрепозиторий после того, как слияние было завершено и отправлено.


Протестировано на Mac OS X El Capitan с git --version 2.9.0и zsh --version 5.2. Ваш пробег может варьироваться.

Ссылки:

25
26.10.2018 20:55:56
Откуда берутся --allow-unrelated-histories?
xpto 7.09.2016 13:38:52
@MarceloFilho Проверьте man git-merge. По умолчанию команда git merge отказывается объединять истории, которые не имеют общего предка. Эта опция может использоваться для отмены этой безопасности при объединении историй двух проектов, которые начали свою жизнь независимо. Поскольку это очень редкий случай, никакая переменная конфигурации для включения этого по умолчанию не существует и не будет добавлена.
Radon Rosborough 7.09.2016 15:18:01
Должны быть доступны на git version 2.7.2.windows.1?
xpto 7.09.2016 15:26:28
@MarceloFilho Это было добавлено в 2.9.0, но в более старых версиях вам не нужно было пропускать эту опцию (она просто будет работать). github.com/git/git/blob/…
Radon Rosborough 7.09.2016 15:34:09
Это сработало хорошо. И я смог использовать ветку фильтра, чтобы переписать имена файлов туда, где я хотел в дереве до слияния. Я полагаю, что требуется больше работы, если вам нужно перенести историю помимо основной ветки.
codeDr 15.09.2016 20:23:48

Эта функция клонирует удаленное репо в локальный каталог репо, после объединения все коммиты будут сохранены, git logбудут показаны исходные коммиты и правильные пути:

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

Если внести небольшие изменения, вы даже можете переместить файлы / каталоги объединенного репо по разным путям, например:

repo="https://github.com/example/example"
path="$(pwd)"

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

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

GIT_ADD_STORED=""

function git-mv-store
{
    from="$(echo "$1" | sed 's/\./\\./')"
    to="$(echo "$2" | sed 's/\./\\./')"

    GIT_ADD_STORED+='s,\t'"$from"',\t'"$to"',;'
}

# NOTICE! This paths used for example! Use yours instead!
git-mv-store 'public/index.php' 'public/admin.php'
git-mv-store 'public/data' 'public/x/_data'
git-mv-store 'public/.htaccess' '.htaccess'
git-mv-store 'core/config' 'config/config'
git-mv-store 'core/defines.php' 'defines/defines.php'
git-mv-store 'README.md' 'doc/README.md'
git-mv-store '.gitignore' 'unneeded/.gitignore'

git filter-branch --index-filter '
    git ls-files -s |
    sed "'"$GIT_ADD_STORED"'" |
    GIT_INDEX_FILE="$GIT_INDEX_FILE.new" git update-index --index-info &&
    mv "$GIT_INDEX_FILE.new" "$GIT_INDEX_FILE"
' HEAD

GIT_ADD_STORED=""

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"

Notices
Paths заменяется на via sed, поэтому после слияния убедитесь, что он перемещен по правильным путям. Параметр существует только с Git> = 2.9.
--allow-unrelated-histories

2
11.04.2017 08:47:56

Слияние 2 репо

git clone ssh://<project-repo> project1
cd project1
git remote add -f project2 project2
git merge --allow-unrelated-histories project2/master
git remote rm project2

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master
5
14.08.2017 14:02:01

Я слегка объединяю проекты вручную, что позволяет мне избежать необходимости сталкиваться с конфликтами слияния.

во-первых, скопируйте файлы из другого проекта, как хотите.

cp -R myotherproject newdirectory
git add newdirectory

следующий шаг в истории

git fetch path_or_url_to_other_repo

скажи git слиться с историей последней полученной вещи

echo 'FETCH_HEAD' > .git/MERGE_HEAD

Теперь совершите, однако вы обычно делаете

git commit
1
15.02.2018 17:38:07

Если вы хотите поместить файлы из ветви в хранилище B в поддерево хранилища A, а также сохранить историю, продолжайте чтение. (В приведенном ниже примере я предполагаю, что мы хотим, чтобы основная ветвь репо B была объединена с основной ветвью репо А.)

В репо А сначала сделайте следующее, чтобы сделать репо В доступным:

git remote add B ../B # Add repo B as a new remote.
git fetch B

Теперь мы создаем совершенно новую ветку (только с одним коммитом) в репо А, который мы называем new_b_root. Полученный коммит будет иметь файлы, которые были зафиксированы в первом коммите главной ветви репозитория B, но помещены в подкаталог с именем path/to/b-files/.

git checkout --orphan new_b_root master
git rm -rf . # Remove all files.
git cherry-pick -n `git rev-list --max-parents=0 B/master`
mkdir -p path/to/b-files
git mv README path/to/b-files/
git commit --date="$(git log --format='%ai' $(git rev-list --max-parents=0 B/master))"

Объяснение: --orphanОпция команды checkout извлекает файлы из главной ветви A, но не создает никакого коммита. Мы могли бы выбрать любой коммит, потому что затем мы все равно очищаем все файлы. Затем, еще не фиксируя ( -n), мы выбираем первый коммит из основной ветви B. (Вишня выбирает исходное сообщение фиксации, которое, похоже, не делает прямая проверка.) Затем мы создаем поддерево, в которое мы хотим поместить все файлы из репозитория B. Затем мы должны переместить все файлы, которые были введены в вишня к поддереву. В приведенном выше примере есть только READMEфайл для перемещения. Затем мы фиксируем нашу корневую фиксацию B-repo, и в то же время мы также сохраняем временную метку первоначальной фиксации.

Теперь мы создадим новую B/masterветку поверх вновь созданной new_b_root. Мы называем новый филиал b:

git checkout -b b B/master
git rebase -s recursive -Xsubtree=path/to/b-files/ new_b_root

Теперь мы объединяем нашу bветку в A/master:

git checkout master
git merge --allow-unrelated-histories --no-commit b
git commit -m 'Merge repo B into repo A.'

Наконец, вы можете удалить Bудаленную и временную ветки:

git remote remove B
git branch -D new_b_root b

Конечный график будет иметь такую ​​структуру:

enter image description here

8
5.05.2018 16:44:28
Отличный ответ, спасибо! Я действительно пропустил в других ответах «git subtree» или «merge --allow-unrelated-history» от Andresch Serj, что в подкаталоге не было журнала.
Ilendir 22.05.2018 10:00:04

Я хотел переместить небольшой проект в подкаталог большего. Поскольку мой небольшой проект не имел много коммитов, я использовал git format-patch --output-directory /path/to/patch-dir. Тогда на более крупном проекте я использовал git am --directory=dir/in/project /path/to/patch-dir/*.

Это чувствует себя способ менее страшно и способ более чище , чем фильтр-отрасли. Конечно, это может быть применимо не ко всем случаям.

0
14.12.2018 21:57:32