/* 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/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 "file-utils/libfsutils/diffs.h"
#include "file-utils/libfsutils/ensure-dir.h"
#include "file-utils/libfsutils/copy-file.h"
#include "file-utils/libfsutils/link-target.h"
#include "file-utils/libawk/associative.h"
#include "file-utils/libarch/invent.h"
#include "file-utils/libarch/project-tree.h"
#include "file-utils/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 void compute_container_map (assoc_table * out, assoc_table dir_tag_of, rel_table index);
static void compute_renames (rel_table * out,
                             assoc_table loc_of_orig_X_tag,
                             assoc_table mod_dir_tag_of,
                             assoc_table orig_container_dir_tag_of_X_tag,
                             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);
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);
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_tag_of,
                                        assoc_table orig_container_dir_tag_of_dir_tag, assoc_table orig_container_dir_tag_of_file_tag,
                                        assoc_table mod_dir_loc_of, assoc_table mod_dir_tag_of,
                                        assoc_table mod_container_dir_tag_of_dir_tag, assoc_table mod_container_dir_tag_of_file_tag,
                                        assoc_table orig_file_tag_of, assoc_table mod_file_tag_of);
static void schedule_missing_containers (rel_table * added_orig_container_tags, assoc_table * orig_has_dir_tag,
                                         rel_table * added_mod_container_tags, assoc_table * mod_has_dir_tag,
                                         rel_table locs_in, assoc_table tag_of_dir_loc,
                                         assoc_table first_container_tag_of_tag, assoc_table container_tag_of_dir_tag,
                                         assoc_table orig_dir_loc_of, assoc_table mod_dir_loc_of);
static void container_closure (rel_table * locs_out,
                               assoc_table * has_tags,
                               t_uchar * deep_tag,
                               assoc_table dir_loc_of,
                               assoc_table container_dir_tag_of_dir_tag);



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_tag_of);
  free_assoc_table (report->orig_dir_loc_of);
  free_assoc_table (report->orig_file_tag_of);
  free_assoc_table (report->orig_file_loc_of);
  free_assoc_table (report->mod_dir_tag_of);
  free_assoc_table (report->mod_dir_loc_of);
  free_assoc_table (report->mod_file_tag_of);
  free_assoc_table (report->mod_file_loc_of);
  free_assoc_table (report->orig_container_dir_tag_of_dir_tag);
  free_assoc_table (report->orig_container_dir_tag_of_file_tag);
  free_assoc_table (report->mod_container_dir_tag_of_dir_tag);
  free_assoc_table (report->mod_container_dir_tag_of_file_tag);

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


void
arch_make_changeset (struct arch_make_changeset_report * report,
                     t_uchar * orig_spec, t_uchar * mod_spec,
                     t_uchar * dest_spec,
                     enum arch_tagging_method method)
{
  int ign;
  int here_fd;
  t_uchar * orig;
  t_uchar * mod;
  t_uchar * orig_root;
  t_uchar * mod_root;
  t_uchar * dest;
  int lim;
  int x;
  rel_table orig_full_index = 0;
  rel_table mod_full_index = 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_tags_seen = 0;
  assoc_table mod_tags_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);

  safe_mkdir (dest_spec, 0777);
  safe_chdir (dest_spec);

  dest = current_working_directory (&ign, 0);
  if (!dest)
    panic ("can't compute current working directory (arch_make_changeset)");

  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);
  arch_changeset_inventory (&report->mod_index, mod_root, mod, method);

  orig_full_index = rel_copy_table (report->orig_index.dirs);
  rel_append_x (&orig_full_index, report->orig_index.dirs);
  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.dirs);
  rel_sort_table_by_field (0, mod_full_index, 1);

  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 too and from tags and locs
   */
  lim = rel_n_records (report->orig_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      assoc_set (&report->orig_dir_tag_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_tag_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_tag_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_tag_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_tag_of, ".", "?_.");
  assoc_set (&report->mod_dir_tag_of, ".", "?_.");
  assoc_set (&report->orig_dir_loc_of, "?_.", ".");
  assoc_set (&report->mod_dir_loc_of, "?_.", ".");

  compute_container_map (&report->orig_container_dir_tag_of_dir_tag, report->orig_dir_tag_of, report->orig_index.dirs);
  compute_container_map (&report->orig_container_dir_tag_of_file_tag, report->orig_dir_tag_of, report->orig_index.files);
  compute_container_map (&report->mod_container_dir_tag_of_dir_tag, report->mod_dir_tag_of, report->mod_index.dirs);
  compute_container_map (&report->mod_container_dir_tag_of_file_tag, report->mod_dir_tag_of, report->mod_index.files);


  /****************************************************************
   * figure out which directories have been renamed.
   */
  compute_renames (&renamed_dirs, report->orig_dir_loc_of, report->mod_dir_tag_of, report->orig_container_dir_tag_of_dir_tag, 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_tag_of, report->orig_container_dir_tag_of_file_tag, 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, 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 Tags
   */
  lim = rel_n_records (report->orig_index.dirs);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * seen;

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

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

      assoc_set (&orig_tags_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_tags_seen, report->orig_index.files[x][1]);

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

      assoc_set (&orig_tags_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_tags_seen, report->mod_index.dirs[x][1]);

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

      assoc_set (&mod_tags_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_tags_seen, report->mod_index.files[x][1]);

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

      assoc_set (&mod_tags_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)
            {
              invoke_report_callback (report, "D/ %s\n", report->removed_dirs[x][0]);
            }
          lim = rel_n_records (report->added_dirs);
          for (x = 0; x < lim; ++x)
            {
              invoke_report_callback (report, "A/ %s\n", report->added_dirs[x][0]);
            }
        }
    }


  /****************************************************************
   * 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)
            {
              invoke_report_callback (report, "D  %s\n", report->removed_files[x][0]);
            }
          lim = rel_n_records (report->added_files);
          for (x = 0; x < lim; ++x)
            {
              invoke_report_callback (report, "A  %s\n", report->added_files[x][0]);
            }
        }
    }


  /****************************************************************
   * 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]);
    }
  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]);
    }

  /****************************************************************
   * 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_tag_of,
                                report->orig_container_dir_tag_of_dir_tag, report->orig_container_dir_tag_of_file_tag,
                                report->mod_dir_loc_of, report->mod_dir_tag_of,
                                report->mod_container_dir_tag_of_dir_tag, report->mod_container_dir_tag_of_file_tag,
                                report->orig_file_tag_of, report->mod_file_tag_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_table_sp (orig_dirs_index_fd, orig_dirs_index_final);
    rel_print_table_sp (orig_files_index_fd, orig_files_index_final);
    rel_print_table_sp (mod_dirs_index_fd, mod_dirs_index_final);
    rel_print_table_sp (mod_files_index_fd, 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)
              {
                invoke_report_callback (report, "/> %s\t%s\n", renamed_dirs[x][0], renamed_dirs[x][1]);
              }
          }
        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)
              {
                invoke_report_callback (report, "=> %s\t%s\n", renamed_files[x][0], renamed_files[x][1]);
              }
          }
      }
  }
  
  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 (orig_full_index);
  rel_free_table (mod_full_index);
  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_tags_seen);
  free_assoc_table (mod_tags_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_tag_of, ".", "?_.");
  assoc_set (&make_report->mod_dir_tag_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_print_table_sp (orig_dirs_index_fd, report->orig_dirs_index);
  rel_print_table_sp (orig_files_index_fd, report->orig_files_index);
  rel_print_table_sp (mod_dirs_index_fd, report->mod_dirs_index);
  rel_print_table_sp (mod_files_index_fd, 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 * tag)
{
  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_tag;
  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, tag, 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, tag, 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_tag_of, mod_loc, tag);
  assoc_set (&make_report->mod_file_loc_of, tag, mod_loc);

  rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, tag, 0), 0);
  rel_sort_table_by_field (0, make_report->mod_index.files, 0);
  rel_uniq_by_field (&make_report->mod_index.files, 0);

  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_tag = assoc_ref (make_report->mod_dir_tag_of, loc_dir);
  if (loc_dir_tag)
    assoc_set (&make_report->mod_container_dir_tag_of_file_tag, tag, loc_dir_tag);

  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 * tag)
{
  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_tag;
  t_uchar * mod_loc_dir = 0;
  t_uchar * mod_loc_dir_tag;
  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.
   */
  rel_add_records (&report->mod_files_index, rel_make_record (mod_loc, tag, 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, tag, 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, tag, 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_tag_of, mod_loc, tag);
  assoc_set (&make_report->mod_file_loc_of, tag, mod_loc);

  assoc_set (&make_report->orig_file_tag_of, orig_loc, tag);
  assoc_set (&make_report->orig_file_loc_of, tag, orig_loc);

  rel_add_records (&make_report->mod_index.files, rel_make_record (mod_loc, tag, 0), 0);
  rel_sort_table_by_field (0, make_report->mod_index.files, 0);
  rel_uniq_by_field (&make_report->mod_index.files, 0);

  rel_add_records (&make_report->orig_index.files, rel_make_record (orig_loc, tag, 0), 0);
  rel_sort_table_by_field (0, make_report->orig_index.files, 0);
  rel_uniq_by_field (&make_report->orig_index.files, 0);

  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_tag = assoc_ref (make_report->mod_dir_tag_of, mod_loc_dir);
  if (mod_loc_dir_tag)
    assoc_set (&make_report->mod_container_dir_tag_of_file_tag, tag, mod_loc_dir_tag);

  orig_loc_dir = file_name_directory_file (0, orig_loc);
  orig_loc_dir_tag = assoc_ref (make_report->orig_dir_tag_of, orig_loc_dir);
  if (orig_loc_dir_tag)
    assoc_set (&make_report->orig_container_dir_tag_of_file_tag, tag, orig_loc_dir_tag);

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








static void
compute_container_map (assoc_table * out, assoc_table dir_tag_of, rel_table index)
{
  int lim;
  int x;

  lim = rel_n_records (index);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * item;
      t_uchar * tag;
      t_uchar * dir;
      t_uchar * dir_as_file;
      t_uchar * container_tag;

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

      dir = file_name_directory (0, item);
      dir_as_file = file_name_from_directory (0, dir);
      container_tag = assoc_ref (dir_tag_of, dir_as_file);
      invariant (!!container_tag);

      assoc_set (out, tag, container_tag);

      lim_free (0, dir);
      lim_free (0, dir_as_file);
    }
}

static void
compute_renames (rel_table * out,
                 assoc_table loc_of_orig_X_tag,
                 assoc_table mod_dir_tag_of,
                 assoc_table orig_container_dir_tag_of_X_tag,
                 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 * tag;
      t_uchar * orig;
      t_uchar * orig_tail;
      t_uchar * mod_container_tag;
      t_uchar * orig_container_tag;

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

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

      mod_container_tag = assoc_ref (mod_dir_tag_of, container);
      orig_container_tag = assoc_ref (orig_container_dir_tag_of_X_tag, tag);

      if (orig && (str_cmp (orig_tail, tail)
                   || (!mod_container_tag && str_cmp (X_index[x][0], orig))
                   || str_cmp (mod_container_tag, orig_container_tag)))
        {
          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;
      struct stat stat_buf;

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

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

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

      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)
{
  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)
            {
              invoke_report_callback (report, "-/ %s\n", mod_loc);
            }
        }

      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", orig_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 * 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)
                {
                  invoke_report_callback (report, "-> %s\n", mod_loc);
                }
            }

          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)
            {
              invoke_report_callback (report, "lf %s\n", mod_loc);
            }
        }

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


      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)
            {
              invoke_report_callback (report, "fl %s\n", mod_loc);
            }
        }

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


      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 * tmp_diff_file;
      int tmp_diff_fd;
      int diff_stat;
      int is_changed;

      is_changed = 0;

      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)
                {
                  invoke_report_callback (report, "-- %s\n", mod_loc);
                }
            }

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

      tmp_diff_file = file_name_in_vicinity (0, patches, ",,diff-output");
      vu_unlink (&ign, tmp_diff_file);
      tmp_diff_fd = safe_open (tmp_diff_file, O_RDWR | O_EXCL | O_CREAT, 0666);

      diff_stat = invoke_diff (tmp_diff_fd, orig_path, orig_loc, mod_path, mod_loc);

      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 (tmp_diff_fd, (off_t)0, SEEK_SET);
          amt = safe_read (tmp_diff_fd, diff_pseudo_magic, sizeof (diff_pseudo_magic) - 1);

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


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

      switch (diff_stat)
        {
        case 1:
          {
            t_uchar * diff_file;

            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)
                  {
                    invoke_report_callback (report, "M  %s\n", mod_loc);
                  }
              }

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

            ensure_directory_exists (patch_file_dir);
            safe_link (tmp_diff_file, diff_file);

            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)
                  {
                    invoke_report_callback (report, "Mb %s\n", mod_loc);
                  }
              }

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

            ensure_directory_exists (patch_file_dir);

            copy_file (orig_path, orig_copy);
            copy_file (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 (tmp_diff_fd);
      safe_unlink (tmp_diff_file);

      lim_free (0, patch_base_file);
      lim_free (0, patch_file_dir);
      lim_free (0, tmp_diff_file);
    }
    
  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_tag_of,
                            assoc_table orig_container_dir_tag_of_dir_tag, assoc_table orig_container_dir_tag_of_file_tag,
                            assoc_table mod_dir_loc_of, assoc_table mod_dir_tag_of,
                            assoc_table mod_container_dir_tag_of_dir_tag, assoc_table mod_container_dir_tag_of_file_tag,
                            assoc_table orig_file_tag_of, assoc_table mod_file_tag_of)
{
  assoc_table orig_has_dir_tag = 0;
  assoc_table mod_has_dir_tag = 0;
  rel_table added_orig_container_tags = 0; /* just the deepest new tag */
  rel_table added_mod_container_tags = 0; /* just the deepest new tag */
  int lim;
  int x;

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

  lim = rel_n_records (*orig_newname_dirs);
  for (x = 0; x < lim; ++x)
    {
      t_uchar * loc;
      t_uchar * tag;

      loc = (*orig_newname_dirs)[x][0];
      tag = assoc_ref (orig_dir_tag_of, loc);

      assoc_set (&orig_has_dir_tag, tag, loc);
    }

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

      loc = (*mod_newname_dirs)[x][0];
      tag = assoc_ref (mod_dir_tag_of, loc);

      assoc_set (&mod_has_dir_tag, tag, 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_tags, &orig_has_dir_tag,
                               &added_mod_container_tags, &mod_has_dir_tag,
                               *orig_newname_dirs, orig_dir_tag_of,
                               orig_container_dir_tag_of_dir_tag, orig_container_dir_tag_of_dir_tag,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_tags, &orig_has_dir_tag,
                               &added_mod_container_tags, &mod_has_dir_tag,
                               orig_newname_files, orig_dir_tag_of,
                               orig_container_dir_tag_of_file_tag, orig_container_dir_tag_of_dir_tag,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_tags, &orig_has_dir_tag,
                               &added_mod_container_tags, &mod_has_dir_tag,
                               *mod_newname_dirs, mod_dir_tag_of,
                               mod_container_dir_tag_of_dir_tag, mod_container_dir_tag_of_dir_tag,
                               orig_dir_loc_of, mod_dir_loc_of);

  schedule_missing_containers (&added_orig_container_tags, &orig_has_dir_tag,
                               &added_mod_container_tags, &mod_has_dir_tag,
                               mod_newname_files, mod_dir_tag_of,
                               mod_container_dir_tag_of_file_tag, mod_container_dir_tag_of_dir_tag,
                               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_tags);
  for (x = 0; x < lim; ++x)
    {
      container_closure (orig_newname_dirs,
                         &orig_has_dir_tag,
                         added_orig_container_tags[x][0],
                         orig_dir_loc_of,
                         orig_container_dir_tag_of_dir_tag);
    }
  lim = rel_n_records (added_mod_container_tags);
  for (x = 0; x < lim; ++x)
    {
      container_closure (mod_newname_dirs,
                         &mod_has_dir_tag,
                         added_mod_container_tags[x][0],
                         mod_dir_loc_of,
                         mod_container_dir_tag_of_dir_tag);
    }

  free_assoc_table (orig_has_dir_tag);
  free_assoc_table (mod_has_dir_tag);
  rel_free_table (added_orig_container_tags);
  rel_free_table (added_mod_container_tags);
}

static void
schedule_missing_containers (rel_table * added_orig_container_tags, assoc_table * orig_has_dir_tag,
                             rel_table * added_mod_container_tags, assoc_table * mod_has_dir_tag,
                             rel_table locs_in, assoc_table tag_of_dir_loc,
                             assoc_table first_container_tag_of_tag, assoc_table container_tag_of_dir_tag,
                             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 * tag;
      t_uchar * container_tag;
      int added_orig;
      int added_mod;


      loc = str_save (0, locs_in[x][0]);
      tag = assoc_ref (tag_of_dir_loc, loc);
      container_tag = assoc_ref (first_container_tag_of_tag, tag);

      added_orig = 0;
      added_mod = 0;

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

          if (!added_orig && !assoc_ref (*orig_has_dir_tag, container_tag) && assoc_ref (orig_dir_loc_of, container_tag))
            {
              rel_add_records (added_orig_container_tags, rel_make_record (container_tag, 0), 0);
              /* assoc_set (orig_has_dir_tag, container_tag, assoc_ref (orig_dir_loc_of, container_tag)); */
              added_orig = 1;
            }
          if (!added_mod && !assoc_ref (*mod_has_dir_tag, container_tag) && assoc_ref (mod_dir_loc_of, container_tag))
            {
              rel_add_records (added_mod_container_tags, rel_make_record (container_tag, 0), 0);
              /* assoc_set (mod_has_dir_tag, container_tag, assoc_ref (mod_dir_loc_of, container_tag)); */
              added_mod = 1;
            }

          t = loc;
          loc = file_name_directory_file (0, loc);
          tag = assoc_ref (tag_of_dir_loc, loc);
          container_tag = assoc_ref (container_tag_of_dir_tag, tag);

          lim_free (0, t);
        }

      lim_free (0, loc);
    }
}

static void
container_closure (rel_table * locs_out,
                   assoc_table * has_tags,
                   t_uchar * deep_tag,
                   assoc_table dir_loc_of,
                   assoc_table container_dir_tag_of_dir_tag)
{
  while (deep_tag && str_cmp (deep_tag, "?_."))
    {
      if (!assoc_ref (*has_tags, deep_tag))
        {
          t_uchar * deep_loc;

          deep_loc = assoc_ref (dir_loc_of, deep_tag);
          rel_add_records (locs_out, rel_make_record (deep_loc, 0), 0);
          assoc_set (has_tags, deep_tag, deep_loc);
        }

      deep_tag = assoc_ref (container_dir_tag_of_dir_tag, deep_tag);
    }
}





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