Как вы объедините два хранилища Git?

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

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

Как объединить A в B, не теряя историю на любой стороне?

Ответы

Ответ 1

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

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.

Ответ 2

Если вы хотите объединить 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 для сохранения тегов.

Ответ 3

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

подмодули

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

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

Слияние субтрий

Вы можете объединить репозиторий A в подкаталог проекта B, используя стратегию слияния поддерева. Это описано в Subtree Merging and You by Markus Prinz.

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

(Для Git> = 2.9.0 требуется опция - --allow-unrelated-histories).

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


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

Ответ 4

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

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

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

git remote add projb [wherever]
git pull projb

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

Дальнейший поиск указывает, что что-то подобное было сделано для объединения gitk в git. Junio ​​C Hamano пишет об этом здесь: http://www.mail-archive.com/[email protected]/msg03395.html

Ответ 5

git-subtree хорош, но, вероятно, это не тот, который вам нужен.

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

git log projectA

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

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


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

(1) В A,

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

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


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

Ответ 6

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

  git fetch git://repository.url/repo.git master: имя_файла
Код>

а затем объединить его в текущий репозиторий:

  git merge --allow-unrelated-historys branch_name
Код>

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

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

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

 <код> git commit
Код>

Ответ 7

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

git clone [email protected]/projA.git projA
git clone [email protected]/projB.git projB

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

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

git rebase --continue

Выполнение этого приводит к одному проекту, имеющему все фиксации из projA, за которым следуют коммиты из projB

Ответ 8

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

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

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, который позволяет мне выбирать dotfiles (например .gitignore) с помощью glob (^(...)).

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

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

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

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

Теперь перейдите в свой main-project репозиторий main-project и проверьте, в какую ветвь вы хотите объединиться. Добавьте локальную копию my-plugin репозитория 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 plugins/my-plugin main-project.

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

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

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

$ git remote remove my-plugin

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


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

Рекомендации:

Ответ 9

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

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

Я бы предположил, что сначала вы введете B и работаете в ветке.

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

cd B

# You are going to merge A into B, so first move all of B 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 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/

Ответ 10

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

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

Ответ 11

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

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

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

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

#!/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
##            [email protected]
##
## 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-адресом для каждого хранилища, например:

[email protected]:eitchnet/ch.eitchnet.parent.git
[email protected]:eitchnet/ch.eitchnet.utils.git
[email protected]:eitchnet/ch.eitchnet.privilege.git

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

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

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

Ответ 12

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

Ответ 13

Если вы хотите поместить файлы из ветки в репо B в поддерево репо A, а также сохранить историю, продолжайте читать. (В приведенном ниже примере я предполагаю, что мы хотим, чтобы главная ветвь репо 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 для команды проверки проверяет файлы из главной ветки, но не создает никаких фиксаций. Мы могли бы выбрать любую фиксацию, потому что в дальнейшем мы все равно очищаем все файлы. Затем, не -n пока (-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

Ответ 14

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

  • Добавление удаленного репо B, указывающего на repo A (git remote add...)
  • Вытягивание текущей ветки (мы не использовали мастер для исправления ошибок) (git pull remoteForRepoA bugFixBranch)
  • Нажатие слияний на github

Работало с удовольствием:)

Ответ 15

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

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

Затем вы вручную объединяетесь.

(адаптировано из сообщение Анара Манафова)

Ответ 16

Если вы хотите объединить три или более проектов в одиночном, выполните действия, описанные в других ответах (remote add -f, merge). Затем (мягкий) reset индекс к старой голове (где не произошло слияния). Добавьте все файлы (git add -A) и скопируйте их (сообщение "Объединение проектов 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. Не всегда добавляйте это, потому что это может привести к перезаписи некоторых коммитов. Подробнее см. справочная страница ветки фильтра, найдите "трансплантаты".

Теперь ваша последняя фиксация имеет соответствующие родители.

Ответ 17

Слить 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

Ответ 18

Объединение 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

Ответ 19

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

git subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

Ответ 20

Эта функция будет клонировать удаленное репо в локальный репозиторий, после того как слияние всех коммитов будет сохранено, 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.

Ответ 21

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

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

  cp -R myotherproject newdirectory
git добавить новый каталог
Код>

Далее в истории

  git fetch path_or_url_to_other_repo
Код>

сообщите git о слиянии в истории последней извлеченной вещи

  echo 'FETCH_HEAD' >.git/MERGE_HEAD
Код>

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

 <код> git commit
Код>

Ответ 22

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

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