Skip to content

Instantly share code, notes, and snippets.

@zebreus
Last active June 13, 2022 13:36
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save zebreus/b38b33a17e618e13d2b0b62acc671180 to your computer and use it in GitHub Desktop.
Save zebreus/b38b33a17e618e13d2b0b62acc671180 to your computer and use it in GitHub Desktop.
Move files to another git repository without losing their history
#!/bin/bash
# Specify source repo
SOURCE_REPO="git@github.com:Zebreus/pruefungsplaner-scheduler.git"
SOURCE_BRANCH="master"
CREATE_TEMP_SOURCE_DIRECTORY=false
CLONE_SOURCE_REPO=true
SOURCE_REPO_DIRECTORY_NAME="source"
SOURCE_REPO_DIRECTORY="."
# Specify target repo
TARGET_REPO="git@github.com:Zebreus/pruefungsplaner-datamodel.git"
TARGET_BRANCH="master"
CREATE_TEMP_TARGET_DIRECTORY=false
CLONE_TARGET_REPO=true
TARGET_REPO_DIRECTORY_NAME="target"
TARGET_REPO_DIRECTORY="."
# By default commits will be cherrypicked onto the target branch, you can change that on line 197.
COMMIT_MESSAGE="Copied files from other repo"
# Source files relative to source repo top level
SOURCE_FILES=(src/plancsvhelper.cpp src/plancsvhelper.h tests/plancsvhelpertest.cpp tests/qthelper.cpp tests/testdatahelper.h tests/testdatatest.cpp tests/data)
# Target files relative to target repo top level. If the sources array has more files, the same name is assumed
TARGET_FILES=(src/plancsvhelper.cpp include/plancsvhelper.h)
COMMAND=""
TRANSFER_DIRECTORY="transfer"
# Fill target files array
for source_id in "${!SOURCE_FILES[@]}"
do
if test -z "${TARGET_FILES[$source_id]}"
then
TARGET_FILES[$source_id]="${SOURCE_FILES[$source_id]}"
fi
done
# Check that all target and source files have relative paths
for source_id in "${!TARGET_FILES[@]}"
do
if [[ "${TARGET_FILES[$source_id]:0:1}" == '/' || "${TARGET_FILES[$source_id]:0:1}" == '~' ]]
then
>&2 echo "You have to use relative paths for the target files. (Offending path : ${TARGET_FILES[$source_id]}"
exit 1
fi
if [[ "${SOURCE_FILES[$source_id]:0:1}" == '/' || "${SOURCE_FILES[$source_id]:0:1}" == '~' ]]
then
>&2 echo "You have to use relative paths for the source files. (Offending path : ${SOURCE_FILES[$source_id]}"
exit 1
fi
done
# Create temporary source directory or check given one
if test $CREATE_TEMP_SOURCE_DIRECTORY = true
then
SOURCE_REPO_DIRECTORY=$(mktemp -d -t gitmove-XXXXXXXXXX)
TMP_FILES[0]="$SOURCE_REPO_DIRECTORY"
trap 'for file in "${TMP_FILES[@]}" ; do rm -rf $file ; done' EXIT
else
if ! test -d "$SOURCE_REPO_DIRECTORY"
then
>&2 echo "Cannot use $SOURCE_REPO_DIRECTORY, as it does not exist"
exit 1
fi
fi
# Create temporary target directory or check given one
if test $CREATE_TEMP_TARGET_DIRECTORY = true
then
TARGET_REPO_DIRECTORY=$(mktemp -d -t gitmove-XXXXXXXXXX)
TMP_FILES[1]="$TARGET_REPO_DIRECTORY"
trap 'for file in "${TMP_FILES[@]}" ; do rm -rf $file ; done' EXIT
else
if ! test -d "$TARGET_REPO_DIRECTORY"
then
>&2 echo "Cannot use $TARGET_REPO_DIRECTORY, as it does not exist"
exit 1
fi
fi
# Clone source repository, or check
if test $CLONE_SOURCE_REPO = true
then
# Create directory for git repo. This should only create the last directory, because the script will have already exited if $SOURCE_REPO_DIRECTORY does not exist
mkdir -p "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
if ls -A "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
then
find "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}/" -mindepth 1 -delete
fi
if ! git ls-remote "$SOURCE_REPO"
then
>&2 echo "Invalid source repo url $SOURCE_REPO"
exit 1
fi
if ! git clone "$SOURCE_REPO" "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
then
>&2 echo "Failed to clone source repo to ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
exit 1
fi
else
if ! test -d "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
then
>&2 echo "Cannot use ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME} as source directory, because it does not exist"
exit 1
elif ! test -z "$(cd ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME} ; git rev-parse --show-prefix 2>&1)"
then
>&2 echo "Cannot use ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME} as source directory, because it does not contain a git repository"
exit 1
fi
fi
# Clone target repository, or check
if test $CLONE_TARGET_REPO = true
then
# Create directory for git repo. This should only create the last directory, because the script will have already exited if $SOURCE_REPO_DIRECTORY does not exist
mkdir -p "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
if ls -A "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
then
find "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}/" -mindepth 1 -delete
fi
if ! git ls-remote "$TARGET_REPO"
then
>&2 echo "Invalid target repo url $TARGET_REPO"
exit 1
fi
if ! git clone "$TARGET_REPO" "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
then
>&2 echo "Failed to clone target repo to ${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
exit 1
fi
else
if ! test -d "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
then
>&2 echo "Cannot use ${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME} as target directory, because it does not exist"
exit 1
elif ! test -z "$(cd ${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME} ; git rev-parse --show-prefix 2>&1)"
then
>&2 echo "Cannot use ${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME} as target directory, because it does not contain a git repository"
exit 1
fi
fi
# Verify, that source files exist
for file in "${SOURCE_FILES[@]}"
do
if ! test -e "${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}/$file"
then
>&2 echo "Can not copy $file, from source, because it does not exist"
exit 1
fi
done
# Verify, that target files do not exist
for file in "${TARGET_FILES[@]}"
do
if test -e "${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}/$file"
then
>&2 echo "Can not copy $file, to target, because a file with the same name does already exist"
exit 1
fi
done
# Add target file directory creation to command
for file in "${TARGET_FILES[@]}"
do
COMMAND="${COMMAND}mkdir -p '$(dirname ${TRANSFER_DIRECTORY}/$file)' ; "
done
# Add moves to command
for source_id in "${!SOURCE_FILES[@]}"
do
COMMAND="${COMMAND}test -e '${SOURCE_FILES[$source_id]}' && mv '${SOURCE_FILES[$source_id]}' '${TRANSFER_DIRECTORY}/${TARGET_FILES[$source_id]}' || echo 'Nothing to do for ${SOURCE_FILES[$source_id]}' ; "
done
echo $(pwd)
source_git="git -C ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME}"
target_git="git -C ${TARGET_REPO_DIRECTORY}/${TARGET_REPO_DIRECTORY_NAME}"
$source_git remote | xargs -n1 $source_git remote remove
# Filter source
$source_git filter-branch --force --tree-filter "$COMMAND"
$source_git filter-branch --force --subdirectory-filter $TRANSFER_DIRECTORY -- --all
# Merge source into target
remote_name=$(cat /dev/urandom | tr -dc 'a-zA-Z' | fold -w 16 | head -n 1)
$target_git remote add -t ${SOURCE_BRANCH} $remote_name "$(readlink -e ${SOURCE_REPO_DIRECTORY}/${SOURCE_REPO_DIRECTORY_NAME})"
$target_git fetch $remote_name
# Comment one of these two lines
$target_git merge --allow-unrelated-histories -m "$COMMIT_MESSAGE" "${remote_name}/${SOURCE_BRANCH}"
#$target_git cherry-pick "..${remote_name}/${SOURCE_BRANCH}"
$target_git remote rm $remote_name
exit 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment