/* changeset-report.c:
 *
 ****************************************************************
 * Copyright (C) 2003 Tom Lord
 *
 * See the file "COPYING" for further information about
 * the copyright and warranty status of this work.
 */


#include "hackerlab/bugs/panic.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/char/pika-escaping-utils.h"
#include "tla/libfsutils/read-line.h"
#include "tla/libawk/relassoc.h"
#include "tla/libfsutils/find-utils.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libarch/diffs.h"
#include "tla/libarch/changeset-utils.h"
#include "tla/libarch/changeset-report.h"


/* __STDC__ prototypes for static functions */
static void fix_mod_locs (assoc_table loc_of, rel_table table);
static void arch_print_file_metadata_diffs (int out_fd,
                                            struct arch_changeset_report * report,
                                            int escape_classes);
static void arch_print_dir_metadata_diffs (int out_fd,
                                           struct arch_changeset_report * report,
                                           int escape_classes);
static void print_custom_diffs(int fd, struct arch_changeset_report * report,
                               t_uchar * orig_dir, t_uchar * mod_dir,
                               t_uchar ** opts);

static void arch_print_file_diffs (int out_fd,
                                   struct arch_changeset_report * report,
                                   int escape_classes);
static void arch_print_added_file_diffs (int out_fd,
                                         struct arch_changeset_report * report,
                                         int escape_classes);
static void
print_removed_file_diffs (int fd, struct arch_changeset_report * report,
                          t_uchar ** opts);
static void print_file_metadata (int out_fd,
                                 struct arch_changeset_report * report,
                                 int escape_classes);
static void print_dir_metadata (int out_fd,
                                struct arch_changeset_report * report,
                                int escape_classes);
static void print_diffs_new(int fd, struct arch_changeset_report * report);

static char * no_dot (char *name);
static void report_unique_files_and_symlinks (rel_table * files, rel_table * symlinks,
                                              rel_table index,
                                              t_uchar * archive_dir, t_uchar * archive_basename);
static void find_renames (rel_table * out, rel_table common, assoc_table orig_dir_id_of, assoc_table mod_dir_id_of);




int
arch_any_changes (struct arch_changeset_report * report)
{
  return (rel_n_records (report->removed_dirs)
          || rel_n_records (report->added_dirs)

          || rel_n_records (report->removed_files)
          || rel_n_records (report->added_files)

          || rel_n_records (report->removed_symlinks)
          || rel_n_records (report->added_symlinks)

          || rel_n_records (report->renamed_files)
          || rel_n_records (report->renamed_dirs)

          || rel_n_records (report->patched_regular_files)
          || rel_n_records (report->patched_symlinks)
          || rel_n_records (report->patched_binaries)
          || rel_n_records (report->file_metadata_changed)
          || rel_n_records (report->dir_metadata_changed)
          || rel_n_records (report->symlink_to_file)
          || rel_n_records (report->file_to_symlink));
}

void
arch_evaluate_changeset (struct arch_changeset_report * report, t_uchar * path)
{
  t_uchar * orig_files_index_path = 0;
  t_uchar * orig_dirs_index_path = 0;
  t_uchar * mod_files_index_path = 0;
  t_uchar * mod_dirs_index_path = 0;

  rel_table removed_dirs_index = rel_table_nil;
  rel_table added_dirs_index = rel_table_nil;

  rel_table removed_files_and_symlinks_index = rel_table_nil;
  rel_table added_files_and_symlinks_index = rel_table_nil;

  rel_table common_files_index = rel_table_nil; /* [0] origloc [1] modloc [2] id */
  rel_table common_dirs_index = rel_table_nil; /* [0] origloc [1] modloc [2] id */

  assoc_table orig_dir_loc_of = 0;
  assoc_table orig_dir_id_of = 0;
  assoc_table orig_file_loc_of = 0;
  assoc_table orig_file_id_of = 0;
  assoc_table mod_dir_loc_of = 0;
  assoc_table mod_dir_id_of = 0;
  assoc_table mod_file_loc_of = 0;
  assoc_table mod_file_id_of = 0;


  orig_files_index_path = file_name_in_vicinity (0, path, "orig-files-index");
  orig_dirs_index_path = file_name_in_vicinity (0, path, "orig-dirs-index");
  mod_files_index_path = file_name_in_vicinity (0, path, "mod-files-index");
  mod_dirs_index_path = file_name_in_vicinity (0, path, "mod-dirs-index");

  report->orig_files_index = arch_read_changeset_index (orig_files_index_path);
  report->orig_dirs_index = arch_read_changeset_index (orig_dirs_index_path);
  report->mod_files_index = arch_read_changeset_index (mod_files_index_path);
  report->mod_dirs_index = arch_read_changeset_index (mod_dirs_index_path);

  /* just to be sure:
   */
  rel_sort_table_by_field (0, report->orig_files_index, 1);
  rel_sort_table_by_field (0, report->orig_dirs_index, 1);
  rel_sort_table_by_field (0, report->mod_files_index, 1);
  rel_sort_table_by_field (0, report->mod_dirs_index, 1);

  orig_dir_id_of = rel_to_assoc (report->orig_dirs_index, 0, 1);
  orig_dir_loc_of = rel_to_assoc (report->orig_dirs_index, 1, 0);
  orig_file_id_of = rel_to_assoc (report->orig_files_index, 0, 1);
  orig_file_loc_of = rel_to_assoc (report->orig_files_index, 1, 0);
  mod_dir_id_of = rel_to_assoc (report->mod_dirs_index, 0, 1);
  mod_dir_loc_of = rel_to_assoc (report->mod_dirs_index, 1, 0);
  mod_file_id_of = rel_to_assoc (report->mod_files_index, 0, 1);
  mod_file_loc_of = rel_to_assoc (report->mod_files_index, 1, 0);


  /****************************************************************
   * Removed and added directories
   */

  {
    t_uchar * original_only_dir_metadata_file = 0;
    t_uchar * modified_only_dir_metadata_file = 0;
    rel_table original_only_dir_metadata = rel_table_nil;
    rel_table modified_only_dir_metadata = rel_table_nil;
    rel_table orig_dirs_by_name = rel_table_nil;
    rel_table mod_dirs_by_name = rel_table_nil;
    rel_table t;        /* [0] name [1] id  -- sort 0*/
    rel_table t2;       /* [0] name [1] id [2] name -- sort 0 -- of dirs sans perms */

    original_only_dir_metadata_file = file_name_in_vicinity (0, path, "original-only-dir-metadata");
    modified_only_dir_metadata_file = file_name_in_vicinity (0, path, "modified-only-dir-metadata");

    original_only_dir_metadata = arch_read_changeset_dir_metadata (original_only_dir_metadata_file);
    modified_only_dir_metadata = arch_read_changeset_dir_metadata (modified_only_dir_metadata_file);

    orig_dirs_by_name = rel_copy_table (report->orig_dirs_index);
    rel_sort_table_by_field (0, orig_dirs_by_name, 0);

    mod_dirs_by_name = rel_copy_table (report->mod_dirs_index);
    rel_sort_table_by_field (0, mod_dirs_by_name, 0);

    removed_dirs_index = rel_join (1, rel_join_output (1,0, 1,1, -1), 1, 1, report->orig_dirs_index, report->mod_dirs_index);
    added_dirs_index = rel_join (2, rel_join_output (2,0, 2,1, -1), 1, 1, report->orig_dirs_index, report->mod_dirs_index);

    t = rel_copy_table (removed_dirs_index);
    rel_sort_table_by_field (0, t, 0);
    report->removed_dirs = rel_join (-1, rel_join_output (1,0, 1,1, 2,1, -1), 0, 0, t, original_only_dir_metadata);
    t2 = rel_join (1, rel_join_output (1,0, 1,1, 1,0, -1), 0, 0, t, original_only_dir_metadata);
    rel_append_x (&report->removed_dirs, t2);
    rel_sort_table_by_field (0, report->removed_dirs, 0);
    rel_free_table (t);
    rel_free_table (t2);
    invariant (rel_n_records (report->removed_dirs) == rel_n_records (removed_dirs_index));

    t = rel_copy_table (added_dirs_index);
    rel_sort_table_by_field (0, t, 0);
    report->added_dirs = rel_join (-1, rel_join_output (1,0, 1,1, 2,1, -1), 0, 0, t, modified_only_dir_metadata);
    t2 = rel_join (1, rel_join_output (1,0, 1,1, 1,0, -1), 0, 0, t, modified_only_dir_metadata);
    rel_append_x (&report->added_dirs, t2);
    rel_sort_table_by_field (0, report->added_dirs, 0);
    rel_free_table (t);
    rel_free_table (t2);
    invariant (rel_n_records (report->added_dirs) == rel_n_records (added_dirs_index));

    lim_free (0, original_only_dir_metadata_file);
    lim_free (0, modified_only_dir_metadata_file);
    rel_free_table (original_only_dir_metadata);
    rel_free_table (modified_only_dir_metadata);
    rel_free_table (orig_dirs_by_name);
    rel_free_table (mod_dirs_by_name);
  }

  /****************************************************************
   * Removed and added filenames and symlinks
   */

  removed_files_and_symlinks_index = rel_join (1, rel_join_output (1,0, 1,1, -1), 1, 1, report->orig_files_index, report->mod_files_index);
  added_files_and_symlinks_index = rel_join (2, rel_join_output (2,0, 2,1, -1), 1, 1, report->orig_files_index, report->mod_files_index);

  report_unique_files_and_symlinks (&report->removed_files, &report->removed_symlinks,
                                    removed_files_and_symlinks_index,
                                    path, "removed-files-archive");

  report_unique_files_and_symlinks (&report->added_files, &report->added_symlinks,
                                    added_files_and_symlinks_index,
                                    path, "new-files-archive");

  /****************************************************************
   * renamed files and dirs
   */
  common_dirs_index = rel_join (-1, rel_join_output (1,0, 2,0, 1,1, -1), 1, 1, report->orig_dirs_index, report->mod_dirs_index);
  common_files_index = rel_join (-1, rel_join_output (1,0, 2,0, 1,1, -1), 1, 1, report->orig_files_index, report->mod_files_index);

  find_renames (&report->renamed_dirs, common_dirs_index, orig_dir_id_of, mod_dir_id_of);
  find_renames (&report->renamed_files, common_files_index, orig_dir_id_of, mod_dir_id_of);

  /****************************************************************
   * Identify patches
   */
  {
    int lim;
    int x;
    int here_fd;
    t_uchar * patches_root;
    rel_table patches_files = rel_table_nil;
    assoc_table saw_patches_file = 0;

    patches_root = file_name_in_vicinity (0, path, "patches");

    here_fd = safe_open (".", O_RDONLY, 0);
    safe_chdir (patches_root);
    find_files (&patches_files, ".");
    safe_fchdir (here_fd);
    safe_close (here_fd);

    saw_patches_file = rel_to_assoc (patches_files, 0, 0);

    lim = rel_n_records (patches_files);
    for (x = 0; x < lim; ++x)
      {
        const t_uchar * patch_file;
        t_uchar * patch_file_tail = 0;
        t_uchar * patch_file_suffix = 0;

        t_uchar * patch_file_loc = 0;
        rel_table * dest = 0;
        t_uchar * complement_patch = 0;

        t_uchar * patch_file_path = 0;

        rel_field id_field = rel_field_nil;

        patch_file = rel_peek_str (patches_files, x, 0);
        patch_file_suffix = str_chr_rindex (patch_file, '.');
        patch_file_tail = file_name_tail (0, patch_file);

        /* Compute the loc and the appropriate table for this
         * this patch.  (patch_file_loc and dest)
         */
        if (!str_cmp (patch_file_tail, "=dir-meta-orig"))
          {
            patch_file_loc = file_name_directory_file (0, patch_file);
            dest = &report->dir_metadata_changed;
            id_field = assoc_get_taking (mod_dir_id_of, rel_make_field_str (patch_file_loc));
          }
        else if (!str_cmp (patch_file_suffix, ".link-orig"))
          {
            patch_file_loc = str_save_n (0, patch_file, patch_file_suffix - patch_file);
            complement_patch = str_alloc_cat (0, patch_file_loc, ".link-mod");
            if (assoc_get_str_taking (saw_patches_file, rel_make_field_str (complement_patch)))
              dest = &report->patched_symlinks;
            else
              dest = &report->symlink_to_file;
            id_field = assoc_get_taking (mod_file_id_of, rel_make_field_str (patch_file_loc));
          }
        else if (!str_cmp (patch_file_suffix, ".original"))
          {
            patch_file_loc = str_save_n (0, patch_file, patch_file_suffix - patch_file);
            complement_patch = str_alloc_cat (0, patch_file_loc, ".modified");
            if (assoc_get_str_taking (saw_patches_file, rel_make_field_str (complement_patch)))
              dest = &report->patched_binaries;
            else
              dest = &report->file_to_symlink;
            id_field = assoc_get_taking (mod_file_id_of, rel_make_field_str (patch_file_loc));
          }
        else if (!str_cmp (patch_file_suffix, ".patch"))
          {
            patch_file_loc = str_save_n (0, patch_file, patch_file_suffix - patch_file);
            dest = &report->patched_regular_files;
            id_field = assoc_get_taking (mod_file_id_of, rel_make_field_str (patch_file_loc));
          }
        else if (!str_cmp (patch_file_suffix, ".meta-orig"))
          {
            patch_file_loc = str_save_n (0, patch_file, patch_file_suffix - patch_file);
            dest = &report->file_metadata_changed;
            id_field = assoc_get_taking (mod_file_id_of, rel_make_field_str (patch_file_loc));
          }
        else
          {
            id_field = rel_field_nil;
          }

        if (dest && patch_file_loc)
          {
            patch_file_path = file_name_in_vicinity (0, patches_root, patch_file_loc);
            rel_add_records (dest,
                             rel_make_record_3_taking (rel_make_field_str (patch_file_loc),
                                                       rel_field_ref (id_field),
                                                       rel_make_field_str (patch_file_path)),
                             rel_record_null);
          }

        rel_field_unref (id_field);
        lim_free (0, patch_file_tail);
        lim_free (0, patch_file_loc);
        lim_free (0, complement_patch);
        lim_free (0, patch_file_path);
      }


    lim_free (0, patches_root);
    rel_free_table (patches_files);
    free_assoc_table (saw_patches_file);
  }

  rel_sort_table_by_field (0, report->removed_dirs, 0);
  rel_sort_table_by_field (0, report->added_dirs, 0);

  rel_sort_table_by_field (0, report->removed_files, 0);
  rel_sort_table_by_field (0, report->added_files, 0);

  rel_sort_table_by_field (0, report->removed_symlinks, 0);
  rel_sort_table_by_field (0, report->added_symlinks, 0);

  rel_sort_table_by_field (0, report->renamed_files, 0);
  rel_sort_table_by_field (0, report->renamed_dirs, 0);

  rel_sort_table_by_field (0, report->patched_regular_files, 0);
  rel_sort_table_by_field (0, report->patched_symlinks, 0);
  rel_sort_table_by_field (0, report->patched_binaries, 0);
  rel_sort_table_by_field (0, report->file_metadata_changed, 0);
  rel_sort_table_by_field (0, report->dir_metadata_changed, 0);
  rel_sort_table_by_field (0, report->symlink_to_file, 0);
  rel_sort_table_by_field (0, report->file_to_symlink, 0);

  lim_free (0, orig_files_index_path);
  lim_free (0, orig_dirs_index_path);
  lim_free (0, mod_files_index_path);
  lim_free (0, mod_dirs_index_path);
  rel_free_table (removed_dirs_index);
  rel_free_table (added_dirs_index);
  rel_free_table (removed_files_and_symlinks_index);
  rel_free_table (added_files_and_symlinks_index);
  free_assoc_table (orig_dir_loc_of);
  free_assoc_table (orig_dir_id_of);
  free_assoc_table (orig_file_loc_of);
  free_assoc_table (orig_file_id_of);
  free_assoc_table (mod_dir_loc_of);
  free_assoc_table (mod_dir_id_of);
  free_assoc_table (mod_file_loc_of);
  free_assoc_table (mod_file_id_of);
  rel_free_table (common_files_index);
  rel_free_table (common_dirs_index);

}


void
arch_reverse_changeset (struct arch_changeset_report * report)
{
  rel_table t;
  int x;
  assoc_table new_mod_file_loc_of = 0;
  assoc_table new_mod_dir_loc_of = 0;

  t = report->orig_files_index;
  report->orig_files_index = report->mod_files_index;
  report->mod_files_index = t;

  t = report->orig_dirs_index;
  report->orig_dirs_index = report->mod_dirs_index;
  report->mod_dirs_index = t;

  t = report->removed_dirs;
  report->removed_dirs = report->added_dirs;
  report->added_dirs = t;

  t = report->removed_files;
  report->removed_files = report->added_files;
  report->added_files = t;

  t = report->removed_symlinks;
  report->removed_symlinks = report->added_symlinks;
  report->added_symlinks = t;

  for (x = 0; x < rel_n_records (report->renamed_files); ++x)
    {
      t_uchar * s;

      s = str_save (0, rel_peek_str (report->renamed_files, x, 0));
      rel_set_taking (report->renamed_files, x, 0, rel_get_field (report->renamed_files, x, 1));
      rel_set_taking (report->renamed_files, x, 1, rel_make_field_str (s));
      lim_free (0, s);
    }
  for (x = 0; x < rel_n_records (report->renamed_dirs); ++x)
    {
      t_uchar * s;

      s = str_save (0, rel_peek_str (report->renamed_dirs, x, 0));
      rel_set_taking (report->renamed_dirs, x, 0, rel_get_field (report->renamed_dirs, x, 1));
      rel_set_taking (report->renamed_dirs, x, 1, rel_make_field_str (s));
      lim_free (0, s);
    }

  new_mod_file_loc_of = rel_to_assoc (report->mod_files_index, 1, 0);
  new_mod_dir_loc_of = rel_to_assoc (report->mod_dirs_index, 1, 0);

  fix_mod_locs (new_mod_file_loc_of, report->patched_regular_files);
  fix_mod_locs (new_mod_file_loc_of, report->patched_symlinks);
  fix_mod_locs (new_mod_file_loc_of, report->patched_binaries);
  fix_mod_locs (new_mod_file_loc_of, report->file_metadata_changed);
  fix_mod_locs (new_mod_dir_loc_of, report->dir_metadata_changed);
  fix_mod_locs (new_mod_file_loc_of, report->symlink_to_file);
  fix_mod_locs (new_mod_file_loc_of, report->file_to_symlink);
}

static void
fix_mod_locs (assoc_table loc_of, rel_table table)
{
  int x;

  for (x = 0; x < rel_n_records (table); ++x)
    {
      rel_field id_field;
      rel_field new_loc_field;

      id_field = rel_get_field (table, x, 1);
      new_loc_field = assoc_get_taking (loc_of, id_field);
      rel_set_taking (table, x, 0, new_loc_field);
    }
}

static void
arch_print_file_metadata_diffs (int out_fd,
                                struct arch_changeset_report * report,
                                int escape_classes)
{
  int x;

  for (x = 0; x < rel_n_records (report->file_metadata_changed); ++x)
    {
      t_uchar * orig_meta_path = 0;
      t_uchar * mod_meta_path = 0;
      t_uchar * orig_meta = 0;
      t_uchar * mod_meta = 0;
      t_uchar * file_metadata_changed = 0;

      file_metadata_changed = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          rel_peek_str (report->file_metadata_changed, x, 0));
      safe_printfmt (out_fd, "    %s\n", file_metadata_changed);

      orig_meta_path = str_alloc_cat (0, rel_peek_str (report->file_metadata_changed, x, 2), ".meta-orig");
      mod_meta_path = str_alloc_cat (0, rel_peek_str (report->file_metadata_changed, x, 2), ".meta-mod");
      orig_meta = read_line_from_file (orig_meta_path);
      mod_meta = read_line_from_file (mod_meta_path);

      safe_printfmt (out_fd, "        %s\n        => %s\n", orig_meta, mod_meta);

      lim_free (0, file_metadata_changed);
      lim_free (0, orig_meta_path);
      lim_free (0, mod_meta_path);
      lim_free (0, orig_meta);
      lim_free (0, mod_meta);
    }
}

static void
arch_print_dir_metadata_diffs (int out_fd,
                               struct arch_changeset_report * report,
                               int escape_classes)
{
  int x;

  for (x = 0; x < rel_n_records (report->dir_metadata_changed); ++x)
    {
      t_uchar * orig_meta_path = 0;
      t_uchar * mod_meta_path = 0;
      t_uchar * orig_meta = 0;
      t_uchar * mod_meta = 0;
      t_uchar * dir_metadata_changed = 0;

      dir_metadata_changed = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                         rel_peek_str (report->dir_metadata_changed, x, 0));
      safe_printfmt (out_fd, "    %s\n", dir_metadata_changed);

      orig_meta_path = file_name_in_vicinity (0, rel_peek_str (report->dir_metadata_changed, x, 2), "=dir-meta-orig");
      mod_meta_path = file_name_in_vicinity (0, rel_peek_str (report->dir_metadata_changed, x, 2), "=dir-meta-mod");
      orig_meta = read_line_from_file (orig_meta_path);
      mod_meta = read_line_from_file (mod_meta_path);

      safe_printfmt (out_fd, "        %s\n        => %s\n", orig_meta, mod_meta);

      lim_free (0, dir_metadata_changed);
      lim_free (0, orig_meta_path);
      lim_free (0, mod_meta_path);
      lim_free (0, orig_meta);
      lim_free (0, mod_meta);
    }
}

static void
arch_print_file_diffs (int out_fd,
                       struct arch_changeset_report * report,
                       int escape_classes)
{
  int x;

  for (x = 0; x < rel_n_records (report->patched_regular_files); ++x)
    {
      t_uchar * patch_file;
      int in_fd;

      patch_file = str_alloc_cat (0, rel_peek_str (report->patched_regular_files, x, 2), ".patch");
      in_fd = safe_open (patch_file, O_RDONLY, 0);
      copy_fd (in_fd, out_fd);
      safe_printfmt (out_fd, "\n\n");
      safe_close (in_fd);
      lim_free (0, patch_file);
    }
}

static void
arch_print_added_file_diffs (int out_fd,
                             struct arch_changeset_report * report,
                             int escape_classes)
{
  int x;

  for (x = 0; x < rel_n_records (report->added_files); ++x)
    {
      arch_really_invoke_diff (1, "/dev/null", NULL, rel_peek_str (report->added_files, x, 2), NULL, NULL);
    }
}


static void print_file_metadata (int out_fd, struct arch_changeset_report *
                                 report, int escape_classes)
{
  if (rel_n_records(report->file_metadata_changed) > 0)
    {
      safe_printfmt (out_fd, "* file metadata changed\n\n");
      arch_print_file_metadata_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }
}


static void print_dir_metadata (int out_fd,
                                struct arch_changeset_report * report,
                                int escape_classes)
{
  if (rel_n_records(report->dir_metadata_changed) > 0)
    {
      safe_printfmt (out_fd, "* dir metadata changed\n\n");
      arch_print_dir_metadata_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }
}

static void print_diffs_new(int out_fd, struct arch_changeset_report * report)
{
  if (rel_n_records(report->patched_regular_files))
    {
      safe_printfmt (out_fd, "* modified files\n\n");
      arch_print_file_diffs (out_fd, report, 0);
      safe_printfmt (out_fd, "\n");
    }
  if (rel_n_records(report->added_files))
    {
      safe_printfmt (out_fd, "* added files\n\n");
      arch_print_added_file_diffs (out_fd, report, 0);
      safe_printfmt (out_fd, "\n");
    }
  print_removed_file_diffs(out_fd, report, NULL);
}


/**
 * \brief Write custom diffs for to a file descriptor
 *
 * The changeset report is used to determine which files to diff, and the
 * partial file paths.  The orig_dir and mod_dir are used to produce full
 * paths.  diff is invoked to produce the output.
 *
 * \param report The report to use for output
 * \param orig_dir The path to a copy of the ORIG tree
 * \param mod_dir The path to a copy of the MOD tree
 */
static void
print_custom_diffs (int out_fd, struct arch_changeset_report * report, t_uchar * orig_dir, t_uchar * mod_dir, t_uchar ** opts)
{
  if (rel_n_records(report->patched_regular_files))
    {
      int x;
      rel_field key;
      assoc_table orig_paths = rel_to_assoc (report->orig_files_index,  out_fd, 0);

      safe_printfmt ( out_fd, "* modified files\n\n");
      for (x = 0; x < rel_n_records (report->patched_regular_files); ++x)
        {
          t_uchar *id = str_save(0, rel_peek_str(report->patched_regular_files, x, 1)) /*report->patched_regular_files[x][1]*/;
          t_uchar *id2 = str_save(0, rel_peek_str(report->patched_regular_files, x, 0)) /*report->patched_regular_files[x][1]*/;

          t_uchar *orig_part_path;
          t_uchar *orig_path;
          t_uchar *mod_path;

          key = rel_make_field_str(id);
          
          rel_field_ref(key);

          orig_part_path = assoc_get_str_taking(orig_paths, key) /*assoc_ref(orig_paths, id)*/;
          orig_path = file_name_in_vicinity(0, orig_dir, orig_part_path);
          mod_path = file_name_in_vicinity(0, mod_dir, id2 /*report->patched_regular_files[x][0]*/);
	  
	  arch_really_invoke_diff ( out_fd, orig_path,  orig_part_path, mod_path, id2 /*report->patched_regular_files[x][0]*/, (char **)opts);
	  
          rel_field_unref(key);
          lim_free(0, mod_path);
          lim_free(0, orig_path);
          lim_free(0, id);
          lim_free(0, id2);
        }
      safe_printfmt ( out_fd, "\n");
      free_assoc_table (orig_paths);
    }
  if (rel_n_records(report->added_files))
    {
      int x;
      safe_printfmt (1, "* added files\n\n");

      for (x = 0; x < rel_n_records (report->added_files); ++x)
        {
          t_uchar *id = str_save(0, rel_peek_str(report->added_files, x, 2));
          t_uchar *id2 = str_save(0, rel_peek_str(report->added_files, x, 0));
          arch_really_invoke_diff ( out_fd, "/dev/null", NULL, id /*report->added_files[x][2]*/, id2 /*report->added_files[x][0]*/, (char**)opts);
          lim_free(0, id);
          lim_free(0, id2);
        }
      safe_printfmt ( out_fd, "\n");
    }
  print_removed_file_diffs ( out_fd, report, opts);
}



/**
 * \brief Print the diffs for any removed files to output.
 *
 * If no files were removed, no output is produced
 * \param report The changeset report to generate results for
 * \param opts Additional parameters to pass to diff, or NULL
 */
static void
print_removed_file_diffs (int out_fd, struct arch_changeset_report * report, t_uchar ** opts)
{
  if (rel_n_records(report->removed_files))
    {
      int x;
      safe_printfmt ( out_fd, "* removed files\n\n");

      for (x = 0; x < rel_n_records (report->removed_files); ++x)
        {
          t_uchar *id = str_save(0, rel_peek_str(report->removed_files, x, 2));
          t_uchar *id2 = str_save(0, rel_peek_str(report->removed_files, x, 0));
          arch_really_invoke_diff ( out_fd, id /*report->removed_files[x][2]*/, id2 /*report->removed_files[x][0]*/, "/dev/null", NULL, (char**)opts);
          lim_free(0, id);
          lim_free(0, id2);
        }
      safe_printfmt ( out_fd, "\n");
    }
}


/**
 * \brief Write a changeset report with custom diffs to a file descriptor
 *
 * \param report The report to use for output
 * \param orig_dir The path to a copy of the ORIG tree
 * \param mod_dir The path to a copy of the MOD tree
 * \param escape_classes The types of characters that should be escaped
 * \todo Consider reimplementing so that a custom changeset is generated, not
 * custom diffs.
 */
void
arch_print_changeset_custom_diffs (int  out_fd, struct arch_changeset_report * report, t_uchar * orig_dir, t_uchar * mod_dir, t_uchar ** opts, int escape_classes)
{
  print_file_metadata ( out_fd, report, escape_classes);
  print_dir_metadata ( out_fd, report, escape_classes);
  if (opts == NULL)
    print_diffs_new ( out_fd, report);
  else
    print_custom_diffs ( out_fd, report, orig_dir, mod_dir, opts);
}





void
arch_print_changeset_diffs (int out_fd, struct arch_changeset_report * report, int escape_classes)
{
  if (rel_n_records (report->file_metadata_changed))
    {
      safe_printfmt (out_fd, "* file metadata changed\n\n");
      arch_print_file_metadata_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->dir_metadata_changed))
    {
      safe_printfmt (out_fd, "* dir metadata changed\n\n");
      arch_print_dir_metadata_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->patched_regular_files))
    {
      safe_printfmt (out_fd, "* modified files\n\n");
      arch_print_file_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }
  if (rel_n_records (report->added_files))
    {
      safe_printfmt (out_fd, "* added files\n\n");
      arch_print_added_file_diffs (out_fd, report, escape_classes);
      safe_printfmt (out_fd, "\n");
    }
}


static char *
no_dot (char *name)
{
  if (name[0] == '.' && name[1] == '/')
    return name + 2;
  else
    return name;
}

void
arch_print_changeset  (int out_fd, struct arch_changeset_report * report, int diffs, int escape_classes)
{
  int x;

  if (rel_n_records (report->removed_dirs))
    {
      safe_printfmt (out_fd, "* removed directories\n\n");
      for (x = 0; x < rel_n_records (report->removed_dirs); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->removed_dirs, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }
  if (rel_n_records (report->added_dirs))
    {
      safe_printfmt (out_fd, "* added directories\n\n");
      for (x = 0; x < rel_n_records (report->added_dirs); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->added_dirs, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->removed_files))
    {
      safe_printfmt (out_fd, "* removed files\n\n");
      for (x = 0; x < rel_n_records (report->removed_files); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->removed_files, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->added_files))
    {
      safe_printfmt (out_fd, "* added files\n\n");
      for (x = 0; x < rel_n_records (report->added_files); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->added_files, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->removed_symlinks))
    {
      safe_printfmt (out_fd, "* removed symlinks\n\n");
      for (x = 0; x < rel_n_records (report->removed_symlinks); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->removed_symlinks, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->added_symlinks))
    {
      safe_printfmt (out_fd, "* added symlinks\n\n");
      for (x = 0; x < rel_n_records (report->added_symlinks); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->added_symlinks, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->renamed_files))
    {
      safe_printfmt (out_fd, "* renamed files and symlinks\n\n");
      for (x = 0; x < rel_n_records (report->renamed_files); ++x)
        {
          t_uchar * item1;
          t_uchar * item2;
          item1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                              rel_peek_str (report->renamed_files, x, 0));
          item2 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                              rel_peek_str (report->renamed_files, x, 1));
          safe_printfmt (out_fd, "    %s\n     => %s\n\n", no_dot (item1), no_dot (item2));
          lim_free (0, item2);
          lim_free (0, item1);
        }
    }

  if (rel_n_records (report->renamed_dirs))
    {
      safe_printfmt (out_fd, "* renamed directories\n\n");
      for (x = 0; x < rel_n_records (report->renamed_dirs); ++x)
        {
          t_uchar * item1;
          t_uchar * item2;
          item1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                              rel_peek_str (report->renamed_dirs, x, 0));
          item2 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                              rel_peek_str (report->renamed_dirs, x, 1));
          safe_printfmt (out_fd, "    %s\n     => %s\n\n", no_dot (item1), no_dot (item2));
          lim_free (0, item2);
          lim_free (0, item1);
        }
    }

  if (rel_n_records (report->file_to_symlink))
    {
      safe_printfmt (out_fd, "* files replaced by symlinks \n\n");
      for (x = 0; x < rel_n_records (report->file_to_symlink); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->file_to_symlink, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->symlink_to_file))
    {
      safe_printfmt (out_fd, "* symlinks replaced by files\n\n");
      for (x = 0; x < rel_n_records (report->symlink_to_file); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->symlink_to_file, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->file_metadata_changed))
    {
      safe_printfmt (out_fd, "* file metadata changed\n\n");

      if (diffs)
        {
          arch_print_file_metadata_diffs (out_fd, report, escape_classes);
        }
      else
        {
          for (x = 0; x < rel_n_records (report->file_metadata_changed); ++x)
            {
              t_uchar * item;
              item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                 rel_peek_str (report->file_metadata_changed, x, 0));
              safe_printfmt (out_fd, "    %s\n", no_dot (item));
              lim_free (0, item);
            }
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->dir_metadata_changed))
    {
      safe_printfmt (out_fd, "* dir metadata changed\n\n");

      if (diffs)
        {
          arch_print_dir_metadata_diffs (out_fd, report, escape_classes);
        }
      else
        {
          for (x = 0; x < rel_n_records (report->dir_metadata_changed); ++x)
            {
              t_uchar * item;
              item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                 rel_peek_str (report->dir_metadata_changed, x, 0));
              safe_printfmt (out_fd, "    %s\n", no_dot (item));
              lim_free (0, item);
            }
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->patched_symlinks))
    {
      safe_printfmt (out_fd, "* retargeted symlinks\n\n");
      for (x = 0; x < rel_n_records (report->patched_symlinks); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->patched_symlinks, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->patched_binaries))
    {
      safe_printfmt (out_fd, "* modified binary files\n\n");
      for (x = 0; x < rel_n_records (report->patched_binaries); ++x)
        {
          t_uchar * item;
          item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                             rel_peek_str (report->patched_binaries, x, 0));
          safe_printfmt (out_fd, "    %s\n", no_dot (item));
          lim_free (0, item);
        }
      safe_printfmt (out_fd, "\n");
    }

  if (rel_n_records (report->patched_regular_files))
    {
      safe_printfmt (out_fd, "* modified files\n\n");

      if (diffs)
        {
          arch_print_file_diffs (out_fd, report, escape_classes);
        }
      else
        {
          for (x = 0; x < rel_n_records (report->patched_regular_files); ++x)
            {
              t_uchar * item;
              item = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                 rel_peek_str (report->patched_regular_files, x, 0));
              safe_printfmt (out_fd, "    %s\n", no_dot (item));
              lim_free (0, item);
            }
        }
      safe_printfmt (out_fd, "\n");
    }
}

void
arch_free_changeset_report_data (struct arch_changeset_report * r)
{
  rel_free_table (r->orig_files_index);                 r->orig_files_index = rel_table_nil;
  rel_free_table (r->orig_dirs_index);                  r->orig_dirs_index = rel_table_nil;
  rel_free_table (r->mod_files_index);                  r->mod_files_index = rel_table_nil;
  rel_free_table (r->mod_dirs_index);                   r->mod_dirs_index = rel_table_nil;

  rel_free_table (r->removed_dirs);                     r->removed_dirs = rel_table_nil;
  rel_free_table (r->added_dirs);                       r->added_dirs = rel_table_nil;

  rel_free_table (r->removed_files);                    r->removed_files = rel_table_nil;
  rel_free_table (r->added_files);                      r->added_files = rel_table_nil;

  rel_free_table (r->removed_symlinks);                 r->removed_symlinks = rel_table_nil;
  rel_free_table (r->added_symlinks);                   r->added_symlinks = rel_table_nil;

  rel_free_table (r->renamed_files);                    r->renamed_files = rel_table_nil;
  rel_free_table (r->renamed_dirs);                     r->renamed_dirs = rel_table_nil;

  rel_free_table (r->patched_regular_files);            r->patched_regular_files = rel_table_nil;
  rel_free_table (r->patched_symlinks);                 r->patched_symlinks = rel_table_nil;
  rel_free_table (r->patched_binaries);                 r->patched_binaries = rel_table_nil;
  rel_free_table (r->file_metadata_changed);            r->file_metadata_changed = rel_table_nil;
  rel_free_table (r->dir_metadata_changed);             r->dir_metadata_changed = rel_table_nil;
  rel_free_table (r->symlink_to_file);                  r->symlink_to_file = rel_table_nil;
  rel_free_table (r->file_to_symlink);                  r->file_to_symlink = rel_table_nil;
}




static void
report_unique_files_and_symlinks (rel_table * files, rel_table * symlinks,
                                  rel_table index,
                                  t_uchar * archive_dir, t_uchar * archive_basename)
{
  t_uchar * archive = 0;
  int lim;
  int x;

  archive = file_name_in_vicinity (0, archive_dir, archive_basename);

  lim = rel_n_records (index);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * archive_path;
      struct stat stat_buf;

      archive_path = file_name_in_vicinity (0, archive, rel_peek_str (index, x, 0));

      safe_lstat (archive_path, &stat_buf);

      if (S_ISLNK (stat_buf.st_mode))
        {
          rel_add_records (symlinks,
                           rel_make_record_3_taking (rel_get_field (index, x, 0),
                                                     rel_get_field (index, x, 1),
                                                     rel_make_field_str (archive_path)),
                           rel_record_null);
        }
      else
        {
          rel_add_records (files,
                           rel_make_record_3_taking (rel_get_field (index, x, 0),
                                                     rel_get_field (index, x, 1),
                                                     rel_make_field_str (archive_path)),
                           rel_record_null);
        }

      lim_free (0, archive_path);
    }

  rel_sort_table_by_field (0, *files, 0);
  rel_sort_table_by_field (0, *symlinks, 0);

  lim_free (0, archive);
}

static void
find_renames (rel_table * out, rel_table common, assoc_table orig_dir_id_of, assoc_table mod_dir_id_of)
{
  int lim;
  int x;

  lim = rel_n_records (common);
  for (x = 0; x < lim; ++x)
    {
      rel_field orig_loc_field;
      rel_field mod_loc_field;
      rel_field id_field;

      const t_uchar * orig_loc;
      const t_uchar * mod_loc;
      const t_uchar * id;

      t_uchar * orig_basename = 0;
      t_uchar * mod_basename = 0;
      t_uchar * orig_dir = 0;
      t_uchar * mod_dir = 0;
      const t_uchar * orig_dir_id;
      const t_uchar * mod_dir_id;

      orig_loc_field = rel_get_field (common, x, 0);
      orig_loc = rel_field_str (orig_loc_field);

      mod_loc_field = rel_get_field (common, x, 1);
      mod_loc = rel_field_str (mod_loc_field);

      id_field = rel_get_field (common, x, 2);
      id = rel_field_str (id_field);

      orig_basename = file_name_tail (0, orig_loc);
      mod_basename = file_name_tail (0, mod_loc);

      orig_dir = file_name_directory_file (0, orig_loc);
      mod_dir = file_name_directory_file (0, mod_loc);

      orig_dir_id = assoc_get_str_taking (orig_dir_id_of, rel_make_field_str (orig_dir));
      mod_dir_id = assoc_get_str_taking (mod_dir_id_of, rel_make_field_str (mod_dir));

      /* The conditional subexpression here attempts to identify renames
       * even when the the changeset is missing locs and ids for
       * one or both of the containing dirs of the object under consideration.
       *
       */

      if (str_cmp (orig_basename, mod_basename)
          || ((orig_dir_id && mod_dir_id)
              ? str_cmp (orig_dir_id, mod_dir_id)
              : str_cmp (orig_dir, mod_dir)))
        {
          rel_field_ref (orig_loc_field);
          rel_field_ref (mod_loc_field);
          rel_field_ref (id_field);
          rel_add_records (out, rel_make_record_3_taking (orig_loc_field,
                                                          mod_loc_field,
                                                          id_field),
                           rel_record_null);
        }


      rel_field_unref (orig_loc_field);
      rel_field_unref (mod_loc_field);
      rel_field_unref (id_field);

      lim_free (0, orig_basename);
      lim_free (0, mod_basename);
      lim_free (0, orig_dir);
      lim_free (0, mod_dir);
    }
}





/* tag: Tom Lord Thu May 15 13:07:24 2003 (changeset-report.c)
 */
