#!/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 variablesshopt -s -o nounset
# Script variablesdeclare SCRIPT_NAME="${0##*/}"declare SCRIPT_DIR="$(cd ${0%/*} ; pwd)"declare ROOT_DIR="$PWD"IFS=$'\n'
# Detect proper usageif [ "$#" -ne "2" ] ; thenecho -e "ERROR: Usage: $0 <new_project> <my_repo_urls.lst>"exit 1fi
## Script variablesPROJECT_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 functionsfunction failed() {echo -e "ERROR: Merging of projects failed:"echo -e "ERROR: Merging of projects failed:" >>${LOG_FILE} 2>&1echo -e "$1"exit 1}
function commit_merge() {current_branch="$(git symbolic-ref HEAD 2>/dev/null)"if [[ ! -f ".git/MERGE_HEAD" ]] ; thenecho -e "INFO: No commit required."echo -e "INFO: No commit required." >>${LOG_FILE} 2>&1elseecho -e "INFO: Committing ${sub_project}..."echo -e "INFO: Committing ${sub_project}..." >>${LOG_FILE} 2>&1if ! git commit -m "[Project] Merged branch '$1' of ${sub_project}" >>${LOG_FILE} 2>&1 ; thenfailed "Failed to commit merge of branch '$1' of ${sub_project} into ${current_branch}"fifi}
# Make sure the REPO_URL_FILE existsif [ ! -e "${REPO_URL_FILE}" ] ; thenecho -e "ERROR: Repo file ${REPO_URL_FILE} does not exist!"exit 1fi
# Make sure the required directories don't existif [ -e "${PROJECT_PATH}" ] ; thenecho -e "ERROR: Project ${PROJECT_NAME} already exists!"exit 1fi
# create the new projectecho -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>&1echo -e "===================================================="echo -e "====================================================" >>${LOG_FILE} 2>&1cd ${ROOT_DIR}mkdir ${PROJECT_NAME}cd ${PROJECT_NAME}git initecho "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 merginggit add initial_commitgit commit --quiet -m "[Project] Initial Master Repo Commit"git rm --quiet initial_commitgit commit --quiet -m "[Project] Initial Master Repo Commit"echo
# Merge all projects into the branches of this projectecho -e "INFO: Merging projects into new repository..."echo -e "INFO: Merging projects into new repository..." >>${LOG_FILE} 2>&1echo -e "===================================================="echo -e "====================================================" >>${LOG_FILE} 2>&1for url in $(cat ${REPO_URL_FILE}) ; do
if [[ "${url:0:1}" == '#' ]] ; thencontinuefi
# extract the name of this projectexport sub_project=${url##*/}sub_project=${sub_project%*.git}
echo -e "INFO: Project ${sub_project}"echo -e "INFO: Project ${sub_project}" >>${LOG_FILE} 2>&1echo -e "----------------------------------------------------"echo -e "----------------------------------------------------" >>${LOG_FILE} 2>&1
# Fetch the projectecho -e "INFO: Fetching ${sub_project}..."echo -e "INFO: Fetching ${sub_project}..." >>${LOG_FILE} 2>&1git remote add "${sub_project}" "${url}"if ! git fetch --tags --quiet ${sub_project} >>${LOG_FILE} 2>&1 ; thenfailed "Failed to fetch project ${sub_project}"fi
# add remote branchesecho -e "INFO: Creating local branches for ${sub_project}..."echo -e "INFO: Creating local branches for ${sub_project}..." >>${LOG_FILE} 2>&1while read branch ; dobranch_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 masterif ! git checkout -b "${sub_project}/${branch_name}" master >>${LOG_FILE} 2>&1 ; then failed "Failed preparing ${branch_name}" ; fiif ! git reset --hard ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fiif ! git clean -d --force ; then failed "Failed preparing ${branch_name}" >>${LOG_FILE} 2>&1 ; fi
# Merge the projectecho -e "INFO: Merging ${sub_project}..."echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1if ! git merge --allow-unrelated-histories --no-commit "remotes/${sub_project}/${branch_name}" >>${LOG_FILE} 2>&1 ; thenfailed "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 directoryif [ "$(ls)" == "${sub_project}" ] ; thenecho -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>&1elseecho -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>&1mkdir ${sub_project}for f in $(ls -a) ; doif [[ "$f" == "${sub_project}" ]] ||[[ "$f" == "." ]] ||[[ "$f" == ".." ]] ; thencontinuefigit mv -k "$f" "${sub_project}/"done
# commit the movingif ! git commit --quiet -m "[Project] Move ${sub_project} files into sub directory" ; thenfailed "Failed to commit moving of ${sub_project} files into sub directory"fifiechodone < <(git ls-remote --heads ${sub_project})
# checkout master of sub probjectif ! git checkout "${sub_project}/master" >>${LOG_FILE} 2>&1 ; thenfailed "sub_project ${sub_project} is missing master branch!"fi
# copy remote tagsecho -e "INFO: Copying tags for ${sub_project}..."echo -e "INFO: Copying tags for ${sub_project}..." >>${LOG_FILE} 2>&1while read tag ; dotag_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.0tag_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>&1if ! git tag "${tag_new_name}" "${tag_ref}" >>${LOG_FILE} 2>&1 ; thenecho -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>&1fidone < <(git ls-remote --tags --refs ${sub_project})
# Remove the remote to the old projectecho -e "INFO: Removing remote ${sub_project}..."echo -e "INFO: Removing remote ${sub_project}..." >>${LOG_FILE} 2>&1git remote rm ${sub_project}
echodone
# Now merge all project master branches into new mastergit checkout --quiet masterecho -e "INFO: Merging projects master branches into new repository..."echo -e "INFO: Merging projects master branches into new repository..." >>${LOG_FILE} 2>&1echo -e "===================================================="echo -e "====================================================" >>${LOG_FILE} 2>&1for url in $(cat ${REPO_URL_FILE}) ; do
if [[ ${url:0:1} == '#' ]] ; thencontinuefi
# extract the name of this projectexport sub_project=${url##*/}sub_project=${sub_project%*.git}
echo -e "INFO: Merging ${sub_project}..."echo -e "INFO: Merging ${sub_project}..." >>${LOG_FILE} 2>&1if ! git merge --allow-unrelated-histories --no-commit "${sub_project}/master" >>${LOG_FILE} 2>&1 ; thenfailed "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"
echodone
# Donecd ${ROOT_DIR}echo -e "INFO: Done."echo -e "INFO: Done." >>${LOG_FILE} 2>&1echo
exit 0
cd B
# You are going to merge A into B, so first move all of B's files into a sub dirmkdir B
# Move all files to B, till there is nothing in the dir but .git and Bgit mv <files> B
git add .
git commit -m "Moving content of project B in preparation for merge from A"
# Now merge A into Bgit remote add -f A <A repo url>
git merge A/<branch>
mkdir A
# move all the files into subdir A, excluding .gitgit mv <files> A
git commit -m "Moved A into subdir"
# Move B's files back to rootgit mv B/* ./
rm -rf B
git commit -m "Reset B to original state"
git push