문제

다음 시나리오를 고려해보세요.

나는 자체 Git 저장소에서 작은 실험 프로젝트 A를 개발했습니다.이제는 성숙해졌으며 A가 자체적으로 큰 저장소를 갖고 있는 더 큰 프로젝트 B에 참여하고 싶습니다.이제 A를 B의 하위 디렉터리로 추가하고 싶습니다.

어느 쪽에서도 기록을 잃지 않고 A를 B로 병합하려면 어떻게 해야 합니까?

도움이 되었습니까?

해결책

다른 저장소의 단일 분기는 해당 기록을 유지하는 하위 디렉터리에 쉽게 배치할 수 있습니다.예를 들어:

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 documentation / -allow-related-histories

업데이트: 추가 --tags 태그를 유지하기 위해 @jstadler가 제안한대로.

가능한 두 가지 해결책은 다음과 같습니다.

하위 모듈

더 큰 프로젝트 B의 별도 디렉토리로 저장소 A를 복사하거나 (아마도 더 나은) 클론 리포지토리 A는 프로젝트 B의 하위 디렉토리로 들어갑니다. git 하위 모듈 이 저장소를 만들기 위해 a 하위 모듈 저장소 B의

이것은 저장소 A가 계속되고 개발의 주요 부분은 A의 별도의 독립형 개발입니다. 하위 모듈 지원 그리고 gitsubmoduletutorial Git Wiki의 페이지.

하위 트리 병합

리포지토리 A를 하위 트리 병합 전략. 이것은 다음에 설명되어 있습니다 하위 트리 병합 및 당신 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

(옵션 --allow-unrelated-histories git> = 2.9.0에 필요합니다.)

또는 사용할 수 있습니다 git 하위 트리 도구 (Github의 저장소) Apenwarr (Avery Pennarun), 예를 들어 그의 블로그 게시물에서 발표 git 서브 모듈에 대한 새로운 대안 : git subtree.


귀하의 경우 (A는 더 큰 프로젝트의 일부가되어야한다고 생각합니다.) 올바른 솔루션은 사용하는 것입니다. 하위 트리 병합.

프로젝트를 별도로 유지 관리하려는 경우 하위 모듈 접근 방식이 좋습니다.그러나 실제로 두 프로젝트를 동일한 저장소에 병합하려면 추가 작업을 수행해야 합니다.

가장 먼저 사용할 것은 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.Junio ​​C Hamano는 이에 대해 다음과 같이 썼습니다. http://www.mail-archive.com/git@vger.kernel.org/msg03395.html

git-subtree 좋지만 아마도 당신이 원하는 것이 아닐 것입니다.

예를 들어, if projectA B로 만든 디렉토리입니다 git subtree,

git log projectA

기울기 단 하나 커밋 : 합병. 병합 된 프로젝트의 커밋은 다른 경로를위한 것이므로 나타나지 않습니다.

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

참고 : 이것은 기록을 다시 작성 하므로이 repo a를 계속 사용하려는 경우 먼저 사본을 복제 (복사) 할 수 있습니다.

(2) 그런 다음 B에서 달리십시오

git pull path/to/A

짜잔! 당신은 있습니다 projectA B.의 디렉토리 git log projectA, 당신은 A의 모든 커밋을 볼 수 있습니다.


제 경우에는 두 개의 하위 디렉터를 원했습니다. projectA 그리고 projectB. 이 경우, 나는 (1)에서 B를 B로했다.

두 리포지토리에 동일한 종류의 파일이있는 경우 (다른 프로젝트에 대한 2 개의 레일 리포지토리와 같은) 보조 저장소의 데이터를 현재 저장소로 가져올 수 있습니다.

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의 커밋이있는 하나의 프로젝트로 이어집니다.

제 경우에는 a가있었습니다 my-plugin 저장소 및 a main-project 저장소, 그리고 나는 그것을 척하고 싶었습니다 my-plugin 항상 개발되었습니다 plugins 서브 디렉토리 main-project.

기본적으로 나는 역사를 다시 썼다 my-plugin 모든 개발이 plugins/my-plugin 하위 디렉토리. 그런 다음 개발 기록을 추가했습니다 my-pluginmain-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. 이것은 각 커밋에 대해 저장된 데이터에서 직접 작동하므로 "작업 디렉토리", "인덱스", "스테이징"등에 대한 개념에 대해 걱정할 필요가 없습니다.

당신이 a filter-branch 실패하는 명령은 .git 디렉토리와 다음에 시도 할 때 filter-branch 당신이 공급하지 않는 한 이것에 대해 불평 할 것입니다. -f 옵션 filter-branch.

실제 명령에 관해서는, 나는 운이 좋지 않았습니다. bash 내가 원하는 것을하기 위해 대신 사용합니다. zsh -c 만들다 zsh 명령을 실행하십시오. 먼저 나는 extended_glob 옵션, 이것이 가능합니다 ^(...) 의 구문 mv 명령뿐만 아니라 glob_dots 옵션, dotfiles를 선택할 수 있습니다 (예 : .gitignore) 지구와 함께 (^(...)).

다음으로, 나는 그것을 사용한다 mkdir -p 둘 다를 만들려면 명령 plugins 그리고 plugins/my-plugin 동시에.

마지막으로, 나는 그것을 사용한다 zsh "음성 글로벌"기능 ^(.git|plugins) 저장소의 루트 디렉토리에있는 모든 파일을 .git 그리고 새로 만든 것 my-plugin 폴더. (을 제외한 .git 여기서는 필요하지 않을 수도 있지만 디렉토리를 그 자체로 옮기려고 시도하는 것은 오류입니다.)

내 저장소에 초기 커밋에는 파일이 포함되지 않았으므로 mv 명령은 초기 커밋에서 오류를 반환했습니다 (이동할 수있는 것이 없기 때문에). 따라서 나는 a를 추가했다 || 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

커밋 역사상 2 개의 관련이없는 나무가 있습니다.

$ 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.

미래의 기고자들에게 Hackery가 두 개의 뿌리로 저장소를 만들기 위해 어떤 일이 있었는지 궁금해하는 미래의 기고자들을위한 설명 커밋 메시지를 입력하십시오.

위의 내용을 사용하여 두 개의 루트 커밋이 있어야하는 새로운 커밋 그래프를 시각화 할 수 있습니다. git log 명령. 주목하십시오 오직 master 지점이 병합됩니다. 이것은 당신이 다른 사람에 대한 중요한 일이 있다면 my-plugin 당신이 합병하려는 가지 main-project 나무, 당신은 삭제를 삼가야합니다 my-plugin 이 합병을 할 때까지 리모컨. 그렇지 않으면 그 지점의 커밋은 여전히 main-project 저장소이지만 일부는 도달 할 수없고 최종 쓰레기 수집에 취약합니다. (원격 삭제는 원격 추적 지점을 제거하기 때문에 SHA를 참조해야합니다.)

선택적으로, 당신이 보관하고 싶은 모든 것을 합병 한 후 my-plugin, 당신은 제거 할 수 있습니다 my-plugin 원격 사용 :

$ git remote remove my-plugin

이제 사본을 안전하게 삭제할 수 있습니다 my-plugin 역사가 바뀌는 저장소. 제 경우에는 Real에도 더 이상 사용되지 않은 통지가 추가되었습니다. 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-on-repository-without-losing-file-history/

repo b의 분기에서 파일을 a에 넣으려면 하위 트리 repo a 그리고 또한 역사를 보존하고 계속 읽으십시오. (아래의 예에서, 우리는 Repo B의 마스터 브랜치를 Repo A의 마스터 브랜치로 병합하기를 원한다고 가정합니다.)

Repo A에서 먼저 REPO B를 사용할 수 있도록 다음을 수행하십시오.

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

이제 우리는 우리가 호출하는 REPO A에서 새로운 브랜치 (단 하나의 커밋 만)를 만듭니다. new_b_root. 결과 커밋에는 첫 번째 Commit of Repo 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은 직선 체크 아웃이하지 않는 원래 커밋 메시지를 보존합니다.) 그러면 우리는 모든 파일을 Repo B에서 배치하려는 하위 트리를 만듭니다. 그런 다음 우리는 다음에 소개 된 모든 파일을 이동해야합니다. 하위 트리에 체리 픽. 위의 예에는 a가 있습니다 README 이동할 파일. 그런 다음 B-Repo Root Commit을 커밋하고 동시에 원래 커밋의 타임 스탬프도 보존합니다.

이제 새로운 것을 만들 것입니다 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

나는 그것이 사실이 지났다는 것을 알고 있지만, 내가 여기서 찾은 다른 답변에 만족하지 않았기 때문에 이것을 썼습니다.

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

나는 여기에 스택 오버플로 등에 대한 많은 정보를 수집했으며, 문제를 해결하는 스크립트를 하나로 묶었습니다.

경고는 각 저장소의 '개발'브랜치 만 고려하여 완전히 새로운 저장소에서 별도의 디렉토리로 병합된다는 것입니다.

태그와 다른 분기는 무시됩니다 - 이것은 당신이 원하는 것이 아닐 수도 있습니다.

스크립트는 기능 브랜치와 태그를 처리합니다. 새 프로젝트에서 이름을 바꾸어 어디에서 왔는지 알 수 있습니다.

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

# disallow using undefined variables
shopt -s -o nounset

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

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


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


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

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


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


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


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


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

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

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

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

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

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

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

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

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

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

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

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


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

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

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

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

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

  echo
done


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

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

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

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

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

  echo
done


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

exit 0

당신은 또한 그것을 얻을 수 있습니다 http://paste.ubuntu.com/11732805

먼저 각 저장소에 URL이있는 파일을 만듭니다.

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

그런 다음 프로젝트 이름과 스크립트의 경로를 제공하는 스크립트를 호출하십시오.

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

스크립트 자체에는 그것이 무엇을하는지 설명해야 할 많은 의견이 있습니다.

두 개의 리포지토리를 단순히 붙잡 으려고한다면 서브 모듈과 하위 트리 합병은 모든 파일 기록을 보존하지 않기 때문에 사용하기에 잘못된 도구입니다 (사람들이 다른 답변에 언급 한 것처럼). 이 답변을 참조하십시오 여기 이 작업을 수행하는 간단하고 올바른 방법.

@Smar와 유사하지만 1 차 및 보조로 설정된 파일 시스템 경로를 사용합니다.

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

그런 다음 수동으로 병합됩니다.

(적응 Anar Manafov에 의해 게시됩니다)

비슷한 도전이 있었지만 제 경우에는 REPO A에서 하나의 버전의 코드베이스를 개발 한 다음 새 버전의 제품에 대한 새로운 리포지트 Repo B로 클로닝했습니다. Repo A에서 일부 버그를 수정 한 후 Repo B 로의 변경 사항을 확인해야했습니다.

  1. Repo A (Git Remote Add ...)를 가리키는 Repo B에 리모컨 추가
  2. 현재 브랜치를 당기기 (우리는 버그 수정에 마스터를 사용하지 않았다) (Git Pull OmertforRepoa BugfixBranch)
  3. 밀어내는 것은 Github와 합병됩니다

치료를했다 :)

3 개 이상의 프로젝트를 하나의 커밋, 다른 답변에 설명 된대로 단계를 수행하십시오 (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>

운영 git filter-branch -- head^..head head^2..head head^3..head. 3 개가 넘는 가지가 있다면 더 많이 추가하십시오. head^n..head 당신은 가지가 있습니다. 태그를 업데이트하려면 추가하십시오 --tag-name-filter cat. 이로 인해 일부 커밋이 다시 쓰여질 수 있기 때문에 항상 추가하지 마십시오. 자세한 내용은 참조하십시오 필터 브랜치의 남자 페이지, "이식편"을 검색하십시오.

이제 마지막 커밋에는 올바른 부모가 관련되어 있습니다.

B 내에서 A를 병합하려면 :

1) 프로젝트에서 a

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

2) 프로젝트에서 b

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

이 지점에서는 모든 작업을 수행하고 커밋해야합니다.

c) 그런 다음 마스터로 돌아가서 두 가지 사이의 고전적인 병합 :

git checkout master
git merge projectA

2 개의 저장소 병합

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

delete the ref to avoid errors
git update-ref -d refs/remotes/project2/master

이 기능은 원격 repo를 로컬 리포 디르로 복제합니다. 모든 커밋을 병합 한 후에는 저장됩니다. 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의 파일/더스를 다른 경로로 이동할 수도 있습니다.

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 이후에만 존재합니다.

프로젝트를 약간 수동으로 병합하여 병합 충돌을 처리 할 필요가 없습니다.

먼저, 다른 프로젝트의 파일을 복사하지만 원하는 파일을 복사하십시오.

cp -R myotherproject newdirectory
git add newdirectory

다음으로 역사를 당깁니다

git fetch path_or_url_to_other_repo

Git에게 마지막으로 가져온 것의 역사에 합병하도록 지시하십시오.

echo 'FETCH_HEAD' > .git/MERGE_HEAD

그러나 이제 당신은 일반적으로 커밋 할 것입니다

git commit

주어진 명령은 내가 제안하는 최선의 해결책입니다.

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

작은 프로젝트를 더 큰 프로젝트의 하위 디렉토리로 옮기고 싶었습니다. 내 작은 프로젝트가 많은 커밋이 없었기 때문에 git format-patch --output-directory /path/to/patch-dir. 그런 다음 더 큰 프로젝트에서 나는 사용했습니다 git am --directory=dir/in/project /path/to/patch-dir/*.

이것은 느껴집니다 방법 필터 브랜치보다 덜 무섭고 더 깨끗합니다. 물론 모든 경우에 적용 할 수는 없습니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top