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

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

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

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

21 ответов


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

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

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

добавить 'rails/' из commit <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
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 / --allow-unrelated-histories


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

подмодулей

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

это хорошее решение для слабо связанных репозиториев, где разработка в репозитории a продолжается, а основные часть разработки-это отдельная самостоятельная разработка в A. см. также SubmoduleSupport и GitSubmoduleTutorial страницы на Git Вики.

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

вы можете объединить репозиторий 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 is необходимо для Git >= 2.9.0.)

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


Я думаю ,что в вашем случае (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. Не должно быть конфликтов, если в репозитории, к которому вы тянете, еще нет


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

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

git log projectA

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

Грег 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

вуаля! У вас есть projectA каталог в B. Если вы запустите 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

Я продолжал терять историю при использовании merge, поэтому я в конечном итоге использовал 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 история, и объединил два дерева вместе. Поскольку нет уже в настоящее время в 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|my-plugin) 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|my-plugin) plugins/my-plugin || true)'" -- --all

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

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

что касается фактической команды, мне не очень повезло получить bash делать то, что я хотел, поэтому я использую zsh -c сделать zsh выполнить команду. Сначала я установил extended_glob опция, которая позволяет ^(...) синтаксис , а также , которым позволяет мне выбирать dotfiles (например,.gitignore) С Глоб (^(...)).

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

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

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

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

Теперь перейдите к вашему 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 предотвращает была и добавил в 2.9.0.

у вас не должно быть конфликтов слияния. Если вы это сделаете, это, вероятно, означает, что либо filter-branch команда не работала правильно или уже была на main-project.

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

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

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

$ git remote remove my-plugin

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


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

ссылки:


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

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

Я бы предложил вам сначала филиал B и работать в филиале.

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

cd B

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

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

git add .

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


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

git merge A/<branch>

mkdir A

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

git commit -m "Moved A into subdir"


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

rm -rf B

git commit -m "Reset B to original state"

git push

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

git log --follow A/<file>

Это был пост, который помогите мне сделать это:

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


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

me=$(basename )

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 "" ]; do
    repo=""; 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

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

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

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

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

#!/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:  <new_project> <my_repo_urls.lst>"
  exit 1
fi


# Script functions
function failed() {
  echo -e "ERROR: Merging of projects failed:"
  echo -e ""
  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 '' of ${sub_project}" ; then
      failed "Failed to commit merge of branch '' of ${sub_project} into ${current_branch}"
    fi
  fi
}


## Script variables
PROJECT_NAME=""
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
REPO_FILE=""
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

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


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


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

в репо A сначала сделайте следующее, Чтобы сделать РЕПО B доступным:

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

теперь мы создаем совершенно новую ветвь (с только одним commit) в репо 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 отделение. (Cherry-pick сохраняет исходное сообщение фиксации, которое, похоже, не делает прямая проверка.) Затем мы создаем поддерево, куда мы хотим поместить все файлы из РЕПО B. Затем мы должны переместить все файлы, которые были введены в cherry-pick в поддерево. В приведенном выше примере, есть только перейти. Затем мы совершаем нашу корневую фиксацию 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


У меня была аналогичная проблема, но в моем случае мы разработали одну версию кодовой базы в репо A, а затем клонировали ее в новое РЕПО, РЕПО B, для новой версии продукта. После исправления некоторых ошибок в репо A нам нужно было внести изменения в репо B. В итоге мы сделали следующее:

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

сработала :)


похоже на @Smar, но использует пути файловой системы, установленные в первичном и вторичном:

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

потом вручную сливать.

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


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

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

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

Run git filter-branch -- head^..head head^2..head head^3..head. Если у вас есть больше, чем три ветви, просто добавьте столько head^n..head а у вас есть филиалы. Чтобы обновить теги, добавьте --tag-name-filter cat. Не всегда добавляю, потому что это может привести к перезаписи некоторых коммитов. Подробнее см. man-страница filter-branch поиск "графты".

теперь у вашего последнего коммита есть правильные родители.


чтобы объединить A A в B:

1) в проекте

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 subtree add --prefix=MY_PROJECT git://github.com/project/my_project.git master

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

function git-add-repo
{
    repo=""
    dir="$(echo "" | 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

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

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 "" | sed 's/\./\./')"
    to="$(echo "" | 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.


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

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

cp -R myotherproject newdirectory
git add newdirectory

следующий тянуть в истории

git fetch path_or_url_to_other_repo

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

echo 'FETCH_HEAD' > .git/MERGE_HEAD

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

git commit