# git-mq-finish-named.sh
# ------------------------------------------------------------------------------
#
# Supplementary shell script fragment; invoked via "mq_require", it performs
# the clean up actions required when an explicitly named sequence of currently
# applied Git-MQ patches are converted from managed patches to regular commits,
# removed from the Git-MQ patch series, and released from Git-MQ control.
#
# ------------------------------------------------------------------------------
#
# $Id$
#
# Written by Keith Marshall <keith@users.osdn.me>
# Copyright (C) 2019, Keith Marshall
#
#
# This file is part of the Git-MQ program suite.
#
# The Git-MQ program suite is free software: you can redistribute it
# and/or modify it under the terms of the GNU General Public Licence
# as published by the Free Software Foundation, either version 3 of
# the Licence, or (at your option) any later version.
#
# The Git-MQ program suite is distributed in the hope that it will be
# useful, but WITHOUT ANY WARRANTY; without even the implied warranty
# of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public Licence for more details.
#
# You should have received a copy of the GNU General Public Licence
# along with this program.  If not, see <https://www.gnu.org/licenses/>.
#
# ------------------------------------------------------------------------------
#
# Invoked exclusively by "git qfinish", when the "--applied" option is NOT
# specified; at least one explicit patch reference, or explicit patch range
# reference is required.
#
  test $# -gt 0 || {
    mq_complain "warning: option '--applied' has not been selected"
    mq_abort 2 "error: at least one patch name must be specified"
  }

# Selective "git qfinish ..." is a more complex operation than the
# comparatively simple "git qfinish --applied"; in this case, we must
# rewrite both the series file, and the status file, so we allocate
# temporary storage for both.
#
  mq_require mq-tmpfile
  mq_tmpfile mq_series_file_tmp '`mktemp --tmpdir="$mq_patchdir"` ||
    $fatal "cannot establish series file update cache"'
  mq_tmpfile mq_status_file_tmp '`mktemp --tmpdir="$mq_patchdir"` ||
    $fatal "cannot establish status file update cache"'

# Regardless of how we eventually update them, we still need to read
# the initial content of both the series, and the status files.
#
  mq_map_control_file_refs "$mq_patchdir" series status

# We will pipe the patch list, derived from the argument vector, into
# an awk filter, to perform the patch release operation; the following
# function provides a convenient hook for informing the backend filter
# that the argument parse has been unsuccessful.
#
  mq_fatal() { mq_complain "fatal: $*"; echo "abort:2"; }

# Actual parsing of the argument vector is accomplished by a compound
# sequence of shell commands.
#
  { for mq_ref
    do mq_rev=`git rev-parse --quiet --verify $mq_ref` &&
	 echo "$mq_rev:$mq_ref" || { git rev-list $mq_ref > /dev/null 2>&1 && {
	   for mq_rev in `git rev-list $mq_ref`
	   do echo "$mq_rev:$mq_ref"
	   done
	 } || mq_fatal "'$mq_ref' is not a valid patch range specification"
       }
    done

# The output from this compound command sequence is ultimately piped
# into the awk filter, which initially verifies that the aggregate of
# all specified arguments represents a contiguous sequence of applied
# patches, at the base of the patch queue, before deleting all of the
# associated patch tags, reassigning the "qparent", and "qbase" tags
# and writing updated copies of the status and series files, without
# any of the records associated with the deleted tags.
#
  } | awk -F: >&2 '/^abort:/ { exit status = $2; }
    function complain( msg ){ print "'"$mq_facility"': error:", msg; }
    FILENAME ~ /status$/ { ref[count=NR] = $1; seq[$1] = NR; name[$1] = $2; }
    FILENAME == "-" { if( seq[$1] ) marked[seq[$1]] = $1; else
      { commit = substr( $1, 1, 7 );
	complain( "argument \47"$2"\47 refers to commit \47"commit"\47" );
	complain( "commit \47"commit"\47 does not represent an applied patch" );
	exit status = 2;
      }
    }
    END { if( status ) exit status; if( ! count )
      { complain( "no patches have been applied" ); exit 2; }
      for( valid = idx = 1; count >= idx; idx++ )
      { if( marked[idx] )
	{ if( ! valid )
	  { complain( "patch \47" name[ref[idx]] "\47 is marked for release" );
	    complain( "but it is preceded by unfinished patches" );
	    exit 2;
	  }
	  else ++mcount;
	}
	else valid = 0;
      }
      if( ! mcount ){ complain( "no patches marked for release" ); exit 2; }
      else if( mcount == count ) taglist = " qtip";
      for( idx = 1; mcount >= idx; idx++ ) taglist = taglist" "name[ref[idx]];
      system( "git tag --delete qparent qbase" taglist " > /dev/null 2>&1" );
      if( mcount < count )
      { system( "git tag qbase "name[ref[mcount + 1]] );
	system( "git tag qparent qbase~1" );
      }
      for( tmpfile = "'"$mq_status_file_tmp"'"; count >= idx; idx++ )
       	print ref[idx]":"name[ref[idx]] > tmpfile;
      FS = " "; tmpfile = "'"$mq_series_file_tmp"'";
      for( idx = 1; ("cat '"$mq_series_file"'" | getline) > 0; )
      { if( marked[idx] && ($1 == name[ref[idx]]) )
	{ patchlist = patchlist" "$1; ++idx; }
	else print > tmpfile;
      }
      if( patchlist ) system( "cd \42'"$mq_patchdir"'\42 && rm" patchlist );
    }
  ' $mq_status_file - && {

# Provided the preceding "git qfinish" operation did not terminate
# abnormally, we update the series and status files, to reflect the
# removal of the specified patches from the queue.
#
    mq_update mq_series_file; mq_update mq_status_file
  }
#
# ------------------------------------------------------------------------------
# $RCSfile$: end of file
