#!/bin/bash ########################################################################### # copies outstanding changesets for a set of tickets between branches ########################################################################### # $1 = name of source branch # $2 = name of target branch # $3 = ticket prefix # $4 = (optional) message suffix # $5... = tickets whose changesets are to be moved ########################################################################### # the mapping of branch names to file system paths is done in # ~/.fli4l/repo-info ########################################################################### rootdir=${0%/*} allowed_options="-q --continue-merge --review-merge --placebo-merge --no-merge-details" # if non-empty, a clean target branch is required clean_before_merge=1 # if non-empty, the script shows the resulting merge via "svn diff" and waits # afterwards for a RETURN before committing review_merge= # by default, all changes are merged recordonly= # by default, provide details for merge commits mergedetails=1 if [ "$(type -t usage)" != "function" ] then usage() { echo "Usage: $(basename $0) (-q|--continue-merge|--review-merge|--placebo-merge)* + (: +)?" exit 1 } fi . "$rootdir/include/config.inc" . "$rootdir/include/options.inc" . "$rootdir/include/display.inc" . "$rootdir/include/utility.inc" . "$rootdir/include/cleanup.inc" . "$rootdir/include/svn.inc" . "$rootdir/include/repo-info.inc" . "$rootdir/include/branching.inc" . "$rootdir/include/ticket.inc" case $1 in -*) warning "Unknown option: $1" usage ;; esac has_option "--continue-merge" && clean_before_merge= has_option "--review-merge" && review_merge=1 has_option "--placebo-merge" && recordonly="--record-only" has_option "--no-merge-details" && mergedetails= if [ $# -lt 5 ] then usage fi [ -n "$1" ] || error "Source branch name is missing" [ -n "$2" ] || error "Target branch name is missing" [ -n "$3" ] || error "Ticket prefix is missing" src="$1" tgt="$2" prefix="$3" msgsfx="$4" shift 4 message "Loading repository information" standout load_repo_info message "Initializing local branches" standout init_local_branch "$src" init_local_branch "$tgt" $clean_before_merge tgtmax=$(get_head_rev "$tgt") [ -n "$tgtmax" ] || error "Computing maximum revision of $tgt failed" srcrevs=$(get_revs "$src" "$tgt") tgtmerged=$(get_merged_revs "$tgt" "$src") tgtnotmerged=$(subtract_list "$srcrevs" "$(expand_list "$tgtmerged")") round=1 revs= tickets= while [ -n "$1" -a "$1" != ":" ] do tickets="$tickets $prefix-$(mask_for_regex "$1")" shift done tickets=${tickets# } shift secondary_tickets= while [ -n "$1" ] do secondary_tickets="$secondary_tickets $prefix-$(mask_for_regex "$1")" shift done secondary_tickets=${secondary_tickets# } old_tickets="" while [ -n "$tickets" -a "$(sort_list "$old_tickets")" != "$(sort_list "$tickets")" ] do message "Determining changesets to be merged (round $round)" standout old_tickets="$tickets" tickets="" while read rev do msg=$(get_log_message "$src" $rev) ok= for ticket in $old_tickets do if echo "$msg" | grep -q "\<$ticket\>" then for t in $(extract_tickets "$msg" "$prefix") do echo "$secondary_tickets" | grep -q "\<$t\>" && continue echo "$tickets" | grep -q "\<$t\>" || tickets+="$t " done ok=1 break fi done if [ -n "$ok" ] then if ! echo "$revs" | grep -q "\<$rev\>" then revs+="$rev " print_log_messages "$src" $rev $mergedetails fi fi done <<< "$tgtnotmerged" tickets="${tickets% }" round=$((round+1)) done if [ -n "$revs" -a -n "$secondary_tickets" ] then message "Determining secondary changesets to be merged" standout set -- $revs eval lastrev=\$\{$#\} [ -n "$tickets" ] && tickets+=" " while read rev do [ $rev -lt $lastrev ] || break msg=$(get_log_message "$src" $rev) ok= for ticket in $secondary_tickets do if echo "$msg" | grep -q "\<$ticket\>" then for t in $(extract_tickets "$msg" "$prefix") do echo "$tickets" | grep -q "\<$t\>" || tickets+="$t " done ok=1 break fi done if [ -n "$ok" ] then if ! echo "$revs" | grep -q "\<$rev\>" then revs+="$rev " print_log_messages "$src" $rev $mergedetails fi fi done <<< "$tgtnotmerged" fi # sort revisions revs=$(echo "$revs" | $SED 's/ /\n/g' | sort -n | $SED ':a;N;$!ba;s/\n/ /g') # make comma-separated list revs=$(echo "$revs" | $SED -e 's/[[:space:]]*$//;s/[[:space:]]\+/,/g') if [ -n "$tickets" ] then message="Tickets to be considered" [ -n "$recordonly" ] && message+=" (record only)" message "$message:" standout for t in $tickets do message " $t: " -n message "$(get_ticket_title "$t")" yellow done message "Please review and press Ctrl+C to abort or RETURN to continue..." -n standout read if [ -n "$recordonly" ] then rev_ranges="${revs#,}" else rev_ranges=$(partition_revs $src ${revs//,/ }) fi set -- $rev_ranges num_ranges=$# part=1 part_message="" push_cleanup clean_branch_if_enabled "$tgt" for range in $rev_ranges do if [ $num_ranges -gt 1 ] then part_message=" (part $part/$num_ranges)" part=$((part+1)) fi while true do message "Merging changesets for $tickets from $src to $tgt${part_message}" standout do_svn_quiet merge $recordonly -c $range --non-interactive \ "$remote_root/$src" "$local_root/$tgt" conflicts=$(get_merge_conflicts "$tgt") if [ -n "$conflicts" ] then while [ -n "$conflicts" ] do analyse_conflicts "$src" "$tgt" "$conflicts" "$tgtnotmerged" \ "${range//,/ }" max_rev -gt "$prefix" answer= while [ "$answer" != y -a "$answer" != n ] do message "Trying to resolve interactively (y/n)? " -n standout read answer done if [ "$answer" = y ] && analyse_conflicts "$src" "$tgt" "$conflicts" \ "$tgtnotmerged" "${range//,/ }" \ max_rev -gt "$prefix" 1 then review_merge=1 conflicts=$(get_merge_conflicts "$tgt") else answer= while [ "$answer" != y -a "$answer" != n ] do message "Cleanup target branch after failed merge (y/n)? " -n standout read answer done case $answer in n) cleanup_after_merge= error "Conflict(s) encountered while merging${part_message}. Please resolve conflicts manually and restart merge using --continue-merge option." ;; *) error "Conflict(s) encountered while merging${part_message}" ;; esac fi done else break fi done if [ -n "$review_merge" ] then while true do svn diff "$local_root/$tgt" | $PAGER answer= while [ "$answer" != y -a "$answer" != n -a "$answer" != r ] do message "Commit merge result? Choose (y)es, (n)o, (r)eview again: " -n standout read answer done case $answer in n) error "Merge aborted" ;; y) break ;; esac done fi message "Building commit message" standout msg="MERGE $tickets${part_message}${msgsfx:+ $msgsfx}${recordonly:+ (record only)} (from /$src)" for rev in $(decompress_revs $range) do msg+=" $(print_log_messages "$src" $rev 1 nocolor)" done message "Committing merge result into $tgt${part_message}" standout do_svn_quiet commit -m "$msg" "$local_root/$tgt" || svn_error "Committing merge result into $tgt${part_message} failed" update_branch "$tgt" || error "Update of branch $1 failed" done do_message "Success: $tickets committed to $tgt, commit [$(_get_head_rev "$remote_root/$tgt")]" green pop_cleanup else error "No changesets for specified tickets found" fi