/* cmd-undo.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/vu/safe-vfdbuf.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libarch/namespace.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/patch-logs.h"
#include "tla/libarch/local-cache.h"
#include "tla/libarch/proj-tree-lint.h"
#include "tla/libarch/inode-sig.h"
#include "tla/libarch/undo.h"
#include "tla/libarch/cmdutils.h"
#include "tla/libarch/cmd.h"
#include "tla/libarch/cmd-undo.h"
#include "tla/libarch/invent.h"


/* __STDC__ prototypes for static functions */


static t_uchar * usage = "[options] [revision] [-- file ...]";
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_dir, "d", "dir DIR", 1, \
      "cd to DIR first") \
  OP (opt_output_dir, "o", "output PATCH-DIR", 1, \
      "save changeset in PATCH-DIR") \
  OP (opt_no_output, "n", "no-output", 0, \
      "do not save the changeset") \
  OP (opt_forward, "N", "forward", 0, \
      "pass the --forward option to `patch'") \
  OP (opt_quiet, "q", "quiet", 0, \
      "no progress reports while computing changeset") \
  OP (opt_exclude, "x", "exclude FILE", 1, \
      "exclude FILE from the files to commit") \
  OP (opt_unescaped, 0, "unescaped", 0, \
      "show filenames in unescaped form")


t_uchar arch_cmd_undo_help[] = ("undo and save changes in a project tree\n"
                                "Compute a patch set describing the changes from REVISION\n"
                                "to the project tree containing DIR.  Save the patch in\n"
                                "PATCH-DIR (which must not already exist) and apply the patch\n"
                                "in reverse to the project tree containing DIR.\n"
                                "\n"
                                "The effect is to remove local changes in the DIR project tree\n"
                                "but save them in a convenient form.\n"
                                "\n"
				"If FILE (a list of file names) is specified, then only\n"
				"changes to those files will be undone.\n"
				"\n"
                                "If REVISION is not specified, the latest ancestor of the\n"
                                "default version of project tree is used.\n"
                                "\n"
                                "If REVISION is specified as a VERSION, the latest ancestor\n"
                                "of the project tree in that VERSION is used.\n"
                                "\n"
                                "If DIR is not specified, \".\" is assumed.\n"
                                "\n"
                                "If PATCH-DIR is not specified, a temporary file-name of the\n"
                                "matching ,,undo-* is used.\n"
                                "\n"
                                "If --no-output is specified, the patch set is not saved.\n"
                                "\n"
                                "See also \"tla redo -H\" and \"tla changes -H\".\n"); 
enum options
{
  OPTS (OPT_ENUM)
};

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



int
arch_cmd_undo (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * dir = 0;
  t_uchar * output = 0;
  t_uchar * default_archive = 0;
  int forward = 0;
  int quiet = 0;
  int no_output = 0;
  int exit_status = 0;
  int opt_end_with_double_dash = 0;
  int escape_classes = arch_escape_classes;
  rel_table exclude_list = rel_table_nil;

  dir = str_save (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_undo_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      
      if (o == opt_double_dash)
        {
          opt_end_with_double_dash = 1;
          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_dir:
          {
            lim_free (0, dir);
            dir = str_save (0, option->arg_string);
            break;
          }

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

        case opt_no_output:
          {
            no_output = 1;
            break;
          }

        case opt_forward:
          {
            forward = 1;
            break;
          }
        case opt_exclude:
          {
            t_uchar * normalized_path = normal_from_path(option->arg_string);
            rel_add_records (&exclude_list, rel_singleton_record_taking (rel_make_field_str (normalized_path)), rel_record_null);
            
            lim_free(0, normalized_path);
            normalized_path = 0;
            break;
          
          }  

        case opt_quiet:
          {
            quiet = 1;
            break;
          }
        
        case opt_unescaped:
          {
            escape_classes = 0;
            break;
          }
        }
    }

  {
    t_uchar * tree_root = 0;
    t_uchar * fqvsn = 0;
    t_uchar * tree_arch = 0;
    t_uchar * tree_version = 0;
    t_uchar * rvsnspec = 0;
    t_uchar * archive = 0;
    t_uchar * revision = 0;
    rel_table file_list = rel_table_nil;
    rel_table file_list2 = rel_table_nil;

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

    tree_root = arch_tree_root (0, dir, 0);

    if (!tree_root)
      {
        safe_printfmt (2, "%s: not in a project tree\n  dir: %s\n",
                       argv[0], dir);
        exit (2);
      }
    fqvsn = arch_tree_version (tree_root);
    if (!fqvsn)
      {
        safe_printfmt (2, "%s: no tree-version set\n  tree: %s\n",
                       argv[0], tree_root);
        exit (2);
      }
    
    tree_arch = arch_parse_package_name (arch_ret_archive, 0, fqvsn);
    tree_version = arch_parse_package_name (arch_ret_non_archive, 0, fqvsn);

    invariant (!!tree_arch);
    invariant (arch_valid_package_name (tree_version, arch_no_archive, arch_req_version, 0));

    if (argc == 1 || opt_end_with_double_dash)
      {
        t_uchar * latest_log = 0;

        latest_log = arch_highest_patch_level (tree_root, tree_arch, tree_version);

        rvsnspec = str_alloc_cat_many (0, fqvsn, "--", latest_log, str_end);

        lim_free (0, latest_log);
      }
    else
      {
        rvsnspec = arch_fqrvsn_from_tree_and_input (argv[0], argv[1], tree_root);
        if (!rvsnspec)
          {
            safe_printfmt (2, "%s: illegal revision name: %s\n", argv[0], rvsnspec);
            exit (2);
          }
      }

    
    if (argc > 2 || opt_end_with_double_dash)
      /* File arguments.  */
      {
        int argx = opt_end_with_double_dash ? 1 : 2;

        if (!opt_end_with_double_dash && str_cmp (argv[argx], "--") == 0)
          argx++;

        if (argx == argc)
          /* --, but no files specified; should this be an error?  */
          goto usage_error;

        while (argx < argc)
          rel_add_records (&file_list, rel_make_record_1_taking (rel_make_field_str (argv[argx++])), rel_record_null);
      }

    lim_free (0, fqvsn);
    lim_free (0, tree_arch);
    lim_free (0, tree_version);

    archive = arch_parse_package_name (arch_ret_archive, default_archive, rvsnspec);
    revision = arch_parse_package_name (arch_ret_non_archive, 0, rvsnspec);

    if (!output)
      {
        output = arch_next_undo_changeset (0, tree_root);
      }


    {
      struct arch_tree_lint_result * lint = 0;

      if (!quiet)
        safe_printfmt (1, "* linting the source tree\n");
      lint = arch_tree_lint (tree_root);
      if (0 > arch_print_tree_lint_report ((quiet ? 2 : 1), lint, escape_classes))
        {
          exit (1);
        }
    }

    {
      t_uchar * orig_tree = 0;
      assoc_table inode_shortcut = 0;

      orig_tree = arch_find_or_make_local_copy ((quiet ? -1 : 1), tree_root, 0, 0, archive, revision);

      if (!orig_tree)
        {
          safe_printfmt (2, "%s: no local copies to compare to (%s/%s)\n  consider `add-pristine --help'\n",
                         argv[0], archive, revision);
          exit (2);
        }

      arch_read_inode_sig (0, &inode_shortcut, tree_root, archive, revision);
      if ((rel_n_records(file_list) == 0) && (rel_n_records(exclude_list) > 0))
          file_list = arch_source_files_file_list(tree_root, 0, 0);
      
      file_list2 = rel_table_substract(file_list, exclude_list);  
        
      arch_undo ((quiet ? -1 : 1), output, no_output,
                 tree_root, orig_tree, file_list2, inode_shortcut, forward, escape_classes);

      lim_free (0, orig_tree);
      free_assoc_table (inode_shortcut);
    }


    rel_free_table (file_list);
    rel_free_table (file_list2);
    lim_free (0, rvsnspec);
    lim_free (0, tree_root);
    lim_free (0, archive);
    lim_free (0, revision);
  }


  lim_free (0, dir);
  lim_free (0, output);
  lim_free (0, default_archive);

  return exit_status;
}



/* tag: Tom Lord Wed Jun  4 23:33:25 2003 (cmd-undo-changes.c)
 */
