Last active
June 13, 2022 13:36
-
-
Save zebreus/b38b33a17e618e13d2b0b62acc671180 to your computer and use it in GitHub Desktop.
Move files to another git repository without losing their history
This file contains 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
#!/bin/bash | |
# Specify source repo | |
SOURCE_REPO="[email protected]: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="[email protected]: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