Created
April 12, 2019 16:10
-
-
Save scott-fleischman/262818ff7f39ba80bd3c91704b5cb608 to your computer and use it in GitHub Desktop.
Add git repository with history
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env bash | |
# NOTE: This is a modified version of another script (not sure the location at the moment). | |
# This script builds on the excellent work by Lucas Jenß, described in his blog | |
# post "Integrating a submodule into the parent repository", but automates the | |
# entire process and cleans up a few other corner cases. | |
# https://x3ro.de/2013/09/01/Integrating-a-submodule-into-the-parent-repository.html | |
function usage(){ | |
echo "Usage: $0 <submodule-name> [<submodule-branch>]" | |
echo "Merge a single branch of <submodule-name> into a repo, retaining file history." | |
echo "If provided then <submodule-branch> will be merged, otherwise master." | |
echo "" | |
echo "options:" | |
echo " -h, --help Print this message" | |
echo " -v, --verbose Display verbose output" | |
} | |
function abort { | |
echo "$(tput setaf 1)$1$(tput sgr0)" | |
exit 1 | |
} | |
function request_confirmation { | |
read -p "$(tput setaf 4)$1 (y/n) $(tput sgr0)" | |
[ "$REPLY" == "y" ] || abort "Aborted!" | |
} | |
function warn() { | |
cat << EOF | |
This script will convert your "${sub}" git submodule into | |
a simple subdirectory in the parent repository while retaining all | |
contents, file history and its own submodules. | |
The script will: | |
* delete the ${sub} submodule configuration from .gitmodules and | |
.git/config and commit it. | |
* rewrite the entire history of the ${sub} submodule so that all | |
paths are prefixed by ${path}. | |
This ensures that git log will correctly follow the original file | |
history. | |
* merge the submodule into its parent repository and commit it. | |
* reinstate any of the submodule's own submodules as part of the parent | |
repository | |
NOTE: This script might completely garble your repository, so PLEASE apply | |
this only to a fresh clone of the repository where it does not matter if | |
the repo is destroyed. It would be wise to keep a backup clone of your | |
repository, so that you can reconstitute it if need be. You have been | |
warned. Use at your own risk. | |
EOF | |
request_confirmation "Do you want to proceed?" | |
} | |
function git_version_lte() { | |
OP_VERSION=$(printf "%03d%03d%03d%03d" $(echo "$1" | tr '.' '\n' | head -n 4)) | |
GIT_VERSION=$(git version) | |
GIT_VERSION=$(printf "%03d%03d%03d%03d" $(echo "${GIT_VERSION#git version }" | sed -E "s/([0-9.]*).*/\1/" | tr '.' '\n' | head -n 4)) | |
echo -e "${GIT_VERSION}\n${OP_VERSION}" | sort | head -n1 | |
[ ${GIT_VERSION} -le ${OP_VERSION} ] | |
} | |
# Convert a url to an absolute url | |
# | |
# Parameters: | |
# $1: The url to check | |
# $2: The base url to use if $1 is a relative path | |
# | |
# Returns an absolute url | |
function absolute_url { | |
local url=$1 | |
local base=$2 | |
if [[ $url =~ \.\. ]]; then | |
echo "$base/$(basename $url)" | |
else | |
echo $url | |
fi | |
} | |
function main() { | |
# warn | |
if [ "${verbose}" == "true" ]; then | |
set -x | |
fi | |
# Remove submodule and commit | |
git config -f .gitmodules --remove-section "submodule.${sub}" | |
if git config -f .git/config --get "submodule.${sub}.url"; then | |
git config -f .git/config --remove-section "submodule.${sub}" | |
fi | |
rm -rf "${path}" | |
git add -A . | |
# git commit -m "Remove submodule ${sub}" | |
rm -rf ".git/modules/${sub}" | |
# Rewrite submodule history | |
local tmpdir="$(mktemp -d -t submodule-rewrite-XXXXXX)" | |
git clone -b "${branch}" "${url}" "${tmpdir}" | |
pushd "${tmpdir}" | |
local tab="$(printf '\t')" | |
local filter="git ls-files -s | sed \"s:${tab}:${tab}${path}/:\" | GIT_INDEX_FILE=\${GIT_INDEX_FILE}.new git update-index --index-info && mv \${GIT_INDEX_FILE}.new \${GIT_INDEX_FILE} || true" | |
git filter-branch --index-filter "${filter}" HEAD | |
popd | |
# Merge in rewritten submodule history | |
git remote add "${sub}" "${tmpdir}" | |
git fetch "${sub}" | |
if git_version_lte 2.8.4 | |
then | |
# Previous to git 2.9.0 the parameter would yield an error | |
ALLOW_UNRELATED_HISTORIES="" | |
else | |
# From git 2.9.0 this parameter is required | |
ALLOW_UNRELATED_HISTORIES="--allow-unrelated-histories" | |
fi | |
git merge -s ours --no-commit ${ALLOW_UNRELATED_HISTORIES} "${sub}/${branch}" | |
rm -rf tmpdir | |
# Add submodule content | |
git clone -b "${branch}" "${url}" "${path}" | |
# Transfer its own submodules to the parent | |
# add_submod_cmds="" | |
# if [ -f ${path}/.gitmodules ]; then | |
# sub_names=$(git config -f ${path}/.gitmodules --get-regex path | sed 's/.* \(.*\)$/\1/g') | |
# for sub_name in ${sub_names}; do | |
# sub_branch=$(git config -f ${path}/.gitmodules --get "submodule.${sub_name}.branch") || true | |
# [ -n "${sub_branch}" ] && sub_branch="-b ${sub_branch}" | |
# sub_path=$(git config -f ${path}/.gitmodules --get "submodule.${sub_name}.path") | |
# sub_url=$(git config -f ${path}/.gitmodules --get "submodule.${sub_name}.url") | |
# # remove the sub-submodule (which should be empty) and cache the command to reinstate it | |
# rmdir ${path}/${sub_path} | |
# add_submod_cmds="$add_submod_cmds git submodule add ${sub_branch} --name ${sub_name} -- ${sub_url} ${path}/${sub_path} ; " | |
# done | |
# fi | |
rm -rf "${path}/.git" "${path}/.gitmodules" | |
git add "${path}" | |
# if [ -n "${add_submod_cmds}" ]; then | |
# bash -c "${add_submod_cmds}" | |
# fi | |
git commit -m "Merge ${sub}/${branch}" | |
git config -f .git/config --remove-section "remote.${sub}" | |
set +x | |
echo "$(tput setaf 2)Submodule merge complete. Push changes after review.$(tput sgr0)" | |
} | |
set -euo pipefail | |
declare verbose=false | |
while [ $# -gt 0 ]; do | |
case "$1" in | |
(-h|--help) | |
usage | |
exit 0 | |
;; | |
(-v|--verbose) | |
verbose=true | |
;; | |
(*) | |
break | |
;; | |
esac | |
shift | |
done | |
declare sub="${1:-}" | |
declare branch="${2:-master}" | |
if [ -z "${sub}" ]; then | |
>&2 echo "Error: No submodule specified" | |
usage | |
exit 1 | |
fi | |
shift | |
if [ -n "${1:-}" ]; then | |
shift | |
fi | |
if [ -n "${1:-}" ]; then | |
>&2 echo "Error: Unknown option: ${1:-}" | |
usage | |
exit 1 | |
fi | |
if ! [ -d ".git" ]; then | |
>&2 echo "Error: No git repository found. Must be run from the root of a git repository" | |
usage | |
exit 1 | |
fi | |
declare path="$(git config -f .gitmodules --get "submodule.${sub}.path")" | |
declare superproject_dir="$(dirname $(git config --get remote.origin.url))" | |
declare url=$(absolute_url $(git config -f .gitmodules --get "submodule.${sub}.url") $superproject_dir) | |
if [ -z "${path}" ]; then | |
>&2 echo "Error: Submodule not found: ${sub}" | |
usage | |
exit 1 | |
fi | |
if [ -z "${superproject_dir}" ]; then | |
>&2 echo "Error: Could not determine the remote origin for this superproject: ${superproject_dir}" | |
usage | |
exit 1 | |
fi | |
if ! [ -d "${path}" ]; then | |
>&2 echo "Error: Submodule path not found: ${path}" | |
usage | |
exit 1 | |
fi | |
main |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment