Как объединить два репозитория Git?
Рассмотрим следующий сценарий:
Я разработал небольшой экспериментальный проект A в своем собственном репозитории Git. Сейчас он созрел, и я бы хотел, чтобы A стал частью более крупного проекта B, у которого есть собственный большой репозиторий. Теперь я хотел бы добавить A в качестве подкаталога B.
Как мне слить А в Б, не теряя истории ни с какой стороны?
28 ответов
Одна ветвь другого репозитория может быть легко помещена в подкаталог, сохраняя свою историю. Например:
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.
Если вы хотите объединить 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, чтобы сохранить теги.
Вот два возможных решения:
подмодули
Либо скопируйте репозиторий A в отдельный каталог в более крупном проекте B, либо (возможно, лучше) скопируйте репозиторий A в подкаталог в проекте B. Затем используйте подмодуль git, чтобы сделать этот репозиторий подмодулем репозитория B.
Это хорошее решение для слабосвязанных репозиториев, где продолжается разработка в репозитории A, и основная часть разработки - это отдельная отдельная разработка в A. См. Также страницы SubmoduleSupport и http://git.or.cz/gitwiki/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.
Я думаю, что в вашем случае (А должен быть частью более крупного проекта Б) правильное решение будет использовать слияние поддеревьев.
Подмодульный подход хорош, если вы хотите поддерживать проект отдельно. Однако, если вы действительно хотите объединить оба проекта в одном репозитории, то у вас есть немного больше работы.
Первым делом было бы использовать 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) Затем в Б, запустить
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
Я продолжал терять историю при использовании слияния, поэтому в итоге я использовал 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
В моем случае у меня был 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 -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
Обратите внимание, что в pre-2.9.0 Git, --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-including-hidden-from-one-directory-to-another
- http://www.refining-linux.org/archives/37/ZSH-Gem-2-Extended-globbing-and-expansion/
- Ошибка очистки файла из Git-репозитория, невозможно создать новую резервную копию
- мерзавец, фильтр-ветка на все ветки
Я собрал много информации здесь о 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 containing the URLs to the repositories
## which are to be merged on separate lines.
##
## Author: Robert von Burg
## eitch@eitchnet.ch
##
## Version: 0.2.0
## Created: 2015-06-17
##
################################################################################
#
# Disallow using undefined variables
shopt -s -o nounset
# Script variables
declare SCRIPT_NAME="${0##*/}"
declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"
declare ROOT_DIR="$PWD"
# Detect proper usage
if [ "$#" -ne "2" ] ; then
echo -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"
exit 1
fi
# Script functions
function failed() {
echo -e "ERROR: Merging of projects failed:"
echo -e "$1"
exit 1
}
function commit_merge() {
current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
CHANGES=$(git status | grep "working directory clean")
MERGING=$(git status | grep "merging")
if [[ "$CHANGES" != "" ]] && [[ "$MERGING" == "" ]] ; then
echo -e "INFO: No commit required."
else
echo -e "INFO: Committing ${sub_project}..."
if ! git commit --quiet -m "[Project] Merged branch '$1' of ${sub_project}" ; then
failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
fi
fi
}
## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"
# 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: Creating new git repository ${PROJECT_NAME}..."
echo -e "===================================================="
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 th branches of this project
echo -e "INFO: Merging projects into new repository..."
echo -e "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# Extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"
echo -e "----------------------------------------------------"
# Fetch the project
echo -e "INFO: Fetching ${sub_project}..."
git remote add "${sub_project}" "${url}"
if ! git fetch --no-tags --quiet ${sub_project} 2>/dev/null ; then
failed "Failed to fetch project ${sub_project}"
fi
# Add remote branches
echo -e "INFO: Creating local branches for ${sub_project}..."
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}..."
# Create and checkout new merge branch off of master
git checkout --quiet -b "${sub_project}/${branch_name}" master
git reset --hard --quiet
git clean -d --force --quiet
# Merge the project
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "remotes/${sub_project}/${branch_name}" 2>/dev/null ; 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."
else
echo -e "INFO: Moving files in branch ${branch_name} of ${sub_project} so we have a single directory..."
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" 2>/dev/null ; then
failed "sub_project ${sub_project} is missing master branch!"
fi
# Copy remote tags
echo -e "INFO: Copying tags for ${sub_project}..."
while read tag ; do
tag_ref=$(echo $tag | tr " " "\t" | cut -f 1)
tag_name=$(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%%^*}"
tag_new_name="${sub_project}/${tag_name}"
echo -e "INFO: Copying tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}..."
if ! git tag "${tag_new_name}" "${tag_ref}" 2>/dev/null ; then
echo -e "WARN: Could not copy tag ${tag_name} to ${tag_new_name} for ref ${tag_ref}"
fi
done < <(git ls-remote --tags ${sub_project})
# Remove the remote to the old project
echo -e "INFO: Removing remote ${sub_project}..."
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 "===================================================="
for url in $(cat ${REPO_URL_FILE}) ; do
# extract the name of this project
export sub_project=${url##*/}
sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."
if ! git merge --quiet --no-commit "${sub_project}/master" 2>/dev/null ; 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
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
Сам сценарий имеет много комментариев, которые должны объяснить, что он делает.
Я пытался сделать то же самое в течение нескольких дней, я использую 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 в поддерево хранилища 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-репо, и в то же время мы также сохраняем временную метку первоначальной фиксации.
Теперь мы создадим новый 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
Слияние 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
У меня была похожая проблема, но в моем случае мы разработали одну версию кодовой базы в репо A, а затем клонировали ее в новый репо B для новой версии продукта. После исправления некоторых ошибок в репо А нам нужно было оформить изменения в репо Б. В итоге мы сделали следующее:
- Добавление удаленного к репо B, которое указывало на репо A (git remote add...)
- Вытащить текущую ветку (мы не использовали master для исправления ошибок) (git pull remoteForRepoA bugFixBranch)
- Толкая слияния в github
Работал лакомством:)
Аналогично @Smar, но использует пути к файловой системе, заданные в PRIMARY и SECONDARY:
PRIMARY=~/Code/project1
SECONDARY=~/Code/project2
cd $PRIMARY
git remote add test $SECONDARY && git fetch test
git merge test/master
Затем вы вручную сливаете.
(взято из поста Анара Манафова)
Если вы хотите объединить три или более проектов в один коммит, выполните шаги, описанные в других ответах (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
, Не всегда добавляйте это, потому что это может вызвать переписывание некоторых коммитов. Для получения дополнительной информации см. Справочную страницу фильтра-ветки, ищите "трансплантаты".
Теперь с вашим последним коммитом связаны правильные родители.
Google использует инструмент Copybara для более сложных случаев использования — https://github.com/google/copybara .
https://github.com/hraban/tomono как еще одно упоминание о решении на основе скриптов.
Я не являюсь автором, но использовал его, и он работает.
Одним из положительных моментов является то, что вы получаете все ветви и всю историю в финальное репо. Для моих репозиториев (в репозиториях нет повторяющихся папок - фактически, они возникли в результате миграции tfs2git) конфликтов не было, и все работало автоматически.
В основном используется (см. Название) для создания монорепозиториев.
Для пользователей Windows: git bash может выполнить файл.sh. Он поставляется со стандартной установкой git.
Чтобы объединить А в Б:
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
Я слегка объединяю проекты вручную, что позволяет мне избежать необходимости сталкиваться с конфликтами слияния.
во-первых, скопируйте файлы из другого проекта, как хотите.
cp -R myotherproject newdirectory
git add newdirectory
следующий шаг в истории
git fetch path_or_url_to_other_repo
скажи git слиться с историей последней полученной вещи
echo 'FETCH_HEAD' > .git/MERGE_HEAD
Теперь совершите, однако вы обычно делаете
git commit
Сегодня мне пришлось решить эту проблему следующим образом: проект A был в битбакете, а проект B был в фиксации кода ... оба являются одними и теми же проектами, но должны были объединить изменения от A до B. (Хитрость заключается в создании ветки с тем же именем в Project А, как в Проекте Б)
- git checkout Project A
- git удаленное удаление источника
- git удаленный добавить источник проекта B
- ветка проверки git
- git add *
- git commit -m "мы переместили код"
- git push
Эта функция клонирует удаленное репо в локальный каталог репо, после объединения все коммиты будут сохранены, 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 >= 2.9.
В дополнение ко всем ответам с использованием
remote add
->
fetch
->
merge
стратегия: если вы хотите сохранить теги из другого репозитория, но не хотите переносить их все в общее пространство имен (и, возможно, получить коллизии), вы можете немного изменить команду выборки:
git fetch --no-tags other_repo
git fetch --no-tags other_repo 'refs/tags/*:refs/tags/other_repo/*'
Первая команда извлекает все ветки как обычно, но пропускает теги, прикрепленные к коммитам, вторая также опускает обычный механизм выборки тегов (
git help fetch
для получения дополнительной информации), и извлекает все теги, отображающие их, из
X
к
other_repo/X
используя функциональность git refspec.
Ссылки (ветки, теги) - это просто файлы в git, и вы можете использовать каталоги для размещения имен. Две приведенные выше команды сохранят теги из первого репозитория как есть, а теги из другого будут иметь префикс
other_repo/
После операции лучше всего удалить другой пульт, чтобы случайно не получить теги обычным способом и не навести беспорядок.
Данная команда - лучшее возможное решение, которое я предлагаю.
git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master
Возможно, на этот вопрос уже был дан ответ, но эта статья должна помочь. Я сделал следующую модификацию, когда подписался
Добавить пульт для и получить старый репозиторий git remote add -f old_a
git fetch - все
Слить файлы из old_a / master в new / master git merge old_a / main --allow-unrelated-stories
Настройте этот сценарий оболочки для автоматического объединения двух веток.
Я хотел переместить небольшой проект в подкаталог большего. Поскольку мой небольшой проект не имел много коммитов, я использовал git format-patch --output-directory /path/to/patch-dir
, Тогда на более крупном проекте я использовал git am --directory=dir/in/project /path/to/patch-dir/*
,
Это гораздо менее страшно и намного чище, чем ветвь фильтра. Конечно, это может быть применимо не ко всем случаям.