#!/bin/sh
# 
# lock-branch.sh - lock a new version
################################################################
# Copyright (C) 2001, 2002 Tom Lord
# 
# See the file "COPYING" for further information about
# the copyright and warranty status of this work.
# 

set -e 
errname=lock-branch

################################################################
# special options
# 
# Some options are special:
# 
#	--version | -V
#	--help | -h
# 
if test $# -ne 0 ; then

  for opt in "$@" ; do
    case $opt in

      --version|-V) exec larch --version
                    ;;


      --help|-h)
		printf "lock a branch\\n"
		printf "usage: lock-branch [options] [archive/]branch-or-version\\n"
		printf "\\n"
		printf " -V --version                  print version info\\n"
		printf " -h --help                     display help\\n"
		printf "\\n"
		printf " -R --root root                specify the local archive root\\n"
		printf " -A --archive archive          specify the archive name\\n"
		printf " -e -errname errname           use ERRNAME in error messages\\n"
		printf "\\n"
		printf " -u --unlock                   release the indicated lock\\n"
		printf " --break                       break the indicated lock\\n"
		printf "\\n"
		printf "Acquire the lock for creating new versions in BRANCH.\\n"
		printf "\\n"
		printf "If a specific VERSION is specified, ensure that that version would\\n"
		printf "be the latest (highest version number).\\n"
		printf "\\n"
		printf "If VERSION would not be the latest version, or if the lock is busy\\n"
		printf "print an error message and exit with non-0 status.\\n"
		printf "\\n"
		printf "The output format is:\\n"
		printf "\\n"
		printf "	\"%%s\\n\" \"\$lockdir\" \\n"
		printf "\\n"
		printf "where LOCKDIR is the path of the lock relative to the root of the\\n"
		printf "archive.\\n"
		printf "\\n"
		exit 0
      		;;

      *)
		;;
    esac
  done
fi

################################################################
# Ordinary Options
# 
# 

archroot=
archive=
unlock=
break=
action=lock

while test $# -ne 0 ; do

  case "$1" in 

    -u|--unlock)	shift
    			unlock=--unlock
			break=
			action=unlock
    			;;
			

    --break)		shift
    			unlock=
			break=--break
			action=break
    			;;
			

    -R|--root)		shift
    			if test $# -eq 0 ; then
			  printf "lock-branch: -R and --root require an argument\\n" 1>&2
			  printf "try --help\\n" 1>&2
			  exit 1
			fi
			archroot="$1"
			shift
			;;

    -A|--archive)	shift
    			if test $# -eq 0 ; then
			  printf "lock-branch: -A and --archive require an argument\\n" 1>&2
			  printf "try --help\\n" 1>&2
			  exit 1
			fi
			archive="$1"
			shift
			;;

    -e|--errname)	shift
    			if test $# -eq 0 ; then
			  printf "lock-branch: -e and --errname require an argument\\n" 1>&2
			  printf "try --help\\n" 1>&2
			  exit 1
			fi
			errname="$1"
			shift
			;;


    -*)			printf "lock-branch: unrecognized option (%s)\\n" "$1" 1>&2
			printf "try --help\\n" 1>&2
			exit 1
			;;

    *)			break
    			;;

  esac

done



################################################################
# Ordinary Arguments
# 

if test $# -ne 1 ; then
  printf "usage: lock-branch [options] proposed-version\\n" 1>&2
  printf "try --help\\n" 1>&2
  exit 1
fi

version="$1"

################################################################
# Sanity Check and Process Defaults
# 

larch valid-package-name -e "$errname" -t -- "$version"
archive=`larch parse-package-name -R "$archroot" -A "$archive" --arch "$version"`
category=`larch parse-package-name --basename "$version"`
branch=`larch parse-package-name --package "$version"`
myid=`larch my-id -u`

################################################################
# Establish an Archive Connection
# 
if test "$WITHARCHIVE" != "$archive" ; then

  exec larch with-archive -A "$archive" \
      larch lock-branch --errname "$errname" $unlock $break "$version"
fi


################################################################
# Which Lock?
# 
# The held lock for the first version is:
# 
# 	+version-lock-held--0.0--id
# 
# The held lock for the version following M.N is:
#
# 	+version-lock-held--M.N--id
# 
# If the caller asked to lock for a specific version (the one to be
# created), make sure that it will be the latest version.
# 
# 

wftp-home
wftp-cd "$category/$branch"

set +e
basere="([a-zA-Z]([a-zA-Z0-9]*(-[a-zA-Z0-9]+)?)*)"
versionre="([0-9]+\\.[0-9]+)"

last=`( printf "%s\\n" "$branch--0.0" ; ( wftp-ls | grep -E "^($basere)(--$basere)?(--$versionre)$" ) )  \
        | sed -e "s/.*--\\([0-9].*\\)/\\1.&/" \
	| sort -t. -k 1,1rn -k 2,2rn \
	| head -1 \
	| sed -e "s/[^.]*\\.[^.]*\\.//"`
set -e


if larch valid-package-name --vsn "$version" ; then

  version=`larch parse-package-name --package-version "$version"`

  set +e
  latest=`( printf "%s\\n%s\\n" "$branch--0.0" "$version" ; ( wftp-ls | grep -E "^($basere)(--$basere)?(--$versionre)$" ) )  \
          | sed -e "s/.*--\\([0-9].*\\)/\\1.&/" \
	  | sort -t. -k 1,1rn -k 2,2rn \
  	  | head -1 \
	  | sed -e "s/[^.]*\\.[^.]*\\.//"`
  set -e

  if test "(" "$last" = "$version" ")" -o "(" "$latest" != "$version" ")" ; then
    printf "%s: trying to create out of sequence version.\\n" "$errname" 1>&2
    printf "  proposed version is %s\\n" "$version" 1>&2
    printf "  latest version is %s\\n" "$latest" 1>&2
    exit 1
  fi
fi

if test "$last" = "$branch--0.0" ; then
  lock_vsn="0.0"
  lock="+version-lock"
  locked_prefix="+version-lock-held--0.0--"
  broken_dir="+version-lock-broken--0.0"
else
  lock_vsn=`larch parse-package-name --vsn "$last"`
  lock="$last/+version-lock"
  locked_prefix="+version-lock-held--$last--"
  broken_dir="+version-lock-broken--`larch parse-package-name --vsn \"$last\"`"
fi

locked="$locked_prefix$myid"
broken="$broken_dir/,,remade-lock--$lock_vsn"

################################################################
# Do It
# 

# 
# Possible Branch States:
# 
# [A] No versions exist yet, lock is unlocked in the usual way:
#
# branch/
#   branch/+version-lock				== $lock
#     branch/+version-lock/+contents
# 
# 
# [B] Version M.N is the most recent, lock is unlocked in the usual way:
# 
# branch/
#   branch/version--M.N
#     branch/version--M.N/+version-lock			== $lock
#       branch/version--M.N/+version-lock/+contents
# 
# 
# [C] Lock was recently broken, or a lock break operation was interrupted
# after breaking the lock, but before cleaning up:
# 
# branch/
#   branch/+version-lock-broken--M.N			== $broken_dir
#     branch/+version-lock-broken--M.N/,,remade-lock--M.N == $broken
#       branch/+version-lock-broken--M.N/,,remade-lock--M.N/+contents
#     branch/+version-lock-broken--M.N/...		junk from failed transaction
# 
# 
# [D] Lock is currently held by user UID
# 
# branch/
#   branch/+version-lock-held--M.N--UID			== $locked
#     branch/+version-lock-held--M.N--UID/+contents
#       branch/+version-lock-held--M.N--UID/+contents/... (stuff being installed)
# 
# 
# [-] Junk that can be left around when cleanups don't finish:
# 
# branch/
#  branch/+version-lock-held--M.N--UID
#    (no +contents subdir)
# 
# branch/
#   branch/+version-lock-broken--M.N
#     (no ,,remade-lock--M.N subdir)
#     branch/+version-lock-broken--M.N/...		junk from failed transaction
# 
# branch/
#   branch/version--(M.N + 1)				i.e., completed next version
#     branch/version--(M.N + 1)/,,remade-lock--M.N	failed attempt to break lock
#       branch/version--(M.N + 1)/,,remade-lock--M.N/...  junk
#     branch/version--(M.N + 1)/...			revision dirs etc.
#
# in a lock dir:
# 
# ,,junk--UID--DATE--SEQ
# 

unlock_failed()
{
  printf "lock-branch: lock break unsuccessful\\n" 1>&2
  printf "  possible causes include:\\n" 1>&2
  printf "    transaction already committed\\n" 1>&2
  printf "    another user already broke the lock\\n" 1>&2
  printf "    communication with archive lost\\n" 1>&2
  printf "    a bug (hopefully not)\\n" 1>&2
  printf "\\n"
  exit 1
}

unlock_procedure()
{
  locked="$1"

  wftp-home
  wftp-cd "$category/$branch"

  if ! wftp-cd "$locked/+contents" > /dev/null 2>&1 ; then
    # [A] -> [A]
    # [B] -> [B]
    # [C] -> [C]
    # 
    printf "lock-branch: lock not held\\n" 1>&2
    printf "\\n" 1>&2
    exit 1

  else
    # there is $branch/$locked/+contents
    # 
    wftp-mkdir ",,remade-lock--$lock_vsn" > /dev/null 2>&1 \
    || true

    wftp-cd ",,remade-lock--$lock_vsn" > /dev/null 2>&1 \
    || unlock_failed

    # there is $branch/$locked/+contents/,,remade-lock--M.N
    # 
    wftp-mkdir "+contents" > /dev/null 2>&1 \
    || true

    wftp-cd "+contents" > /dev/null 2>&1 \
    || unlock_failed

    # there is $branch/$locked/+contents/,,remade-lock--M.N/+contents
    # 
    junk_name=,,junk--$myid--`date +%Y-%m-%d-%H:%M:%S--$$`

    (   wftp-rename "../../../../$broken_dir" "$junk_name" > /dev/null 2>&1 \
     && larch arch-rmrf "$junk_name" > /dev/null 2>&1 ) \
    || true

    wftp-cd ../../.. > /dev/null 2>&1 \
    || true
    
    # there is $branch/$locked
    # [D] -> [C]
    # 
    # 
    wftp-rename +contents "../$broken_dir" > /dev/null 2>&1 \
    || unlock_failed

    # unlock succeeded -- clean up
    #
    wftp-cd .. > /dev/null 2>&1 \
    || true

    # there is $branch
    #
    wftp-rmdir "$locked" > /dev/null 2>&1 \
    || true


    wftp-cd "$broken_dir"
    for f in `wftp-ls` ; do
      if test "$f" != ",,remade-lock--$lock_vsn" ; then
        larch arch-rmrf "$f"
      fi
    done
    wftp-cd ".."

    # [C] -> [A] or [B]
    #  
    wftp-rename "$broken" "$lock" > /dev/null 2>&1 \
    || true

    wftp-rmdir "$broken_dir" > /dev/null 2>&1 \
    || true
    
  fi
}


case $action in

  lock)			if wftp-cd "$locked/+contents" > /dev/null 2>&1 ; then
			  # [D] -> [D]
			  #
			  # Lock already held by this user.  Clean out any old
			  # contents.
			  # 
			  for file in `wftp-ls` ; do
			    if test "$file" != ",,remade-lock--$lock_vsn" ; then
			      larch arch-rmrf "$file"
			    fi
			  done
			  printf "%s\\n" "$category/$branch/$locked"
			  exit 0

		        elif wftp-rename "$lock" "$locked" ; then
			  # [A], [B] -> [D]
			  # 
			  # newly acquired lock
			  # 
    			  printf "%s\\n" "$category/$branch/$locked"

			elif wftp-rename "$broken" "$locked" ; then
			  # [C] -> [D]
			  #
			  # newly acquired, recently broken lock
			  # 
    			  printf "%s\\n" "$category/$branch/$locked"

			else

			  printf "%s: unable to obtain lock for version\\n" "$errname" 1>&2
			  printf "  lock may be busy\\n" 1>&2
			  printf "  see \"larch lock-branch --help\" for information\\n" 1>&2
			  printf "  about the --break option\\n" 1>&2
			  exit 1

			fi
			;;


  unlock)		unlock_procedure "$locked"
			;;

  break)		broke_it=
			for f in `wftp-ls` ; do
			  suffix=${f#$locked_prefix}

			  if test "$suffix" = "$f" ; then
			    continue
			  fi

			  wftp-home
			  wftp-cd "$category/$branch/$f/+contents" > /dev/null 2>&1 \
			  || continue

			  wftp-cd ../..
			  unlock_procedure "$f"
			  broke_it=yes
			  break
			done
			if test -z "$broke_it" ; then
			  printf "lock-branch: branch not locked\\n"
			else
			  printf "lock-branch: lock broken\\n"
			fi
			exit 0
			;;


esac
