كيف تقوم بدمج مستودعين Git؟


Answers

إذا كنت تريد دمج 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

مأخوذ من: بوابة دمج مستودعات مختلفة؟

عملت هذه الطريقة بشكل جيد بالنسبة لي ، انها أقصر وفي رأيي أنظف كثيرا.

ملاحظة: --allow-unrelated-histories توجد المعلمة --allow-unrelated-histories إلا بعد git> = 2.9. راجع Git - git merge Documentation / - تاريخ غير مرتبط

Question

خذ بعين الاعتبار السيناريو التالي:

لقد طورت مشروعًا تجريبيًا صغيرًا A في Git repo الخاص به. لقد نضجت الآن ، وأرغب في أن أكون جزءًا من مشروع أكبر B ، الذي يحتوي على مستودع كبير خاص به. أود الآن إضافة A كدليل فرعي لـ B.

كيف أقوم بدمج A إلى B ، دون فقدان التاريخ من أي جانب؟




Given command is the best possible solution I suggest.

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



إذا كان لدى مستودعات التخزين نفس نوع الملفات (مثل مستودعين 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



To merge a A within B:

1) In the project A

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

2) In the project B

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

In this branch do all operations you need to do and commit them.

C) Then back to the master and a classical merge between the two branches:

git checkout master
git merge projectA



على غرارSmar ولكن يستخدم مسارات نظام الملفات ، وتعيين في الابتدائي والثانوي:

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

ثم تقوم يدويا دمج.

(مقتبس من المنشور عن طريق انار Manafov )




أعلم أن هذا متأخرا بعض الشيء ، ولكن بالنسبة لأي شخص لا يزال يبحث عن طريقة للقيام بذلك ، فقد جمعت الكثير من المعلومات هنا على وما إلى ذلك ، وتمكنت من وضع برنامج نصي معًا يحل المشكلة بالنسبة لي.

التحذير هو أنه يأخذ بعين الاعتبار فقط فرع "تطوير" من كل مستودع ويدمجها في دليل منفصل في مستودع جديد تماما.

يتم تجاهل العلامات والفروع الأخرى - قد لا يكون هذا ما تريده.

البرنامج النصي حتى يعالج الفروع والعلامات - إعادة تسمية لهم في المشروع الجديد حتى تعرف من أين أتوا.

#!/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.2.0
## Created: 2015-06-17
##
################################################################################
#

# disallow using undefined variables
shopt -s -o nounset

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


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


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

function commit_merge() {
  current_branch="$(git symbolic-ref HEAD 2>/dev/null)"
  CHANGES=$(git status | grep "working directory clean")
  MERGING=$(git status | grep "merging")
  if [[ "$CHANGES" != "" ]] && [[ "$MERGING" == "" ]] ; then
    echo -e "INFO:   No commit required."
  else
    echo -e "INFO:   Committing ${sub_project}..."
    if ! git commit --quiet -m "[Project] Merged branch '$1' of ${sub_project}" ; then
      failed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"
    fi
  fi
}


## Script variables
PROJECT_NAME="${1}"
PROJECT_PATH="${ROOT_DIR}/${PROJECT_NAME}"
REPO_FILE="${2}"
REPO_URL_FILE="${ROOT_DIR}/${REPO_FILE}"


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


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


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


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

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

  echo -e "INFO: Project ${sub_project}"
  echo -e "----------------------------------------------------"

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

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

    echo -e "INFO:   Creating branch ${branch_name}..."

    # create and checkout new merge branch off of master
    git checkout --quiet -b "${sub_project}/${branch_name}" master
    git reset --hard --quiet
    git clean -d --force --quiet

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

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

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

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


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

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

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

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

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

  echo
done


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

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

  echo -e "INFO:   Merging ${sub_project}..."
  if ! git merge --quiet --no-commit "${sub_project}/master" 2>/dev/null ; then
    failed "Failed to merge branch ${sub_project}/master into master"
  fi

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

  echo
done


# Done
cd ${ROOT_DIR}
echo -e "INFO: Done."
echo

exit 0

يمكنك أيضًا الحصول عليه من http://paste.ubuntu.com/11732805

قم أولاً بإنشاء ملف بعنوان URL لكل مستودع ، على سبيل المثال:

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

ثم استدعاء البرنامج النصي بإعطاء اسم للمشروع ومسار البرنامج النصي:

./mergeGitRepositories.sh eitchnet_test eitchnet.lst

يحتوي النص نفسه على الكثير من التعليقات التي يجب أن توضح ما يفعله.




في حالتي ، كان my-plugin مستودع my-plugin ومستودع main-project ، وأردت التظاهر بأن my-plugin قد تم تطويره دائمًا في الدليل الفرعي my-plugin main-project .

في الأساس ، أعيد كتابة سجل مستودع my-plugin بحيث يبدو أن جميع التطويرات تمت في الدليل الفرعي plugins/my-plugin . ثم أضفت تاريخ تطوير my-plugin إلى تاريخ main-project ودمجت الشجرَين معًا. نظرًا لعدم وجود plugins/my-plugin دليل 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|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 الخاصة بك مهما كانت 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 . لاحظ أن هذا يعمل مباشرةً على البيانات المخزنة لكل التزام ، لذلك لا داعي للقلق حول مفاهيم "دليل العمل" و "الفهرس" و "التدريج" وما إلى ذلك.

إذا قمت بتشغيل أمر filter-branch الذي يفشل ، .git بعض الملفات في دليل .git وفي المرة التالية التي تحاول فيها filter-branch من ذلك ، إلا إذا قمت بتوفير خيار -f filter-branch .

أما بالنسبة للأمر الفعلي ، لم يكن لدي الكثير من الحظ bash على ما أريد ، لذلك استخدم zsh -c لجعل zsh تنفيذ أمر. أولاً أقوم بتعيين خيار extended_glob ، وهو ما يمكّن بناء الجملة ^(...) في الأمر mv ، بالإضافة إلى خيار glob_dots ، والذي يسمح لي بتحديد dotfiles (مثل .gitignore ) مع كرة ( ^(...) ).

بعد ذلك ، يمكنني استخدام الأمر mkdir -p لإنشاء كل من plugins plugins/my-plugin في نفس الوقت.

وأخيرًا ، أستخدم ميزة zsh "negative glob" ^(.git|my-plugin) لمطابقة جميع الملفات في الدليل الجذر للمستودع باستثناء .git ومجلد my-plugin الذي تم إنشاؤه حديثًا. (قد لا يكون من الضروري هنا استبعاد .git ، لكن محاولة نقل دليل إلى نفسه خطأ).

في مستودع التخزين الخاص بي ، لم يتضمن الإلتزام الأولي أي ملفات ، لذا قام الأمر mv بإرجاع خطأ في الالتزام الأولي (حيث لا يتوفر أي شيء لنقل). لذلك ، أضفت || true || true حتى أن git filter-branch لن يتم إحباط.

يخبر الخيار --all filter-branch لإعادة كتابة المحفوظات لجميع الفروع في المستودع ، والإضافي -- ضروري لإخبار git لتفسيره كجزء من قائمة الخيارات للفروع لإعادة الكتابة ، بدلاً من كخيار filter-branch نفسها.

الآن ، انتقل إلى مستودع main-project وتحقق من أي فرع تريد دمجه. إضافة نسختك المحلية من مستودع my-plugin (مع تعديل تاريخه) كنائبة عن main-project مع:

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

سيكون لديك الآن شجران غير مرتبطين في تاريخ التزامك ، والتي يمكنك تصورها بشكل رائع باستخدام:

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

لدمجها ، استخدم:

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

لاحظ أنه في Git ما قبل 2.9.0 ، لا يوجد خيار --allow-unrelated-histories . إذا كنت تستخدم أحد هذه الإصدارات ، --allow-unrelated-histories فقط بحذف الخيار: رسالة الخطأ التي تم --allow-unrelated-histories التي تم --allow-unrelated-histories تم إضافتها أيضًا في 2.9.0.

يجب ألا يكون لديك أي تعارضات دمج. إذا قمت بذلك ، فمن المحتمل أن ذلك إما أن أمر filter-branch لم يعمل بشكل صحيح أو كان هناك بالفعل دليل plugins/my-plugin في main-project .

تأكد من إدخال رسالة تفسيرية تفسيرية لأي مساهمين مستقبليين يتساءلون عن ما يجري في مجال الحواسيب لإنشاء مستودع ذو جذرين.

يمكنك تصور الرسم البياني الجديد للالتزام ، والذي يجب أن يحتوي على جذرتين ، باستخدام أمر git log أعلاه. لاحظ أنه سيتم دمج الفرع master فقط . هذا يعني أنه إذا كان لديك عمل مهم على فروع my-plugin الأخرى التي تريد دمجها في شجرة main-project ، فيجب عليك الامتناع عن حذف جهاز التحكم عن بعد my-plugin حتى تقوم بتنفيذ هذه الدمج. إذا لم تقم بذلك ، فستستمر الإلتزامات من تلك الفروع في مستودع main-project ، ولكن بعضها لن يكون قابلاً للوصول إلى مجموعة النفايات في نهاية المطاف. (ستحتاج أيضًا إلى الرجوع إليها بواسطة SHA ، لأن حذف وحدة التحكم عن بعد يزيل فروع التتبع عن بعد.)

اختياريًا ، بعد دمج كل ما تريد الاحتفاظ به من my-plugin ، يمكنك إزالة جهاز my-plugin عن بُعد باستخدام:

$ git remote remove my-plugin

يمكنك الآن حذف نسخة مخزون my-plugin التي قمت بتغيير سجلها بأمان. في حالتي ، أضفت أيضًا إشعارًا بالإيقاف إلى مستودع my-plugin الحقيقي بعد اكتمال الدمج ودفعه.

اختبارها على نظام التشغيل Mac OS X El Capitan مع git --version 2.9.0 و zsh --version 5.2 . الأميال الخاص بك قد تختلف.

المراجع:




I merge projects slightly manually, which allows me to avoid needing to deal with merge conflicts.

أولاً ، انسخ في الملفات من المشروع الآخر كيفما تريد.

cp -R myotherproject newdirectory
git add newdirectory

سحب المقبل في التاريخ

git fetch path_or_url_to_other_repo

اقول git للدمج في تاريخ آخر شيء تم إحضاره

echo 'FETCH_HEAD' > .git/MERGE_HEAD

الآن ارتكاب لكن عادة ما ترتكب

git commit



أعلم أنه بعد فترة طويلة من الحقيقة ، لكنني لم أكن سعيدًا بالإجابات الأخرى التي وجدتها هنا ، لذلك كتبت هذا:

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



يكون النهج الفرعي جيدًا إذا كنت ترغب في الحفاظ على المشروع بشكل منفصل. ومع ذلك ، إذا كنت تريد بالفعل دمج كلا المشروعين في نفس المستودع ، فحينئذٍ سيكون لديك المزيد من العمل للقيام به.

سيكون أول شيء هو استخدام 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/ directory.

مزيد من البحث يشير إلى أنه تم القيام بشيء مماثل لدمج gitk في git . يكتب Junio ​​C Hamano عن ذلك هنا: http://www.mail-archive.com/git@vger.kernel.org/msg03395.html