Как объединить два репозитория Git?
-
07-07-2019 - |
Вопрос
Рассмотрим следующий сценарий:
Я разработал небольшой экспериментальный проект А в собственном репозитории Git.Теперь он созрел, и я бы хотел, чтобы А стал частью более крупного проекта Б, у которого есть собственный большой репозиторий.Теперь я хотел бы добавить A как подкаталог B.
Как мне объединить A с B, не потеряв историю ни на одной стороне?
Решение
Одна ветвь другого репозитория может быть легко помещена в подкаталог, сохраняя свою историю. Например:
git subtree add --prefix=rails git://github.com/rails/rails.git master
Это будет выглядеть как один коммит, в котором все файлы главной ветки Rails добавляются в " rails " каталог. Однако заголовок коммита содержит ссылку на старое древо истории:
Добавить 'rails /' из commit
<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.
Другие советы
Если вы хотите объединить 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- несвязанные-истории р>
Обновление : добавлено --tags
в соответствии с предложением @jstadler для сохранения тегов.
Вот два возможных решения:
Подмодули
Либо скопируйте хранилище A в отдельный каталог в более крупном проекте B, либо (возможно, лучше) скопируйте хранилище A в подкаталог в проекте B. Затем используйте подмодуль git , чтобы сделать этот репозиторий < strong> субмодуль хранилища B.
Это хорошее решение для слабосвязанных репозиториев, где продолжается разработка в репозитории A, а основная часть разработки - это отдельная автономная разработка в A. См. также 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 subtree ( репозиторий на GitHub ) от apenwarr (Avery Pennarun), анонсированная, например, в его блоге Новая альтернатива подмодулям Git: git subtree .
<Ч>Я думаю, что в вашем случае (A должен быть частью более крупного проекта B) правильным решением было бы использовать слияние поддеревьев .
Подмодульный подход хорош, если вы хотите поддерживать проект отдельно. Тем не менее, если вы действительно хотите объединить оба проекта в одном хранилище, у вас есть немного больше работы.
Первое, что нужно сделать, это использовать 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 р>
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) Затем в B запустите
git pull path/to/A
Вуаля!У тебя projectA
каталог в Б.Если ты бежишь git log projectA
, вы увидите все коммиты из A.
В моем случае мне нужны были два подкаталога: projectA
и projectB
.В этом случае я также выполнил шаг (1) до B.
Если оба хранилища имеют одинаковые типы файлов (например, два хранилища 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
Я продолжал терять историю при использовании слияния, поэтому в итоге я использовал перебазирование, поскольку в моем случае два репозитория достаточно разные, чтобы не сливаться при каждом коммите:
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.
В моем случае у меня был my-plugin
хранилище и main-project
репозиторий, и я хотел притвориться, что my-plugin
всегда разрабатывался в plugins
подкаталог main-project
.
По сути, я переписал историю my-plugin
репозиторий, чтобы казалось, что вся разработка происходила в plugins/my-plugin
подкаталог.Затем я добавил историю развития my-plugin
в main-project
историю и объединил два дерева вместе.Поскольку не было plugins/my-plugin
каталог уже присутствует в main-project
репозитория, это было тривиальное слияние без конфликтов.Полученный репозиторий содержал всю историю обоих исходных проектов и имел два корня.
ТЛ;ДР
$ 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 -c
делать zsh
выполнить команду.Сначала я установил extended_glob
вариант, который позволяет ^(...)
синтаксис в mv
команда, а также glob_dots
опция, которая позволяет мне выбирать точечные файлы (например, .gitignore
) с шариком (^(...)
).
Далее я использую 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
.Ваш пробег может отличаться.
Использованная литература:
- https://git-scm.com/docs/git-filter-branch
- https://unix.stackexchange.com/questions/6393/how-do-you-move-all-files-включая-скрытый-из-одного-каталога-в-другой
- http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
- Очистка файла из репозитория Git не удалась, невозможно создать новую резервную копию
- git, фильтр-ветвь на всех ветках
Я пытался делать то же самое в течение нескольких дней, я использую 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
Если вы сейчас зарегистрируете какой-либо из файлов в подкаталоге A, вы получите полную историю
git log --follow A/<file>
Это был пост, который помог мне сделать это:
Если вы хотите поместить файлы из ветки в репозиторий B в поддерево репо А и также сохраните историю, продолжайте читать.(В приведенном ниже примере я предполагаю, что мы хотим объединить главную ветку репозитория B с основной веткой репо A.)
В репозитории A сначала выполните следующие действия, чтобы сделать репозиторий B доступным:
git remote add B ../B # Add repo B as a new remote.
git fetch B
Теперь мы создаем новую ветку (только с одним коммитом) в репозитории A, которую называем 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
Итоговый график будет иметь такую структуру:
Я знаю, что это долго после факта, но я не был доволен другими ответами, которые я нашел здесь, поэтому я написал это:
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
Я собрал много информации здесь в Stack & nbsp; 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
Сам скрипт содержит множество комментариев, которые должны объяснить, что он делает.
Если вы пытаетесь просто склеить два репозитория, подмодули и слияния поддеревьев - это неправильный инструмент, так как они не сохраняют всю историю файлов (как люди отмечали в других ответах). Посмотрите этот ответ здесь , чтобы узнать, как это сделать просто и правильно.
Аналогично @Smar, но использует пути файловой системы, заданные в PRIMARY и SECONDARY:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
Затем вы выполняете слияние вручную.
(адаптировано из сообщения Анара Манафова а>) р>
У меня была похожая проблема, но в моем случае мы разработали одну версию кодовой базы в репо A, а затем клонировали ее в новый репозиторий B для новой версии продукта. После исправления некоторых ошибок в репо A нам нужно было отправить изменения в репо B. Чтобы закончить, сделайте следующее:
<Ол>Получил удовольствие :)
Если вы хотите объединить три или более проекта в одиночный коммит, выполните действия, описанные в других ответах (remote add -f
, merge
). Затем (мягкая) сбросить индекс на старую голову (где слияние не произошло). Добавьте все файлы (git add -A
) и зафиксируйте их (сообщение & Quot; Объединение проектов A, B, C и D в один проект). Теперь это идентификатор коммита мастера.
Теперь создайте .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
. Не всегда добавляйте это, потому что это может вызвать переписывание некоторых коммитов. Для получения дополнительной информации см. справочную страницу ответвления фильтра , найдите " grafts ".
Теперь с вашим последним коммитом связаны правильные родители.
Чтобы объединить A с B:
1) В проекте A
git fast-export --all --date-order > /tmp/ProjectAExport
2) В проекте B
git checkout -b projectA
git fast-import --force < /tmp/ProjectAExport
В этой ветке выполняйте все необходимые операции и фиксируйте их.
C) Затем вернемся к мастеру и классическому слиянию двух ветвей:
git checkout master
git merge projectA
Слияние 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
Эта функция клонирует удаленное репо в локальный каталог репо, после объединения все коммиты будут сохранены, 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"
Уведомления
Пути заменяются с помощью sed
, поэтому после слияния убедитесь, что они перемещены по правильным путям.
Параметр --allow-unrelated-histories
существует только с git & Gt; = 2.9.
Я слегка объединяю проекты вручную, что позволяет мне избежать необходимости сталкиваться с конфликтами слияния.
сначала скопируйте файлы из другого проекта так, как вы этого хотите.
cp -R myotherproject newdirectory
git add newdirectory
следующий шаг в истории
git fetch path_or_url_to_other_repo
сказать git слиться с историей последней выбранной вещи
echo 'FETCH_HEAD' > .git/MERGE_HEAD
теперь фиксируйте, но вы обычно делаете это
git commit
Данная команда является наилучшим возможным решением, которое я предлагаю.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
Я хотел переместить небольшой проект в подкаталог большего. Поскольку в моем небольшом проекте было мало коммитов, я использовал git format-patch --output-directory /path/to/patch-dir
. Затем в более крупном проекте я использовал git am --directory=dir/in/project /path/to/patch-dir/*
.
Это кажется намного менее пугающим и намного чище, чем ветвь фильтра. Конечно, это может быть применимо не ко всем случаям.