Skip to content

Instantly share code, notes, and snippets.

@jwarkentin
Created March 8, 2024 09:43
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 jwarkentin/0cd53beda345afde53cf1464237f7f32 to your computer and use it in GitHub Desktop.
Save jwarkentin/0cd53beda345afde53cf1464237f7f32 to your computer and use it in GitHub Desktop.
Migrate stashes and branches from an old git repo to a new clone
#!/usr/bin/env bash
TMP_SRC_REMOTE="git-stash-copy-source"
TMP_TARGET_REMOTE="git-stash-copy-target"
FROMDIR="$( cd "$1" && pwd )"
TODIR="$( cd "$2" && pwd )"
if ! cd "$FROMDIR" && git rev-parse --git-dir 2> /dev/null ; then
echo "Error: \"$FROMDIR\" is not a git repository"
exit 1
fi
if ! cd "$TODIR" && git rev-parse --git-dir 2> /dev/null ; then
echo "Error: \"$TODIR\" is not a git repository"
exit 1
fi
UNTRACKED_FILES="$(git -C "$TODIR" ls-files --others --exclude-standard | wc -l)"
UNSTAGED_FILES="$(git -C "$TODIR" diff --name-only | wc -l)"
STAGED_FILES="$(git -C "$TODIR" diff --cached --name-only | wc -l)"
if [ $UNTRACKED_FILES -gt 0 ] || [ $UNSTAGED_FILES -gt 0 ] || [ $STAGED_FILES -gt 0 ]; then
echo "Error: \"$TODIR\" has uncommitted changes"
exit 1
fi
printf "From Dir: $FROMDIR\nTo Dir: $TODIR\n"
read -p "Continue? [Y/n] " -n 1 -r
printf "\n"
if [[ ! $REPLY =~ ^[Yy]$ ]]; then
exit 1
fi
printf "\n"
echo "Adding old repository as temporary remote \"$TMP_SRC_REMOTE\""
git -C "$TODIR" remote add $TMP_SRC_REMOTE "$FROMDIR" > /dev/null 2>&1
CURRENT_BRANCH="$(git -C "$TODIR" branch --show-current)"
CURRENT_HASH="$(git -C "$TODIR" rev-parse HEAD)"
CURRENT_REF=$([[ -z "$CURRENT_BRANCH" ]] && echo "$CURRENT_HASH" || echo "$CURRENT_BRANCH")
NUM_STASHES="$(git -C "$FROMDIR" stash list | wc -l)"
for i in $(seq $(($NUM_STASHES - 1)) 0); do
printf "\n"
echo "Copying stash@{$i}"
STASH_MSG="$(git -C "$FROMDIR" log --format=%B -n 1 stash@{$i})"
PARENT_COMMIT="$(git -C "$FROMDIR" rev-parse stash@{$i}^)"
PATCH="$(git -C "$FROMDIR" stash show -p stash@{$i})"
# git -C "$TODIR" checkout "$PARENT_COMMIT" > /dev/null 2>&1
if ! git -C "$TODIR" checkout "$PARENT_COMMIT" > /dev/null 2>&1 ; then
echo "Could not find parent commit in local copy. Fetching from remote."
if ! git -C "$TODIR" -c uploadpack.allowReachableSHA1InWant fetch --depth=1 $TMP_SRC_REMOTE "$PARENT_COMMIT" ; then
echo "Error: Could not fetch from remote. Skipping stash."
else
git -C "$TODIR" checkout "$PARENT_COMMIT" > /dev/null 2>&1 || exit 1
fi
fi
echo "$PATCH" | git -C "$TODIR" apply
git -C "$TODIR" add .
git -C "$TODIR" stash save -m "$STASH_MSG"
done
git -C "$TODIR" checkout "$CURRENT_REF" > /dev/null 2>&1
printf "\n"
echo "Deleting temporary remote \"$TMP_SRC_REMOTE\""
git -C "$TODIR" remote remove $TMP_SRC_REMOTE
printf "\n"
read -p "Would you like to copy your branches? [Y/n] " -n 1 -r
printf "\n"
if [[ $REPLY =~ ^[Yy]$ ]]; then
echo "Adding new repository as temporary remote \"$TMP_TARGET_REMOTE\""
git -C "$FROMDIR" remote add $TMP_TARGET_REMOTE "$TODIR" > /dev/null 2>&1
BRANCHES="$(git -C "$FROMDIR" for-each-ref --format='%(refname:short)' refs/heads/)"
echo "$BRANCHES"
for BRANCH in $BRANCHES; do
if [[ $BRANCH != "main" && $BRANCH != "master" ]]; then
printf "\n"
echo "Copying branch \"$BRANCH\""
git -C "$FROMDIR" push $TMP_TARGET_REMOTE "$BRANCH"
fi
done
echo "Deleting temporary remote \"$TMP_TARGET_REMOTE\""
git -C "$FROMDIR" remote remove $TMP_TARGET_REMOTE
fi
printf "\n"
echo "Done."
@jwarkentin
Copy link
Author

After downloading the file, usage is simple:

chmod u+x ./migrate-git-repo.sh
./migrate-git-repo.sh <path-to-old-repo> <path-to-new-repo>

Relative paths are fine. It will prompt to confirm before starting and ask whether you want to copy branches or just copy stashes.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment