#!/bin/sh
# tla-file-log -- Output a historical log for a file in an arch project tree
# Usage: tla-file-log FILE [VERSION]
#
#  Copyright (C) 2003  Miles Bader <miles@gnu.org>
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# Written by Miles Bader <miles@gnu.org>
# 
#--
# This command outputs the log message from all patch-logs in the
# current project tree in which FILE was modified.  The format used
# contains minimal headers, and separates log message with a dashed
# line (similar to the output format of cvs log).  Only patch-logs in
# VERSION are used; if VERSION is not specified, the current tree
# version is used.

# (---- beginning of hdr.shpp ----)
# hdr.shpp

me=`basename $0`

bindir='/usr/bin'
AWK='/usr/bin/nawk'; export AWK
TLA='tla'; export TLA
SED='/bin/sed'; export SED
UUIDGEN='uuidgen'; export UUIDGEN

# (---- TLA_TOOLS_VERSION defined from ,tla-tools-version ----)
TLA_TOOLS_VERSION='srivasta@debian.org--etch/tla-tools--devo--0--patch-4
'
# (---- end of TLA_TOOLS_VERSION defined from ,tla-tools-version ----)

TLA_TOOL_PFX="${bindir+$bindir/}"
export TLA_TOOL_PFX

TLA_ESCAPE='yes'

if test "$TLA_ESCAPE" = yes; then
  TLA_UNESCAPED_OPT='--unescaped'
else
  TLA_UNESCAPED_OPT=''
fi

# Some tools get completely confused in stupid ways by non-default
# settings of LANG (like gawk, which fucks up regexp character ranges).
LANG=C; export LANG

# (---- end of hdr.shpp ----)
# (---- beginning of simple-cmd-line.shpp ----)
# simple-cmd-line.shpp -- Simple command-line processing for no-option commands

# (---- beginning of cmd-line.shpp ----)
# cmd-line.shpp -- Command-line helper functions for shell scripts

script="$0"
case "$script" in
  */*) ;;
  *)   script="${TLA_TOOL_PFX}$script";;
esac

usage ()
{
  $SED -n -e '/^\([^#]\|#-* *$\)/{s@.*@Usage: '"$me"' [--help|--version]@p;q;}'	\
         -e '/^# *Usage:/,/^# *$/{s/^# //p;q;}'				\
     < "$script"
}

short_help ()
{
  $SED -n -e '/^\([^#]\|-*# *$\|# *Usage:\)/q'				\
	 -e '/^#!/d;s/^.*-- */# /;s/^#[ 	]*//p'			\
     < "$script" | fmt
}

help_body ()
{
  $SED -n '/^ *$/q;/^#-/,/^[^#]/s/^#\( \|$\)//p' < "$script"
}

help ()
{
  usage
  short_help
  echo ''
  help_body
}

version ()
{
  local no_nl_vers=`echo "$TLA_TOOLS_VERSION"`
  echo "$me (tla-tools) $no_nl_vers"
  $SED -n '/^[^#]/q;/^#-/q;s/^# *\(Written by\)/\
\1/p' < "$script"
  $SED -n '/^[^#]/q;/^#-/q;s/^# *\(Copyright\)/\
\1/p' < "$script"
}

unrec_opt ()
{
  echo 1>&2 "$me: unrecognized option "\`"$1'"
  echo 1>&2 "Try "\`"$me --help' for more information."
}

cmd_line_err ()
{
  usage 1>&2
  echo 1>&2 "Try "\`"$me --help' for more information."
}

long_opt_val ()
{
  echo "$1" | $SED 's/^[^=]*=//'
}

short_opt_val ()
{
  echo "$1" | $SED 's/^-.//'
}

# (---- end of cmd-line.shpp ----)

case "$1" in
  --help)
    usage
    short_help
    echo ''
    echo "      --help           Display this help message and exit"
    echo "      --version        Display a release identifier string and exit"
    echo ''
    help_body
    exit 0
    ;;
  --version|-V)
    version; exit 0;;
  --)
    shift;;
  -*)
    unrec_opt "$1"; exit 1;;
esac

# (---- end of simple-cmd-line.shpp ----)

# (---- TLA_AWK_FUNS defined from tla-tools-funs.awk ----)
TLA_AWK_FUNS='# tla-tools-funs.awk -- AWK functions used by my tla-* shell scripts

function _append_cmd_arg(cmd, arg)
{
  if (arg) {
    gsub (/'\''/, "'\''\\'\'''\''", arg)
    cmd = cmd " '\''" arg "'\''"
  }
  return cmd
}

# Return a shell command string corresponding to CMD with args
# ARG1...ARG4.  CMD is included as-is, so can contain shell
# meta-characters; ARG1...ARG4 are quoted to prevent evaluation by the
# shell, and correctly handle any embedded spaces.
function make_cmd(cmd, arg1, arg2, arg3, arg4)
{
  cmd = _append_cmd_arg(cmd, arg1)
  cmd = _append_cmd_arg(cmd, arg2)
  cmd = _append_cmd_arg(cmd, arg3)
  cmd = _append_cmd_arg(cmd, arg4)
  return cmd
}

# Run CMD with args ARG1...ARG4, return non-zero if successful.
# CMD is passed raw to the shell, so can contain shell meta-characters;
# ARG1...ARG4 are quoted to prevent evaluation by the shell, and 
# correctly handle any embedded spaces.  Returns 1 if the command
# succeeded, and 0 otherwise.
function run_cmd(cmd, arg1, arg2, arg3, arg4)
{
  # print "run_cmd: " make_cmd(cmd, arg1, arg2, arg3, arg4)
  return (system(make_cmd(cmd, arg1, arg2, arg3, arg4)) == 0) ? 1 : 0
}

# Run CMD with args ARG1...ARG4, return the first line of output, or 0
# if the command returned a failure status (or the command could not be
# executed).  CMD is passed raw to the shell, so can contain shell
# meta-characters; ARG1...ARG4 are quoted to prevent evaluation by the
# shell, and correctly handle any embedded spaces.
function run_cmd_first_line(cmd, arg1, arg2, arg3, arg4  ,result)
{
  cmd = make_cmd(cmd, arg1, arg2, arg3, arg4)
  if ((cmd| getline result) <= 0)
    result = 0
  close (cmd)
  # print "run_cmd_first_line: " cmd " => " result
  return result
}

# Return the first line of FILE
function file_first_line(file)
{
  return run_cmd_first_line("sed 1q", file)
}

# Return the last line of FILE
function file_last_line(file)
{
  return run_cmd_first_line("sed -n", "$p", file)
}

# Return the number of lines in FILE
function file_num_lines(file)
{
  return run_cmd_first_line("wc -l <", file) + 0
}

function file_is_dir(file)
{
  return run_cmd("ls -d >/dev/null 2>/dev/null", file "/.")
}

function file_exists(file  ,line,result)
{
  result = (getline line < file)
  close (file)
  return result >= 0
}

# Append TEXT to FILE, with an intervening blank line if LAST_LINE
# isn'\''t blank.  Returns 1 if succesful, and 0 otherwise.
function append_text(file, text, last_line  ,append_cmd)
{
  append_cmd = make_cmd("cat >>", file)
  if (last_line && last_line !~ /^[ \t]*$/)
    print "" |append_cmd
  printf ("%s\n", text) |append_cmd
  return close (append_cmd) == 0
}

function file_explicit_id_dir(file  ,dir)
{
  dir = file
  sub (/\/[^\/]*$/, "", dir)
  sub (/.*\//, "", file)
  return ((dir && dir != file) ? dir "/.arch-ids" : ".arch-ids")
}
function file_explicit_id_file(file  ,dir)
{
  dir = file
  sub (/\/[^\/]*$/, "", dir)
  sub (/.*\//, "", file)
  return ((dir && dir != file) ? dir "/.arch-ids/" : ".arch-ids/") file ".id"
}

function file_from_explicit_id_file(file  ,dir)
{
  sub (/\.id$/, "", file)
  
  dir = file
  sub (/\/[^\/]*$/, "", dir)
  sub (/.*\//, "", file)

  sub (/\.arch-ids$/, "", dir)

  return dir file
}

function file_has_explicit_id(file)
{
  return file_exists(file_explicit_id_file(file))
}

# Returns the id-tag and tagging-method of FILE, in tla "METH_ID" format
# (i.e., explicit ids have "x_" prepended to them, and taglines have "i_").
# FILE may be in a different project tree than the current directory.
# If no id can be found for FILE, 0 is returned instead.
function file_meth_id(file  ,output,parts)
{
  if (! (file in _file_meth_ids)) {
    output = run_cmd_first_line("$TLA id 2>/dev/null", file)
    if (! output)
      return 0

    split (output, parts)
    _file_meth_ids[file] = parts[2]
  }

  return _file_meth_ids[file]
}

# Returns the id-tag of FILE.
# FILE may be in a different project tree than the current directory.
# If no id can be found for FILE, 0 is returned instead.
function file_id(file  ,id)
{
  id = file_meth_id(file)
  if (id)
    sub (/^._/, "", id)
  return id
}

# Return the (absolute) filename corresponding to ID in TREE_ROOT,
# or zero if there is none.  If DIRS_ONLY is true, only directories are
# searched for (which can be slightly faster).
function id_file(id, tree_root, dirs_only  ,level,type_opt,inven_cmd,cmd_status,inven_line,parts)
{
  level = dirs_only ? 1 : 2;

  if (_id_files_tree_level[tree_root] + 0 < level) {
    # We have not searched TREE_ROOT before, or only searched for dirs
    type_opt = (dirs_only ? " --directories" : " --both")

    inven_cmd = make_cmd("$TLA inventory --ids --source 2>/dev/null" type_opt, tree_root)

    while ((cmd_status = (inven_cmd |getline inven_line)) > 0) {
      split (inven_line, parts)

      # Add to _file_meth_ids array since we have the info handy
      _file_meth_ids[parts[1]] = parts[2]

      # Add all entries to _id_files
      sub (/^._/, "", parts[2])
      _id_files[parts[2], tree_root] = parts[1]
    }

    if (cmd_status >= 0)
      close (inven_cmd)

    _id_files_tree_level[tree_root] = level
  }

  return _id_files[id, tree_root]
}

# Return a prefix suitable for prepending to filenames in the current
# directory to make them properly project-tree-root relative, to the
# tree-root TREE_ROOT; if TREE_ROOT is zero (or not given), then the tla
# `tree-root'\'' command is invoked to compute the current tree-root.  If
# the current directory is a tree-root, then the result is the empty
# string.
function tree_root_prefix(tree_root  ,cwd)
{
  if (! tree_root)
    tree_root = run_cmd_first_line("$TLA tree-root 2>/dev/null")
  cwd = run_cmd_first_line("pwd")
  if (cwd != tree_root && substr (cwd, 1, length (tree_root)) == tree_root)
    return substr (cwd, length (tree_root) + 2) "/"
  else
    return ""
}

# Return the path to FILE in a pristine version (either a revision
# library entry or a pristine tree) of the latest revision, or 0 if one
# cannot be found.
function pristine_file(file  ,latest_rev,revlib,revlibs_cmd,revlibs_cmd_status,greedy)
{
  if (! pristine_root) {
    # Find the latest revision and make sure we have a pristine tree for
    # it; by `pristine tree'\'' we really mean revlib entry or pristine tree

    latest_rev = run_cmd_first_line("$TLA logs -f | sed -n '\''$p'\''")

    # See if we'\''ve got a revlib entry handy
    pristine_root = run_cmd_first_line("$TLA library-find --silent", latest_rev)

    if (! pristine_root) {
      # No revlib entry; can we add one to a greedy library?

      # Search for a greedy revision library
      revlibs_cmd = make_cmd("$TLA my-revision-library 2>/dev/null")
      while ((revlibs_cmd_status = (revlibs_cmd |getline revlib)) > 0) {
	greedy = run_cmd_first_line(make_cmd("$TLA library-config", revlib) \
				    "| grep '\''^greedy[?]'\''")
	if (greedy ~ /yes$/)
	  break
      }
      if (revlibs_cmd_status >= 0)
	close (revlibs_cmd)

      if (revlibs_cmd_status > 0) {
	# Found a greedy library, add an entry for this revision to it

	if (run_cmd("$TLA library-add", latest_rev))
	  pristine_root = run_cmd_first_line("$TLA library-find", latest_rev)
      }

      if (! pristine_root) {
	# Give up with revlibs and try to add a pristine tree

	if (run_cmd("$TLA add-pristine", latest_rev))
	  pristine_root = run_cmd_first_line("$TLA find-pristine", latest_rev)
      }
    }
  }

  if (pristine_root)
    return pristine_root "/" file
  else
    return 0
}

# Return a unique ID string
function unique_id() { return run_cmd_first_line("$UUIDGEN") }

# Return the filename FILE with any leading `./'\'' removed
function no_dot(file) { sub (/^\.\//, "", file); return file }

# Returns the (fully-specified) revision REV with the patch-level
# component removed
function revision_version(rev  ,archive,parts,ver)
{
  if (split (rev, parts, "/") == 2) {
    archive = parts[1]
    rev = parts[2]
  } else
    archive = 0
    
  split (rev, parts, "--")

  ver = parts[1] "--" parts[2] "--" parts[3]
  if (archive)
    ver = archive "/" ver

  return ver
}

# Returns the patch-level component of the (fully-specified) revision REV
function revision_patch_level(rev  ,parts)
{
  # Note that the archive component can have embedded -- markers too,
  # but that does not effect the result
  return parts[split (rev, parts, "--")]
}

function patch_log_file_name(rev   ,archive,parts)
{
  split (rev, parts, "/")
  archive = parts[1]
  rev = parts[2]
    
  split (rev, parts, "--")

  return								\
    "{arch}/"								\
    parts[1]								\
    "/" parts[1] "--" parts[2]						\
    "/" parts[1] "--" parts[2] "--" parts[3]				\
    "/" archive								\
    "/patch-log/" parts[4]
}

'
# (---- end of TLA_AWK_FUNS defined from tla-tools-funs.awk ----)

test "$#" -eq 1 -o "$#" -eq 2 || { cmd_line_err; exit 1; }

TREE_ROOT=`$TLA tree-root 2>/dev/null` || { echo 1>&2 "$me: Not in an arch project tree"; exit 2; }
export TREE_ROOT

FILE="$1"; shift
export FILE

$TLA logs -r -f "$@" | xargs -n1 $TLA cat-log | $AWK '
'"$TLA_AWK_FUNS"'

BEGIN {
  file = ENVIRON["FILE"]
  sub (/^\.\//, "", file)

  file = tree_root_prefix(ENVIRON["TREE_ROOT"]) file

  files[file] = file
  reset()
}

function reset()
{
  matches = 0
  action = 0
  in_hdrs = 1
  hdr_cont = 0
}

function scan_hdr(first_field, possible_action)
{
  while (first_field <= NF)
    if (no_dot($first_field) in files) {
      matches = 1
      if (possible_action)
        action = possible_action
      break
    }
  else
    first_field++

  hdr_cont = (possible_action ? possible_action : "modified")
}

function scan_rename_hdr(first_field)
{
  if (no_dot($first_field) in files) {
    matches = 1
    action = "rename " $first_field " => " $(first_field + 1)
    files[$(first_field + 1)] = file
  }
  if (no_dot($(first_field + 1)) in files) {
    matches = 1
    action = "rename " $first_field " => " $(first_field + 1)
    files[$first_field] = file
  }
  hdr_cont = "renamed"
}

function hdr_text(  txt)
{
  txt = substr ($0, length ($1) + 1)
  sub (/^ */, "", txt)
  return txt
}

/^Revision:/ { reset(); revision = hdr_text(); next }
in_hdrs && /^Archive:/ { archive = hdr_text(); next }
in_hdrs && /^Creator:/ { creator = hdr_text(); next }
in_hdrs && /^Summary: *[^ ]/ { summary = hdr_text(); next }
in_hdrs && /^Standard-date: *[^ ]/ { date = hdr_text(); next }

in_hdrs && /^Modified-files:/ { scan_hdr(2, 0); next }
in_hdrs && /^New-(files|directories):/ { scan_hdr(2, "added"); next }
in_hdrs && /^Removed-(files|directories):/ { scan_hdr(2, "removed"); next }
in_hdrs && /^Renamed-(files|directories):/ { scan_rename_hdr(2); next }
hdr_cont == "renamed" && /^[ 	]/ { scan_rename_hdr(1); next }
hdr_cont && /^[ 	]/ { scan_hdr(1); next }

in_hdrs && /^$/ {
  in_hdrs = 0
  if (matches) {
    print "----------------"
    print "Revision: " archive "/" revision
    print "Date: " date
    print "Creator: " creator
    if (action)
      print "Action: " action
    if (summary)
      print "Summary: " summary
    print ""
  }
  next
}
in_hdrs { hdr_cont = 0; next }

{ if (matches) print }
'

