/* make-changeset.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/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/char/char-class.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/sort/qsort.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/fs/cwd.h"
#include "hackerlab/vu/safe.h"
#include "hackerlab/char/pika-escaping-utils.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/dir-as-cwd.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libfsutils/link-target.h"
#include "tla/libawk/associative.h"
#include "tla/libarch/arch.h"
#include "tla/libarch/diffs.h"
#include "tla/libarch/invent.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/make-changeset.h"


/* __STDC__ prototypes for static functions */


static void invoke_report_callback (struct arch_make_changeset_report * report, const char * fmt, ...)  __attribute__((format (printf, 2, 3)));

static const char * no_dot (const char *name);
static void compute_renames (rel_table * out,
                             assoc_table loc_of_orig_X_id,
                             assoc_table mod_dir_id_of,
                             assoc_table orig_container_dir_id_of_X_id,
                             rel_table X_index);
static void write_dir_metadata (const t_uchar * file,
                                const t_uchar * tree_root,
                                rel_table dirs,
                                int full_meta);
static void copy_files_and_symlinks (const t_uchar * dest,
                                     const t_uchar * tree_root,
                                     rel_table loc_list,
                                     int full_meta);
static void emit_dir_patches (rel_table * orig_index_names_output,
                              rel_table * mod_index_names_output,
                              struct arch_make_changeset_report * report,
                              const t_uchar * patches,
                              const t_uchar * orig,
                              const t_uchar * orig_loc,
                              const t_uchar * mod,
                              const t_uchar * mod_loc,
                              int escape_classes,
                              int full_meta);
static void emit_file_or_symlink_patches (rel_table * orig_index_names_output,
                                          rel_table * mod_index_names_output,
                                          struct arch_make_changeset_report * report,
                                          const t_uchar * patches,
                                          const t_uchar * orig,
                                          const t_uchar * orig_loc,
                                          const t_uchar * mod,
                                          const t_uchar * mod_loc,
                                          const t_uchar * id,
                                          assoc_table inode_sig_shortcuts_of_mod,
                                          int link_same,
                                          int escape_classes,
                                          int full_meta);
static void compute_parent_dir_closure (rel_table * orig_newname_dirs,
                                        rel_table * mod_newname_dirs,
                                        rel_table orig_newname_files,
                                        rel_table mod_newname_files,
                                        assoc_table orig_dir_loc_of,
                                        assoc_table orig_dir_id_of,
                                        assoc_table orig_container_dir_id_of_dir_id,
                                        assoc_table orig_container_dir_id_of_file_id,
                                        assoc_table mod_dir_loc_of,
                                        assoc_table mod_dir_id_of,
                                        assoc_table mod_container_dir_id_of_dir_id,
                                        assoc_table mod_container_dir_id_of_file_id,
                                        assoc_table orig_file_id_of,
                                        assoc_table mod_file_id_of);
static void schedule_missing_containers (rel_table * added_orig_container_ids,
                                         assoc_table * orig_has_dir_id,
                                         rel_table * added_mod_container_ids,
                                         assoc_table * mod_has_dir_id,
                                         rel_table locs_in,
                                         assoc_table id_of_dir_loc,
                                         assoc_table first_container_id_of_id,
                                         assoc_table container_id_of_dir_id,
                                         assoc_table orig_dir_loc_of,
                                         assoc_table mod_dir_loc_of);
static void container_closure (rel_table * locs_out,
                               assoc_table * has_ids,
                               const t_uchar * deep_id,
                               assoc_table dir_loc_of,
                               assoc_table container_dir_id_of_dir_id);
static void link_force (const t_uchar *old_path,
                        const t_uchar *new_path);



static int cut_field_0[] = { 0, -1 };
static int cut_field_1[] = { 1, -1 };




void
arch_free_make_changeset_report_data (struct arch_make_changeset_report * report)
{
  arch_free_changeset_inventory_data (&report->orig_index);
  arch_free_changeset_inventory_data (&report->mod_index);

  free_assoc_table (report->orig_dir_id_of);
  free_assoc_table (report->orig_dir_loc_of);
  free_assoc_table (report->orig_file_id_of);
  free_assoc_table (report->orig_file_loc_of);
  free_assoc_table (report->mod_dir_id_of);
  free_assoc_table (report->mod_dir_loc_of);
  free_assoc_table (report->mod_file_id_of);
  free_assoc_table (report->mod_file_loc_of);
  free_assoc_table (report->orig_container_dir_id_of_dir_id);
  free_assoc_table (report->orig_container_dir_id_of_file_id);
  free_assoc_table (report->mod_container_dir_id_of_dir_id);
  free_assoc_table (report->mod_container_dir_id_of_file_id);

  rel_free_table (report->renamed_dirs);
  rel_free_table (report->renamed_files);
  rel_free_table (report->modified_files);
  rel_free_table (report->perms_changed_files);
  rel_free_table (report->perms_changed_dirs);
  rel_free_table (report->added_files);
  rel_free_table (report->added_dirs);
  rel_free_table (report->removed_files);
  rel_free_table (report->removed_dirs);
}


#if !defined(__GNUC__)
#  undef __attribute__
#  define __attribute__(X)
#endif

static void __attribute__((format (printf, 2, 3)))
invoke_report_callback (struct arch_make_changeset_report * report, const char * fmt, ...)
{
  va_list ap;

  va_start (ap, fmt);
  report->callback (report->thunk, fmt, ap);
  va_end (ap);
}


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


void
arch_make_changeset (struct arch_make_changeset_report * report,
                     const t_uchar * orig_spec,
                     const t_uchar * mod_spec,
                     const t_uchar * dest_spec,
                     enum arch_id_tagging_method method,
                     enum arch_inventory_category untagged_source_category,
                     rel_table limits_spec,
                     assoc_table inode_sig_shortcuts_of_mod,
		     int link_same,
                     int escape_classes)
{
  int here_fd;
  t_uchar * orig;
  t_uchar * mod;
  t_uchar * orig_root;
  t_uchar * mod_root;
  int full_meta;
  t_uchar * dest;
  rel_table limits = rel_table_nil;
  assoc_table loc_transl = 0;
  int lim;
  int x;
  rel_table orig_full_index = rel_table_nil;
  rel_table mod_full_index = rel_table_nil;
  rel_table mod_full_index_by_name = rel_table_nil;
  rel_table orig_full_index_by_name = rel_table_nil;
  rel_table orig_dirs_full_index_by_name = rel_table_nil;
  rel_table mod_dirs_full_index_by_name = rel_table_nil;
  rel_table orig_dir_ids = rel_table_nil;
  rel_table mod_dir_ids = rel_table_nil;
  rel_table original_only_dir_ids = rel_table_nil;
  rel_table modified_only_dir_ids = rel_table_nil;
  rel_table dir_ids_in_both = rel_table_nil;
  rel_table original_only_dirs = rel_table_nil;
  rel_table modified_only_dirs = rel_table_nil;
  rel_table renamed_dirs = rel_table_nil;
  rel_table orig_files_full_index_by_name = rel_table_nil;
  rel_table mod_files_full_index_by_name = rel_table_nil;
  rel_table orig_file_ids = rel_table_nil;
  rel_table mod_file_ids = rel_table_nil;
  rel_table original_only_file_ids = rel_table_nil;
  rel_table modified_only_file_ids = rel_table_nil;
  rel_table original_only_files = rel_table_nil;
  rel_table modified_only_files = rel_table_nil;
  rel_table file_ids_in_both = rel_table_nil;
  rel_table renamed_files = rel_table_nil;
  rel_table files_in_both = rel_table_nil;
  rel_table dirs_in_both = rel_table_nil;
  assoc_table orig_ids_seen = 0;
  assoc_table mod_ids_seen = 0;
  t_uchar * original_only_dir_metadata_file = 0;
  t_uchar * modified_only_dir_metadata_file = 0;
  t_uchar * removed_files_archive_dir = 0;
  t_uchar * new_files_archive_dir = 0;
  t_uchar * patches_dir = 0;
  t_uchar * orig_files_index_file = 0;
  t_uchar * orig_dirs_index_file = 0;
  t_uchar * mod_files_index_file = 0;
  t_uchar * mod_dirs_index_file = 0;
  rel_table orig_files_index_names_output = rel_table_nil;
  rel_table orig_dirs_index_names_output = rel_table_nil;
  rel_table mod_files_index_names_output = rel_table_nil;
  rel_table mod_dirs_index_names_output = rel_table_nil;


  here_fd = safe_open (".", O_RDONLY, 0);

  safe_chdir (orig_spec);
  orig_root = arch_tree_root (0, ".", 0);
  orig = safe_current_working_directory ();
  safe_fchdir (here_fd);

  safe_chdir (mod_spec);
  mod_root = arch_tree_root (0, ".", 0);
  mod = safe_current_working_directory ();
  safe_fchdir (here_fd);

  full_meta = arch_tree_has_meta_flag (mod_root);

  /* canonicalize limits
   *
   * In other words, turn them into mod-tree locs, and eliminate
   * nested limits.
   */
  if (rel_n_records (limits_spec))
    {
      rel_table lim_spec_locs = rel_table_nil;
      int mod_root_length;
      int l;

      mod_root_length = str_length (mod_root);

      for (l = 0; l < rel_n_records (limits_spec); ++l)
        {
          const t_uchar * spec;
          t_uchar * spec_dir = 0;
          t_uchar * spec_tail = 0;
          t_uchar * spec_dir_abs = 0;
          t_uchar * spec_loc = 0;

          spec = rel_peek_str (limits_spec, l, 0);
          spec_dir = file_name_directory_file (0, spec);
          if (!spec_dir)
            spec_dir = str_save (0, ".");
          spec_tail = file_name_tail (0, spec);

          spec_dir_abs = directory_as_cwd (spec_dir);

          if (str_cmp_prefix (mod_root, spec_dir_abs) || (spec_dir_abs[mod_root_length] && (spec_dir_abs[mod_root_length] != '/')))
            {
              safe_printfmt (2, "make-changeset: limit %s is not in tree %s\n", spec, mod_root);
              exit (2);
            }

          spec_loc = str_alloc_cat_many (0, ".", spec_dir_abs + mod_root_length, "/", spec_tail, str_end);

          rel_add_records (&lim_spec_locs, rel_singleton_record_taking (rel_make_field_str (spec_loc)), rel_record_null);

          lim_free (0, spec_dir);
          lim_free (0, spec_tail);
          lim_free (0, spec_dir_abs);
          lim_free (0, spec_loc);
        }

      for (l = 0; l < rel_n_records (lim_spec_locs); ++l)
        {
          int m;
          int dont_want = 0;

          for (m = 0; (!dont_want && (m < rel_n_records (limits))); ++m)
            {
              if (file_name_is_path_prefix (rel_peek_str (limits, m, 0), rel_peek_str (lim_spec_locs, l, 0)))
                {
                  dont_want = 1;
                }
              else if (file_name_is_path_prefix (rel_peek_str (lim_spec_locs, l, 0), rel_peek_str (limits, m, 0)))
                {
                  rel_set_taking (limits, m, 0, rel_get_field (lim_spec_locs, l, 0));
                  dont_want = 1;
                }
            }
          if (!dont_want)
            rel_add_records (&limits, rel_singleton_record_taking (rel_get_field (lim_spec_locs, l, 0)), rel_record_null);
        }

      rel_sort_table_by_field (0, limits, 0);

      rel_free_table (lim_spec_locs);
    }


  /* create the output directory
   */
  safe_mkdir (dest_spec, 0777);
  safe_chdir (dest_spec);

  dest = safe_current_working_directory ();

  safe_fchdir (here_fd);

  /* inventory orig and mod
   */
  mem_set0 ((t_uchar *)&report->orig_index, sizeof (report->orig_index));
  mem_set0 ((t_uchar *)&report->mod_index, sizeof (report->mod_index));

  arch_changeset_inventory (&report->orig_index, orig_root, orig, method,
                            untagged_source_category, escape_classes);
  arch_changeset_inventory (&report->mod_index, mod_root, mod, method,
                            untagged_source_category, escape_classes);

  orig_full_index = rel_copy_table (report->orig_index.dirs);
  rel_append_x (&orig_full_index, report->orig_index.files);
  rel_add_records (&orig_full_index,
                   rel_make_record_2_taking (rel_make_field_str ("."),
                                             rel_make_field_str ("?_.")),
                   rel_record_null);
  rel_sort_table_by_field (0, orig_full_index, 1);

  mod_full_index = rel_copy_table (report->mod_index.dirs);
  rel_append_x (&mod_full_index, report->mod_index.files);
  rel_add_records (&mod_full_index,
                   rel_make_record_2_taking (rel_make_field_str ("."),
                                             rel_make_field_str ("?_.")),
                   rel_record_null);
  rel_sort_table_by_field (0, mod_full_index, 1);

  mod_full_index_by_name = rel_copy_table (mod_full_index);
  rel_sort_table_by_field (0, mod_full_index_by_name, 0);

  orig_full_index_by_name = rel_copy_table (orig_full_index);
  rel_sort_table_by_field (0, orig_full_index_by_name, 0);

  /* if limits were specified, smudge the MOD index and load-up the translation table
   */
  if (rel_n_records (limits))
    {
      /* The goal here is to create a fake mod index -- the index of the
       * mod tree as it would be after we ignore changes outside the limits.
       *
       * If there were apparently renames across the limit boundaries, we have to
       * fail.
       *
       * Additionally, we need a "mod-path-translation" -- a mapping from fake-inventory
       * locs to actual mod locs to use when diffing.    If we want to pretend/presume
       * that some item hasn't been modified, it's left out of this translation.
       */

      /* Three cases of items to deal with in the fake inventory:
       *
       * 1) Items common to both ORIG and MOD
       * 2) Items unique to ORIG
       * 3) Items unique to MOD
       *
       *
       * Little lemma:  for each (canonicalized) limit $PREFIX/$TAIL,
       * the item in MOD with loc $PREFIX must exist in ORIG.   Because,
       * otherwise, the limit wants to add files to dirs that have not been
       * added to ORIG (a user error).
       *
       */

      /* currently, limits is just [0] == mod_path */



      exit (2);
    }

  orig_dirs_full_index_by_name = rel_copy_table (report->orig_index.dirs);
  rel_sort_table_by_field (0, orig_dirs_full_index_by_name, 0);

  mod_dirs_full_index_by_name = rel_copy_table (report->mod_index.dirs);
  rel_sort_table_by_field (0, mod_dirs_full_index_by_name, 0);

  orig_dir_ids = rel_cut (cut_field_1, report->orig_index.dirs);
  mod_dir_ids = rel_cut (cut_field_1, report->mod_index.dirs);

  original_only_dir_ids = rel_join (1, rel_join_output (1,0, -1), 0, 0, orig_dir_ids, mod_dir_ids);
  modified_only_dir_ids = rel_join (2, rel_join_output (2,0, -1), 0, 0, orig_dir_ids, mod_dir_ids);
  dir_ids_in_both = rel_join (-1, rel_join_output (0,0, -1), 0, 0, orig_dir_ids, mod_dir_ids);

  original_only_dirs = rel_join (-1, rel_join_output (2,0, -1), 0, 1, original_only_dir_ids, report->orig_index.dirs);
  rel_sort_table_by_field (0, original_only_dirs, 0);

  modified_only_dirs = rel_join (-1, rel_join_output (2,0, -1), 0, 1, modified_only_dir_ids, report->mod_index.dirs);
  rel_sort_table_by_field (0, modified_only_dirs, 0);

  /****************************************************************
   * build associative tables to and from ids and locs
   */
  lim = rel_n_records (report->orig_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      assoc_set_taking (&report->orig_dir_id_of, rel_get_field (report->orig_index.dirs, x, 0), rel_get_field (report->orig_index.dirs, x, 1));
      assoc_set_taking (&report->orig_dir_loc_of, rel_get_field (report->orig_index.dirs, x, 1), rel_get_field (report->orig_index.dirs, x, 0));
    }
  lim = rel_n_records (report->orig_index.files);
  for (x = 0; x < lim; ++x)
    {
      assoc_set_taking (&report->orig_file_id_of, rel_get_field (report->orig_index.files, x, 0), rel_get_field (report->orig_index.files, x, 1));
      assoc_set_taking (&report->orig_file_loc_of, rel_get_field (report->orig_index.files, x, 1), rel_get_field (report->orig_index.files, x, 0));
    }
  lim = rel_n_records (report->mod_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      assoc_set_taking (&report->mod_dir_id_of, rel_get_field (report->mod_index.dirs, x, 0), rel_get_field (report->mod_index.dirs, x, 1));
      assoc_set_taking (&report->mod_dir_loc_of, rel_get_field (report->mod_index.dirs, x, 1), rel_get_field (report->mod_index.dirs, x, 0));
    }
  lim = rel_n_records (report->mod_index.files);
  for (x = 0; x < lim; ++x)
    {
      assoc_set_taking (&report->mod_file_id_of, rel_get_field (report->mod_index.files, x, 0), rel_get_field (report->mod_index.files, x, 1));
      assoc_set_taking (&report->mod_file_loc_of, rel_get_field (report->mod_index.files, x, 1), rel_get_field (report->mod_index.files, x, 0));
    }

  assoc_set_taking (&report->orig_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&report->mod_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&report->orig_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));
  assoc_set_taking (&report->mod_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));

  arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_dir_id, report->orig_dir_id_of, report->orig_index.dirs);
  arch_make_changeset_compute_container_map (&report->orig_container_dir_id_of_file_id, report->orig_dir_id_of, report->orig_index.files);
  arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_dir_id, report->mod_dir_id_of, report->mod_index.dirs);
  arch_make_changeset_compute_container_map (&report->mod_container_dir_id_of_file_id, report->mod_dir_id_of, report->mod_index.files);


  /****************************************************************
   * figure out which directories have been renamed.
   */
  compute_renames (&renamed_dirs, report->orig_dir_loc_of, report->mod_dir_id_of, report->orig_container_dir_id_of_dir_id, report->mod_index.dirs);

  /****************************************************************
   * file lists
   */

  orig_files_full_index_by_name = rel_copy_table (report->orig_index.files);
  rel_sort_table_by_field (0, orig_files_full_index_by_name, 0);

  mod_files_full_index_by_name = rel_copy_table (report->mod_index.files);
  rel_sort_table_by_field (0, mod_files_full_index_by_name, 0);

  orig_file_ids = rel_cut (cut_field_1, report->orig_index.files);
  mod_file_ids = rel_cut (cut_field_1, report->mod_index.files);

  original_only_file_ids = rel_join (1, rel_join_output (1,0, -1), 0, 0, orig_file_ids, mod_file_ids);
  modified_only_file_ids = rel_join (2, rel_join_output (2,0, -1), 0, 0, orig_file_ids, mod_file_ids);
  file_ids_in_both = rel_join (-1, rel_join_output (1,0, -1), 0, 0, orig_file_ids, mod_file_ids);

  original_only_files = rel_join (-1, rel_join_output (2,0, -1), 0, 1, original_only_file_ids, report->orig_index.files);
  rel_sort_table_by_field (0, original_only_files, 0);
  modified_only_files = rel_join (-1, rel_join_output (2,0, -1), 0, 1, modified_only_file_ids, report->mod_index.files);
  rel_sort_table_by_field (0, modified_only_files, 0);

  compute_renames (&renamed_files, report->orig_file_loc_of, report->mod_dir_id_of, report->orig_container_dir_id_of_file_id, report->mod_index.files);

  /***************************************************************
   * {files,dir}_in_both  [0] orig_loc  [1] mod_loc
   */

  {
    rel_table tmp_table;

    tmp_table = rel_join (-1, rel_join_output (1,0, 2,0, -1), 0, 1, file_ids_in_both, report->mod_index.files);
    files_in_both = rel_join (-1, rel_join_output (2,0, 1,1, 1,0, -1), 0, 1, tmp_table, report->orig_index.files);
    rel_free_table (tmp_table);
  }

  {
    rel_table tmp_table;

    tmp_table = rel_join (-1, rel_join_output (1,0, 2,0, -1), 0, 1, dir_ids_in_both, report->mod_index.dirs);
    dirs_in_both = rel_join (-1, rel_join_output (2,0, 1,1, -1), 0, 1, tmp_table, report->orig_index.dirs);
    rel_free_table (tmp_table);
  }

  /****************************************************************
   * Check for Duplicate Inventory Ids
   */
  lim = rel_n_records (report->orig_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * seen;

      seen = assoc_get_str_taking (orig_ids_seen, rel_get_field (report->orig_index.dirs, x, 1));

      if (seen)
        panic ("duplicate ids in ORIG tree, try tree-lint");

      assoc_set_taking (&orig_ids_seen, rel_get_field (report->orig_index.dirs, x, 1), rel_get_field (report->orig_index.dirs, x, 0));
    }
  lim = rel_n_records (report->orig_index.files);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * seen;

      seen = assoc_get_str_taking (orig_ids_seen, rel_get_field (report->orig_index.files, x, 1));

      if (seen)
        panic ("duplicate ids in ORIG tree, try tree-lint");

      assoc_set_taking (&orig_ids_seen, rel_get_field (report->orig_index.files, x, 1), rel_get_field (report->orig_index.files, x, 0));
    }
  lim = rel_n_records (report->mod_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * seen;

      seen = assoc_get_str_taking (mod_ids_seen, rel_get_field (report->mod_index.dirs, x, 1));

      if (seen)
        panic ("duplicate ids in MOD tree, try tree-lint");

      assoc_set_taking (&mod_ids_seen, rel_get_field (report->mod_index.dirs, x, 1), rel_get_field (report->mod_index.dirs, x, 0));
    }
  lim = rel_n_records (report->mod_index.files);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * seen;

      seen = assoc_get_str_taking (mod_ids_seen, rel_get_field (report->mod_index.files, x, 1));

      if (seen)
        panic ("duplicate ids in MOD tree, try tree-lint");

      assoc_set_taking (&mod_ids_seen, rel_get_field (report->mod_index.files, x, 1), rel_get_field (report->mod_index.files, x, 0));
    }

  /****************************************************************
   * Capture the Permissions of Removed and New Directories
   */
  original_only_dir_metadata_file = file_name_in_vicinity (0, dest, "original-only-dir-metadata");
  modified_only_dir_metadata_file = file_name_in_vicinity (0, dest, "modified-only-dir-metadata");

  write_dir_metadata (original_only_dir_metadata_file, orig, original_only_dirs, full_meta);
  write_dir_metadata (modified_only_dir_metadata_file, mod, modified_only_dirs, full_meta);

  if (report)
    {
      report->removed_dirs = rel_copy_table (original_only_dirs);
      report->added_dirs = rel_copy_table (modified_only_dirs);
      if (report->callback)
        {
          lim = rel_n_records (report->removed_dirs);
          for (x = 0; x < lim; ++x)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       rel_peek_str (report->removed_dirs, x, 0));
              invoke_report_callback (report, "D/ %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
          lim = rel_n_records (report->added_dirs);
          for (x = 0; x < lim; ++x)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       rel_peek_str (report->added_dirs, x, 0));
              invoke_report_callback (report, "A/ %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
        }
    }


  /****************************************************************
   * Save Copies of new and removed files.
   */
  removed_files_archive_dir = file_name_in_vicinity (0, dest, "removed-files-archive");
  new_files_archive_dir = file_name_in_vicinity (0, dest, "new-files-archive");

  safe_mkdir (removed_files_archive_dir, 0777);
  safe_mkdir (new_files_archive_dir, 0777);

  copy_files_and_symlinks (removed_files_archive_dir, orig, original_only_files, full_meta);
  copy_files_and_symlinks (new_files_archive_dir, mod, modified_only_files, full_meta);

  if (report)
    {
      report->removed_files = rel_copy_table (original_only_files);
      report->added_files = rel_copy_table (modified_only_files);
      if (report->callback)
        {
          lim = rel_n_records (report->removed_files);
          for (x = 0; x < lim; ++x)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       rel_peek_str (report->removed_files, x, 0));
              invoke_report_callback (report, "D  %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
          lim = rel_n_records (report->added_files);
          for (x = 0; x < lim; ++x)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       rel_peek_str (report->added_files, x, 0));
              invoke_report_callback (report, "A  %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
        }
    }


  /****************************************************************
   * Save patches for common files and directories.
   */
  patches_dir = file_name_in_vicinity (0, dest, "patches");
  safe_mkdir (patches_dir, 0777);

  lim = rel_n_records (files_in_both);
  for (x = 0; x < lim; ++x)
    {
      emit_file_or_symlink_patches (&orig_files_index_names_output, &mod_files_index_names_output, report,
                                    patches_dir, orig, rel_peek_str (files_in_both, x, 0), mod, rel_peek_str (files_in_both, x, 1),
                                    rel_peek_str (files_in_both, x, 2),
                                    inode_sig_shortcuts_of_mod, link_same, escape_classes, full_meta);
    }
  lim = rel_n_records (dirs_in_both);
  for (x = 0; x < lim; ++x)
    {
      emit_dir_patches (&orig_dirs_index_names_output, &mod_dirs_index_names_output, report,
                        patches_dir, orig, rel_peek_str (dirs_in_both, x, 0), mod, rel_peek_str (dirs_in_both, x, 1), escape_classes,
                        full_meta);
    }

  /****************************************************************
   * Write the file indexes.
   */
  orig_files_index_file = file_name_in_vicinity (0, dest, "orig-files-index");
  orig_dirs_index_file = file_name_in_vicinity (0, dest, "orig-dirs-index");
  mod_files_index_file = file_name_in_vicinity (0, dest, "mod-files-index");
  mod_dirs_index_file = file_name_in_vicinity (0, dest, "mod-dirs-index");

  {
    rel_table orig_newname_dir_names = rel_table_nil;
    rel_table orig_newname_file_names = rel_table_nil;
    rel_table mod_newname_dir_names = rel_table_nil;
    rel_table mod_newname_file_names = rel_table_nil;

    rel_table orig_dirs_index_final = rel_table_nil;
    rel_table orig_files_index_final = rel_table_nil;
    rel_table mod_dirs_index_final = rel_table_nil;
    rel_table mod_files_index_final = rel_table_nil;

    int orig_dirs_index_fd;
    int orig_files_index_fd;
    int mod_dirs_index_fd;
    int mod_files_index_fd;

    orig_newname_dir_names = rel_cut (cut_field_0, renamed_dirs);
    orig_newname_file_names = rel_cut (cut_field_0, renamed_files);
    mod_newname_dir_names = rel_cut (cut_field_1, renamed_dirs);
    mod_newname_file_names = rel_cut (cut_field_1, renamed_files);

    rel_append_x (&orig_newname_dir_names, original_only_dirs);
    rel_append_x (&orig_newname_file_names, original_only_files);
    rel_append_x (&mod_newname_dir_names, modified_only_dirs);
    rel_append_x (&mod_newname_file_names, modified_only_files);

    compute_parent_dir_closure (&orig_newname_dir_names, &mod_newname_dir_names,
                                orig_newname_file_names, mod_newname_file_names,
                                report->orig_dir_loc_of, report->orig_dir_id_of,
                                report->orig_container_dir_id_of_dir_id, report->orig_container_dir_id_of_file_id,
                                report->mod_dir_loc_of, report->mod_dir_id_of,
                                report->mod_container_dir_id_of_dir_id, report->mod_container_dir_id_of_file_id,
                                report->orig_file_id_of, report->mod_file_id_of);


    rel_append_x (&orig_dirs_index_names_output, orig_newname_dir_names);
    rel_append_x (&orig_files_index_names_output, orig_newname_file_names);
    rel_append_x (&mod_dirs_index_names_output, mod_newname_dir_names);
    rel_append_x (&mod_files_index_names_output, mod_newname_file_names);

    rel_sort_table_by_field (0, orig_dirs_index_names_output, 0);
    rel_sort_table_by_field (0, orig_files_index_names_output, 0);
    rel_sort_table_by_field (0, mod_dirs_index_names_output, 0);
    rel_sort_table_by_field (0, mod_files_index_names_output, 0);
    rel_uniq_by_field (&orig_dirs_index_names_output, 0);
    rel_uniq_by_field (&orig_files_index_names_output, 0);
    rel_uniq_by_field (&mod_dirs_index_names_output, 0);
    rel_uniq_by_field (&mod_files_index_names_output, 0);

    orig_dirs_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, orig_dirs_index_names_output, orig_dirs_full_index_by_name);
    orig_files_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, orig_files_index_names_output, orig_files_full_index_by_name);
    mod_dirs_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, mod_dirs_index_names_output, mod_dirs_full_index_by_name);
    mod_files_index_final = rel_join (-1, rel_join_output (2,0, 2,1, -1), 0, 0, mod_files_index_names_output, mod_files_full_index_by_name);

    rel_sort_table_by_field (0, orig_dirs_index_final, 1);
    rel_sort_table_by_field (0, orig_files_index_final, 1);
    rel_sort_table_by_field (0, mod_dirs_index_final, 1);
    rel_sort_table_by_field (0, mod_files_index_final, 1);

    orig_dirs_index_fd = safe_open (orig_dirs_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
    orig_files_index_fd = safe_open (orig_files_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
    mod_dirs_index_fd = safe_open (mod_dirs_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666);
    mod_files_index_fd = safe_open (mod_files_index_file, O_WRONLY | O_CREAT | O_EXCL, 0666);

    rel_print_pika_escape_iso8859_1_table_sp (orig_dirs_index_fd, arch_escape_classes, orig_dirs_index_final);
    rel_print_pika_escape_iso8859_1_table_sp (orig_files_index_fd, arch_escape_classes, orig_files_index_final);
    rel_print_pika_escape_iso8859_1_table_sp (mod_dirs_index_fd, arch_escape_classes, mod_dirs_index_final);
    rel_print_pika_escape_iso8859_1_table_sp (mod_files_index_fd, arch_escape_classes, mod_files_index_final);

    safe_close (orig_dirs_index_fd);
    safe_close (orig_files_index_fd);
    safe_close (mod_dirs_index_fd);
    safe_close (mod_files_index_fd);

    rel_free_table (orig_newname_dir_names);
    rel_free_table (orig_newname_file_names);
    rel_free_table (mod_newname_dir_names);
    rel_free_table (mod_newname_file_names);

    if (report)
      {
        lim = rel_n_records (renamed_dirs);
        for (x = 0; x < lim; ++x)
          {
            rel_add_records (&report->renamed_dirs, rel_copy_record (rel_peek_record (renamed_dirs, x)), rel_record_null);
            if (report->callback)
              {
                t_uchar * escape_tmp0;
                t_uchar * escape_tmp1;
                escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          rel_peek_str (report->renamed_dirs, x, 0));
                escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          rel_peek_str (report->renamed_dirs, x, 1));
                invoke_report_callback (report, "/> %s\t%s\n", no_dot (escape_tmp0), no_dot (escape_tmp1));
                lim_free (0, escape_tmp1);
                lim_free (0, escape_tmp0);
              }
          }
        lim = rel_n_records (renamed_files);
        for (x = 0; x < lim; ++x)
          {
            rel_add_records (&report->renamed_files, rel_copy_record (rel_peek_record (renamed_files, x)), rel_record_null);
            if (report->callback)
              {
                t_uchar * escape_tmp0;
                t_uchar * escape_tmp1;
                escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          rel_peek_str (report->renamed_files, x, 0));
                escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          rel_peek_str (report->renamed_files, x, 1));
                invoke_report_callback (report, "=> %s\t%s\n", no_dot (escape_tmp0), no_dot (escape_tmp1));
                lim_free (0, escape_tmp1);
                lim_free (0, escape_tmp0);
              }
          }
      }
  }

  safe_fchdir (here_fd);
  safe_close (here_fd);
  lim_free (0, orig);
  lim_free (0, mod);
  lim_free (0, orig_root);
  lim_free (0, mod_root);
  lim_free (0, dest);
  rel_free_table (limits);
  free_assoc_table (loc_transl);
  rel_free_table (orig_full_index);
  rel_free_table (mod_full_index);
  rel_free_table (mod_full_index_by_name);
  rel_free_table (orig_full_index_by_name);
  rel_free_table (orig_dirs_full_index_by_name);
  rel_free_table (mod_dirs_full_index_by_name);
  rel_free_table (orig_dir_ids);
  rel_free_table (mod_dir_ids);
  rel_free_table (original_only_dir_ids);
  rel_free_table (modified_only_dir_ids);
  rel_free_table (dir_ids_in_both);
  rel_free_table (original_only_dirs);
  rel_free_table (modified_only_dirs);
  rel_free_table (renamed_dirs);
  rel_free_table (orig_files_full_index_by_name);
  rel_free_table (mod_files_full_index_by_name);
  rel_free_table (orig_file_ids);
  rel_free_table (mod_file_ids);
  rel_free_table (original_only_file_ids);
  rel_free_table (modified_only_file_ids);
  rel_free_table (original_only_files);
  rel_free_table (modified_only_files);
  rel_free_table (file_ids_in_both);
  rel_free_table (renamed_files);
  rel_free_table (files_in_both);
  rel_free_table (dirs_in_both);
  free_assoc_table (orig_ids_seen);
  free_assoc_table (mod_ids_seen);
  lim_free (0, original_only_dir_metadata_file);
  lim_free (0, modified_only_dir_metadata_file);
  lim_free (0, removed_files_archive_dir);
  lim_free (0, new_files_archive_dir);
  lim_free (0, patches_dir);
  lim_free (0, orig_files_index_file);
  lim_free (0, orig_dirs_index_file);
  lim_free (0, mod_files_index_file);
  lim_free (0, mod_dirs_index_file);
  rel_free_table (orig_files_index_names_output);
  rel_free_table (orig_dirs_index_names_output);
  rel_free_table (mod_files_index_names_output);
  rel_free_table (mod_dirs_index_names_output);
}


void
arch_make_empty_changeset (struct arch_make_changeset_report * make_report,
                           struct arch_changeset_report * report,
                           const t_uchar * changeset_path)
{
  t_uchar * new_files_archive = 0;
  t_uchar * removed_files_archive = 0;
  t_uchar * patches = 0;
  t_uchar * original_only_dir_metadata = 0;
  t_uchar * modified_only_dir_metadata = 0;
  t_uchar * orig_files_index = 0;
  t_uchar * orig_dirs_index = 0;
  t_uchar * mod_files_index = 0;
  t_uchar * mod_dirs_index = 0;
  int fd;


  new_files_archive = file_name_in_vicinity (0, changeset_path, "new-files-archive");
  removed_files_archive = file_name_in_vicinity (0, changeset_path, "removed-files-archive");
  patches = file_name_in_vicinity (0, changeset_path, "patches");
  original_only_dir_metadata = file_name_in_vicinity (0, changeset_path, "original-only-dir-metadata");
  modified_only_dir_metadata = file_name_in_vicinity (0, changeset_path, "modified-only-dir-metadata");
  orig_files_index = file_name_in_vicinity (0, changeset_path, "orig-files-index");
  orig_dirs_index = file_name_in_vicinity (0, changeset_path, "orig-dirs-index");
  mod_files_index = file_name_in_vicinity (0, changeset_path, "mod-files-index");
  mod_dirs_index = file_name_in_vicinity (0, changeset_path, "mod-dirs-index");

  safe_mkdir (changeset_path, 0777);
  safe_mkdir (patches, 0777);
  safe_mkdir (new_files_archive, 0777);
  safe_mkdir (removed_files_archive, 0777);

  fd = safe_open (original_only_dir_metadata, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);
  fd = safe_open (modified_only_dir_metadata, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);
  fd = safe_open (orig_files_index, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);
  fd = safe_open (orig_dirs_index, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);
  fd = safe_open (mod_files_index, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);
  fd = safe_open (mod_dirs_index, O_WRONLY | O_CREAT | O_EXCL, 0666);
  safe_close (fd);

  assoc_set_taking (&make_report->orig_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&make_report->mod_dir_id_of, rel_make_field_str ("."), rel_make_field_str ("?_."));
  assoc_set_taking (&make_report->orig_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));
  assoc_set_taking (&make_report->mod_dir_loc_of, rel_make_field_str ("?_."), rel_make_field_str ("."));

  lim_free (0, new_files_archive);
  lim_free (0, removed_files_archive);
  lim_free (0, patches);
  lim_free (0, original_only_dir_metadata);
  lim_free (0, modified_only_dir_metadata);
  lim_free (0, orig_files_index);
  lim_free (0, orig_dirs_index);
  lim_free (0, mod_files_index);
  lim_free (0, mod_dirs_index);
}


void
arch_changeset_rewrite_indexes (const t_uchar * changeset_path,
                                struct arch_changeset_report * report)
{
  int ign;
  t_uchar * orig_dirs_index_path = 0;
  t_uchar * orig_files_index_path = 0;
  t_uchar * mod_dirs_index_path = 0;
  t_uchar * mod_files_index_path = 0;
  int orig_dirs_index_fd = -1;
  int orig_files_index_fd = -1;
  int mod_dirs_index_fd = -1;
  int mod_files_index_fd = -1;

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

  vu_unlink (&ign, orig_dirs_index_path);
  orig_dirs_index_fd = safe_open (orig_dirs_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
  vu_unlink (&ign, orig_files_index_path);
  orig_files_index_fd = safe_open (orig_files_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
  vu_unlink (&ign, mod_dirs_index_path);
  mod_dirs_index_fd = safe_open (mod_dirs_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
  vu_unlink (&ign, mod_files_index_path);
  mod_files_index_fd = safe_open (mod_files_index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);

  rel_sort_table_by_field (0, report->orig_dirs_index, 1);
  rel_sort_table_by_field (0, report->orig_files_index, 1);
  rel_sort_table_by_field (0, report->mod_dirs_index, 1);
  rel_sort_table_by_field (0, report->mod_files_index, 1);

  rel_print_pika_escape_iso8859_1_table_sp (orig_dirs_index_fd, arch_escape_classes, report->orig_dirs_index);
  rel_print_pika_escape_iso8859_1_table_sp (orig_files_index_fd, arch_escape_classes, report->orig_files_index);
  rel_print_pika_escape_iso8859_1_table_sp (mod_dirs_index_fd, arch_escape_classes, report->mod_dirs_index);
  rel_print_pika_escape_iso8859_1_table_sp (mod_files_index_fd, arch_escape_classes, report->mod_files_index);

  safe_close (orig_dirs_index_fd);
  safe_close (orig_files_index_fd);
  safe_close (mod_dirs_index_fd);
  safe_close (mod_files_index_fd);

  lim_free (0, orig_dirs_index_path);
  lim_free (0, orig_files_index_path);
  lim_free (0, mod_dirs_index_path);
  lim_free (0, mod_files_index_path);
}



int
arch_changeset_add_file (t_uchar ** path_ret,
                         struct arch_changeset_report * report,
                         struct arch_make_changeset_report * make_report,
                         const t_uchar * changeset_path,
                         const t_uchar * mod_loc,
                         const t_uchar * id)
{
  int ign;
  t_uchar * new_file_archive_path = 0;
  t_uchar * new_file_path = 0;
  t_uchar * new_file_dir_path = 0;
  t_uchar * loc_dir = 0;
  rel_field loc_dir_id_field;
  int dest_fd = -1;

  new_file_archive_path = file_name_in_vicinity (0, changeset_path, "new-files-archive");
  new_file_path = file_name_in_vicinity (0, new_file_archive_path, mod_loc);

  if (path_ret)
    *path_ret = str_save (0, new_file_path);

  new_file_dir_path = file_name_directory_file (0, new_file_path);
  ensure_directory_exists (new_file_dir_path);
  vu_unlink (&ign, new_file_path);
  dest_fd = safe_open (new_file_path, O_WRONLY | O_CREAT | O_EXCL, 0666);

  /* update the changeset report.
   */
  rel_add_records (&report->mod_files_index,
                   rel_make_record_2_taking (rel_make_field_str (mod_loc), rel_make_field_str (id)),
                   rel_record_null);
  rel_sort_table_by_field (0, report->mod_files_index, 0);
  rel_uniq_by_field (&report->mod_files_index, 0);

  rel_add_records (&report->added_files,
                   rel_make_record_3_taking (rel_make_field_str (mod_loc),
                                             rel_make_field_str (id),
                                             rel_make_field_str (new_file_path)),
                   rel_record_null);
  rel_sort_table_by_field (0, report->added_files, 0);
  rel_uniq_by_field (&report->added_files, 0);

  /* update the make-changeset report.
   */
  assoc_set_taking (&make_report->mod_file_id_of, rel_make_field_str (mod_loc), rel_make_field_str (id));
  assoc_set_taking (&make_report->mod_file_loc_of, rel_make_field_str (id), rel_make_field_str (mod_loc));

  rel_add_records (&make_report->mod_index.files,
                   rel_make_record_2_taking (rel_make_field_str (mod_loc),
                                             rel_make_field_str (id)),
                   rel_record_null);
  rel_sort_table_by_field (0, make_report->mod_index.files, 1);
  rel_uniq_by_field (&make_report->mod_index.files, 1);

  rel_add_records (&make_report->added_files, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);
  rel_sort_table_by_field (0, make_report->added_files, 0);
  rel_uniq_by_field (&make_report->added_files, 0);

  loc_dir = file_name_directory_file (0, mod_loc);
  loc_dir_id_field = assoc_get_taking (make_report->mod_dir_id_of, rel_make_field_str (loc_dir));
  if (rel_field_str (loc_dir_id_field))
    {
      assoc_set_taking (&make_report->mod_container_dir_id_of_file_id, rel_make_field_str (id), rel_field_ref (loc_dir_id_field));
    }

  rel_field_unref (loc_dir_id_field);

  lim_free (0, new_file_archive_path);
  lim_free (0, new_file_path);
  lim_free (0, new_file_dir_path);
  lim_free (0, loc_dir);

  return dest_fd;
}


int
arch_changeset_add_diffs (struct arch_changeset_report * report,
                          struct arch_make_changeset_report * make_report,
                          const t_uchar * changeset_path,
                          const t_uchar * orig_loc,
                          const t_uchar * mod_loc,
                          const t_uchar * id)
{
  int ign;
  t_uchar * patches_path = 0;
  t_uchar * patch_path = 0;
  t_uchar * patch_dir_path = 0;
  t_uchar * orig_loc_dir = 0;
  rel_field orig_loc_dir_id_field;
  t_uchar * mod_loc_dir = 0;
  rel_field mod_loc_dir_id_field;
  int dest_fd = -1;

  patches_path = file_name_in_vicinity (0, changeset_path, "patches");
  patch_path = file_name_in_vicinity (0, patches_path, mod_loc);
  patch_path = str_realloc_cat (0, patch_path, ".patch");
  patch_dir_path = file_name_directory_file (0, patch_path);
  ensure_directory_exists (patch_dir_path);
  vu_unlink (&ign, patch_path);
  dest_fd = safe_open (patch_path, O_WRONLY | O_CREAT | O_EXCL, 0666);

  /* update the changeset report.
   */
  if (report)
    {
      rel_add_records (&report->mod_files_index,
                       rel_make_record_2_taking (rel_make_field_str (mod_loc), rel_make_field_str (id)),
                       rel_record_null);
      rel_sort_table_by_field (0, report->mod_files_index, 0);
      rel_uniq_by_field (&report->mod_files_index, 0);

      rel_add_records (&report->orig_files_index,
                       rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (id)),
                       rel_record_null);
      rel_sort_table_by_field (0, report->orig_files_index, 0);
      rel_uniq_by_field (&report->orig_files_index, 0);

      rel_add_records (&report->patched_regular_files,
                       rel_make_record_3_taking (rel_make_field_str (mod_loc),
                                                 rel_make_field_str (id),
                                                 rel_make_field_str (patch_path)),
                       rel_record_null);
      rel_sort_table_by_field (0, report->patched_regular_files, 0);
      rel_uniq_by_field (&report->patched_regular_files, 0);
    }

  /* update the make-changeset report.
   */
  assoc_set_taking (&make_report->mod_file_id_of, rel_make_field_str (mod_loc), rel_make_field_str (id));
  assoc_set_taking (&make_report->mod_file_loc_of, rel_make_field_str (id), rel_make_field_str (mod_loc));

  assoc_set_taking (&make_report->orig_file_id_of, rel_make_field_str (orig_loc), rel_make_field_str (id));
  assoc_set_taking (&make_report->orig_file_loc_of, rel_make_field_str (id), rel_make_field_str (orig_loc));

  rel_add_records (&make_report->mod_index.files,
                   rel_make_record_2_taking (rel_make_field_str (mod_loc),
                                             rel_make_field_str (id)),
                   rel_record_null);
  rel_sort_table_by_field (0, make_report->mod_index.files, 1);
  rel_uniq_by_field (&make_report->mod_index.files, 1);

  rel_add_records (&make_report->orig_index.files,
                   rel_make_record_2_taking (rel_make_field_str (orig_loc),
                                             rel_make_field_str (id)),
                   rel_record_null);
  rel_sort_table_by_field (0, make_report->orig_index.files, 1);
  rel_uniq_by_field (&make_report->orig_index.files, 1);

  rel_add_records (&make_report->modified_files,
                   rel_make_record_2_taking (rel_make_field_str (orig_loc),
                                             rel_make_field_str (mod_loc)),
                   rel_record_null);
  rel_sort_table_by_field (0, make_report->modified_files, 0);
  rel_uniq_by_field (&make_report->modified_files, 0);

  mod_loc_dir = file_name_directory_file (0, mod_loc);
  mod_loc_dir_id_field = assoc_get_taking (make_report->mod_dir_id_of, rel_make_field_str (mod_loc_dir));
  if (rel_field_str (mod_loc_dir_id_field))
    {
      assoc_set_taking (&make_report->mod_container_dir_id_of_file_id, rel_make_field_str (id), rel_field_ref (mod_loc_dir_id_field));
    }

  orig_loc_dir = file_name_directory_file (0, orig_loc);
  orig_loc_dir_id_field = assoc_get_taking (make_report->orig_dir_id_of, rel_make_field_str (orig_loc_dir));
  if (rel_field_str (orig_loc_dir_id_field))
    {
      assoc_set_taking (&make_report->orig_container_dir_id_of_file_id, rel_make_field_str (id), rel_field_ref (orig_loc_dir_id_field));
    }

  rel_field_unref (mod_loc_dir_id_field);
  rel_field_unref (orig_loc_dir_id_field);

  lim_free (0, patches_path);
  lim_free (0, patch_path);
  lim_free (0, patch_dir_path);
  lim_free (0, orig_loc_dir);
  lim_free (0, mod_loc_dir);
  return dest_fd;
}




void
arch_make_changeset_compute_container_map (assoc_table * out,
                                           assoc_table dir_id_of,
                                           rel_table index)
{
  int lim;
  int x;

  lim = rel_n_records (index);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * item;
      rel_field id_field;
      t_uchar * dir_as_file = 0;
      rel_field container_id_field;

      item = rel_peek_str (index, x, 0);
      id_field = rel_get_field (index, x, 1);

      dir_as_file = file_name_directory_file (0, item);

      container_id_field = assoc_get_taking (dir_id_of, rel_make_field_str (dir_as_file));
      invariant (!!rel_field_str (container_id_field));

      assoc_set_taking (out, rel_field_ref (id_field), rel_field_ref (container_id_field));

      rel_field_unref (id_field);
      rel_field_unref (container_id_field);

      lim_free (0, dir_as_file);
    }
}


static void
compute_renames (rel_table * out,
                 assoc_table loc_of_orig_X_id,
                 assoc_table mod_dir_id_of,
                 assoc_table orig_container_dir_id_of_X_id,
                 rel_table X_index)
{
  int lim;
  int x;

  lim = rel_n_records (X_index);

  for (x = 0; x < lim; ++x)
    {
      t_uchar * container = 0;
      t_uchar * tail = 0;
      rel_field id_field;
      rel_field orig_field;
      t_uchar * orig_tail = 0;
      rel_field mod_container_id_field;
      rel_field orig_container_id_field;

      container = file_name_directory_file (0, rel_peek_str (X_index, x, 0));
      tail = file_name_tail (0, rel_peek_str (X_index, x, 0));

      id_field = rel_get_field (X_index, x, 1);
      orig_field = assoc_get_taking (loc_of_orig_X_id, rel_field_ref (id_field));
      orig_tail = (rel_field_str (orig_field) ? file_name_tail (0, rel_field_str (orig_field)) : 0);

      mod_container_id_field = assoc_get_taking (mod_dir_id_of, rel_make_field_str (container));
      orig_container_id_field = assoc_get_taking (orig_container_dir_id_of_X_id, rel_field_ref (id_field));

      if (rel_field_str (orig_field) && (str_cmp (orig_tail, tail)
                   || (!rel_field_str (mod_container_id_field) && str_cmp (rel_peek_str (X_index, x, 0), rel_field_str (orig_field)))
                   || str_cmp (rel_field_str (mod_container_id_field), rel_field_str (orig_container_id_field))))
        {
          rel_add_records (out,
                           rel_make_record_2_taking (rel_field_ref (orig_field), rel_get_field (X_index, x, 0)),
                           rel_record_null);
        }

      rel_field_unref (id_field);
      rel_field_unref (orig_field);
      rel_field_unref (mod_container_id_field);
      rel_field_unref (orig_container_id_field);

      lim_free (0, container);
      lim_free (0, tail);
      lim_free (0, orig_tail);
    }

  rel_sort_table_by_field (0, *out, 1);
}


static void
write_dir_metadata (const t_uchar * file,
                    const t_uchar * tree_root,
                    rel_table dirs,
                    int full_meta)
{
  int out_fd;
  int lim;
  int x;

  out_fd = safe_open (file, O_WRONLY | O_CREAT | O_EXCL, 0666);

  lim = rel_n_records (dirs);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * path;
      t_uchar * escape_tmp;
      struct stat stat_buf;

      path = file_name_in_vicinity (0, tree_root, rel_peek_str (dirs, x, 0));
      safe_stat (path, &stat_buf);

      escape_tmp = pika_save_escape_iso8859_1 (0, 0, arch_escape_classes, rel_peek_str (dirs, x, 0));

      if (!full_meta)
        {
          safe_printfmt (out_fd, "--permissions %lo\t%s\n",
                         (unsigned long)(stat_buf.st_mode & 0777),
                         escape_tmp);
        }
      else
        {
          safe_printfmt (out_fd, "--permissions %lo,%ld,%ld\t%s\n",
                         (unsigned long)(stat_buf.st_mode & 07777),
                         (long)stat_buf.st_uid,
                         (long)stat_buf.st_gid,
                         escape_tmp);
        }

      lim_free (0, escape_tmp);
      lim_free (0, path);
    }

  safe_close (out_fd);
}


static void
copy_files_and_symlinks (const t_uchar * dest,
                         const t_uchar * tree_root,
                         rel_table loc_list,
                         int full_meta)
{
  int lim;
  int x;

  lim = rel_n_records (loc_list);
  for (x = 0; x < lim; ++x)
    {
      const t_uchar * loc;
      t_uchar * source_name = 0;
      t_uchar * dest_name = 0;
      t_uchar * dest_dir = 0;

      loc = rel_peek_str (loc_list, x, 0);
      source_name = file_name_in_vicinity (0, tree_root, loc);
      dest_name = file_name_in_vicinity (0, dest, loc);
      dest_dir = file_name_directory_file (0, dest_name);


      ensure_directory_exists (dest_dir);
      copy_file_or_symlink (source_name, dest_name);
      copy_permissions  (source_name, dest_name, full_meta);

      lim_free (0, source_name);
      lim_free (0, dest_name);
      lim_free (0, dest_dir);
    }
}


static void
emit_dir_patches (rel_table * orig_index_names_output,
                  rel_table * mod_index_names_output,
                  struct arch_make_changeset_report * report,
                  const t_uchar * patches,
                  const t_uchar * orig,
                  const t_uchar * orig_loc,
                  const t_uchar * mod,
                  const t_uchar * mod_loc,
                  int escape_classes,
                  int full_meta)
{
  t_uchar * orig_path;
  t_uchar * mod_path;
  struct stat orig_stat;
  struct stat mod_stat;
  t_ulong orig_mode;
  t_ulong mod_mode;
  long orig_uid;
  long mod_uid;
  long orig_gid;
  long mod_gid;

  orig_path = file_name_in_vicinity (0, orig, orig_loc);
  mod_path = file_name_in_vicinity (0, mod, mod_loc);

  safe_stat (orig_path, &orig_stat);
  safe_stat (mod_path, &mod_stat);

  if (full_meta)
    {
      orig_mode = (t_ulong)(orig_stat.st_mode & 07777);
      mod_mode = (t_ulong)(mod_stat.st_mode & 07777);
    }
  else
    {
      orig_mode = (t_ulong)(orig_stat.st_mode & 0777);
      mod_mode = (t_ulong)(mod_stat.st_mode & 0777);
    }

  orig_uid = orig_stat.st_uid;
  mod_uid = mod_stat.st_uid;
  orig_gid = orig_stat.st_gid;
  mod_gid = mod_stat.st_gid;

  if ((orig_mode != mod_mode) || (full_meta && ((orig_uid != mod_uid) || (orig_gid != mod_gid))))
    {
      t_uchar * patches_dir;
      t_uchar * orig_patch;
      t_uchar * mod_patch;
      int orig_patch_fd;
      int mod_patch_fd;

      rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
      rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

      if (report)
        {
          rel_add_records (&report->perms_changed_dirs, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
          if (report->callback)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       mod_loc);
              invoke_report_callback (report, "-/ %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
        }

      patches_dir = file_name_in_vicinity (0, patches, mod_loc);
      orig_patch = file_name_in_vicinity (0, patches_dir, "=dir-meta-orig");
      mod_patch = file_name_in_vicinity (0, patches_dir, "=dir-meta-mod");

      ensure_directory_exists (patches_dir);

      orig_patch_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);
      mod_patch_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);

      if (!full_meta)
        {
          safe_printfmt (orig_patch_fd, "--permissions %lo\n", orig_mode);
          safe_printfmt (mod_patch_fd, "--permissions %lo\n", mod_mode);
        }
      else
        {
          safe_printfmt (orig_patch_fd, "--permissions %lo,%ld,%ld\n", orig_mode, orig_uid, orig_gid);
          safe_printfmt (mod_patch_fd, "--permissions %lo,%ld,%ld\n", mod_mode, mod_uid, mod_gid);
        }

      safe_close (orig_patch_fd);
      safe_close (mod_patch_fd);

      lim_free (0, patches_dir);
      lim_free (0, orig_patch);
      lim_free (0, mod_patch);
    }


  lim_free (0, orig_path);
  lim_free (0, mod_path);
}


static void
emit_file_or_symlink_patches (rel_table * orig_index_names_output,
                              rel_table * mod_index_names_output,
                              struct arch_make_changeset_report * report,
                              const t_uchar * patches,
                              const t_uchar * orig,
                              const t_uchar * orig_loc,
                              const t_uchar * mod,
                              const t_uchar * mod_loc,
                              const t_uchar * id,
                              assoc_table inode_sig_shortcuts_of_mod,
			      int link_same,
                              int escape_classes,
                              int full_meta)
{
  t_uchar * orig_path = 0;
  t_uchar * mod_path = 0;
  struct stat orig_stat;
  struct stat mod_stat;
  int orig_is_symlink;
  int mod_is_symlink;
  t_uchar * patch_base_file = 0;
  t_uchar * patch_file_dir = 0;

  orig_path = file_name_in_vicinity (0, orig, orig_loc);
  mod_path = file_name_in_vicinity (0, mod, mod_loc);

  safe_lstat (orig_path, &orig_stat);
  safe_lstat (mod_path, &mod_stat);

  orig_is_symlink = S_ISLNK (orig_stat.st_mode);
  mod_is_symlink = S_ISLNK (mod_stat.st_mode);

  if (orig_is_symlink && mod_is_symlink)
    {
      t_uchar * orig_target;
      t_uchar * mod_target;

      orig_target = link_target (orig_path);
      mod_target = link_target (mod_path);

      if (str_cmp (orig_target, mod_target))
        {
          t_uchar * patch_basename_path;
          t_uchar * orig_patch;
          t_uchar * mod_patch;
          t_uchar * patch_dir;
          int orig_out_fd;
          int mod_out_fd;

          rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
          rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

          if (report)
            {
              rel_add_records (&report->modified_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
              if (report->callback)
                {
                  t_uchar * escape_tmp;
                  escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                           mod_loc);
                  invoke_report_callback (report, "-> %s\n", no_dot (escape_tmp));
                  lim_free (0, escape_tmp);
                }
            }

          patch_basename_path = file_name_in_vicinity (0, patches, mod_loc);
          orig_patch = str_alloc_cat (0, patch_basename_path, ".link-orig");
          mod_patch = str_alloc_cat (0, patch_basename_path, ".link-mod");
          patch_dir = file_name_directory_file (0, patch_basename_path);

          ensure_directory_exists (patch_dir);

          orig_out_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);
          mod_out_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);

          safe_printfmt (orig_out_fd, "%s\n", orig_target);
          safe_printfmt (mod_out_fd, "%s\n", mod_target);

          lim_free (0, patch_basename_path);
          lim_free (0, orig_patch);
          lim_free (0, mod_patch);
          lim_free (0, patch_dir);
        }

      lim_free (0, orig_target);
      lim_free (0, mod_target);
    }
  else if (orig_is_symlink)
    {
      t_uchar * orig_target;
      t_uchar * patch_basename_path;
      t_uchar * orig_patch;
      t_uchar * mod_patch;
      t_uchar * patch_dir;
      int orig_out_fd;

      rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
      rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

      if (report)
        {
          rel_add_records (&report->modified_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
          if (report->callback)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       mod_loc);
              invoke_report_callback (report, "lf %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
        }

      orig_target = link_target (orig_path);

      patch_basename_path = file_name_in_vicinity (0, patches, mod_loc);
      orig_patch = str_alloc_cat (0, patch_basename_path, ".link-orig");
      mod_patch = str_alloc_cat (0, patch_basename_path, ".modified");
      patch_dir = file_name_directory_file (0, patch_basename_path);

      ensure_directory_exists (patch_dir);

      orig_out_fd = safe_open (orig_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);

      safe_printfmt (orig_out_fd, "%s\n", orig_target);
      copy_file (mod_path, mod_patch);
      copy_permissions (mod_path, mod_patch, full_meta);


      lim_free (0, orig_target);
      lim_free (0, patch_basename_path);
      lim_free (0, orig_patch);
      lim_free (0, mod_patch);
      lim_free (0, patch_dir);
    }
  else if (mod_is_symlink)
    {
      t_uchar * mod_target;
      t_uchar * patch_basename_path;
      t_uchar * orig_patch;
      t_uchar * mod_patch;
      t_uchar * patch_dir;
      int mod_out_fd;

      rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
      rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

      if (report)
        {
          rel_add_records (&report->modified_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
          if (report->callback)
            {
              t_uchar * escape_tmp;
              escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                       mod_loc);
              invoke_report_callback (report, "fl %s\n", no_dot (escape_tmp));
              lim_free (0, escape_tmp);
            }
        }

      mod_target = link_target (mod_path);

      patch_basename_path = file_name_in_vicinity (0, patches, mod_loc);
      orig_patch = str_alloc_cat (0, patch_basename_path, ".original");
      mod_patch = str_alloc_cat (0, patch_basename_path, ".link-mod");
      patch_dir = file_name_directory_file (0, patch_basename_path);

      ensure_directory_exists (patch_dir);

      mod_out_fd = safe_open (mod_patch, O_WRONLY | O_CREAT | O_EXCL, 0666);

      safe_printfmt (mod_out_fd, "%s\n", mod_target);
      copy_file (orig_path, orig_patch);
      copy_permissions (orig_path, orig_patch, full_meta);


      lim_free (0, mod_target);
      lim_free (0, patch_basename_path);
      lim_free (0, orig_patch);
      lim_free (0, mod_patch);
      lim_free (0, patch_dir);
    }
  else
    {
      int ign;
      mode_t orig_mode;
      mode_t mod_mode;
      long orig_uid;
      long mod_uid;
      long orig_gid;
      long mod_gid;
      t_uchar * diff_file;
      int diff_fd;
      int diff_stat;
      int is_changed;

      is_changed = 0;

      if (arch_file_stats_differ (&orig_stat, &mod_stat) == 0) 
        goto path_free;

      patch_base_file = file_name_in_vicinity (0, patches, mod_loc);
      patch_file_dir = file_name_directory_file (0, patch_base_file);

      if (full_meta)
        {
          orig_mode = orig_stat.st_mode & 07777;
          mod_mode = mod_stat.st_mode & 07777;
        }
      else
        {
          orig_mode = orig_stat.st_mode & 0777;
          mod_mode = mod_stat.st_mode & 0777;
        }
      orig_uid = orig_stat.st_uid;
      mod_uid = mod_stat.st_uid;
      orig_gid = orig_stat.st_gid;
      mod_gid = mod_stat.st_gid;

      if ((orig_mode != mod_mode) || (full_meta && ((orig_uid != mod_uid) || (orig_gid != mod_gid))))
        {
          t_uchar * orig_patch_meta_file;
          t_uchar * mod_patch_meta_file;
          int orig_meta_fd;
          int mod_meta_fd;

          rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
          rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

          if (report)
            {
              rel_add_records (&report->perms_changed_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
              if (report->callback)
                {
                  t_uchar * escape_tmp;
                  escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                           mod_loc);
                  invoke_report_callback (report, "-- %s\n", no_dot (escape_tmp));
                  lim_free (0, escape_tmp);
                }
            }

          orig_patch_meta_file = str_alloc_cat (0, patch_base_file, ".meta-orig");
          mod_patch_meta_file = str_alloc_cat (0, patch_base_file, ".meta-mod");

          ensure_directory_exists (patch_file_dir);

          orig_meta_fd = safe_open (orig_patch_meta_file, O_WRONLY | O_EXCL | O_CREAT, 0666);
          mod_meta_fd = safe_open (mod_patch_meta_file, O_WRONLY | O_EXCL | O_CREAT, 0666);

          if (!full_meta)
            {
              safe_printfmt (orig_meta_fd, "--permissions %lo\n", (t_ulong)orig_mode);
              safe_printfmt (mod_meta_fd, "--permissions %lo\n", (t_ulong)mod_mode);
            }
          else
            {
              safe_printfmt (orig_meta_fd, "--permissions %lo,%ld,%ld\n", (t_ulong)orig_mode, orig_uid, orig_gid);
              safe_printfmt (mod_meta_fd, "--permissions %lo,%ld,%ld\n", (t_ulong)mod_mode, mod_uid, mod_gid);
            }

          safe_close (orig_meta_fd);
          safe_close (mod_meta_fd);
          lim_free (0, orig_patch_meta_file);
          lim_free (0, mod_patch_meta_file);

          is_changed = 1;
        }

      if ((arch_inode_sigs_differ (&mod_stat, id, inode_sig_shortcuts_of_mod) == 0)
          || (arch_filename_contents_differ (orig_path, mod_path) == 0))
        {
	  if (orig_stat.st_dev == mod_stat.st_dev && link_same)
	    link_force (orig_path, mod_path);
	  goto path_free;
	}

      ensure_directory_exists (patch_file_dir);
      diff_file = str_alloc_cat (0, patch_base_file, ".patch");

      vu_unlink (&ign, diff_file);
      diff_fd = safe_open (diff_file, O_RDWR | O_EXCL | O_CREAT, 0666);

      diff_stat = arch_really_invoke_diff (diff_fd, orig_path, orig_loc, mod_path, mod_loc, NULL);
      invariant (diff_stat!=0);

      if (diff_stat == 1)
        {
          t_uchar diff_pseudo_magic[sizeof ("binary files ")];
          long amt;

          /* diff might have just said "binary files differ" or something
           * similar.
           */

          safe_lseek (diff_fd, (off_t)0, SEEK_SET);
          amt = safe_read (diff_fd, diff_pseudo_magic, sizeof (diff_pseudo_magic) - 1);

          if (amt > 0)
            {
              diff_pseudo_magic[amt] = 0;
              if (!str_casecmp (diff_pseudo_magic, "binary files "))
                diff_stat = 2;
            }
        }
      if (diff_stat == 2)
        safe_unlink (diff_file);


      /* diff_stat: 0 == same, 1 == patch in diff_fd, 2 == differing binary files
       */

      switch (diff_stat)
        {
        case 1:
          {

            rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
            rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

            if (report)
              {
                rel_add_records (&report->modified_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
                if (report->callback)
                  {
                    t_uchar * escape_tmp;
                    escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                             mod_loc);
                    invoke_report_callback (report, "M  %s\n", no_dot (escape_tmp));
                    lim_free (0, escape_tmp);
                  }
              }

            is_changed = 1;

            lim_free (0, diff_file);
            break;
          }

        case 2:
          {
            t_uchar * orig_copy;
            t_uchar * mod_copy;

            rel_add_records (orig_index_names_output, rel_singleton_record_taking (rel_make_field_str (orig_loc)), rel_record_null);
            rel_add_records (mod_index_names_output, rel_singleton_record_taking (rel_make_field_str (mod_loc)), rel_record_null);

            if (report)
              {
                rel_add_records (&report->modified_files, rel_make_record_2_taking (rel_make_field_str (orig_loc), rel_make_field_str (mod_loc)), rel_record_null);
                if (report->callback)
                  {
                    t_uchar * escape_tmp;
                    escape_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                             mod_loc);
                    invoke_report_callback (report, "Mb %s\n", no_dot (escape_tmp));
                    lim_free (0, escape_tmp);
                  }
              }

            orig_copy = str_alloc_cat (0, patch_base_file, ".original");
            mod_copy = str_alloc_cat (0, patch_base_file, ".modified");

            copy_file (orig_path, orig_copy);
            copy_permissions (orig_path, orig_copy, full_meta);
            copy_file (mod_path, mod_copy);
            copy_permissions (mod_path, mod_copy, full_meta);

            lim_free (0, orig_copy);
            lim_free (0, mod_copy);

            is_changed = 1;

            break;
          }

        default:
          {
            break;
          }
        }


#if 0
      if (is_changed)
        {
          *(int *)ar_push ((void **)&map.changed_files[0], 0, sizeof (int)) = map.common_files[0][x];
          *(int *)ar_push ((void **)&map.changed_files[1], 0, sizeof (int)) = map.common_files[1][x];
        }
#endif


      safe_close (diff_fd);
    }

 path_free:
  lim_free (0, patch_base_file);
  lim_free (0, patch_file_dir);
  lim_free (0, orig_path);
  lim_free (0, mod_path);
}



static void
compute_parent_dir_closure (rel_table * orig_newname_dirs,
                            rel_table * mod_newname_dirs,
                            rel_table orig_newname_files,
                            rel_table mod_newname_files,
                            assoc_table orig_dir_loc_of,
                            assoc_table orig_dir_id_of,
                            assoc_table orig_container_dir_id_of_dir_id,
                            assoc_table orig_container_dir_id_of_file_id,
                            assoc_table mod_dir_loc_of,
                            assoc_table mod_dir_id_of,
                            assoc_table mod_container_dir_id_of_dir_id,
                            assoc_table mod_container_dir_id_of_file_id,
                            assoc_table orig_file_id_of,
                            assoc_table mod_file_id_of)
{
  assoc_table orig_has_dir_id = 0;
  assoc_table mod_has_dir_id = 0;
  rel_table added_orig_container_ids = rel_table_nil; /* just the deepest new id */
  rel_table added_mod_container_ids = rel_table_nil; /* just the deepest new id */
  int lim;
  int x;

  /* Note what dir ids are already in the
   * final indexes.
   */

  lim = rel_n_records (*orig_newname_dirs);
  for (x = 0; x < lim; ++x)
    {
      rel_field loc_field;
      rel_field id_field;

      loc_field = rel_get_field (*orig_newname_dirs, x, 0);

      id_field = assoc_get_taking (orig_dir_id_of, rel_field_ref (loc_field));

      assoc_set_taking (&orig_has_dir_id, rel_field_ref (id_field), rel_field_ref (loc_field));

      rel_field_unref (id_field);
      rel_field_unref (loc_field);
    }

  lim = rel_n_records (*mod_newname_dirs);
  for (x = 0; x < lim; ++x)
    {
      rel_field loc_field;
      rel_field id_field;

      loc_field = rel_get_field (*mod_newname_dirs, x, 0);

      id_field = assoc_get_taking (mod_dir_id_of, rel_field_ref (loc_field));

      assoc_set_taking (&mod_has_dir_id, rel_field_ref (id_field), rel_field_ref (loc_field));

      rel_field_unref (id_field);
      rel_field_unref (loc_field);
    }



  /* For each newname dir in one tree,
   * find the deepest container in that
   * tree that is present in each tree
   * but not in that tree's index,
   * and slate it for addition to the index.
   */

  schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id,
                               &added_mod_container_ids, &mod_has_dir_id,
                               *orig_newname_dirs, orig_dir_id_of,
                               orig_container_dir_id_of_dir_id, orig_container_dir_id_of_dir_id,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id,
                               &added_mod_container_ids, &mod_has_dir_id,
                               orig_newname_files, orig_dir_id_of,
                               orig_container_dir_id_of_file_id, orig_container_dir_id_of_dir_id,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id,
                               &added_mod_container_ids, &mod_has_dir_id,
                               *mod_newname_dirs, mod_dir_id_of,
                               mod_container_dir_id_of_dir_id, mod_container_dir_id_of_dir_id,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_ids, &orig_has_dir_id,
                               &added_mod_container_ids, &mod_has_dir_id,
                               mod_newname_files, mod_dir_id_of,
                               mod_container_dir_id_of_file_id, mod_container_dir_id_of_dir_id,
                               orig_dir_loc_of, mod_dir_loc_of);


  /* Add the closure of container paths of added containers
   * to the output lists.
   */

  lim = rel_n_records (added_orig_container_ids);
  for (x = 0; x < lim; ++x)
    {
      container_closure (orig_newname_dirs,
                         &orig_has_dir_id,
                         rel_peek_str (added_orig_container_ids, x, 0),
                         orig_dir_loc_of,
                         orig_container_dir_id_of_dir_id);
    }
  lim = rel_n_records (added_mod_container_ids);
  for (x = 0; x < lim; ++x)
    {
      container_closure (mod_newname_dirs,
                         &mod_has_dir_id,
                         rel_peek_str (added_mod_container_ids, x, 0),
                         mod_dir_loc_of,
                         mod_container_dir_id_of_dir_id);
    }

  free_assoc_table (orig_has_dir_id);
  free_assoc_table (mod_has_dir_id);
  rel_free_table (added_orig_container_ids);
  rel_free_table (added_mod_container_ids);
}

static void
schedule_missing_containers (rel_table * added_orig_container_ids,
                             assoc_table * orig_has_dir_id,
                             rel_table * added_mod_container_ids,
                             assoc_table * mod_has_dir_id,
                             rel_table locs_in,
                             assoc_table id_of_dir_loc,
                             assoc_table first_container_id_of_id,
                             assoc_table container_id_of_dir_id,
                             assoc_table orig_dir_loc_of,
                             assoc_table mod_dir_loc_of)
{
  int lim;
  int x;

  lim = rel_n_records (locs_in);

  for (x = 0; x < lim; ++x)
    {
      t_uchar * loc = 0;
      rel_field id_field;
      rel_field container_id_field;
      int added_orig;
      int added_mod;


      loc = str_save (0, rel_peek_str (locs_in, x, 0));
      id_field = assoc_get_taking (id_of_dir_loc, rel_make_field_str (loc));
      container_id_field = assoc_get_taking (first_container_id_of_id, rel_field_ref (id_field));

      added_orig = 0;
      added_mod = 0;

      while ((!added_orig || !added_mod) && rel_field_str (container_id_field) && str_cmp (rel_field_str (id_field), "?_."))
        {
          t_uchar * t = 0;

          if (!added_orig && !assoc_get_str_taking (*orig_has_dir_id, rel_field_ref (container_id_field)) && assoc_get_str_taking (orig_dir_loc_of, rel_field_ref (container_id_field)))
            {
              rel_add_records (added_orig_container_ids, rel_singleton_record_taking (rel_field_ref (container_id_field)), rel_record_null);
              added_orig = 1;
            }
          if (!added_mod && !assoc_get_str_taking (*mod_has_dir_id, rel_field_ref (container_id_field)) && assoc_get_str_taking (mod_dir_loc_of, rel_field_ref (container_id_field)))
            {
              rel_add_records (added_mod_container_ids, rel_singleton_record_taking (rel_field_ref (container_id_field)), rel_record_null);
              added_mod = 1;
            }

          t = loc;
          loc = file_name_directory_file (0, loc);
          rel_field_unref (id_field);
          id_field = assoc_get_taking (id_of_dir_loc, rel_make_field_str (loc));
          container_id_field = assoc_get_taking (container_id_of_dir_id, rel_field_ref (id_field));

          lim_free (0, t);
        }

      rel_field_unref (id_field);
      rel_field_unref (container_id_field);

      lim_free (0, loc);
    }
}


static void
container_closure (rel_table * locs_out,
                   assoc_table * has_ids,
                   const t_uchar * deep_id,
                   assoc_table dir_loc_of,
                   assoc_table container_dir_id_of_dir_id)
{
  while (deep_id && str_cmp (deep_id, "?_."))
    {
      if (!assoc_get_str_taking (*has_ids, rel_make_field_str (deep_id)))
        {
          rel_field deep_loc_field;

          deep_loc_field = assoc_get_taking (dir_loc_of, rel_make_field_str (deep_id));
          rel_add_records (locs_out, rel_singleton_record_taking (rel_field_ref (deep_loc_field)), rel_record_null);
          assoc_set_taking (has_ids, rel_make_field_str (deep_id), rel_field_ref (deep_loc_field));

          rel_field_unref (deep_loc_field);
        }

      deep_id = assoc_get_str_taking (container_dir_id_of_dir_id, rel_make_field_str (deep_id));
    }
}


static void 
link_force (const t_uchar *old_path,
            const t_uchar *new_path)
{
  t_uchar *dir;   
  t_uchar *tmp_nam;
  dir = file_name_directory (0, new_path);
  tmp_nam = file_name_in_vicinity (0, dir, ",,tmp");
  safe_link (old_path, tmp_nam);
  safe_rename (tmp_nam, new_path);
  lim_free (0, dir);
  lim_free (0, tmp_nam);
}




/* tag: Tom Lord Wed May 14 18:53:05 2003 (make-changeset.c)
 */
