/* cmd-star-merge.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 "tla/libarch/namespace.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/star-merge.h"
#include "tla/libarch/cmdutils.h"
#include "tla/libarch/cmd.h"
#include "tla/libarch/cmd-star-merge.h"



static t_uchar * usage = "[options] [FROM]";
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 tree archive") \
  OP (opt_changes, "c", "changes OUTPUT", 1, \
      "Generate but don't apply the changeset.") \
  OP (opt_reference, "r", "reference VERSION", 1 , \
      "Set reference version (default: project tree version)") \
  OP (opt_forward, "N", "forward", 0, \
      "pass the --forward option to `patch'") \
  OP (opt_three_way, "t", "three-way", 0, \
      "Perform a 3-way (diff3-style) merge.") \
  OP (opt_dir, "d", "dir DIR", 1, \
      "Operate on project tree in DIR (default `.')") \
  OP (opt_unescaped, 0, "unescaped", 0, \
      "show filenames in unescaped form")


t_uchar arch_cmd_star_merge_help[] = ("merge mutually merged branches\n"

                                      "Merge changes from FROM into the project tree, considering common\n"
                                      "ancestry from the project tree reference version, REFERENCE (which\n"
                                      "defaults to the tree version of the project tree).\n"
                                      "If FROM is not given, it defaults to the tree revision.\n"
                                      "\n"
                                      "The purpose of this command is to merge changes between development\n"
                                      "lines which may have merged in both directions.   It uses merge history\n"
                                      "to attempt to minimize the spurious conflicts that can arise from `mutual\n"
                                      "merging'.\n"
                                      "\n"
                                      "FROM indicates a revision (it may be specified as a version name,\n"
                                      "indicating the latest revision in that version).\n"
                                      "\n"
                                      "TREE is the project tree into which the merge will occur.\n"
                                      "\n"
                                      "Star-merge works by computing the most recent ANCESTOR revision of\n"
                                      "REFERENCE and FROM and then applying the changeset:\n"
                                      "\n"
                                      "         delta (ANCESTOR, FROM)\n"
                                      "\n"
                                      "to TREE.\n"
                                      "\n"
                                      "The \"most recent ancestor\" is defined as follows:\n"
                                      "\n"
                                      "MAYBE_ANCESTOR_1 is defined as the highest patch level of FROM in REFERENCE\n"
                                      "for which both TREE and FROM have a patch log. In other words, it is the latest\n"
                                      "REFERENCE revision of FROM's version already merged into TREE.\n"
                                      "\n"
                                      "MAYBE_ANCESTOR_2 is defined as the highest patch level in REFERENCE for\n"
                                      "which both FROM and REFERENCE have a patch log.  In other words, it is\n"
                                      "the latest revision of REFERENCE already merged into FROM.\n"
                                      "\n"
                                      "MAYBE_ANCESTOR_2, if it is not \"nil\", was merged into FROM at some revision\n"
                                      "of FROM's version, which we can call LAST_MERGE_INTO_FROM.\n"
                                      "\n"
                                      "If both MAYBE_ANCESTOR_1 or MAYBE_ANCESTOR_2 are nil, star-merge can do\n"
                                      "nothing.\n"
                                      "\n"
                                      "If just one of MAYBE_ANCESTOR_1 is MAYBE_ANCESTOR_2 is not nil, then that\n"
                                      "non-nil value is ANCESTOR.\n"
                                      "\n"
                                      "If both MAYBE_ANCESTOR_1 and MAYBE_ANCESTOR_2 are not nil, then\n"
                                      "LAST_MERGE_INTO_FROM and MAYBE_ANCESTOR_1 are compared (both are revisions in\n"
                                      "FROM's version). If MAYBE_ANCESTOR_1 is the later revision, then\n"
                                      "MAYBE_ANCESTOR_1 is ANCESTOR, otherwise, MAYBE_ANCESTOR_2 is ANCESTOR.\n");

enum options
{
  OPTS (OPT_ENUM)
};

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



int
arch_cmd_star_merge (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  t_uchar * default_archive = 0;
  int exit_status = 2;
  t_uchar * changeset = 0;
  t_uchar * reference = 0;
  t_uchar * dir = 0;
  int three_way = 0;
  int forward = 0;
  int escape_classes = arch_escape_classes;

  default_archive = 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_star_merge_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_changes:
          {
            lim_free (0, changeset);
            changeset = str_save (0, option->arg_string);
            break;
          }

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

        case opt_three_way:
          {
            three_way = 1;
            break;
          }

        case opt_forward:
          {
            forward = 1;
            break;
          }

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

  if (argc > 2)
    goto usage_error;

  {
    t_uchar * from_spec = 0;
    t_uchar * from_archive = 0;
    struct arch_archive * from_arch = 0;
    t_uchar * from_revision = 0;
    t_uchar * tree_root = 0;
    t_uchar * tree_version = 0;
    t_uchar * tree_archive = 0;
    t_uchar * to_archive = 0;
    t_uchar * to_version = 0;
    t_uchar * cache_dir = 0;

    tree_root = arch_tree_root (0, dir, 0);
    if (!tree_root)
      {
        safe_printfmt (2, "%s: not within a project tree (%s)\n", argv[0], dir);
        exit (2);
      }
    tree_version = arch_tree_version (tree_root);
    tree_archive = arch_parse_package_name (arch_ret_archive, 0, tree_version);

    if (!reference)
      {
        reference = str_save (0, tree_version);
        if (!reference)
          {
            safe_printfmt (2, "%s: project tree has no default version (%s)\n", argv[0], tree_root);
            exit (2);
          }
      }

    if (!arch_valid_package_name (reference, arch_maybe_archive, arch_req_version, 0))
      {
        safe_printfmt (2, "%s: invalid VERSION spec (%s)\n", argv[0], reference);
        exit (2);
      }

    to_archive = arch_parse_package_name (arch_ret_archive, default_archive, reference);
    to_version = arch_parse_package_name (arch_ret_non_archive, 0, reference);

    if (argc < 2)
      from_spec = str_save (0, to_version);
    else
      from_spec = str_save (0, argv[1]);

    if (!arch_valid_package_name (from_spec, arch_maybe_archive, arch_req_version, 1))
      {
        safe_printfmt (2, "%s: invalid FROM revision spec (%s)\n", argv[0], from_spec);
        exit (2);
      }

    from_archive = arch_parse_package_name (arch_ret_archive, tree_archive, from_spec);
    from_arch = arch_archive_connect (from_archive, 0);

    if (arch_valid_package_name (from_spec, arch_maybe_archive, arch_req_patch_level, 0))
      {
        if (argc == 2)
          arch_check_for (from_arch, arch_req_patch_level, from_spec);
        
        from_revision = arch_parse_package_name (arch_ret_non_archive, 0, from_spec);
      }
    else
      {
        t_uchar * from_version = 0;
        rel_table revisions = rel_table_nil;
        
        if (argc == 2) 
	  arch_check_for (from_arch, arch_req_version, from_spec);

        from_version = arch_parse_package_name (arch_ret_non_archive, 0, from_spec);

        revisions = arch_archive_revisions (from_arch, from_version, 2);

        if (!rel_n_records (revisions))
          {
            safe_printfmt (2, "%s: FROM version has no revisions (%s/%s)\n",
                           argv[0], from_archive, from_version);
            exit (2);
          }

        from_revision = str_save (0, rel_peek_str (revisions, rel_n_records (revisions) - 1, 0));

        lim_free (0, from_version);
        rel_free_table (revisions);
      }

    cache_dir = file_name_directory_file (0, tree_root);

    exit_status = arch_star_merge (1, from_arch, from_archive, from_revision, tree_root, 0, to_archive, to_version, cache_dir, changeset, three_way, forward, escape_classes);

    lim_free (0, from_spec);
    lim_free (0, from_archive);
    arch_archive_close (from_arch);
    lim_free (0, from_revision);

    lim_free (0, tree_archive);
    lim_free (0, tree_root);

    lim_free (0, to_archive);
    lim_free (0, to_version);

    lim_free (0, cache_dir);
  }

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

  return exit_status;
}




/* tag: Tom Lord Sat Jun 28 20:22:10 2003 (cmd-star-merge.c)
 */
