/* 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 __attribute__((format (printf, 2, 3))) invoke_report_callback (struct arch_make_changeset_report * report, char * fmt, ...);
static char * no_dot (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 (t_uchar * file, t_uchar * tree_root, rel_table dirs);
static void copy_files_and_symlinks (t_uchar * dest, t_uchar * tree_root, rel_table loc_list);
static void emit_dir_patches (rel_table * orig_index_names_output,
                              rel_table * mod_index_names_output,
                              struct arch_make_changeset_report * report,
                              t_uchar * patches,
                              t_uchar * orig,
                              t_uchar * orig_loc,
                              t_uchar * mod,
                              t_uchar * mod_loc,
                              int escape_classes);
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,
                                          t_uchar * patches,
                                          t_uchar * orig,
                                          t_uchar * orig_loc,
                                          t_uchar * mod,
                                          t_uchar * mod_loc,
                                          t_uchar * id,
                                          assoc_table inode_sig_shortcuts_of_mod,
                                          int link_same,
                                          int escape_classes);
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,
                               t_uchar * deep_id,
                               assoc_table dir_loc_of,
                               assoc_table container_dir_id_of_dir_id);
static void link_force (t_uchar *old_path, t_uchar *new_path);



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, char * fmt, ...)
{
  va_list ap;

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


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

void
arch_make_changeset (struct arch_make_changeset_report * report,
                     t_uchar * orig_spec, t_uchar * mod_spec,
                     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;
  t_uchar * dest;
  rel_table limits = 0;
  assoc_table loc_transl = 0;
  int lim;
  int x;
  rel_table orig_full_index = 0;
  rel_table mod_full_index = 0;
  rel_table mod_full_index_by_name = 0;
  rel_table orig_full_index_by_name = 0;
  rel_table orig_dirs_full_index_by_name = 0;
  rel_table mod_dirs_full_index_by_name = 0;
  rel_table orig_dir_ids = 0;
  rel_table mod_dir_ids = 0;
  rel_table original_only_dir_ids = 0;
  rel_table modified_only_dir_ids = 0;
  rel_table dir_ids_in_both = 0;
  rel_table original_only_dirs = 0;
  rel_table modified_only_dirs = 0;
  rel_table renamed_dirs = 0;
  rel_table orig_files_full_index_by_name = 0;
  rel_table mod_files_full_index_by_name = 0;
  rel_table orig_file_ids = 0;
  rel_table mod_file_ids = 0;
  rel_table original_only_file_ids = 0;
  rel_table modified_only_file_ids = 0;
  rel_table original_only_files = 0;
  rel_table modified_only_files = 0;
  rel_table file_ids_in_both = 0;
  rel_table renamed_files = 0;
  rel_table files_in_both = 0;
  rel_table dirs_in_both = 0;
  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 = 0;
  rel_table orig_dirs_index_names_output = 0;
  rel_table mod_files_index_names_output = 0;
  rel_table mod_dirs_index_names_output = 0;


  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);

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

      mod_root_length = str_length (mod_root);

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

          spec = 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_make_record (spec_loc, 0), 0);

          lim_free (0, spec);
          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 (limits[m][0], lim_spec_locs[l][0]))
                {
                  dont_want = 1;
                }
              else if (file_name_is_path_prefix (lim_spec_locs[l][0], limits[m][0]))
                {
                  lim_free (0, limits[m][0]);
                  limits[m][0] = str_save (0, lim_spec_locs[l][0]);
                  dont_want = 1;
                }
            }
          if (!dont_want)
            rel_add_records (&limits, rel_make_record (lim_spec_locs[l][0], 0), 0);
        }

      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 (".", "?_.", 0), 0);
  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 (".", "?_.", 0), 0);
  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 (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 (rel_cut_list (1, -1), report->orig_index.dirs);
  mod_dir_ids = rel_cut (rel_cut_list (1, -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 (&report->orig_dir_id_of, report->orig_index.dirs[x][0], report->orig_index.dirs[x][1]);
      assoc_set (&report->orig_dir_loc_of, report->orig_index.dirs[x][1], report->orig_index.dirs[x][0]);
    }
  lim = rel_n_records (report->orig_index.files);
  for (x = 0; x < lim; ++x)
    {
      assoc_set (&report->orig_file_id_of, report->orig_index.files[x][0], report->orig_index.files[x][1]);
      assoc_set (&report->orig_file_loc_of, report->orig_index.files[x][1], report->orig_index.files[x][0]);
    }
  lim = rel_n_records (report->mod_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      assoc_set (&report->mod_dir_id_of, report->mod_index.dirs[x][0], report->mod_index.dirs[x][1]);
      assoc_set (&report->mod_dir_loc_of, report->mod_index.dirs[x][1], report->mod_index.dirs[x][0]);
    }
  lim = rel_n_records (report->mod_index.files);
  for (x = 0; x < lim; ++x)
    {
      assoc_set (&report->mod_file_id_of, report->mod_index.files[x][0], report->mod_index.files[x][1]);
      assoc_set (&report->mod_file_loc_of, report->mod_index.files[x][1], report->mod_index.files[x][0]);
    }

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

  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 (rel_cut_list (1, -1), report->orig_index.files);
  mod_file_ids = rel_cut (rel_cut_list (1, -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)
    {
      t_uchar * seen;

      seen = assoc_ref (orig_ids_seen, report->orig_index.dirs[x][1]);

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

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

      seen = assoc_ref (orig_ids_seen, report->orig_index.files[x][1]);

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

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

      seen = assoc_ref (mod_ids_seen, report->mod_index.dirs[x][1]);

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

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

      seen = assoc_ref (mod_ids_seen, report->mod_index.files[x][1]);

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

      assoc_set (&mod_ids_seen, report->mod_index.files[x][1], 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);
  write_dir_metadata (modified_only_dir_metadata_file, mod, modified_only_dirs);

  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,
                                                       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,
                                                       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);
  copy_files_and_symlinks (new_files_archive_dir, mod, modified_only_files);

  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,
                                                       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,
                                                       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, files_in_both[x][0], mod, files_in_both[x][1],
                                    files_in_both[x][2],
                                    inode_sig_shortcuts_of_mod, link_same, escape_classes);
    }
  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, dirs_in_both[x][0], mod, dirs_in_both[x][1], escape_classes);
    }

  /****************************************************************
   * 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 = 0;
    rel_table orig_newname_file_names = 0;
    rel_table mod_newname_dir_names = 0;
    rel_table mod_newname_file_names = 0;

    rel_table orig_dirs_index_final = 0;
    rel_table orig_files_index_final = 0;
    rel_table mod_dirs_index_final = 0;
    rel_table mod_files_index_final = 0;

    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 (rel_cut_list (0, -1), renamed_dirs);
    orig_newname_file_names = rel_cut (rel_cut_list (0, -1), renamed_files);
    mod_newname_dir_names = rel_cut (rel_cut_list (1, -1), renamed_dirs);
    mod_newname_file_names = rel_cut (rel_cut_list (1, -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 (renamed_dirs[x]), 0);
            if (report->callback)
              {
                t_uchar * escape_tmp0;
                t_uchar * escape_tmp1;
                escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          report->renamed_dirs[x][0]);
                escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          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 (renamed_files[x]), 0);
            if (report->callback)
              {
                t_uchar * escape_tmp0;
                t_uchar * escape_tmp1;
                escape_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          report->renamed_files[x][0]);
                escape_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes,
                                                          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,
                           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 (&make_report->orig_dir_id_of, ".", "?_.");
  assoc_set (&make_report->mod_dir_id_of, ".", "?_.");
  assoc_set (&make_report->orig_dir_loc_of, "?_.", ".");
  assoc_set (&make_report->mod_dir_loc_of, "?_.", ".");

  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 (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,
                         t_uchar * changeset_path,
                         t_uchar * mod_loc,
                         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;
  t_uchar * loc_dir_id;
  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 (mod_loc, id, 0), 0);
  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 (mod_loc, id, new_file_path, 0), 0);
  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 (&make_report->mod_file_id_of, mod_loc, id);
  assoc_set (&make_report->mod_file_loc_of, id, mod_loc);

  rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, id, 0), 0);
  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_make_record (mod_loc, 0), 0);
  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 = assoc_ref (make_report->mod_dir_id_of, loc_dir);
  if (loc_dir_id)
    assoc_set (&make_report->mod_container_dir_id_of_file_id, id, loc_dir_id);

  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,
                          t_uchar * changeset_path,
                          t_uchar * orig_loc,
                          t_uchar * mod_loc,
                          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;
  t_uchar * orig_loc_dir_id;
  t_uchar * mod_loc_dir = 0;
  t_uchar * mod_loc_dir_id;
  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 (mod_loc, id, 0), 0);
      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 (orig_loc, id, 0), 0);
      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 (mod_loc, id, patch_path, 0), 0);
      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 (&make_report->mod_file_id_of, mod_loc, id);
  assoc_set (&make_report->mod_file_loc_of, id, mod_loc);

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

  rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, id, 0), 0);
  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 (orig_loc, id, 0), 0);
  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 (orig_loc, mod_loc, 0), 0);
  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 = assoc_ref (make_report->mod_dir_id_of, mod_loc_dir);
  if (mod_loc_dir_id)
    assoc_set (&make_report->mod_container_dir_id_of_file_id, id, mod_loc_dir_id);

  orig_loc_dir = file_name_directory_file (0, orig_loc);
  orig_loc_dir_id = assoc_ref (make_report->orig_dir_id_of, orig_loc_dir);
  if (orig_loc_dir_id)
    assoc_set (&make_report->orig_container_dir_id_of_file_id, id, orig_loc_dir_id);

  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)
    {
      t_uchar * item;
      t_uchar * id;
      t_uchar * dir_as_file;
      t_uchar * container_id;

      item = index[x][0];
      id = index[x][1];

      dir_as_file = file_name_directory_file (0, item);
      container_id = assoc_ref (dir_id_of, dir_as_file);
      invariant (!!container_id);

      assoc_set (out, id, container_id);

      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 = ar_size ((void *)X_index, 0, sizeof (rel_record));
  for (x = 0; x < lim; ++x)
    {
      t_uchar * container;
      t_uchar * tail;
      t_uchar * id;
      t_uchar * orig;
      t_uchar * orig_tail;
      t_uchar * mod_container_id;
      t_uchar * orig_container_id;

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

      id = X_index[x][1];
      orig = assoc_ref (loc_of_orig_X_id, id);
      orig_tail = (orig ? file_name_tail (0, orig) : 0);

      mod_container_id = assoc_ref (mod_dir_id_of, container);
      orig_container_id = assoc_ref (orig_container_dir_id_of_X_id, id);

      if (orig && (str_cmp (orig_tail, tail)
                   || (!mod_container_id && str_cmp (X_index[x][0], orig))
                   || str_cmp (mod_container_id, orig_container_id)))
        {
          rel_add_records (out,
                           rel_make_record (orig, X_index[x][0], 0),
                           0);
        }

      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 (t_uchar * file, t_uchar * tree_root, rel_table dirs)
{
  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, dirs[x][0]);
      safe_stat (path, &stat_buf);

      escape_tmp = pika_save_escape_iso8859_1 (0, 0, arch_escape_classes, dirs[x][0]);

      safe_printfmt (out_fd, "--permissions %lo\t%s\n",
                     (unsigned long)(stat_buf.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)),
                     escape_tmp);

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

  safe_close (out_fd);
}


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

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

      loc = 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);

      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,
                  t_uchar * patches,
                  t_uchar * orig,
                  t_uchar * orig_loc,
                  t_uchar * mod,
                  t_uchar * mod_loc,
                  int escape_classes)
{
  t_uchar * orig_path;
  t_uchar * mod_path;
  struct stat orig_stat;
  struct stat mod_stat;
  t_ulong orig_mode;
  t_ulong mod_mode;

  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);

  orig_mode = (t_ulong)(orig_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));
  mod_mode = (t_ulong)(mod_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

  if (orig_mode != mod_mode)
    {
      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_make_record (orig_loc, 0), 0);
      rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

      if (report)
        {
          rel_add_records (&report->perms_changed_dirs, rel_make_record (orig_loc, mod_loc, 0), 0);
          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);

      safe_printfmt (orig_patch_fd, "--permissions %lo\n", orig_mode);
      safe_printfmt (mod_patch_fd, "--permissions %lo\n", mod_mode);

      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,
                              t_uchar * patches,
                              t_uchar * orig,
                              t_uchar * orig_loc,
                              t_uchar * mod,
                              t_uchar * mod_loc,
                              t_uchar * id,
                              assoc_table inode_sig_shortcuts_of_mod,
			      int link_same,
                              int escape_classes)
{
  t_uchar * orig_path;
  t_uchar * mod_path;
  struct stat orig_stat;
  struct stat mod_stat;
  int orig_is_symlink;
  int mod_is_symlink;

  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_make_record (orig_loc, 0), 0);
          rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

          if (report)
            {
              rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0);
              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_make_record (orig_loc, 0), 0);
      rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

      if (report)
        {
          rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0);
          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);


      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_make_record (orig_loc, 0), 0);
      rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

      if (report)
        {
          rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0);
          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);


      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;
      t_uchar * patch_base_file;
      t_uchar * patch_file_dir;
      mode_t orig_mode;
      mode_t mod_mode;
      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;
      if (arch_inode_sigs_differ (&mod_stat, id, inode_sig_shortcuts_of_mod) == 0)
        {
          if (orig_stat.st_dev == mod_stat.st_dev && link_same)
            link_force (orig_path, mod_path);
          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);

      orig_mode = orig_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);
      mod_mode = mod_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO);

      if (orig_mode != mod_mode)
        {
          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_make_record (orig_loc, 0), 0);
          rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

          if (report)
            {
              rel_add_records (&report->perms_changed_files, rel_make_record (orig_loc, mod_loc, 0), 0);
              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);

          safe_printfmt (orig_meta_fd, "--permissions %lo\n", (t_ulong)orig_mode);
          safe_printfmt (mod_meta_fd, "--permissions %lo\n", (t_ulong)mod_mode);

          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_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);
      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_make_record (orig_loc, 0), 0);
            rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

            if (report)
              {
                rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0);
                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_make_record (orig_loc, 0), 0);
            rel_add_records (mod_index_names_output, rel_make_record (mod_loc, 0), 0);

            if (report)
              {
                rel_add_records (&report->modified_files, rel_make_record (orig_loc, mod_loc, 0), 0);
                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);
            copy_file (mod_path, mod_copy);
            copy_permissions (mod_path, mod_copy);

            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);

      lim_free (0, patch_base_file);
      lim_free (0, patch_file_dir);
    }

 path_free:
  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 = 0; /* just the deepest new id */
  rel_table added_mod_container_ids = 0; /* 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)
    {
      t_uchar * loc;
      t_uchar * id;

      loc = (*orig_newname_dirs)[x][0];
      id = assoc_ref (orig_dir_id_of, loc);

      assoc_set (&orig_has_dir_id, id, loc);
    }

  lim = rel_n_records (*mod_newname_dirs);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * loc;
      t_uchar * id;

      loc = (*mod_newname_dirs)[x][0];
      id = assoc_ref (mod_dir_id_of, loc);

      assoc_set (&mod_has_dir_id, id, loc);
    }



  /* 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,
                         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,
                         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;
      t_uchar * id;
      t_uchar * container_id;
      int added_orig;
      int added_mod;


      loc = str_save (0, locs_in[x][0]);
      id = assoc_ref (id_of_dir_loc, loc);
      container_id = assoc_ref (first_container_id_of_id, id);

      added_orig = 0;
      added_mod = 0;

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

          if (!added_orig && !assoc_ref (*orig_has_dir_id, container_id) && assoc_ref (orig_dir_loc_of, container_id))
            {
              rel_add_records (added_orig_container_ids, rel_make_record (container_id, 0), 0);
              /* assoc_set (orig_has_dir_id, container_id, assoc_ref (orig_dir_loc_of, container_id)); */
              added_orig = 1;
            }
          if (!added_mod && !assoc_ref (*mod_has_dir_id, container_id) && assoc_ref (mod_dir_loc_of, container_id))
            {
              rel_add_records (added_mod_container_ids, rel_make_record (container_id, 0), 0);
              /* assoc_set (mod_has_dir_id, container_id, assoc_ref (mod_dir_loc_of, container_id)); */
              added_mod = 1;
            }

          t = loc;
          loc = file_name_directory_file (0, loc);
          id = assoc_ref (id_of_dir_loc, loc);
          container_id = assoc_ref (container_id_of_dir_id, id);

          lim_free (0, t);
        }

      lim_free (0, loc);
    }
}

static void
container_closure (rel_table * locs_out,
                   assoc_table * has_ids,
                   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_ref (*has_ids, deep_id))
        {
          t_uchar * deep_loc;

          deep_loc = assoc_ref (dir_loc_of, deep_id);
          rel_add_records (locs_out, rel_make_record (deep_loc, 0), 0);
          assoc_set (has_ids, deep_id, deep_loc);
        }

      deep_id = assoc_ref (container_dir_id_of_dir_id, deep_id);
    }
}


static void 
link_force (t_uchar *old_path, 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)
 */
