/* status.c
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 * Copyright (C) 2004, 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/bugs/exception.h"
#include "hackerlab/cmd/main.h"
#include "libarch/chatter.h"
#include "libarch/conflict-handling.h"
#include "libarch/inode-sig.h"
#include "libarch/local-cache.h"
#include "libarch/make-changeset.h"
#include "libarch/project-tree.h"
#include "libarch/proj-tree-lint.h"
#include "libfsutils/rmrf.h"
#include "commands/cmd.h"
#include "commands/diff.h"
#include "commands/status.h"
#include "commands/version.h"



static t_uchar * usage = N_("[options] [dir]");
static void lint (struct arch_tree_lint_result * lint_result,  int categories_filter, int escape_classes);
static int lint_status (struct arch_tree_lint_result * lint_result,  int categories_filter);
static int changes (arch_project_tree_t *tree, t_uchar * program_name);

#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 and exit")) \
  OP (opt_broken_symlink, "s", "broken-symlinks", 0, \
      N_("Just list broken symlinks")) \
  OP (opt_unrecognized_files, "u", "unrecognized-files", 0, \
      N_("Just list files violating naming conventions")) \
  OP (opt_untagged_files, "t", "untagged-files", 0, \
      N_("Just list files lacking inventory ids")) \
  OP (opt_missing_files, "m", "missing-files", 0, \
      N_("Just list inventory ids lacking corresponding files")) \
  OP (opt_duplicate_ids, "d", "duplicate-ids", 0, \
      N_("Just list duplicated ids")) \
  OP (opt_conflicted, "c", "conflicted", 0, \
      N_("Just list conflicted files")) \
  OP (opt_lint, 0, "lint", 0,\
      N_("Only lint the project tree")) \
  OP (opt_diffs, 0, "diffs", 0, \
      N_("deprecated. use baz diff instead")) \
  OP (opt_strict, 0, "strict", 0, \
      N_("exit with non-0 status on _any_ oddity")) \
  OP (opt_unescaped, 0, "unescaped", 0, \
      N_("show filenames in unescaped form"))

t_uchar arch_cmd_status_help[] = N_("scan a project tree and show the tree status.\n"
                                      "\n"
                                     "Audit a source tree for various changes, the characters in the first column mean:\n"
                                     "C - conflicts\n"
				     "R - renames\n"
				     "A - adds\n"
				     "D - deletes\n"
				     "P - permissions\n"
				     "? - unexpected files\n"
                                     "By default, changes will report the status \n"
                                     "for everything within a tree. Specific changes\n"
                                     "may be ignored by passing options to the\n"
                                     "changes command\n\n"
                                     "Note: If conflicts have occurred, when you have\n"
				     "resolved them, notify baz by run baz resolved --all\n");
                                     
enum options
{
  OPTS (OPT_ENUM)
};

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



int
arch_cmd_status (t_uchar * program_name, int argc, char * argv[])
{
  int o;
  struct opt_parsed * option;
  char * dir;
  int strict = 0;
  int only_lint = 0;
  int result = 0;

  t_uint categories_filter = 0;
  int escape_classes = arch_escape_classes;
  int conflicted_only = 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_status_help, opt_help_msg, opt_long_help, opt_version);
      if (o == opt_none)
        break;
      switch (o)
        {
/* Begin Option error handling------------------------------------- */
        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;
/* Begin General options ------------------------------------- */
        case opt_strict:
          {
            strict = 1;
            break;
          }

        case opt_lint :
          {
            only_lint = 1;
	    break;
          }

/* BEGIN Lint options ------------------------------------- */
        case opt_broken_symlink:
          {
            categories_filter = categories_filter | symlinks_sans_targets;
            break;
          }

        case opt_unrecognized_files:
          {
            categories_filter = categories_filter | unrecognized_files;
            break;
          }

        case opt_untagged_files:
          {
            categories_filter = categories_filter | untagged_files;
            break;
          }

        case opt_missing_files:
          {
            categories_filter = categories_filter | ids_sans_files;
            break;
          }

        case opt_duplicate_ids:
          {
            categories_filter = categories_filter | duplicate_id_groups;
            break;
          }

        case opt_conflicted:
          {
            conflicted_only = 1;
            break;
          }

	case opt_unescaped:
	  {
	    escape_classes = 0;
	    break;
	  }
	case opt_diffs:
	  {
	    safe_printfmt(2, "This option is deprecated. Please baz diff instead\n");
	    exit(2);
	    break;
	  }
        }
    }

  if (argc > 2)
    goto usage_error;

  {
    int status;
    int outstanding_rejects = 0;
    arch_project_tree_t * tree;
    struct arch_tree_lint_result * lint_result;
    
    /* The first thing we do is determine the tree root, which
     * is used by several of the command suboptions
     */
    if (argc == 1)
      dir = ".";
    else
      dir = argv[1];

    tree = arch_project_tree_new (talloc_context, dir);

    if (!tree->root)
      {
        safe_printfmt (2, _("%s: directory is not in a project tree.\n"),
                       argv[0]);
        exit (1);
      }
    if (!tree->version)
      {
        safe_printfmt (2, _("%s: tree-version is not set.\n"),
                       argv[0]);
        exit (1);
      }
    if (!tree->fqrevision)
      {
        safe_printfmt (2, _("%s: %s is not imported yet. Try baz lint if you need to check\n"
                           "the tree inventory\n"),
                       argv[0], tree->version);
        exit (1);
      }

    /* Now, we know what directory we're working with. Now, we need to
     * run our problem finding commands. Each of the following commands
     * runs a specific set of checks for us, telling the user of changes,
     * returning to us whether or not there was success or failure. If there
     * is any failure, then we'll return a pass/fail error code back to the
     * shell
     * FIXME: Would it be useful to return a an enumerated set of bools
     * back to the shell, so that things like pybaz and fai can tell what went
     * wrong?
     */

    /* perhaps the output from lint and changes should be combined for the UI ? 
     * if so, perhaps print_compact can be given a lint report as an option 
     *
     * Note: We need to lint first, so that if things bomb, the user has a
     * better understanding of why
     */

    lint_result = arch_tree_lint (tree);
    status = lint_status (lint_result, categories_filter);
    if (strict && status != 0)
      status = 1;
    else if (status > 0)
      status = 0;

    if (! only_lint  && ! categories_filter && ! conflicted_only)
      {
	
	/* -1 is hard errors, 1 is warnings */
	if (status < 0)
	  {
	    lint (lint_result, categories_filter, escape_classes);
	    safe_printfmt(2, _("Tree is not lint clean. Unable to continue past this point\n"));
	    exit(1);
	  }
	status |= changes (tree, argv[0]);
      }

    if (! conflicted_only)
      lint (lint_result, categories_filter, escape_classes);

    outstanding_rejects = arch_tree_conflicts_exist (tree);
    if ( outstanding_rejects && ! categories_filter && ! only_lint)
      {
        rel_table conflicts = 0;
        int loop;
        int conflicts_error;
	/* maybe this should be part of lint ? */
        conflicts_error = arch_tree_show_conflicts (argv[0], tree, &conflicts);
        status |= conflicts_error;

        if (conflicts_error)
          panic(_("Unknown error with arch_tree_show_rejects\n"));
        
        /* MM: Useless (?)
           safe_printfmt(1, _("* The following %d files are conflicted:\n\n"),
           rel_n_records(conflicts)); */
        for (loop = 0; loop < rel_n_records(conflicts); loop++)
          {
            safe_printfmt(1, " C  %s\n", conflicts[loop][0]);
          }
      }


    result = status;

    lim_free (0, lint_result);
    arch_project_tree_delete (tree);
  }

  return result;
}




/* Supporting functions are below this point */


/* This function prints the lints from tree */
void
lint (struct arch_tree_lint_result * lint_result, int categories_filter, int escape_classes)
{
  if (categories_filter)
    {
      arch_print_filtered_tree_lint_report (1, lint_result, categories_filter,
                                                   escape_classes);
      return;
    }
  else
    {
      arch_print_tree_lint_report (1, lint_result, escape_classes);
      return;
    }
}

/* get the status for a lint result */
int 
lint_status (struct arch_tree_lint_result * lint_result,  int categories_filter)
{
  if (categories_filter)
    {
      return arch_tree_lint_report_filtered_status (lint_result, categories_filter);
    }
  else
    {
      return arch_tree_lint_report_status (lint_result);
    }
}

/* this function generates a summary of the non lint related changes */
int
changes (arch_project_tree_t *tree, t_uchar * program_name)
{
    t_uchar * output = 0;
    rel_table limits = 0;
    int result = 0;
    
    output = arch_diff_default_output_dir (talloc_context, tree->root, 0 );

    /****************************************************************
     * The heart of the matter.
     */
    {
      struct arch_make_changeset_report make_report = {0, };
      struct arch_changeset_report report = {0, };
      assoc_table inode_shortcut = 0;
      arch_project_tree_t * orig;

      arch_chatter (1, "* looking for %s/%s to compare with\n", tree->archive, tree->revision);

      /* should be less noisy - RBC 20041209 */
      orig = arch_find_or_make_local_tree_copy (1, tree, 0, 0, tree->archive, tree->revision);

      if (!orig)
        {
          safe_printfmt (2, "%s: no local copies to compare to (%s/%s)\n  consider `add-pristine --help'\n",
                         program_name, tree->archive, tree->revision);
          exit (2);
        }

      arch_chatter (1, "* comparing to %s/%s\n", tree->archive, tree->revision);

      arch_read_inode_sig_ids (0, &inode_shortcut, tree->root, tree->archive, tree->revision);
      arch_make_changeset (&make_report, orig, tree, output, arch_unspecified_id_tagging, arch_inventory_unrecognized, limits, inode_shortcut, 0, arch_escape_classes);
      arch_evaluate_changeset (&report, output);
      result = arch_any_changes (&report);

      if (result)
        {
	  safe_printfmt (1, "\n");
	  arch_changeset_report_print_compact (1, &report, arch_escape_classes);
        }

      arch_free_make_changeset_report_data (&make_report);
      arch_free_changeset_report_data (&report);

      rmrf_file (output);

      free_assoc_table (inode_shortcut);
      arch_project_tree_delete (orig);
    }
    talloc_free (output);
    return result;
}

/* tag: Tom Lord Mon May 12 12:25:44 2003 (tree-lint.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1