gitea_assignment_migrator/migrate_assignments.sh

264 lines
7.9 KiB
Bash
Executable File

#!/bin/bash
# Migrate assignments for Gitea-Issues after a Gitea2Gitea-Migration
# 2024 MILA, Benjamin Winter <winterb+mila@uni-greifswald.de>
#
# errors
# 1 parameter error
# 2 repository-check error
# 5 internal error
# 42 command cannot be found
set -eu
readonly PROGNAME=`/usr/bin/basename $0`
readonly PROGPATH=`echo $0 | sed -e 's,[\\/][^\\/][^\\/]*$,,'`
readonly REVISION="1.5.0"
repo="" # the OLD repo
transfer="" # the NEW repo
login_old=""
login_new=""
dry_run=true
wait_time=0.25
use_colour=true
#
# FUNCTIONS
#
print_help ()
{
echo "--- $PROGNAME $REVISION"
cat << "EOF"
PURPOSE
- Migrate assignments for Gitea-Issues after a Gitea2Gitea-Migration
- issues must have the same ID on both systems
- users must have the same NAME on both systems
- users must have the right to be assigned on both systems
- example
- OLD: https://oldgitea.example.com/my_organisation/my_repository
- NEW: https://newgitea.example.com/my_organisation/my_repository
- Typical Workflow
1. migrate your repository from your OLD Gitea instance to your NEW one
2. use this script to migrate the assignments
INFORMATION
- this script will start in DRY MODE by default - see --GO parameter
- this script will check the environment and stops on any error
- this script will check the OLD and NEW repository for inconsistencies
- issue-ids are checked; titles are not checked
- assignments in new repo are checked
- the --wait time slows down the script to reduce e-mail-sending-speed
- Troubleshooting
- "Error: could not edit issue:"
- please check, if the assigned person(s) have the right to be assigned (are they in the Organisation? Collaborators?)
- "Error: The target couldn't be found."
- please check, if the --repo is the same for OLD and NEW
INSTALLATION AND PREPARATION
- install
- jq - Command-line JSON processor - https://github.com/stedolan/jq
- tea - Command line tool to interact with Gitea - https://gitea.com/gitea/tea
- create Gitea tokens for tea
- permissions on the OLD system:
- user: read
- issue: read
- permissions on the NEW system:
- user: read
- issue: read and write
- set up login via tea → "tea login add --help", i.e.
- tea login add --name OLD --url https://oldgitea.example.com --token 123456789123456789
- tea login add --name NEW --url https://newgitea.example.com --token 123459789123456789
USAGE
SWITCHES
-h | --help print this help
-c | --colour | --color disable colour output
--GO stop dry mode
PARAMETERS
-r | --repo required, i.e. "my_organisation/my_repository"
-t | --transfer optional, if the new repo has another organisation / name
if not set transfer is set to "repo"
-o | --login_old required, tea-id of your old login
-n | --login_new required, tea-id of your new login
-w | --wait wait time in s between issue handling (default 0.25)
EOF
}
error_in_or_out_not_specified ()
{
echo -e "Error: --repo, --login_old and --login_new must be specified\n";
print_help;
exit 1
}
# @param $1 "command" → the command to check for
checkCommand ()
{
command -v "$1" >/dev/null 2>&1 || { echo >&2 "Command ${1} is required but it's not installed. Aborting."; exit 42; }
}
# collects all data from the given gitea instance+repo,
# uses pagination to really get all data (limit may be set by the server)
# get_all_data_from_gitea "$login_old" "$repo"
get_all_data_from_gitea ()
{
local readonly l="$1"
local readonly r="$2"
local _collect=""
local _page=1
while true; do
# get data from repo and remove [ and ]
local _tmp=$(tea issues list --output json --login "$l" --repo "$r" --state all --limit 100 --fields index,author,assignees --page $_page | sed 's/\[//g' | sed 's/\]//g')
if [[ $(echo "[$_tmp]" | jq length) -eq 0 ]]; then
break
fi
if [[ -z "$_collect" ]] then
_collect="$_tmp"
else
_collect="$_collect,$_tmp"
fi
_page=$(($_page+1));
done
echo "[$_collect]"
return 0;
}
#
# PARAMETERS
#
if [[ "$@" == "" ]] ; then error_in_or_out_not_specified ; fi
readonly TEMP=$(getopt -o h,r:t:o:n:w:c --long help,GO,colour,color,repo:,transfer:,login_old:,login_new:,wait: -n "${PROGNAME}" -- "$@")
eval set -- "${TEMP}"
while true ; do
case "$1" in
--help|-h)
print_help
exit 0
;;
--colour|--color|-c)
use_colour=false
;;
--repo|-r)
repo="$2"
shift
;;
--transfer|-t)
transfer="$2"
shift
;;
--login_old|-o)
login_old="$2"
shift
;;
--login_new|-n)
login_new="$2"
shift
;;
--wait|-w)
wait_time="$2"
shift
;;
--GO)
dry_run=false
;;
--) shift ; break ;;
*) echo "Internal error!"
exit 5
;;
esac
shift
done
if [[ "$transfer" == "" ]]; then
transfer="$repo"
fi
# check required
if [[ -z "$repo" || -z "$transfer" || -z "$login_old" || -z "$login_new" ]] ; then error_in_or_out_not_specified ; fi
#
# START
#
if $dry_run; then
t="###\n### DRY RUN - will not change anything\n###"
if [ $use_colour = true ]; then
echo -e "\e[1m\e[38;5;214m$t\e[0m"
else
echo -e "$t"
fi
fi
# check commands
checkCommand tea
checkCommand jq
checkCommand sort
checkCommand tr
# check access and get data
echo "# checking access to OLD (\"$login_old\") and NEW (\"$login_new\")"
readonly json_old=$(get_all_data_from_gitea "$login_old" "$repo")
readonly json_new=$(get_all_data_from_gitea "$login_new" "$transfer")
echo "# running consistency checks"
readonly json_old_ids=$(echo "$json_old" | jq ".[] | .index" | sort -n)
readonly json_new_ids=$(echo "$json_new" | jq ".[] | .index" | sort -n)
# check if issues
if [[ "$json_old_ids" == "" || "$json_new_ids" == "" ]]; then echo >&2 "OLD and/or NEW repo contains no issues. Aborting."; exit 2; fi
# check same issues
if [[ "$json_old_ids" != "$json_new_ids" ]]; then
echo >&2 "OLD repositories' ids differ from NEW repository. Aborting.";
echo >&2 "OLD repositories' ids: $(echo "$json_old_ids" | tr '\n' ,)";
echo >&2 "NEW repositories' ids: $(echo "$json_new_ids" | tr '\n' ,)";
exit 2;
fi
# check if NEW.assignments are empty
readonly json_new_assignments=$(echo "$json_new" | jq --join-output ".[] | .assignees" )
if [[ "$json_new_assignments" != "" ]]; then echo >&2 "NEW repo already contains issues with assignments. Aborting."; exit 2; fi
# process data
echo "# processing data"
while IFS="¬"; read -r index author assignees; do
assignees=$(echo "$assignees" | tr ' ' ,)
echo -n "… #$index - by \"$author\" assigned to \"$assignees\""
# comment for the original author
if [ $dry_run = false ]; then
echo "migrator: this issue was created by @$author and will be assigned to '$assignees'" | tea comment --login "$login_new" --repo "$transfer" "$index" >/dev/null
fi
# assignment
t="OK"
if [[ -n $assignees ]]; then
if [ $dry_run = false ]; then
tea issue edit --login "$login_new" --repo "$transfer" --add-assignees "$assignees" "$index" >/dev/null
fi
else
t="OK (no assignee)"
fi
if [ $use_colour = true ]; then
echo -e " → \e[1m\e[48;5;22m$t\e[0m"
else
echo "$t"
fi
sleep $wait_time
done< <(echo "$json_old" | jq --raw-output '.[] | "\(.index)¬\(.author)¬\(.assignees)"')
if $dry_run; then
t="###\n### DRY RUN - nothing changed\n###"
if [ $use_colour = true ]; then
echo -e "\e[1m\e[38;5;214m$t\e[0m"
else
echo -e "$t"
fi
fi
echo "# done"
exit 0