/* cmd-replay.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */




#include "config-options.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/vu-dash.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/dir-as-cwd.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/my.h"
#include "tla/libarch/missing.h"
#include "tla/libarch/whats-new.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/archive.h"
#include "tla/libarch/pristines.h"
#include "tla/libarch/build-revision.h"
#include "tla/libarch/replay.h"
#include "tla/libarch/cmd.h"
#include "tla/libarch/cmd-replay.h"
#include "tla/libarch/cmdutils.h"



static t_uchar * usage = "[options] [version/revision...]";
static t_uchar * version_string = (cfg__std__package " from regexps.com\n"
                                   "\n"
                                   "Copyright 2003 Tom Lord\n"
                                   "\n"
                                   "This is free software; see the source for copying conditions.\n"
                                   "There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A\n"
                                   "PARTICULAR PURPOSE.\n"
                                   "\n"
                                   "Report bugs to " cfg__tla_bug_mail ".\n"
                                   "\n"
                                   cfg__std__release_id_string
                                   "\n");

#define OPTS(OP) \
  OP (opt_help_msg, "h", "help", 0, \
      "Display a help message and exit.") \
  OP (opt_long_help, "H", 0, 0, \
      "Display a verbose help message and exit.") \
  OP (opt_version, "V", "version", 0, \
      "Display a release identifier string\n" \
      "and exit.") \
  OP (opt_archive, "A", "archive", 1, \
      "Override `my-default-archive'") \
  OP (opt_list, 0, "list FILE", 1, \
      "read a list of patches to apply") \
  OP (opt_new, 0, "new", 0, \
      "replay only new patches") \
  OP (opt_reverse, 0, "reverse", 0, \
      "reverse the named patch") \
  OP (opt_forward, "N", "forward", 0, \
      "pass the --forward option to `patch'") \
  OP (opt_dir, "d", "dir DIR", 1, \
      "Operate on project tree in DIR (default `.')") \
  OP (opt_dest, 0, "dest DEST", 1, \
      "Instead of modifying the project tree in-place,\n" \
      "make a copy of it to DEST and apply the result to that") \
  OP (opt_skip_present, 0, "skip-present", 0, \
      "skip patches that contain 1 or more patch logs already in this tree") \
  OP (opt_unescaped, 0, "unescaped", 0, \
      "show filenames in unescaped form")


t_uchar arch_cmd_replay_help[] = ("apply revision changesets to a project tree\n"

                                  "The result is formed by applying patches in the latest revision of\n"
                                  "VERSION (or the default version of the project tree), stopping after\n"
                                  "the first patch that causes conflicts.  If multiple VERSIONs are\n"
                                  "specified, they are applied in turn.\n"
                                  "\n"
                                  "If one or more specific REVISIONs (including patch-levels) is specified\n"
                                  "instead, only those patch sets, and no others, will be applied.\n"
                                  "\n"
                                  "With the --list option, read a list of patches to apply from FILE\n"
                                  "(- for standard input).  Complete revision names should be listed, one\n"
                                  "per line.  replay will stop at the first patch in the list that causes\n"
                                  "a merge conflict, leaving behind files with names of the form:\n"
                                  "\n"
                                  "   ,,replay.conflicts-in --  the name of the patch that caused conflicts\n"
                                  "\n"
                                  "   ,,replay.remaining    --  the list of patches not yet applied\n"
                                  );

enum options
{
  OPTS (OPT_ENUM)
};

static struct opt_desc opts[] =
{
  OPTS (OPT_DESC)
    {-1, 0, 0, 0, 0}
};



enum op
{
  arch_replay_op_unspecified,
  arch_replay_op_missing,
  arch_replay_op_exact,
  arch_replay_op_list,
  arch_replay_op_new,
};

int
arch_cmd_replay (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * default_archive = 0;
  t_uchar * list_file = 0;
  t_uchar * dir = 0;
  t_uchar * dest = 0;
  int reverse = 0;
  int forward = 0;
  int skip_present = 0;
  enum op action = arch_replay_op_unspecified;
  int escape_classes = arch_escape_classes;
  int exit_status = 0;

  vu_push_dash_handler (0);
  safe_buffer_fd (1, 0, O_WRONLY, 0);

  option = 0;

  while (1)
    {
      o = opt_standard (lim_use_must_malloc, &option, opts, &argc, argv, program_name, usage, version_string, arch_cmd_replay_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      switch (o)
        {
        default:
          safe_printfmt (2, "unhandled option `%s'\n", option->opt_string);
          panic ("internal error parsing arguments");

        usage_error:
          opt_usage (2, argv[0], program_name, usage, 1);
          exit (1);

          /* bogus_arg: */
          safe_printfmt (2, "ill-formed argument for `%s' (`%s')\n", option->opt_string, option->arg_string);
          goto usage_error;

        case opt_archive:
          {
            lim_free (0, default_archive);
            default_archive = str_save (0, option->arg_string);
            break;
          }

        case opt_list:
          {
            action = arch_replay_op_list;
            lim_free (0, list_file);
            list_file = str_save (0, option->arg_string);
            break;
          }

        case opt_new:
          {
            action = arch_replay_op_new;
            break;
          }

        case opt_dir:
          {
            lim_free (0, dir);
            dir = str_save (0, option->arg_string);
            break;
          }

        case opt_dest:
          {
            lim_free (0, dest);
            dest = str_save (0, option->arg_string);
            break;
          }

        case opt_reverse:
          {
            reverse = 1;
            break;
          }

        case opt_forward:
          {
            forward = 1;
            break;
          }

        case opt_skip_present:
          {
            skip_present = 1;
            break;
          }

	case opt_unescaped:
	  {
	    escape_classes = 0;
	    break;
	  }
        }
    }

  if (! dir)
    dir = str_save (0, ".");

  if (default_archive && !arch_valid_archive_name (default_archive))
    {
      safe_printfmt (2, "%s: invalid archive name (%s)\n",
                     argv[0], default_archive);
      exit (1);
    }

  {
    t_uchar * tree_root = 0;

    tree_root = arch_tree_root (0, dir, 0);
    if (!tree_root)
      {
        safe_printfmt (2, "%s: dir not in a project tree (%s)\n",
                       argv[0], dir);
        exit (1);
      }

    if (dest)
      {
        t_uchar * dest_dir_spec = 0;
        t_uchar * dest_dir = 0;
        t_uchar * dest_tail = 0;

        dest_dir_spec = file_name_directory_file (0, dest);
        if (!dest_dir_spec)
          dest_dir_spec = str_save (-0, ".");
        dest_dir = directory_as_cwd (dest_dir_spec);
        dest_tail = file_name_tail (0, dest);

        lim_free (0, dest);
        dest = file_name_in_vicinity (0, dest_dir, dest_tail);

        lim_free (0, dest_dir_spec);
        lim_free (0, dest_dir);
        lim_free (0, dest_tail);
      }

    /* Validate arguments.  */
    if (argc == 1)
      /* No arguments.  */
      {
        if (action == arch_replay_op_unspecified)
          action = arch_replay_op_missing; /* use default */
      }
    else
      /* At least one version/revision argument.  */
      {
        int i;

        if (action == arch_replay_op_list)
          {
            safe_printfmt (2, "%s: --list requires no arguments\n", argv[0]);
            exit (1);
          }

        /* If no specific operation was specified, pick one based on
           what sort of argument the user gave.  */
        if (action == arch_replay_op_unspecified)
          {
            if (arch_valid_package_name (argv[1], arch_maybe_archive,
                                         arch_req_patch_level, 1))
              action = arch_replay_op_exact; /* a revision arg */
            else
              action = arch_replay_op_missing; /* (maybe) a version arg */
          }

        /* Make sure all arguments are syntactically correct.  */
        for (i = 1; i < argc; i++)
          if (! arch_valid_package_name (argv[i], arch_maybe_archive,
                                         (action == arch_replay_op_exact
                                          ? arch_req_patch_level
                                          : arch_req_version),
                                         0))
            {
              safe_printfmt (2, "%s: invalid %s spec (%s)\n",
                             argv[0],
                             (action == arch_replay_op_exact
                              ? "revision"
                              : "version"),
                             argv[i]);
              exit (1);
            }
      }

    if (dest)
      {
        rel_table to_copy = rel_table_nil;
        const int full_meta = arch_tree_has_meta_flag (tree_root);

        safe_printfmt (1, "* copying target tree\n");
        safe_flush (1);

        to_copy = arch_source_inventory (tree_root, 1, 1, 0);
        safe_mkdir (dest, 0777);
        copy_file_list (dest, tree_root, to_copy, full_meta);

        rel_free_table (to_copy);
      }
    else
      {
        dest = str_save (0, tree_root);
      }

    switch (action)
      {
      default:
        {
          panic ("internal error -- unrecognized `op'");
          break;                /* notreached */
        }

      case arch_replay_op_exact:
        {
          int i;

          for (i = 1; i < argc && exit_status == 0; i++)
            {
              t_uchar * archive = arch_parse_package_name (arch_ret_archive, default_archive, argv[i]);
              struct arch_archive *arch = arch_archive_connect (archive, 0);
              t_uchar * revision = arch_parse_package_name (arch_ret_non_archive, 0, argv[i]);
	      arch_check_for (arch, arch_req_patch_level, revision);

	      if ((!skip_present) || (!arch_revision_has_present_patch (tree_root, arch, revision)))
		exit_status = arch_replay_exact (1, 0, dest, arch, revision, reverse, forward, escape_classes);

              lim_free (0, revision);
              lim_free (0, archive);
              arch_archive_close (arch);
            }
          break;
        }

      case arch_replay_op_missing:
      case arch_replay_op_new:
        {
          int total_records = 0;
          int arg;

          if (reverse)
            {
              safe_printfmt (2, "%s: --reverse requires a specific revision\n",
                             argv[0]);
              exit (1);
            }

          /* Note the trick we use here:  we use a do-while loop to
           * iterate over the arguments; in the case where the initial
           * loop index is already past the loop bound, the loop will
           * still be executed once, which the loop body will consider
           * to mean `use the default'.
           */
          arg = 1;
          do
            {
              t_uchar * src_spec = 0;
              t_uchar * archive = 0;
              struct arch_archive * arch = 0;
              t_uchar * version = 0;
              rel_table revisions = rel_table_nil;
              int records, x;

              if (arg == argc)
                /* Actually no argument were specified, use the default.  */
                src_spec = arch_tree_version (tree_root);
              else
                src_spec = argv[arg];

              archive = arch_parse_package_name (arch_ret_archive, default_archive, src_spec);
              arch = arch_archive_connect (archive, 0);
 
              version = arch_parse_package_name (arch_ret_non_archive, 0, src_spec);
	      arch_check_for (arch, arch_req_version, src_spec);

              if (action == arch_replay_op_missing)
                revisions = arch_missing (tree_root, arch, version, skip_present);
              else
                revisions = arch_whats_new (tree_root, arch, version, skip_present);

              records = rel_n_records (revisions);
              total_records += records;

              for (x = 0; x < records; ++x)
                {
                  const t_uchar * t;
                  t_uchar * new_t = 0;

                  t = rel_peek_str (revisions, x, 0);
                  new_t = str_alloc_cat_many (0, version, "--", t, str_end);
                  rel_set_taking (revisions, x, 0, rel_make_field_str (new_t));

                  lim_free (0, new_t);
                }

              exit_status = arch_replay_list (1, 0, dest, arch, revisions, 0, forward, escape_classes);

              lim_free (0, version);
              rel_free_table (revisions);
              lim_free (0, archive);
              arch_archive_close (arch);
            }
          while (exit_status == 0 && ++arg < argc);

          if (total_records == 0)
              safe_printfmt (1, "* tree is already up to date\n");

          break;
        }

      case arch_replay_op_list:
        {
          int in_fd = -1;
          rel_table revisions = rel_table_nil;
          int x;

          in_fd = safe_open (list_file, O_RDONLY, 0);
          revisions = rel_read_table (in_fd, 1, argv[0], list_file);

          for (x = 0; x < rel_n_records (revisions); ++x)
            {
              if (!arch_valid_package_name (rel_peek_str (revisions, x, 0), arch_maybe_archive, arch_req_patch_level, 0))
                {
                  safe_printfmt (2, "%s: invalid revision name in input list (%s)\n",
                                 argv[0], rel_peek_str (revisions, x, 0));
                  exit (1);
                }
            }

          exit_status = arch_replay_fqlist (1, 0, dest, default_archive, revisions,
                                            reverse, forward, escape_classes);

          safe_close (in_fd);
          rel_free_table (revisions);
          break;
        }
      }

    lim_free (0, tree_root);
  }

  lim_free (0, dir);
  lim_free (0, dest);
  lim_free (0, default_archive);
  lim_free (0, list_file);

  if (exit_status)
    {
      safe_printfmt (2, "\nreplay: conflicts occured during replay\n");
    }

  return exit_status;
}




/* tag: Tom Lord Mon Jun  2 16:50:36 2003 (cmd-replay.c)
 */
