/* 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 "po/gettext.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/vu-dash.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/copy-file.h"
#include "libfsutils/dir-as-cwd.h"
#include "libarch/conflict-handling.h"
#include "libarch/namespace.h"
#include "libarch/project-tree.h"
#include "libarch/proj-tree-lint.h"
#include "libarch/my.h"
#include "libarch/missing.h"
#include "libarch/whats-new.h"
#include "libarch/invent.h"
#include "libarch/archive.h"
#include "libarch/pristines.h"
#include "libarch/build-revision.h"
#include "libarch/replay.h"
#include "commands/cmd.h"
#include "commands/replay.h"
#include "commands/cmdutils.h"
#include "commands/version.h"



static t_uchar * usage = N_("[options] [version/revision...]");

#define OPTS(OP) \
  OP (opt_help_msg, "h", "help", 0, \
      N_("Display a help message and exit.")) \
  OP (opt_long_help, "H", 0, 0, \
      N_("Display a verbose help message and exit.")) \
  OP (opt_version, "V", "version", 0, \
      N_("Display a release identifier string\n" \
      "and exit.")) \
  OP (opt_list, 0, "list FILE", 1, \
      N_("read a list of patches to apply")) \
  OP (opt_new, 0, "new", 0, \
      N_("replay only new patches")) \
  OP (opt_reverse, 0, "reverse", 0, \
      N_("reverse the named patch")) \
  OP (opt_dir, "d", "dir DIR", 1, \
      N_("Operate on project tree in DIR (default `.')")) \
  OP (opt_dest, 0, "dest DEST", 1, \
      N_("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, \
      N_("skip patches that contain 1 or more patch logs already in this tree")) \
  OP (opt_unescaped, 0, "unescaped", 0, \
      N_("show filenames in unescaped form"))


t_uchar arch_cmd_replay_help[] = N_("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,
};

static void
replay_version (t_uchar const * const tree_root, int const action, int const skip_present, int * total_records, int * exit_status, t_uchar const * const dest, int const escape_classes, int const reverse, struct arch_archive * arch, t_uchar const * const package, t_uchar const * const version)
{
    rel_table revisions = 0;
    int records, x;

    if (reverse)
      {
         safe_printfmt (2, "replay: --reverse requires a specific revision\n");
         exit (1);
      }

    arch_check_for (arch, arch_req_version, package);

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

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

    for (x = 0; x < records; ++x)
    {
        t_uchar * t = str_alloc_cat_many (0, version, "--", revisions[x][0], str_end);
	rel_replace_record (revisions, x, rel_make_record (t, NULL));
        lim_free (0, t);
    }

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

    rel_free_table (revisions);
}

/**
 * doesn't compile
 */

static void
process_things (t_uchar * dest, int action, int argc, char ** argv, int skip_present, int * exit_status, t_uchar * default_archive, int reverse, int escape_classes, arch_project_tree_t * tree )
{
    int total_records = 0;
    if (argc == 1)
      {
	t_uchar * package = NULL;
	struct arch_archive * arch;
	t_uchar * src_spec = arch_tree_version (tree->root);
	t_uchar * version = NULL;
	arch_project_tree_check_name (tree, &arch, &package, src_spec);
	if (!arch)
	  {
	    safe_printfmt (2, "Failed to connect to archive %s\n", src_spec);
	    *exit_status = -1;
	  }
	else
	  {
	    version = arch_parse_package_name (arch_ret_non_archive, 0, package);
	    replay_version (tree->root, action, skip_present, &total_records, exit_status, dest, escape_classes, reverse, arch, package, version);
	  }
	lim_free (0, package);
	lim_free (0, src_spec);
	lim_free (0, version);
	arch_archive_close (arch);
      }
    else
    {
        int index;
        for (index = 1; index < argc && *exit_status == 0; index++)
        {
            enum op local_action = action;
	    t_uchar * src_spec = str_save (0, argv[index]);
	    t_uchar * package = NULL;
	    t_uchar * version = NULL;
	    struct arch_archive * arch;
	    arch_project_tree_check_name (tree, &arch, &package, src_spec);
	    if (!arch)
	      {
		safe_printfmt (2, "Failed to connect to archive %s\n", src_spec);
		*exit_status = -1;
		goto done;
	      }
	    version = arch_parse_package_name (arch_ret_non_archive, 0, package);
            if (action == arch_replay_op_unspecified)
              {
                if (arch_valid_package_name (version, arch_maybe_archive, arch_req_patch_level, 1))
                    local_action = arch_replay_op_exact;
                else
                    local_action = arch_replay_op_missing;
              }
            if (local_action == arch_replay_op_exact)
	      {
		arch_check_for (arch, arch_req_patch_level, version);
		*exit_status = arch_replay_exact (1, 0, dest, arch, version, reverse, 0, escape_classes);
		total_records += 1;
	      }
            else if (local_action == arch_replay_op_missing || action == arch_replay_op_new)
	      {
		replay_version (tree->root, local_action, skip_present, &total_records, exit_status, dest, escape_classes, reverse, arch, package, version);
	      }
            else
                panic ("unknown replay operation\n");
done:
	    lim_free (0, package);
	    lim_free (0, version);
	    lim_free (0, src_spec);
	    arch_archive_close (arch);
        }
    }
    if (total_records == 0 && !*exit_status)
        safe_printfmt (1, "* tree is already up to date\n");
}

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 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, libarch_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_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_skip_present:
          {
            skip_present = 1;
            break;
          }

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

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

  {
    arch_project_tree_t * tree = arch_project_tree_new (talloc_context, dir);

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

    arch_tree_ensure_no_conflicts (tree);


    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)
      /* At least one version/revision argument.  */
      {
        if (action == arch_replay_op_list)
          {
            safe_printfmt (2, "%s: --list requires no arguments\n", argv[0]);
            exit (1);
          }
      }

    if (dest)
      {
        rel_table to_copy = 0;

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

        to_copy = arch_source_inventory (tree, 1, 1, 0);
        safe_mkdir (dest, 0777);
        copy_file_list (dest, tree->root, to_copy);

        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:
      case arch_replay_op_missing:
      case arch_replay_op_unspecified:
      case arch_replay_op_new:
	process_things (dest, action, argc, argv, skip_present, &exit_status, default_archive, reverse, escape_classes, tree);
	break;

      case arch_replay_op_list:
        {
          int in_fd = -1;
          rel_table revisions = 0;
          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 (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], revisions[x][0]);
                  exit (1);
                }
            }

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

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

    arch_project_tree_delete (tree);
  }

  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 occurred during replay\n");
    }

  return exit_status;
}




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


syntax highlighted by Code2HTML, v. 0.9.1