# 
# 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 

    --silent)           shift   # ignored
                        ;;
    --quiet)            shift   # ignored
                        ;;
    --report)           shift   # ignored
                        ;;
    --verbose)          shift   # ignored
                        ;;


    -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 -n 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 -n 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
