etc./StackOverFlow

두 Git 리포지토리를 어떻게 병합합니까?

청렴결백한 만능 재주꾼 2021. 12. 30. 01:24
반응형

질문자 :static_rtti


다음 시나리오를 고려하십시오.

자체 Git 리포지토리에서 작은 실험 프로젝트 A를 개발했습니다. 이제 성숙해졌고 A가 자체적으로 큰 저장소가 있는 더 큰 프로젝트 B의 일부가 되기를 바랍니다. 이제 A를 B의 하위 디렉토리로 추가하고 싶습니다.

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



project-aproject-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 merge 다른 저장소?

이 방법은 저에게 매우 효과적이었습니다. 더 짧고 제 생각에는 훨씬 깨끗합니다.

project-a 를 하위 디렉토리에 넣으려면 git-filter-repo 사용할 수 있습니다( filter-branch권장되지 않음 ). 위의 명령보다 먼저 다음 명령을 실행합니다.

 cd path/to/project-a git filter-repo --to-subdirectory-filter project-a

2개의 큰 리포지토리를 병합하고 그 중 하나를 하위 디렉토리에 넣는 예: https://gist.github.com/x-yuri/9890ab1079cf4357d6f269d073fd9731

참고: --allow-unrelated-histories 매개변수는 git >= 2.9 이후에만 존재합니다.Git - git merge 문서 / --allow-unrelated-historys 참조

업데이트 : 태그를 유지하기 위해 --tags


Andresch Serj

다음은 두 가지 가능한 솔루션입니다.

서브모듈

저장소 A를 더 큰 프로젝트 B의 별도 디렉토리에 복사하거나 (아마도 더 나은) 저장소 A를 프로젝트 B의 하위 디렉토리에 복제합니다. 그런 다음 git submodule 을 사용하여 이 저장소를 저장소 B 의 하위 모듈로 만듭니다.

이것은 저장소 A에서 개발이 계속되고 개발의 주요 부분이 A에서 별도의 독립 실행형 개발인 느슨하게 연결된 저장소에 대한 좋은 솔루션입니다. Git Wiki의 SubmoduleSupportGitSubmoduleTutorial 페이지도 참조하십시오.

하위 트리 병합

하위 트리 병합 전략을 사용하여 리포지토리 A를 프로젝트 B의 하위 디렉터리에 병합할 수 있습니다. 이것은 Markus Prinz의 Subtree Merging and You 에 설명되어 있습니다.

 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에 필요합니다.)

또는 apenwarr(Avery Pennarun)의 git subtree 도구( GitHub 저장소 )를 사용할 수 있습니다. 예를 들어 그의 블로그 게시물 Git 하위 모듈의 새로운 대안: git subtree .


귀하의 경우 (A는 더 큰 프로젝트 B의 일부임) 올바른 솔루션은 하위 트리 병합을 사용하는 것입니다


Community Wiki

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

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

이것은 Rails 마스터 브랜치의 모든 파일이 "rails" 디렉토리에 추가되는 단일 커밋으로 나타납니다. 그러나 커밋 제목에는 이전 기록 트리에 대한 참조가 포함되어 있습니다.

<rev> 에서 'rails/' 추가

여기서 <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과 함께 직접 설치해야 할 수도 있습니다.


Simon Perepelitsa

프로젝트를 별도로 유지 관리하려는 경우 하위 모듈 접근 방식이 좋습니다. 그러나 두 프로젝트를 동일한 저장소로 병합하려는 경우에는 더 많은 작업이 필요합니다.

첫 번째는 git filter-branch 를 사용하여 두 번째 저장소에 있는 모든 항목의 이름을 원하는 하위 디렉토리로 다시 작성하는 것입니다. 따라서 foo.c , bar.html projb/foo.cprojb/bar.html 있습니다.

그러면 다음과 같이 할 수 있어야 합니다.

 git remote add projb [wherever] git pull projb

git pullgit fetch 수행한 다음 git merge 합니다. projb/ 디렉토리가 없으면 충돌이 없어야 합니다.

gitkgit 으로 병합하기 위해 유사한 작업이 수행되었음을 나타냅니다. Junio C Hamano는 여기에 대해 씁니다. http://www.mail-archive.com/git@vger.kernel.org/msg03395.html


Greg Hewgill

git-subtree 는 훌륭하지만 아마도 원하는 것이 아닐 것입니다.

예를 들어, 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

참고: 이렇게 하면 기록이 다시 작성됩니다. 먼저 A를 백업할 수 있습니다.

참고 장점: 파일 이름이나 경로에 ASCII가 아닌 문자(또는 흰색 문자)를 사용하는 경우 sed 명령 내에서 대체 스크립트를 수정해야 합니다. 이 경우 "ls-files -s"에 의해 생성된 레코드 내의 파일 위치는 따옴표로 시작합니다.

(2) 그런 다음 B에서 실행

 git pull path/to/A

짜잔! projectA 디렉토리가 있습니다. git log projectA 를 실행하면 A의 모든 커밋을 볼 수 있습니다.


projectAprojectB 라는 두 개의 하위 디렉토리를 원했습니다. 그런 경우에는 (1)에서 B까지도 했습니다.


Paul Draper

두 리포지토리에 동일한 종류의 파일이 있는 경우(예: 다른 프로젝트에 대한 두 개의 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

Smar

병합을 사용할 때 계속 히스토리가 손실되어 결국 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의 커밋이 이어지는 하나의 프로젝트가 생성됩니다.


Calahad

제 경우에는 my-plugin 저장소와 main-project my-plugin main-project plugins 하위 디렉토리에서 개발된 것처럼 가장하고 싶었습니다.

plugins/my-plugin 하위 디렉토리에서 발생한 것처럼 보이도록 my-plugin 저장소의 히스토리를 다시 작성했습니다. main-project my-plugin 의 개발 이력을 추가하고 두 트리를 병합했습니다. main-project 저장소에 이미 plugins/my-plugin 디렉토리가 없었기 때문에 이것은 사소한 충돌 없는 병합이었습니다. 결과 리포지토리에는 두 원본 프로젝트의 모든 기록이 포함되었으며 두 개의 루트가 있습니다.

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-pluginplugins 대체해야 합니다.

 $ 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 글로브 (와)을 ^(...) ).

다음으로 mkdir -p 명령을 사용하여 pluginsplugins/my-plugin 을 동시에 생성합니다.

마지막으로 zsh "negative glob" 기능 ^(.git|plugins) .git 및 새로 생성된 my-plugin 폴더를 제외한 저장소의 루트 디렉터리에 있는 모든 파일을 일치시킵니다. ( .git 제외할 필요는 없지만 디렉토리를 자체적으로 이동하려고 하면 오류가 발생합니다.)

내 저장소에서 초기 커밋에는 파일이 포함되지 않았으므로 mv 명령은 초기 커밋에서 오류를 반환했습니다(이동할 수 있는 항목이 없었기 때문에). 따라서 나는 || true git filter-branch 가 중단되지 않도록 || true

--all 옵션은 filter-branch 에게 저장소의 모든 분기에 대한 기록을 다시 쓰도록 -- git 이 이를 분기가 다시 쓸 옵션 목록의 일부로 해석하도록 지시하는 데 필요합니다. filter-branch 자체.

이제 main-project 저장소로 이동하여 병합하려는 분기를 확인하십시오. my-plugin 리포지토리의 로컬 복사본(내역 수정됨)을 다음을 사용하여 main-project 의 원격으로 추가합니다.

 $ git remote add --fetch my-plugin $PATH_TO_MY_PLUGIN_REPOSITORY

이제 커밋 기록에 두 개의 관련 없는 트리가 있으며 다음을 사용하여 멋지게 시각화할 수 있습니다.

 $ git log --color --graph --decorate --all

병합하려면 다음을 사용하십시오.

 $ git merge my-plugin/master --allow-unrelated-histories

Git 2.9.0 이전에는 --allow-unrelated-histories 옵션이 존재하지 않습니다. 이러한 버전 중 하나를 사용하는 경우 옵션을 생략하면 됩니다. --allow-unrelated-histories 방지 하는 오류 메시지도 2.9.0에 추가되었습니다.

병합 충돌이 없어야 합니다. 그렇다면 filter-branch 명령이 올바르게 작동하지 않았거나 main-project plugins/my-plugin 디렉토리가 있다는 의미일 수 있습니다.

두 개의 루트를 가진 저장소를 만드는 해커가 무엇인지 궁금해하는 미래의 기여자를 위해 설명 커밋 메시지를 입력해야 합니다.

git log 명령을 사용하여 두 개의 루트 커밋이 있어야 하는 새 커밋 그래프를 시각화할 수 있습니다. master 브랜치만 병합됩니다. main-project 트리에 병합하려는 my-plugin 분기에 대한 중요한 작업이 있는 경우 이러한 병합을 완료할 때까지 my-plugin 원격 삭제를 삼가해야 합니다. 그렇지 않은 경우 해당 분기의 커밋은 여전히 main-project 저장소에 있지만 일부는 연결할 수 없고 궁극적인 가비지 수집에 취약합니다. (또한 리모컨을 삭제하면 원격 추적 분기가 제거되므로 SHA에서 참조해야 합니다.)

my-plugin 에서 유지하려는 모든 항목을 병합한 후 다음을 사용하여 my-plugin 원격을 제거할 수 있습니다.

 $ git remote remove my-plugin

이제 기록을 변경 my-plugin 저장소의 복사본을 안전하게 삭제할 수 있습니다. 제 경우에는 병합이 완료되고 푸시된 후 my-plugin 저장소에 사용 중단 알림을 추가했습니다.


git --version 2.9.0zsh --version 5.2 하여 Mac OS X El Capitan에서 테스트했습니다. 귀하의 마일리지가 다를 수 있습니다.

참조:


Radon Rosborough

나는 며칠 동안 같은 일을하려고 노력해 왔으며 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>

이 작업을 수행하는 데 도움이 된 게시물입니다.

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


Rian

당신이 REPO A의 하위 트리에 환매 특약 B에 지점에서 파일을 넣고도 역사를 보존하려면, 계속 읽어. (아래 예에서는 repo B의 master 브랜치를 repo A의 master 브랜치에 병합하기를 원한다고 가정합니다.)

리포지토리 A에서 먼저 다음을 수행하여 리포지토리 B를 사용할 수 있도록 합니다.

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

new_b_root 라고 하는 repo A에 새로운 브랜치를 생성합니다(하나의 커밋만 포함). 결과 커밋에는 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 옵션은 A의 마스터 브랜치에서 파일을 체크아웃하지만 커밋을 생성하지 않습니다. 다음에 어쨌든 모든 파일을 지우기 때문에 커밋을 선택할 수 있습니다. 그런 다음 아직 커밋하지 않고( -n ) B의 마스터 브랜치에서 첫 번째 커밋을 선택합니다. (cherry-pick은 직접 체크아웃이 하지 않는 것처럼 보이는 원본 커밋 메시지를 유지합니다.) 그런 다음 저장소 B의 모든 파일을 넣을 하위 트리를 만듭니다. 그런 다음 하위 트리에 대한 체리 선택. 위의 예에서는 README 파일만 있습니다. 그런 다음 B-repo 루트 커밋을 커밋하고 동시에 원래 커밋의 타임스탬프도 보존합니다.

이제 새로 생성된 new_b_root B/master 브랜치를 생성합니다. 우리는 새로운 브랜치를 b 라고 부릅니다.

 git checkout -bb 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

최종 그래프는 다음과 같은 구조를 가질 것입니다:

여기에 이미지 설명 입력


Finn Haakansson

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

스크립트 자체에는 스크립트가 수행하는 작업을 설명하는 주석이 많이 있습니다.


eitch

나는 그것이 사실 이후 오래되었다는 것을 알고 있지만 여기에서 찾은 다른 답변에 만족하지 않아 다음과 같이 썼습니다.

 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

jettero

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


Eric Lee

비슷한 문제가 있었지만 제 경우에는 repo A에서 한 버전의 코드베이스를 개발한 다음 새 버전의 제품을 위해 이를 새 repo인 repo B에 복제했습니다. repo A의 일부 버그를 수정한 후 repo B에 변경 사항을 FI해야 했습니다. 결국 다음을 수행했습니다.

  1. repo A를 가리키는 repo B에 remote 추가(git remote add...)
  2. 현재 분기 가져오기(버그 수정을 위해 마스터를 사용하지 않았습니다) (git pull remoteForRepoA bugFixBranch)
  3. 병합을 github에 푸시

치료를 했습니다 :)


David Lemphers

@Smar와 유사하지만 PRIMARY 및 SECONDARY에 설정된 파일 시스템 경로를 사용합니다.

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

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

( Anar Manafov의 게시물 에서 수정)


Turadg

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

RahulMohan Kolakandy

단일 커밋으로 3개 이상의 프로젝트를 병합하려면 다른 답변에 설명된 단계를 수행하십시오( remote add -f , merge ). 그런 다음 인덱스를 이전 헤드(병합이 발생하지 않은 위치)로 (소프트) 재설정합니다. 모든 파일을 추가하고( git add -A ) 커밋합니다(메시지 "프로젝트 A, B, C 및 D를 하나의 프로젝트로 병합). 이제 이것이 마스터의 커밋 ID입니다.

이제 다음 내용으로 .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 추가하십시오. 일부 커밋을 다시 작성할 수 있으므로 항상 추가하지 마십시오. 자세한 내용은 filter-branch의 man 페이지를 참조하고 "grafts"를 검색하십시오.

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


koppor

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

user123568943685

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

먼저 다른 프로젝트의 파일을 원하는 대로 복사합니다.

 cp -R myotherproject newdirectory git add newdirectory

역사의 다음 당김

 git fetch path_or_url_to_other_repo

마지막으로 가져온 것의 기록에서 병합하도록 git에 지시

 echo 'FETCH_HEAD' > .git/MERGE_HEAD

이제 커밋하지만 일반적으로 커밋합니다.

 git commit

Collin Anderson

이 기능은 원격 repo를 로컬 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="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 이후에만 존재합니다.


Andrey Izman

오늘은 다음과 같이 해결해야 했습니다. 프로젝트 A는 bitbucket에 있고 프로젝트 B는 코드 커밋에 있습니다. 둘 다 동일한 프로젝트이지만 A에서 B로 변경 사항을 병합해야 했습니다. (트릭은 프로젝트에서 동일한 이름의 분기를 만드는 것입니다. A, 프로젝트 B와 동일)

  • 자식 체크 아웃 프로젝트 A
  • git 원격 원점 제거
  • git 원격 추가 원점 프로젝트 B
  • 자식 체크 아웃 지점
  • 자식 추가 *
  • git commit -m "코드를 옮겼습니다"
  • 자식 푸시

Ngen CMS

주어진 명령은 내가 제안하는 최상의 솔루션입니다.

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

Praveen Kumar

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

이 필터 분기보다 훨씬 덜 무서운 방법이 더 청소기를 느낀다. 물론 모든 경우에 적용되는 것은 아닙니다.


Mike

스크립트 기반 솔루션에 대한 또 다른 언급은 https://github.com/hraban/tomono입니다.

나는 저자가 아니지만 그것을 사용하고 그것은 일을합니다.

한 가지 긍정적인 측면은 모든 지점과 모든 기록을 최종 리포지토리로 가져오는 것입니다. 내 repos(repos에 중복 폴더 없음 - 실제로는 tfs2git 마이그레이션에서 나옴)의 경우 충돌이 없었고 모든 것이 자동화되었습니다.

주로 monorepos를 만드는 데 사용됩니다(이름 참조).

Windows 사용자의 경우: git bash는 .sh 파일을 실행할 수 있습니다. 표준 git 설치와 함께 제공됩니다.


Andreas Reiff

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 for more)을 생략하고 git의 refspec을 사용하여 X 에서 other_repo/X 로 매핑하는 모든 태그를 가져옵니다. 기능.

참조(가지, 태그)는 git의 파일일 뿐이며 디렉토리를 네임스페이스로 사용할 수 있습니다. 위의 두 명령은 첫 번째 저장소의 태그를 있는 그대로 유지하고 다른 저장소의 태그에는 other_repo/

작업 후에는 다른 리모컨을 제거하는 것이 가장 좋으므로 실수로 태그를 정상적인 방법으로 가져와 엉망으로 만드는 일이 없습니다.


urxvtcd

출처 : http:www.stackoverflow.com/questions/1425892/how-do-you-merge-two-git-repositories

반응형