#!/bin/bash # Migrate assignments for Gitea-Issues after a Gitea2Gitea-Migration # 2024 MILA, Benjamin Winter # # 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