/* cmd-import.c
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *               2005 Canonical Ltd.
 *
 * 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/os/errno.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "hackerlab/fs/file-names.h"
#include "libfsutils/file-contents.h"
#include "libarch/namespace.h"
#include "libarch/project-tree.h"
#include "libarch/patch-logs.h"
#include "libarch/archive.h"
#include "libarch/invent.h"
#include "libarch/proj-tree-lint.h"
#include "libarch/patch-id.h"
#include "libarch/archive-setup.h"
#include "libarch/import.h"
#include "commands/cmd.h"
#include "commands/cmdutils.h"
#include "commands/import.h"
#include "commands/version.h"
#include "commands/import.h"


static void arch_cmd_import_add_dir (arch_project_tree_t * tree);
static void arch_cmd_import_add_list (t_uchar const * tree_root, rel_table to_add);

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

#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_dir, "d", "dir DIR", 1, \
      N_("Import the directory DIR.")) \
  OP (opt_log, "l", "log FILE", 1, \
      N_("Commit with log file FILE.")) \
  OP (opt_summary, "s", "summary TEXT", 1, \
      N_("log with summary TEXT plus log-for-merge output") ) \
  OP (opt_automatic, "a", "automatic", 0, \
      N_("Initialise dir as a working tree, add all source-looking files in dir and import. This operation requires VERSION.") ) \
  OP (opt_log_msg, "L", "log-message TEXT", 1, \
      N_("log with TEXT"))

t_uchar arch_cmd_import_help[] = N_("archive a full-source base-0 revision\n"
                                  "Archive a from-scratch base revision of the tree DIR\n"
                                  "(or the current directory).  Use this command\n"
                                  "to create the first revision of a new project.\n"
				  "\n"
				  "If --log-message is specified without --summary, then TEXT is used both\n"
				  "as the summary and the first line of the log body.\n");

enum options
{
  OPTS (OPT_ENUM)
};

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



void
arch_cmd_import_add_list (t_uchar const * tree_root, rel_table to_add)
{
  int index;
  for (index = 0 ; index < rel_n_records (to_add) ; index++)
    {
      t_uchar * filename = file_name_in_vicinity (0, tree_root, to_add[index][0]);
      if (!arch_add_id (filename, NULL))
	{
	  /* FIXME: this should probably undo the previously successful adds. */
	  safe_printfmt (2, "Failed to add '%s'.\n", filename);
	  exit (1);
	}
      safe_printfmt (1, "%s\n", filename);
      lim_free (0, filename);
    }
}

/**
 * \brief add all the source files and directories under tree_root
 * \param tree_root the root directory of a baz tree
 */
void
arch_cmd_import_add_dir (arch_project_tree_t * tree)
{
  rel_table to_add = arch_source_inventory (tree, 0, 1, 0);
  rel_table already_added = NULL;
  while (rel_n_records(to_add) > 0)
    {
      arch_cmd_import_add_list (tree->root, to_add);
      rel_append_x (&already_added, to_add);
      to_add = rel_set_subtract (arch_source_inventory (tree, 0, 1, 0), already_added);
    }
}

int
arch_cmd_import (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  arch_project_tree_t * tree;
  t_uchar * new_tree_root = NULL;
  t_uchar * log_file = 0;
  t_uchar * log_text = 0;
  t_uchar * summary = 0;
  t_uchar * package = 0;
  int escape_classes = arch_escape_classes;
  int automatic = 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_import_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_dir:
	  {
	    new_tree_root = str_replace (new_tree_root, str_save (0, option->arg_string));
	    break;
	  }

        case opt_log:
          {
            log_file = str_save (0, option->arg_string);
            break;
          }

        case opt_log_msg:
          {
            lim_free (0, log_text);
            log_text = str_save (0, option->arg_string);
            lim_free (0, log_file);
            log_file = 0;
            break;
          }

        case opt_summary:
          {
            lim_free (0, summary);
            summary = str_save (0, option->arg_string);
            lim_free (0, log_file);
            log_file = 0;
            break;
          }

        case opt_automatic:
          {
            automatic = 1;
            break;
          }

        }
    }

  if (!new_tree_root)
      new_tree_root = str_save (0, "./");

  if (argc > 2)
      Throw (exception (EINVAL, "import only accepts one positional argument."));
  
  if (automatic && argc != 2)
    {
      Throw (exception(EINVAL, "Automatic imports require an archive/version parameter."));
    }
  else if (automatic)
    {
      arch_patch_id * patch_id = arch_patch_id_new (argv[1]);
      arch_init_tree (new_tree_root);
      arch_set_tree_version (new_tree_root, patch_id);
      talloc_free (patch_id);
    }
  tree = arch_project_tree_new (talloc_context, new_tree_root);

  if (argc == 2)
    {
      package = str_save (0, argv[1]);
    }
  else
    {
      if (!tree->fqversion)
	  Throw (exception(ENOENT, "Project tree has no version assigned."));
      package = str_save (0, tree->fqversion);
    }


  {
    t_uchar * log = 0;
    struct arch_archive * arch = 0;
    arch_patch_id patch_id;

    if (!arch_valid_package_name (package, arch_maybe_archive, arch_req_version, 0))
      {
        safe_printfmt (2, "%s: invalid version name -- %s\n",
                       argv[0], package);
        exit (1);
      }

    arch_patch_id_init (&patch_id, package);

    if (arch_is_system_package_name (arch_patch_id_version (&patch_id)))
      {
        safe_printfmt (2, "%s: user's can not import into system versions\n  version: %s\n", argv[0], arch_patch_id_branch (&patch_id));
        exit (2);
      }

    if (log_text || summary)
      {
        if (! summary)
          summary = log_text;
        log = arch_auto_log_message (tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id),
                                     summary, log_text);
      }
    else
       {
         if (!log_file)
           {
             log_file = arch_make_log_file (tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id));
           }
         else
           {
             if (safe_access (log_file, F_OK))
               {
                 safe_printfmt (2, "%s: specified log file not found (%s)\n", argv[0], log_file);
                 exit (1);
               }
           }

         if (!safe_access (log_file, F_OK))
           {
             log = file_contents (log_file);
             if (!arch_valid_log_file (log))
               {
                 safe_printfmt (2, "%s: invalid log file (%s)\n", argv[0], log_file);
                 exit (1);
               }
           }
         else
           {
             lim_free (0, log_file);
             log_file = 0;
           }
       }

    if (automatic)
      {
	/* Add patchlog for import revision. */
	arch_add_log_version (tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id));
	arch_set_tree_version (tree->root, &patch_id);

	arch_cmd_import_add_dir (tree);
      }
    else
      {
	if (!arch_has_patch_log (tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id)))
	  {
	    safe_printfmt (2, "%s: tree has no patch log for version\n    tree: %s\n    version: %s/%s\n",
			   argv[0], tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id));
	    exit (1);
	  }

	  {
	    rel_table log_entries = 0;

	    log_entries = arch_logs (tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id), 0);

	    if (log_entries)
	      {
		safe_printfmt (2, "%s: tree already has patch log entries for version\n    tree: %s\n    version: %s/%s\n",
			       argv[0], tree->root, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id));
		exit (1);
	      }
	  }

      }

    {
      struct arch_tree_lint_result * lint = 0;
      int status;

      lint = arch_tree_lint (tree);
      status = arch_print_tree_lint_report (2, lint, escape_classes);

      if (status < 0)
        {
          safe_printfmt (2, "%s: import aborted - tree lint failure.\n", argv[0]);
          exit (1);
        }
    }

    arch = arch_archive_connect_commitable_branch (arch_patch_id_archive (&patch_id), NULL);
    if (!arch)
      {
	safe_printfmt (2, "%s: import aborted - cannot connect to archive (%s).\n", argv[0], arch_patch_id_archive (&patch_id));
	exit (2);
      }

    if ( ! arch_version_exists(arch, arch_patch_id_version (&patch_id)) )
      arch_setup_archive_simple (1, arch_patch_id_archive (&patch_id), arch_patch_id_version (&patch_id));

    arch_import (arch, arch_patch_id_version (&patch_id), tree,  log);

    safe_printfmt (1, "* imported %s\n", arch_patch_id_branch (&patch_id));

    if (log_file)
      safe_unlink (log_file);

    lim_free(0, log_file);
    lim_free (0, log);
    arch_patch_id_finalise (&patch_id);
  }

  lim_free (0, new_tree_root);
  arch_project_tree_delete (tree);

  return 0;
}


syntax highlighted by Code2HTML, v. 0.9.1