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

#include "config-options.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/errno-to-string.h"
#include "hackerlab/os/sys/types.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/os/signal.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fmt/cvt.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/link-target.h"
#include "tla/libfsutils/read-line.h"
#include "tla/libfsutils/tmp-files.h"
#include "tla/libfsutils/rmrf.h"
#include "tla/libfsutils/ensure-dir.h"
#include "tla/libfsutils/copy-file.h"
#include "tla/libarch/diffs.h"
#include "tla/libawk/associative.h"
#include "tla/libawk/relassoc.h"
#include "tla/libawk/numbers.h"
#include "tla/libarch/changelogs.h"
#include "tla/libarch/project-tree.h"
#include "tla/libarch/changeset-utils.h"
#include "tla/libarch/exec.h"
#include "tla/libarch/apply-changeset.h"



struct running_inventory_assocs
{
  assoc_table dir_loc_of;
  assoc_table dir_id_of;
  assoc_table file_loc_of;
  assoc_table file_id_of;
};


/* __STDC__ prototypes for static functions */
static char * no_dot (char *name);
static void invoke_apply_changeset_callback (struct arch_apply_changeset_report * r, t_uchar * fmt, ...);
static t_uchar * set_aside_shuffled_dirs (rel_table * file_set_aside_with,
                                          rel_table * dir_set_aside_with,
                                          t_uchar * target_loc,
                                          t_uchar * id,
                                          int seq_n,
                                          t_uchar * dest_root,
                                          struct running_inventory_assocs * running,
                                          t_uchar * target,
                                          struct arch_changeset_inventory * inv);
static void preserve_old_patch_spew (t_uchar * dest_root, t_uchar * target, t_uchar * loc);
static int deferred_conflict (rel_table * deferred_conflicts, t_uchar * spew_root, t_uchar * target, t_uchar * loc, t_uchar * orig_copy);
static int dir_depth_cmp (t_uchar * a, t_uchar * b);
static void analyze_install (struct running_inventory_assocs * running,
                             int is_dir,
                             t_uchar ** target_has_dir,
                             t_uchar ** install_dir,
                             t_uchar ** install_name,
                             t_uchar ** install_loc,
                             t_uchar * target,
                             t_uchar * mod_loc,
                             t_uchar * id,
                             assoc_table mod_dir_id_of,
                             rel_table file_set_aside_with,
                             rel_table dir_set_aside_with);
static void ensure_directory_eliminating_conflicts (rel_table * deferred_conflicts,
                                                    t_uchar * spew_root,
                                                    t_uchar * target_has_path,
                                                    t_uchar * dir,
                                                    t_uchar * target);
static int run_diff3 (t_uchar * basename,
                      t_uchar * mine_path,
                      t_uchar * older_path,
                      t_uchar * yours_path);
static int run_patch  (int apply_in_reverse,
                       int forward_opt_to_patch,
                       char * patch_path,
                       char * tree_file_base,
                       char * original_inode_name);
static void save_patch_for_missing_file (t_uchar * missing_patch_dir, t_uchar * patch_path, t_uchar * mod_loc);



int
arch_conflicts_occured (struct arch_apply_changeset_report * r)
{
  return (r->conflict_files || r->conflict_dirs || r->metadata_conflict_files || r->metadata_conflict_dirs);
}

void
arch_free_apply_changeset_report_data (struct arch_apply_changeset_report * r)
{
  rel_free_table (r->removed_files);
  rel_free_table (r->removed_dirs);

  rel_free_table (r->missing_removed_files);
  rel_free_table (r->missing_removed_dirs);

  rel_free_table (r->missing_renamed_files);
  rel_free_table (r->missing_renamed_dirs);

  rel_free_table (r->new_dirs);
  rel_free_table (r->renamed_dirs);
  rel_free_table (r->new_files);
  rel_free_table (r->renamed_files);

  rel_free_table (r->modified_files);
  rel_free_table (r->modified_dirs);
  rel_free_table (r->missing_file_for_patch);
  rel_free_table (r->missing_dir_for_patch);

  rel_free_table (r->meta_modified_files);
  rel_free_table (r->meta_modified_dirs);
  rel_free_table (r->missing_file_for_meta_patch);
  rel_free_table (r->missing_dir_for_meta_patch);

  rel_free_table (r->conflict_files);
  rel_free_table (r->conflict_dirs);
  rel_free_table (r->metadata_conflict_files);
  rel_free_table (r->metadata_conflict_dirs);
}


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

void
arch_apply_changeset (struct arch_apply_changeset_report * r,
                      t_uchar * changeset_spec, t_uchar * target_spec,
                      enum arch_id_tagging_method method,
                      enum arch_inventory_category untagged_source_category,
                      int reverse, int forward,
                      int use_diff3, assoc_table older_table, assoc_table yours_table,
                      int escape_classes)
{
  int x;
  int here_fd;

  assoc_table mod_dir_id_of = 0;
  assoc_table new_dir_perms = 0;

  t_uchar * changeset_path = 0;
  t_uchar * target = 0;
  t_uchar * missing_patch_dir = 0;
  t_uchar * tree_root = 0;
  struct arch_changeset_report changeset;
  struct arch_changeset_inventory inventory;
  struct arch_changeset_inventory inventory_by_name;

  rel_table removed_files_index = 0;
  t_uchar * tmp_removed_files_root = 0;

  rel_table renamed_files_index = 0;
  rel_table present_renamed_files_index = 0; /* [0] tgtloc [1] origloc [2] modloc [3] id */
  t_uchar * tmp_renamed_files_root = 0;

  rel_table renamed_dirs_index = 0;
  rel_table present_renamed_dirs_index = 0; /* [0] tgtloc [1] origloc [2] modloc [3] id [4] tmp_name  (sort -r [0])*/

  rel_table removed_dirs_index = 0;
  rel_table present_removed_dirs_index = 0; /* [0] tgtloc [1] id [2] tmp_name (sort -r [0]) */

  t_uchar * tmp_shuffled_dirs_root = 0;

  rel_table dir_set_aside_with_dir_id = 0; /* [0] shuffled-dir-id [1] rel-loc-in-shuffled-dir [2] id */
  rel_table file_set_aside_with_dir_id = 0; /* [0] shuffled-dir-id [1] rel-loc-in-shuffled-dir [2] id */

  t_uchar * removed_patch_conflict_files_path = 0;

  struct running_inventory_assocs running = {0, 0, 0, 0};

  rel_table install_dirs_plan = 0; /* [0] modloc [1] path-or-empty-str [2] id [3] oldtgtloc  */
  rel_table deferred_conflicts = 0; /* [0] final target location */

  rel_table added_files_and_symlinks = 0;

  rel_table patched_changelogs = 0; /* [0] final-loc [1] id [2] target_path */

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

  safe_chdir (changeset_spec);
  changeset_path = safe_current_working_directory ();
  safe_fchdir (here_fd);

  safe_chdir (target_spec);
  target = safe_current_working_directory ();
  tree_root = arch_tree_root (0, ".", 0);
  safe_fchdir (here_fd);

  missing_patch_dir = tmp_seq_file (tree_root, "++patches-missing-files");

  /****************************************************************
   * Read and study the changeset.
   */
  mem_set0 ((t_uchar *)&changeset, sizeof (changeset));
  arch_evaluate_changeset (&changeset, changeset_path);
  if (reverse)
    {
      arch_reverse_changeset (&changeset);
    }
  mod_dir_id_of = rel_to_assoc (changeset.mod_dirs_index, 0, 1);
  new_dir_perms = rel_to_assoc (changeset.added_dirs, 0, 2);


  /****************************************************************
   * Inventory the target tree.
   */
  mem_set0 ((t_uchar *)&inventory, sizeof (inventory));
  arch_changeset_inventory (&inventory, tree_root, target, method,
                            untagged_source_category, escape_classes);

  mem_set0 ((t_uchar *)&inventory_by_name, sizeof (inventory_by_name));

  inventory_by_name.dirs = rel_copy_table (inventory.dirs);
  rel_sort_table_by_field (0, inventory_by_name.dirs, 0);

  inventory_by_name.files = rel_copy_table (inventory.files);
  rel_sort_table_by_field (0, inventory_by_name.files, 0);

  /****************************************************************
   * Build assoc tables of the inventory.
   *
   * These will be kept up-to-date as files and dirs get
   * deleted, added, and renamed.
   */
  running.dir_loc_of = rel_to_assoc (inventory.dirs, 1, 0);
  running.dir_id_of = rel_to_assoc (inventory.dirs, 0, 1);
  running.file_loc_of = rel_to_assoc (inventory.files, 1, 0);
  running.file_id_of = rel_to_assoc (inventory.files, 0, 1);

  assoc_set (&running.dir_id_of, ".", "?_.");
  assoc_set (&running.dir_loc_of, "?_.", ".");


  /****************************************************************
   * Set aside and delete removed files.
   */
  removed_files_index = rel_copy_table (changeset.removed_files);
  rel_append_x (&removed_files_index, changeset.removed_symlinks);
  rel_sort_table_by_field (0, removed_files_index, 1);

  r->removed_files = rel_join (-1, rel_join_output (2,0, 2,1, -1), 1, 1, removed_files_index, inventory.files);
  r->missing_removed_files = rel_join (1, rel_join_output (1,0, 1,1, -1), 1, 1, removed_files_index, inventory.files);

  rel_sort_table_by_field (0, r->removed_files, 0);
  rel_sort_table_by_field (0, r->missing_removed_files, 0);

  tmp_removed_files_root = tmp_file_name (target, ",,tmp-removed-files");
  rmrf_file (tmp_removed_files_root);
  safe_mkdir (tmp_removed_files_root, 0777);

  for (x = 0; x < rel_n_records (r->removed_files); ++x)
    {
      t_uchar * target_loc = 0;
      t_uchar * target_id = 0;
      t_uchar * target_path = 0;
      t_uchar * dest_path = 0;
      t_uchar * dest_dir = 0;
      t_uchar * escaped_tmp = 0;

      target_loc = r->removed_files[x][0];
      target_id = r->removed_files[x][1];
      target_path = file_name_in_vicinity (0, target, target_loc);
      dest_path = file_name_in_vicinity (0, tmp_removed_files_root, target_loc);
      dest_dir = file_name_directory_file (0, dest_path);

      escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
      invoke_apply_changeset_callback (r, "D   %s\n", no_dot (escaped_tmp));

      ensure_directory_exists (dest_dir);
      safe_rename (target_path, dest_path);

      assoc_del (running.file_id_of, target_loc);
      assoc_del (running.file_loc_of, target_id);

      lim_free (0, escaped_tmp);
      lim_free (0, target_path);
      lim_free (0, dest_path);
      lim_free (0, dest_dir);
    }


  /****************************************************************
   * Set aside renamed files.
   */

  renamed_files_index = rel_copy_table (changeset.renamed_files);
  rel_sort_table_by_field (0, renamed_files_index, 2);

  present_renamed_files_index = rel_join (-1, rel_join_output (2,0, 1,0, 1,1, 2,1, -1), 2, 1, renamed_files_index, inventory.files);
  r->missing_renamed_files = rel_join (1, rel_join_output (1,0, 1,1, 1,2, -1), 2, 1, renamed_files_index, inventory.files);
  for (x = 0; x < rel_n_records (r->missing_renamed_files); ++x)
    {
      t_uchar * escaped_tmp0;
      t_uchar * escaped_tmp1;
      escaped_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, r->missing_renamed_files[x][0]);
      escaped_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, r->missing_renamed_files[x][1]);
      invoke_apply_changeset_callback (r, "?r  %s\n     => %s\n",
                                       no_dot (escaped_tmp0),
                                       no_dot (escaped_tmp1));
      lim_free (0, escaped_tmp1);
      lim_free (0, escaped_tmp0);
    }

  rel_sort_table_by_field (0, r->missing_removed_files, 0);

  tmp_renamed_files_root = tmp_file_name (target, ",,tmp-renamed-files");
  rmrf_file (tmp_renamed_files_root);
  safe_mkdir (tmp_renamed_files_root, 0777);

  for (x = 0; x < rel_n_records (present_renamed_files_index); ++x)
    {
      t_uchar * target_loc = 0;
      t_uchar * target_id = 0;
      t_uchar * target_path = 0;
      t_uchar * dest_path = 0;
      t_uchar * dest_dir = 0;

      target_loc = present_renamed_files_index[x][0];
      target_id = present_renamed_files_index[x][3];
      target_path = file_name_in_vicinity (0, target, target_loc);
      dest_path = file_name_in_vicinity (0, tmp_renamed_files_root, target_loc);
      dest_dir = file_name_directory_file (0, dest_path);

      ensure_directory_exists (dest_dir);
      safe_rename (target_path, dest_path);

      assoc_del (running.file_id_of, target_loc);
      assoc_del (running.file_loc_of, target_id);

      lim_free (0, target_path);
      lim_free (0, dest_path);
      lim_free (0, dest_dir);
    }

  /****************************************************************
   * Set Aside Renamed and Removed Directories
   */

  renamed_dirs_index = rel_copy_table (changeset.renamed_dirs);
  rel_sort_table_by_field (0, renamed_dirs_index, 2);

  present_renamed_dirs_index = rel_join (-1, rel_join_output (2,0, 1,0, 1,1, 1,2, -1), 2, 1, renamed_dirs_index, inventory.dirs);
  rel_sort_table_by_field (1, present_renamed_dirs_index, 0);

  r->missing_renamed_dirs = rel_join (1, rel_join_output (1,0, 1,1, 1,2, -1), 2, 1, renamed_dirs_index, inventory.dirs);
  rel_sort_table_by_field (0, r->missing_renamed_dirs, 0);
  for (x = 0; x < rel_n_records (r->missing_renamed_dirs); ++x)
    {
      t_uchar * escaped_tmp0;
      t_uchar * escaped_tmp1;
      escaped_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, r->missing_renamed_dirs[x][0]);
      escaped_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, r->missing_renamed_dirs[x][1]);
      invoke_apply_changeset_callback (r, "?r/ %s\n     => %s\n",
                                       no_dot (escaped_tmp0),
                                       no_dot (escaped_tmp1));
      lim_free (0, escaped_tmp1);
      lim_free (0, escaped_tmp0);
    }

  removed_dirs_index = rel_copy_table (changeset.removed_dirs);
  rel_sort_table_by_field (0, removed_dirs_index, 1);

  present_removed_dirs_index = rel_join (-1, rel_join_output (2,0, 2,1, -1), 1, 1, removed_dirs_index, inventory.dirs);
  rel_sort_table_by_field (1, present_removed_dirs_index, 0);

  r->missing_removed_dirs = rel_join (1, rel_join_output (1,0, 1,1, -1), 1, 1, removed_dirs_index, inventory.dirs);
  rel_sort_table_by_field (0, r->missing_removed_dirs, 0);

  tmp_shuffled_dirs_root = tmp_file_name (target, ",,tmp-shuffled-dirs");
  rmrf_file (tmp_shuffled_dirs_root);
  safe_mkdir (tmp_shuffled_dirs_root, 0777);

  /* It's important to set aside shuffled dirs from deepest
   * to shallowest.
   */
  {
    int seq;
    int ren_pos;
    int rem_pos;

    seq = 0;
    ren_pos = 0;
    rem_pos = 0;

    while (1)
      {
        int ren_done;
        int rem_done;
        int ren_first;
        t_uchar * target_loc;
        t_uchar * id;
        rel_record * dest_record;
        t_uchar * tmp_name;

        ren_done = (ren_pos >= rel_n_records (present_renamed_dirs_index));
        rem_done = (rem_pos >= rel_n_records (present_removed_dirs_index));

        if (ren_done && rem_done)
          break;

        ren_first = (rem_done
                     || (!ren_done && (0 < str_cmp (present_renamed_dirs_index[ren_pos][0], present_removed_dirs_index[rem_pos][0]))));

        if (ren_first)
          {
            target_loc = present_renamed_dirs_index[ren_pos][0];
            id = present_renamed_dirs_index[ren_pos][3];
            dest_record = &present_renamed_dirs_index[ren_pos];
            ++ren_pos;
          }
        else
          {
            t_uchar * escaped_tmp;
            target_loc = present_removed_dirs_index[rem_pos][0];
            id = present_removed_dirs_index[rem_pos][1];
            dest_record = &present_removed_dirs_index[rem_pos];
            ++rem_pos;

            escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
            invoke_apply_changeset_callback (r, "D/  %s\n", no_dot (escaped_tmp));
            lim_free (0, escaped_tmp);

            rel_add_records (&r->removed_dirs, rel_make_record (target_loc, id, 0), 0);
          }
        tmp_name = set_aside_shuffled_dirs (&file_set_aside_with_dir_id, &dir_set_aside_with_dir_id, target_loc, id, seq, tmp_shuffled_dirs_root, &running, target, &inventory);
        ++seq;
        rel_add_field (dest_record, tmp_name);
        lim_free (0, tmp_name);
      }

    rel_sort_table_by_field (0, r->removed_dirs, 0);
    rel_sort_table_by_field (0, dir_set_aside_with_dir_id, 0);
    rel_sort_table_by_field (0, file_set_aside_with_dir_id, 0);
  }

  /****************************************************************
   * Make a name for a dir in which to stash removed .rej and .orig files
   */
  removed_patch_conflict_files_path = tmp_file_name (target, "+removed-conflict-files");


  /****************************************************************
   * What we have:
   *
   *    We have the target tree, with all renamed/removed dirs and files
   *    set aside.
   *
   *    The removed files are all under $tmp_removed_files_root/$tgtloc.
   *
   *    The renamed files are all under $tmp_renamed_files_root/$tgtloc.
   *
   *        (in both of the above cases, $tgtloc is the original target loc)
   *
   *    Both the renamed and removed directories are set aside
   *    in $tmp_shuffled_dirs_root under integer filenames.
   *    The tmp_name fields of present_renamed_dirs_index and
   *    present_renamed_dirs_index are full paths to these temp
   *    names.
   *
   * Todo:
   *
   *    We have to install renamed and new directories, from shallowest to
   *    deepest, then install renamed and new files (in any order).
   *
   *    Each installed item has a destination path that has, in essense,
   *    three parts:
   *
   *            $tgthas / $newrelpath / $basename
   *
   *    where $tgthas is the deepest part of the destination path that
   *    the target tree already has when the item is installed,
   *    $newrelpath are additional intermediate directories that need to
   *    be created (possibly causing conflicts) and $basename is the final
   *    name for the item (possibly causing a conflict).
   *
   *    That three part path is derived from the $modloc of the item
   *    by finding the deepest containing directory in $modloc which
   *    is present (by id) in $target (that's $tgthas).
   *
   *    In the code that follows:
   *
   *            tgthas == $tgthas
   *            install_dir == $tgthas / $newrelpath
   *            install_name == $tgthas / $newrelpath / $basename
   *
   *    Finally, then, we have to apply individual file and dir patches.
   */

  /****************************************************************
   * Compute an install plan for new and renamed directories.
   *
   * We have to add or rename containing dirs before contained.
   *
   * So, we need a plan for that.
   */

  install_dirs_plan = rel_cut (rel_cut_list (2, 4, 3, 0, -1), present_renamed_dirs_index);
  for (x = 0; x < rel_n_records (changeset.added_dirs); ++x)
    {
      rel_add_records (&install_dirs_plan, rel_make_record (changeset.added_dirs[x][0], "", changeset.added_dirs[x][1], 0), 0);
    }
  rel_sort_table_by_field_fn (0, install_dirs_plan, 0, dir_depth_cmp);


  /****************************************************************
   * Install dirs.
   */
  for (x = 0; x < rel_n_records (install_dirs_plan); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * take_from;
      t_uchar * id;

      t_uchar * target_has_dir = 0;
      t_uchar * target_has_path = 0;
      t_uchar * install_dir = 0;
      t_uchar * install_name = 0;
      t_uchar * install_loc = 0;

      mod_loc = install_dirs_plan[x][0];
      take_from = install_dirs_plan[x][1];
      id = install_dirs_plan[x][2];

      if (!*take_from)
        take_from = 0;

      analyze_install (&running, 1, &target_has_dir, &install_dir, &install_name, &install_loc,
                       target, mod_loc, id, mod_dir_id_of,
                       file_set_aside_with_dir_id, dir_set_aside_with_dir_id);

      if (!str_cmp (target_has_dir, "."))
        target_has_path = str_save (0, target);
      else
        target_has_path = file_name_in_vicinity (0, target, 2 + target_has_dir); /* over "./" */

      ensure_directory_eliminating_conflicts (&deferred_conflicts,
                                              removed_patch_conflict_files_path,
                                              target_has_path, install_dir, target);

      if (!take_from)
        {
          if (!safe_file_is_directory (install_name))
            {
              int trust_umask = 0;
              int ign;
              t_uchar * perms_str;
              unsigned long perms;
              t_uchar * escaped_tmp = 0;

              perms_str = assoc_ref (new_dir_perms, mod_loc);
              if (cvt_octal_to_ulong (&ign, &perms, perms_str, str_length (perms_str)))
                {
                  perms = 0777;
                  trust_umask = 1;
                }

              escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, install_loc);
              
              if (!deferred_conflict (&deferred_conflicts, removed_patch_conflict_files_path, target, install_loc, 0))
                {
                  invoke_apply_changeset_callback (r, "A/  %s\n", no_dot (escaped_tmp));
                  rel_add_records (&r->new_dirs, rel_make_record (install_loc, id, 0), 0);
                }
              else
                {
                  invoke_apply_changeset_callback (r, "CA/ %s\n", no_dot (escaped_tmp));
                  rel_add_records (&r->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
                }

              safe_mkdir (install_name, perms);
              if (!trust_umask)
                safe_chmod (install_name, perms);

              lim_free (0, escaped_tmp);
            }
        }
      else
        {
          t_uchar * oldtgtloc;
          t_uchar * escaped_tmp0 = 0;
          t_uchar * escaped_tmp1 = 0;

          oldtgtloc = install_dirs_plan[x][3];

          escaped_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, oldtgtloc);
          escaped_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, install_loc);

          if (!deferred_conflict (&deferred_conflicts, removed_patch_conflict_files_path, target, install_loc, 0))
            {
              invoke_apply_changeset_callback (r, "/>  %s\t%s\n", escaped_tmp0, no_dot (escaped_tmp1));
              rel_add_records (&r->renamed_dirs, rel_make_record (oldtgtloc, install_loc, id, 0), 0);
            }
          else
            {
              invoke_apply_changeset_callback (r, "C/> %s\t%s\n", escaped_tmp0, no_dot (escaped_tmp1));
              rel_add_records (&r->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
            }

          safe_rename (take_from, install_name);
          lim_free (0, escaped_tmp1);
          lim_free (0, escaped_tmp0);
        }

      lim_free (0, target_has_dir);
      lim_free (0, target_has_path);
      lim_free (0, install_dir);
      lim_free (0, install_name);
      lim_free (0, install_loc);
    }

  rel_sort_table_by_field (0, r->new_dirs, 0);
  rel_sort_table_by_field (0, r->renamed_dirs, 0);

  /****************************************************************
   * Install Renamed Files and Symlinks
   */

  for (x = 0; x < rel_n_records (present_renamed_files_index); ++x)
    {
      t_uchar * old_target_loc;
      t_uchar * mod_loc;
      t_uchar * id;

      t_uchar * take_from = 0;

      t_uchar * target_has_dir = 0;
      t_uchar * install_dir = 0;
      t_uchar * install_name = 0;
      t_uchar * install_loc = 0;

      t_uchar * target_has_path = 0;

      t_uchar * escaped_tmp0 = 0;
      t_uchar * escaped_tmp1 = 0;

      old_target_loc = present_renamed_files_index[x][0];
      mod_loc = present_renamed_files_index[x][2];
      id = present_renamed_files_index[x][3];

      take_from = file_name_in_vicinity (0, tmp_renamed_files_root, old_target_loc);

      analyze_install (&running, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                       target, mod_loc, id, mod_dir_id_of, 0, 0);

      if (!str_cmp (target_has_dir, "."))
        target_has_path = str_save (0, target);
      else
        target_has_path = file_name_in_vicinity (0, target, 2 + target_has_dir); /* over "./" */

      ensure_directory_eliminating_conflicts (&deferred_conflicts,
                                              removed_patch_conflict_files_path,
                                              target_has_path, install_dir, target);

      escaped_tmp0 = pika_save_escape_iso8859_1 (0, 0, escape_classes, old_target_loc);
      escaped_tmp1 = pika_save_escape_iso8859_1 (0, 0, escape_classes, install_loc);

      if (!deferred_conflict (&deferred_conflicts, removed_patch_conflict_files_path, target, install_loc, 0))
        {
          invoke_apply_changeset_callback (r, "=>  %s\t%s\n", escaped_tmp0, no_dot (escaped_tmp1));
          rel_add_records (&r->renamed_files, rel_make_record (old_target_loc, install_loc, id, 0), 0);
        }
      else
        {
          invoke_apply_changeset_callback (r, "C=> %s\t%s\n", escaped_tmp0, no_dot (escaped_tmp1));
          rel_add_records (&r->conflict_files, rel_make_record (install_loc, id, 0), 0);
        }

      safe_rename (take_from, install_name);

      lim_free (0, escaped_tmp1);
      lim_free (0, escaped_tmp0);
      lim_free (0, take_from);
      lim_free (0, target_has_dir);
      lim_free (0, install_dir);
      lim_free (0, install_name);
      lim_free (0, install_loc);
      lim_free (0, target_has_path);
    }


  added_files_and_symlinks = rel_copy_table (changeset.added_files);
  rel_append_x (&added_files_and_symlinks, changeset.added_symlinks);

  for (x = 0; x < rel_n_records (added_files_and_symlinks); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * take_from;

      t_uchar * target_has_dir = 0;
      t_uchar * install_dir = 0;
      t_uchar * install_name = 0;
      t_uchar * install_loc = 0;

      t_uchar * target_has_path = 0;

      t_uchar * escaped_tmp = 0;

      mod_loc = added_files_and_symlinks[x][0];
      id = added_files_and_symlinks[x][1];
      take_from = added_files_and_symlinks[x][2];

      analyze_install (&running, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                       target, mod_loc, id, mod_dir_id_of, 0, 0);

      if (!str_cmp (target_has_dir, "."))
        target_has_path = str_save (0, target);
      else
        target_has_path = file_name_in_vicinity (0, target, 2 + target_has_dir); /* over "./" */

      ensure_directory_eliminating_conflicts (&deferred_conflicts,
                                              removed_patch_conflict_files_path,
                                              target_has_path, install_dir, target);

      escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, install_loc);
      
      if (!deferred_conflict (&deferred_conflicts, removed_patch_conflict_files_path, target, install_loc, take_from))
        {
          invoke_apply_changeset_callback (r, "A   %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->new_files, rel_make_record (install_loc, id, 0), 0);
        }
      else
        {
          invoke_apply_changeset_callback (r, "CA  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (install_loc, id, 0), 0);
        }

      copy_file_or_symlink (take_from, install_name);
      copy_permissions (take_from, install_name);

      lim_free (0, escaped_tmp);
      lim_free (0, target_has_dir);
      lim_free (0, install_dir);
      lim_free (0, install_name);
      lim_free (0, install_loc);
      lim_free (0, target_has_path);
    }

  rel_sort_table_by_field (0, r->renamed_files, 0);


  /****************************************************************
   * Patch Regular Files
   */

  for (x = 0; x < rel_n_records (changeset.patched_regular_files); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      patch_path = str_alloc_cat (0, changeset.patched_regular_files[x][2], ".patch");
      mod_loc = changeset.patched_regular_files[x][0];
      id = changeset.patched_regular_files[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?M  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_loc);

      if (S_ISLNK (target_stat.st_mode))
        {
          t_uchar * orig_name;
          t_uchar * rej_name;

          orig_name = str_alloc_cat (0, target_path, ".orig");
          rej_name = str_alloc_cat (0, target_path, ".rej");
          safe_rename (target_path, orig_name);
          copy_file (patch_path, rej_name);
          copy_permissions (patch_path, rej_name);

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "C-> %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);
          lim_free (0, escaped_tmp);
        }
      else if (arch_id_indicates_changelog (id))
        {
          rel_add_records (&patched_changelogs, rel_make_record (target_loc, id, target_path, 0), 0);
        }
      else
        {
          struct stat patch_file_stat;

          safe_stat (patch_path, &patch_file_stat);

          if (patch_file_stat.st_size)
            {
              int errn;
              t_uchar * target_dir = 0;
              t_uchar * basename = 0;
              t_uchar * original_inode_basename = 0;
              t_uchar * original_inode_tmpname = 0;
              int patch_stat = -1;

              target_dir = file_name_directory_file (0, target_path);
              basename = file_name_tail (0, target_path);
              original_inode_basename = str_alloc_cat_many (0, ",,dopatch.", basename, ".", str_end);

              safe_chdir (target_dir);

              original_inode_tmpname = tmp_file_name (".", original_inode_basename);
              safe_rename (basename, original_inode_tmpname);

              if (vu_unlink (&errn, ",,patch-output") && (errn != ENOENT))
                {
                  safe_printfmt (2, "arch_apply_changeset: unable to unlink file\n");
                  safe_printfmt (2, "  file: %s/,,patch-output\n", target_dir);
                  safe_printfmt (2, "  error: %s\n", errno_to_string (errn));
                  exit (2);
                }

              if (!use_diff3)
                {
                run_patch_anyway:
                  patch_stat = run_patch (reverse, forward, patch_path, basename, original_inode_tmpname);
                }
              else
                {
                  t_uchar * older_path = assoc_ref (older_table, id);
                  t_uchar * yours_path = assoc_ref (yours_table, id);

                  if (!(older_path && yours_path))
                    goto run_patch_anyway;

                  if (!reverse)
                    patch_stat = run_diff3 (basename, original_inode_tmpname, older_path, yours_path);
                  else
                    patch_stat = run_diff3 (basename, original_inode_tmpname, yours_path, older_path);
                }

              if (patch_stat == 0)
                {
                  copy_permissions (original_inode_tmpname, basename);
                  safe_unlink (original_inode_tmpname);

                  escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
                  invoke_apply_changeset_callback (r, "M   %s\n",
                                                   no_dot (escaped_tmp));
                  rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);
                  lim_free (0, escaped_tmp);
                }
              else if (patch_stat == 1)
                {
                  t_uchar * orig_name;

                  copy_permissions (original_inode_tmpname, basename);
                  orig_name = str_alloc_cat (0, basename, ".orig");
                  safe_rename (original_inode_tmpname, orig_name);

                  escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
                  invoke_apply_changeset_callback (r, "C   %s\n",
                                                   no_dot (escaped_tmp));
                  rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);

                  lim_free (0, escaped_tmp);
                  lim_free (0, orig_name);
                }
              else
                {
                  int in_fd;

                  safe_printfmt (2, "arch_apply_changeset: internal error (patch(1) returned odd status)\n");
                  safe_printfmt (2, "   patch(1) exit status: %d\n", patch_stat);
                  safe_printfmt (2, "\n");
                  in_fd = safe_open (",,patch-output", O_RDONLY, 0);
                  copy_fd (in_fd, 2);
                  safe_printfmt (2, "\n");
                  exit (2);
                }

              if (vu_unlink (&errn, ",,patch-output") && (errn != ENOENT))
                {
                  safe_printfmt (2, "arch_apply_changeset: unable to unlink file\n");
                  safe_printfmt (2, "  file: %s/,,patch-output\n", target_dir);
                  safe_printfmt (2, "  error: %s\n", errno_to_string (errn));
                  exit (2);
                }

              safe_fchdir (here_fd);

              lim_free (0, target_dir);
              lim_free (0, basename);
              lim_free (0, original_inode_basename);
              lim_free (0, original_inode_tmpname);
            }
        }

      lim_free (0, patch_path);
      lim_free (0, target_path);
    }

  /****************************************************************
   * Patch Symlinks
   */

  for (x = 0; x < rel_n_records (changeset.patched_symlinks); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = str_alloc_cat (0, changeset.patched_symlinks[x][2], ".link-orig");
          mod_patch_path = str_alloc_cat (0, changeset.patched_symlinks[x][2], ".link-mod");
        }
      else
        {
          orig_patch_path = str_alloc_cat (0, changeset.patched_symlinks[x][2], ".link-mod");
          mod_patch_path = str_alloc_cat (0, changeset.patched_symlinks[x][2], ".link-orig");
        }

      mod_loc = changeset.patched_symlinks[x][0];
      id = changeset.patched_symlinks[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?M  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_loc);

      if (!S_ISLNK (target_stat.st_mode))
        {
          t_uchar * orig_name = 0;
          t_uchar * rej_name = 0;
          int out_fd;
          int in_fd;

        symlink_conflict:

          orig_name = str_alloc_cat (0, target_path, ".orig");
          rej_name = str_alloc_cat (0, target_path, ".rej");
          safe_rename (target_path, orig_name);

          out_fd = safe_open (rej_name, O_WRONLY | O_CREAT | O_EXCL, 0666);
          safe_printfmt (out_fd, "Patched wanted to retarget a symbolic link:\n\n");
          safe_printfmt (out_fd, "  from: ");
          in_fd = safe_open (orig_patch_path, O_RDONLY, 0);
          copy_fd (in_fd, out_fd);
          safe_close (in_fd);
          safe_printfmt (out_fd, "\n to: ");
          in_fd = safe_open (mod_patch_path, O_RDONLY, 0);
          copy_fd (in_fd, out_fd);
          safe_close (in_fd);
          safe_printfmt (out_fd, "\n");

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "C-> %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);

          lim_free (0, escaped_tmp);
          lim_free (0, orig_name);
          lim_free (0, rej_name);
        }
      else
        {
          /****************************************************************
           * FIX THIS
           *
           * Strictly speaking:
           *
           * We should really try to "remap" the orig and mod link targets for
           * the current tree layout.
           */
          t_uchar * orig_link_target = 0;
          t_uchar * mod_link_target = 0;
          t_uchar * tree_link_target = 0;
          int patch_stat;

          orig_link_target = read_line_from_file (orig_patch_path);
          mod_link_target = read_line_from_file (mod_patch_path);
          tree_link_target = link_target (target_path);

          if (!str_cmp (mod_link_target, tree_link_target))
            {
              patch_stat = 0;
            }
          else if (!str_cmp (orig_link_target, tree_link_target))
            {
              t_uchar * target_path_dir = 0;
              t_uchar * target_path_tail = 0;
              t_uchar * tmp_file_basename = 0;
              t_uchar * tmp_file_path = 0;

              target_path_dir = file_name_directory_file (0, target_path);
              target_path_tail = file_name_tail (0, target_path);
              tmp_file_basename = str_alloc_cat_many (0, ",,dopatch.", target_path_tail, ".", str_end);
              tmp_file_path = tmp_file_name (target_path_dir, tmp_file_basename);

              rmrf_file (tmp_file_path);
              safe_rename (target_path, tmp_file_path);
              safe_symlink (mod_link_target, target_path);
              safe_unlink (tmp_file_path);
              patch_stat = 0;

              lim_free (0, target_path_dir);
              lim_free (0, target_path_tail);
              lim_free (0, tmp_file_basename);
              lim_free (0, tmp_file_path);
            }
          else
            {
              patch_stat = 1;
            }

          if (patch_stat == 0)
            {
              escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
              invoke_apply_changeset_callback (r, "M-> %s\n",
                                               no_dot (escaped_tmp));
              rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);
              lim_free (0, escaped_tmp);
            }
          else
            goto symlink_conflict;

          lim_free (0, orig_link_target);
          lim_free (0, mod_link_target);
          lim_free (0, tree_link_target);
        }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
    }


  /****************************************************************
   * Patch Binaries
   */

  for (x = 0; x < rel_n_records (changeset.patched_binaries); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = str_alloc_cat (0, changeset.patched_binaries[x][2], ".original");
          mod_patch_path = str_alloc_cat (0, changeset.patched_binaries[x][2], ".modified");
        }
      else
        {
          orig_patch_path = str_alloc_cat (0, changeset.patched_binaries[x][2], ".modified");
          mod_patch_path = str_alloc_cat (0, changeset.patched_binaries[x][2], ".original");
        }

      mod_loc = changeset.patched_binaries[x][0];
      id = changeset.patched_binaries[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?M  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_loc);

      if (S_ISLNK (target_stat.st_mode))
        {
          t_uchar * orig_name;
          t_uchar * rej_name;

        binary_conflict:

          orig_name = str_alloc_cat (0, target_path, ".orig");
          rej_name = str_alloc_cat (0, target_path, ".rej");
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "Cb  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);

          lim_free (0, escaped_tmp);
          lim_free (0, orig_name);
          lim_free (0, rej_name);
        }
      else
        {
          int patch_stat;

          if (arch_binary_files_differ (orig_patch_path, target_path, 0, 0))
            {
              patch_stat = 1;
            }
          else
            {
              t_uchar * target_path_dir = 0;
              t_uchar * target_path_tail = 0;
              t_uchar * tmp_file_basename = 0;
              t_uchar * tmp_file_path = 0;

              target_path_dir = file_name_directory_file (0, target_path);
              target_path_tail = file_name_tail (0, target_path);
              tmp_file_basename = str_alloc_cat_many (0, ",,dopatch.", target_path_tail, ".", str_end);
              tmp_file_path = tmp_file_name (target_path_dir, tmp_file_basename);

              rmrf_file (tmp_file_path);
              safe_rename (target_path, tmp_file_path);
              copy_file (mod_patch_path, target_path);
              copy_permissions (mod_patch_path, target_path);
              safe_unlink (tmp_file_path);
              patch_stat = 0;

              lim_free (0, target_path_dir);
              lim_free (0, target_path_tail);
              lim_free (0, tmp_file_basename);
              lim_free (0, tmp_file_path);
            }

          if (patch_stat == 0)
            {
              escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
              invoke_apply_changeset_callback (r, "Mb  %s\n",
                                               no_dot (escaped_tmp));
              rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);
              lim_free (0, escaped_tmp);
            }
          else
            goto binary_conflict;
        }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
    }


  /****************************************************************
   * Symlinks->File Patches
   */

  for (x = 0; x < rel_n_records (changeset.symlink_to_file); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = str_alloc_cat (0, changeset.symlink_to_file[x][2], ".link-orig");
          mod_patch_path = str_alloc_cat (0, changeset.symlink_to_file[x][2], ".modified");
        }
      else
        {
          orig_patch_path = str_alloc_cat (0, changeset.symlink_to_file[x][2], ".modified");
          mod_patch_path = str_alloc_cat (0, changeset.symlink_to_file[x][2], ".link-orig");
        }

      mod_loc = changeset.symlink_to_file[x][0];
      id = changeset.symlink_to_file[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?M  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_loc);

      if (!S_ISLNK (target_stat.st_mode))
        {
          t_uchar * orig_name;
          t_uchar * rej_name;

        symlink_to_file_conflict:

          orig_name = str_alloc_cat (0, target_path, ".orig");
          rej_name = str_alloc_cat (0, target_path, ".rej");
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "Cch %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);

          lim_free (0, escaped_tmp);
          lim_free (0, orig_name);
          lim_free (0, rej_name);
        }
      else
        {
          t_uchar * orig_link_target = 0;
          t_uchar * tree_link_target = 0;
          int patch_stat;

          orig_link_target = read_line_from_file (orig_patch_path);
          tree_link_target = link_target (target_path);

          if (str_cmp (orig_link_target, tree_link_target))
            {
              patch_stat = 1;
            }
          else
            {
              t_uchar * target_path_dir = 0;
              t_uchar * target_path_tail = 0;
              t_uchar * tmp_file_basename = 0;
              t_uchar * tmp_file_path = 0;

              target_path_dir = file_name_directory_file (0, target_path);
              target_path_tail = file_name_tail (0, target_path);
              tmp_file_basename = str_alloc_cat_many (0, ",,dopatch.", target_path_tail, ".", str_end);
              tmp_file_path = tmp_file_name (target_path_dir, tmp_file_basename);

              rmrf_file (tmp_file_path);
              safe_rename (target_path, tmp_file_path);
              copy_file (mod_patch_path, target_path);
              copy_permissions (mod_patch_path, target_path);
              safe_unlink (tmp_file_path);
              patch_stat = 0;

              lim_free (0, target_path_dir);
              lim_free (0, target_path_tail);
              lim_free (0, tmp_file_basename);
              lim_free (0, tmp_file_path);
            }

          if (patch_stat == 0)
            {
              escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
              invoke_apply_changeset_callback (r, "ch  %s\n",
                                               no_dot (escaped_tmp));
              rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);
              lim_free (0, escaped_tmp);
            }
          else
            goto symlink_to_file_conflict;

          lim_free (0, orig_link_target);
          lim_free (0, tree_link_target);
        }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
    }

  /****************************************************************
   * File->Symlink Patches
   */

  for (x = 0; x < rel_n_records (changeset.file_to_symlink); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = str_alloc_cat (0, changeset.file_to_symlink[x][2], ".original");
          mod_patch_path = str_alloc_cat (0, changeset.file_to_symlink[x][2], ".link-mod");
        }
      else
        {
          orig_patch_path = str_alloc_cat (0, changeset.file_to_symlink[x][2], ".link-mod");
          mod_patch_path = str_alloc_cat (0, changeset.file_to_symlink[x][2], ".original");
        }

      mod_loc = changeset.file_to_symlink[x][0];
      id = changeset.file_to_symlink[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?M  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_loc);

      if (S_ISLNK (target_stat.st_mode))
        {
          t_uchar * orig_name;
          t_uchar * rej_name;
          int out_fd;
          int in_fd;

        file_to_symlink_conflict:

          orig_name = str_alloc_cat (0, target_path, ".orig");
          rej_name = str_alloc_cat (0, target_path, ".rej");
          safe_rename (target_path, orig_name);

          out_fd = safe_open (rej_name, O_WRONLY | O_CREAT | O_EXCL, 0666);
          safe_printfmt (out_fd, "MOD had a symlink to: ");
          in_fd = safe_open (mod_patch_path, O_RDONLY, 0);
          copy_fd (in_fd, out_fd);
          safe_close (in_fd);
          safe_close (out_fd);

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "Cch %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->conflict_files, rel_make_record (target_loc, id, 0), 0);

          lim_free (0, escaped_tmp);
          lim_free (0, orig_name);
          lim_free (0, rej_name);
        }
      else
        {
          t_uchar * mod_link_target = 0;
          t_uchar * tree_link_target = 0;
          int patch_stat;

          mod_link_target = read_line_from_file (mod_patch_path);

          if (arch_binary_files_differ (orig_patch_path, target_path, 0, 0))
            {
              patch_stat = 1;
            }
          else
            {
              t_uchar * target_path_dir = 0;
              t_uchar * target_path_tail = 0;
              t_uchar * tmp_file_basename = 0;
              t_uchar * tmp_file_path = 0;

              target_path_dir = file_name_directory_file (0, target_path);
              target_path_tail = file_name_tail (0, target_path);
              tmp_file_basename = str_alloc_cat_many (0, ",,dopatch.", target_path_tail, ".", str_end);
              tmp_file_path = tmp_file_name (target_path_dir, tmp_file_basename);

              rmrf_file (tmp_file_path);
              safe_rename (target_path, tmp_file_path);
              safe_symlink (mod_link_target, target_path);
              safe_unlink (tmp_file_path);
              patch_stat = 0;

              lim_free (0, target_path_dir);
              lim_free (0, target_path_tail);
              lim_free (0, tmp_file_basename);
              lim_free (0, tmp_file_path);
            }

          if (patch_stat == 0)
            {
              escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
              invoke_apply_changeset_callback (r, "ch  %s\n",
                                               no_dot (escaped_tmp));
              rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);
              lim_free (0, escaped_tmp);
            }
          else
            goto file_to_symlink_conflict;

          lim_free (0, mod_link_target);
          lim_free (0, tree_link_target);
        }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
    }


  /****************************************************************
   * Patch File Metadata
   */
  for (x = 0; x < rel_n_records (changeset.file_metadata_changed); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * target_spew_loc = 0;
      t_uchar * target_spew_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = str_alloc_cat (0, changeset.file_metadata_changed[x][2], ".meta-orig");
          mod_patch_path = str_alloc_cat (0, changeset.file_metadata_changed[x][2], ".meta-mod");
        }
      else
        {
          orig_patch_path = str_alloc_cat (0, changeset.file_metadata_changed[x][2], ".meta-mod");
          mod_patch_path = str_alloc_cat (0, changeset.file_metadata_changed[x][2], ".meta-orig");
        }

      mod_loc = changeset.file_metadata_changed[x][0];
      id = changeset.file_metadata_changed[x][1];
      target_loc = assoc_ref (running.file_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?-- %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_file_for_meta_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);
      target_spew_loc = str_alloc_cat (0, target_loc, ".meta");
      target_spew_path = file_name_in_vicinity (0, target, target_spew_loc);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_spew_loc);

      safe_lstat (target_path, &target_stat);

      if (S_ISLNK (target_stat.st_mode))
        {
          t_uchar * rej_name;
          int out_fd;
          int in_fd;

          rej_name = str_alloc_cat (0, target_spew_path, ".rej");

          out_fd = safe_open (rej_name, O_WRONLY | O_CREAT | O_EXCL, 0666);
          safe_printfmt (out_fd, "MOD had a metadata change: ");
          in_fd = safe_open (mod_patch_path, O_RDONLY, 0);
          copy_fd (in_fd, out_fd);
          safe_close (in_fd);
          safe_close (out_fd);

          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "C-- %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->metadata_conflict_files, rel_make_record (target_loc, id, 0), 0);

          lim_free (0, escaped_tmp);
          lim_free (0, rej_name);
        }
      else
        {
          mode_t orig_perms;
          mode_t mod_perms;

          mod_perms = arch_read_permissions_patch (mod_patch_path);
          orig_perms = arch_read_permissions_patch (orig_patch_path);

          if (mod_perms != (target_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
            {
              t_uchar * target_path_dir = 0;
              t_uchar * target_path_tmp = 0;

              target_path_dir = file_name_directory_file (0, target_path);
              target_path_tmp = tmp_file_name (target_path_dir, ",,meta-tmp");
              rmrf_file (target_path_tmp);
              copy_file (target_path, target_path_tmp);
              safe_chmod (target_path_tmp, mod_perms);
              safe_rename (target_path_tmp, target_path);

              lim_free (0, target_path_dir);
              lim_free (0, target_path_tmp);
            }
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
          invoke_apply_changeset_callback (r, "--  %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->meta_modified_files, rel_make_record (target_loc, id, 0), 0);
          lim_free (0, escaped_tmp);
        }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
      lim_free (0, target_spew_loc);
      lim_free (0, target_spew_path);
    }




  /****************************************************************
   * Patch Dir Metadata
   */

  for (x = 0; x < rel_n_records (changeset.dir_metadata_changed); ++x)
    {
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * target_spew_loc = 0;
      t_uchar * target_spew_path = 0;
      t_uchar * escaped_tmp = 0;
      struct stat target_stat;

      if (!reverse)
        {
          orig_patch_path = file_name_in_vicinity (0, changeset.dir_metadata_changed[x][2], "=dir-meta-orig");
          mod_patch_path = file_name_in_vicinity (0, changeset.dir_metadata_changed[x][2], "=dir-meta-mod");
        }
      else
        {
          orig_patch_path = file_name_in_vicinity (0, changeset.dir_metadata_changed[x][2], "=dir-meta-mod");
          mod_patch_path = file_name_in_vicinity (0, changeset.dir_metadata_changed[x][2], "=dir-meta-orig");
        }

      mod_loc = changeset.dir_metadata_changed[x][0];
      id = changeset.dir_metadata_changed[x][1];
      target_loc = assoc_ref (running.dir_loc_of, id);

      if (!target_loc)
        {
          escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, mod_loc);
          invoke_apply_changeset_callback (r, "?-/ %s\n", no_dot (escaped_tmp));
          rel_add_records (&r->missing_dir_for_meta_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, orig_patch_path, mod_loc);
          save_patch_for_missing_file (missing_patch_dir, mod_patch_path, mod_loc);
          lim_free (0, escaped_tmp);
          continue;
        }

      target_path = file_name_in_vicinity (0, target, target_loc);
      target_spew_loc = str_alloc_cat (0, target_loc, ".meta");
      target_spew_path = file_name_in_vicinity (0, target, target_spew_loc);

      preserve_old_patch_spew (removed_patch_conflict_files_path, target, target_spew_loc);

      safe_stat (target_path, &target_stat);

      {
        mode_t orig_perms;
        mode_t mod_perms;

        mod_perms = arch_read_permissions_patch (mod_patch_path);
        orig_perms = arch_read_permissions_patch (orig_patch_path);

        if (mod_perms != (target_stat.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO)))
          {
            safe_chmod (target_path, mod_perms);
          }

        escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
        invoke_apply_changeset_callback (r, "--/ %s\n", no_dot (escaped_tmp));
        rel_add_records (&r->meta_modified_dirs, rel_make_record (target_loc, id, 0), 0);
        lim_free (0, escaped_tmp);
      }

      lim_free (0, orig_patch_path);
      lim_free (0, mod_patch_path);
      lim_free (0, target_path);
      lim_free (0, target_spew_loc);
      lim_free (0, target_spew_path);
    }



  /****************************************************************
   * Update Changelogs
   */
  for (x = 0; x < rel_n_records (patched_changelogs); ++x)
    {
      t_uchar * target_loc;
      t_uchar * id;
      t_uchar * target_path;
      t_uchar * target_dir = 0;
      t_uchar * target_tmp = 0;
      t_uchar * archive = 0;
      t_uchar * version = 0;
      t_uchar * escaped_tmp = 0;
      int out_fd;
      struct stat stat_was;
      mode_t mode;

      target_loc = patched_changelogs[x][0];
      id = patched_changelogs[x][1];
      target_path = patched_changelogs[x][2];
      target_dir = file_name_directory_file (0, target_path);
      target_tmp = tmp_file_name (target_dir, ",,new-changeset");

      safe_stat (target_path, &stat_was);
      mode = (stat_was.st_mode & (S_IRWXU | S_IRWXG | S_IRWXO));

      arch_parse_changelog_id (&archive, &version, id);

      rmrf_file (target_tmp);
      out_fd = safe_open (target_tmp, O_WRONLY | O_EXCL | O_CREAT, mode);
      safe_fchmod (out_fd, mode);
      safe_buffer_fd (out_fd, 0, O_WRONLY, 0);
      arch_generate_changelog (out_fd, tree_root, 0, 0, 0, 0, archive, version);
      safe_close (out_fd);
      safe_rename (target_tmp, target_path);

      escaped_tmp = pika_save_escape_iso8859_1 (0, 0, escape_classes, target_loc);
      invoke_apply_changeset_callback (r, "cl  %s\n", no_dot (escaped_tmp));
      rel_add_records (&r->modified_files, rel_make_record (target_loc, id, 0), 0);

      lim_free (0, escaped_tmp);
      lim_free (0, target_dir);
      lim_free (0, target_tmp);
      lim_free (0, archive);
      lim_free (0, version);
    }


  /****************************************************************
   * Finish Up Deferred Conflicts
   */

  rel_sort_table_by_field (1, deferred_conflicts, 0);
  for (x = 0; x < rel_n_records (deferred_conflicts); ++x)
    {
      t_uchar * target_path = 0;
      t_uchar * rej_path = 0;

      target_path = file_name_in_vicinity (0, target, deferred_conflicts[x][0]);
      rej_path = str_alloc_cat (0, target_path, ".rej");
      safe_rename (target_path, rej_path);

      lim_free (0, target_path);
      lim_free (0, rej_path);
    }

  /****************************************************************
   * Sort and Uniq Report Fields
   */

  rel_sort_table_by_field (0, r->removed_files, 0);
  rel_uniq_by_field (&r->removed_files, 0);
  rel_sort_table_by_field (0, r->removed_dirs, 0);
  rel_uniq_by_field (&r->removed_dirs, 0);

  rel_sort_table_by_field (0, r->missing_removed_files, 0);
  rel_uniq_by_field (&r->missing_removed_files, 0);
  rel_sort_table_by_field (0, r->missing_removed_dirs, 0);
  rel_uniq_by_field (&r->missing_removed_dirs, 0);

  rel_sort_table_by_field (0, r->missing_renamed_files, 0);
  rel_uniq_by_field (&r->missing_renamed_files, 0);
  rel_sort_table_by_field (0, r->missing_renamed_dirs, 0);
  rel_uniq_by_field (&r->missing_renamed_dirs, 0);

  rel_sort_table_by_field (0, r->new_dirs, 0);
  rel_uniq_by_field (&r->new_dirs, 0);
  rel_sort_table_by_field (0, r->renamed_dirs, 0);
  rel_uniq_by_field (&r->renamed_dirs, 0);
  rel_sort_table_by_field (0, r->new_files, 0);
  rel_uniq_by_field (&r->new_files, 0);
  rel_sort_table_by_field (0, r->renamed_files, 0);
  rel_uniq_by_field (&r->renamed_files, 0);

  rel_sort_table_by_field (0, r->modified_files, 0);
  rel_uniq_by_field (&r->modified_files, 0);
  rel_sort_table_by_field (0, r->modified_dirs, 0);
  rel_uniq_by_field (&r->modified_dirs, 0);
  rel_sort_table_by_field (0, r->missing_file_for_patch, 0);
  rel_uniq_by_field (&r->missing_file_for_patch, 0);
  rel_sort_table_by_field (0, r->missing_dir_for_patch, 0);
  rel_uniq_by_field (&r->missing_dir_for_patch, 0);

  rel_sort_table_by_field (0, r->meta_modified_files, 0);
  rel_uniq_by_field (&r->meta_modified_files, 0);
  rel_sort_table_by_field (0, r->meta_modified_dirs, 0);
  rel_uniq_by_field (&r->meta_modified_dirs, 0);
  rel_sort_table_by_field (0, r->missing_file_for_meta_patch, 0);
  rel_uniq_by_field (&r->missing_file_for_meta_patch, 0);
  rel_sort_table_by_field (0, r->missing_dir_for_meta_patch, 0);
  rel_uniq_by_field (&r->missing_dir_for_meta_patch, 0);

  rel_sort_table_by_field (0, r->conflict_files, 0);
  rel_uniq_by_field (&r->conflict_files, 0);
  rel_sort_table_by_field (0, r->conflict_dirs, 0);
  rel_uniq_by_field (&r->conflict_dirs, 0);
  rel_sort_table_by_field (0, r->metadata_conflict_files, 0);
  rel_uniq_by_field (&r->metadata_conflict_files, 0);
  rel_sort_table_by_field (0, r->metadata_conflict_dirs, 0);
  rel_uniq_by_field (&r->metadata_conflict_dirs, 0);


  /****************************************************************
   * cleanup
   */

  free_assoc_table (mod_dir_id_of);
  free_assoc_table (new_dir_perms);
  safe_close (here_fd);
  lim_free (0, changeset_path);
  arch_free_changeset_report_data (&changeset);
  lim_free (0, target);
  lim_free (0, missing_patch_dir);
  lim_free (0, tree_root);
  arch_free_changeset_inventory_data (&inventory);
  arch_free_changeset_inventory_data (&inventory_by_name);
  rel_free_table (removed_files_index);
  rmrf_file (tmp_removed_files_root);
  lim_free (0, tmp_removed_files_root);
  rel_free_table (renamed_files_index);
  rel_free_table (present_renamed_files_index);
  rmrf_file (tmp_renamed_files_root);
  lim_free (0, tmp_renamed_files_root);
  rel_free_table (renamed_dirs_index);
  rel_free_table (present_renamed_dirs_index);
  rel_free_table (removed_dirs_index);
  rel_free_table (present_removed_dirs_index);
  rmrf_file (tmp_shuffled_dirs_root);
  lim_free (0, tmp_shuffled_dirs_root);
  rel_free_table (file_set_aside_with_dir_id);
  rel_free_table (dir_set_aside_with_dir_id);
  lim_free (0, removed_patch_conflict_files_path);
  free_assoc_table (running.dir_loc_of);
  free_assoc_table (running.dir_id_of);
  free_assoc_table (running.file_loc_of);
  free_assoc_table (running.file_id_of);
  rel_free_table (install_dirs_plan);
  rel_free_table (deferred_conflicts);
  rel_free_table (added_files_and_symlinks);
  rel_free_table (patched_changelogs);
}




static void
invoke_apply_changeset_callback (struct arch_apply_changeset_report * r, t_uchar * fmt, ...)
{
  va_list ap;

  if (r->callback)
    {
      va_start (ap, fmt);
      r->callback (r->thunk, fmt, ap);
      va_end (ap);
    }
}


static t_uchar *
set_aside_shuffled_dirs (rel_table * file_set_aside_with,
                         rel_table * dir_set_aside_with,
                         t_uchar * target_loc,
                         t_uchar * id,
                         int seq_n,
                         t_uchar * dest_root,
                         struct running_inventory_assocs * running,
                         t_uchar * target,
                         struct arch_changeset_inventory * inv)
{
  t_uchar * target_path = 0;
  t_uchar * seq = 0;
  t_uchar * dest_path = 0;
  t_uchar * target_loc_as_dir = 0;
  size_t target_loc_as_dir_len;
  int y;

  target_path = file_name_in_vicinity (0, target, target_loc);
  seq = int_to_string (seq_n);
  dest_path = file_name_in_vicinity (0, dest_root, seq);

  ensure_directory_exists (dest_root);
  safe_rename (target_path, dest_path);

  target_loc_as_dir = file_name_as_directory (0, target_loc);
  target_loc_as_dir_len = str_length (target_loc_as_dir);

  for (y = 0; y < rel_n_records (inv->files); ++y)
    {
      if (!str_cmp_prefix (target_loc_as_dir, inv->files[y][0]))
        {
          if (assoc_ref (running->file_id_of, inv->files[y][0]))
            {
              assoc_del (running->file_id_of, inv->files[y][0]);
              assoc_del (running->file_loc_of, inv->files[y][1]);

              rel_add_records (file_set_aside_with, rel_make_record (id, inv->files[y][0] + target_loc_as_dir_len, inv->files[y][1], 0), 0);
            }
        }
    }
  for (y = 0; y < rel_n_records (inv->dirs); ++y)
    {
      if (!str_cmp_prefix (target_loc_as_dir, inv->dirs[y][0]))
        {
          if (assoc_ref (running->dir_id_of, inv->dirs[y][0]))
            {
              assoc_del (running->dir_id_of, inv->dirs[y][0]);
              assoc_del (running->dir_loc_of, inv->dirs[y][1]);

              rel_add_records (dir_set_aside_with, rel_make_record (id, inv->dirs[y][0] + target_loc_as_dir_len, inv->dirs[y][1], 0), 0);
            }
        }
    }
  assoc_del (running->dir_id_of, target_loc);
  assoc_del (running->dir_loc_of, id);

  lim_free (0, target_path);
  lim_free (0, seq);
  lim_free (0, target_loc_as_dir);

  return dest_path;
}


static void
preserve_old_patch_spew (t_uchar * dest_root, t_uchar * target, t_uchar * loc)
{
  t_uchar * target_path = 0;
  t_uchar * orig_path = 0;
  t_uchar * rej_path = 0;
  t_uchar * dest_path = 0;
  t_uchar * dest_dir;
  t_uchar * orig_dest = 0;
  t_uchar * rej_dest = 0;

  target_path = file_name_in_vicinity (0, target, loc);
  orig_path = str_alloc_cat (0, target_path, ".orig");
  rej_path = str_alloc_cat (0, target_path, ".rej");

  dest_path = file_name_in_vicinity (0, dest_root, loc);
  dest_dir = file_name_directory_file (0, dest_path);
  orig_dest = str_alloc_cat (0, dest_path, ".orig");
  rej_dest = str_alloc_cat (0, dest_path, ".rej");

  if (!safe_access (orig_path, F_OK))
    {
      ensure_directory_exists (dest_dir);
      safe_rename (orig_path, orig_dest);
    }

  if (!safe_access (rej_path, F_OK))
    {
      ensure_directory_exists (dest_dir);
      safe_rename (rej_path, rej_dest);
    }

  lim_free (0, target_path);
  lim_free (0, orig_path);
  lim_free (0, rej_path);
  lim_free (0, dest_path);
  lim_free (0, dest_dir);
  lim_free (0, orig_dest);
  lim_free (0, rej_dest);
}

static int
deferred_conflict (rel_table * deferred_conflicts, t_uchar * spew_root, t_uchar * target, t_uchar * loc, t_uchar * orig_copy)
{
  t_uchar * path = 0;
  t_uchar * orig_path = 0;
  int conflict_detected;

  path = file_name_in_vicinity (0, target, loc);
  orig_path = str_alloc_cat (0, path, ".orig");
  conflict_detected = 0;

  if (!safe_access (path, F_OK))
    {
      struct stat orig_stat;
      struct stat target_stat;

      if (orig_copy)
        {
          safe_stat (orig_copy, &orig_stat);
          safe_stat (path, &target_stat);

          if (S_ISREG (orig_stat.st_mode) && S_ISREG (target_stat.st_mode) && !arch_binary_files_differ (orig_copy, path, 0, 0))
            {
              safe_unlink (path);
            }
          else
            goto conflict;
        }
      else
        {
        conflict:
          preserve_old_patch_spew (spew_root, target, loc);
          rel_add_records (deferred_conflicts, rel_make_record (loc, 0), 0);

          safe_rename (path, orig_path);
          conflict_detected = 1;
        }
    }

  lim_free (0, path);
  lim_free (0, orig_path);

  return conflict_detected;
}

static int
dir_depth_cmp (t_uchar * a, t_uchar * b)
{
  return str_cmp (a, b);
}

static void
analyze_install (struct running_inventory_assocs * running,
                 int is_dir,
                 t_uchar ** target_has_dir,
                 t_uchar ** install_dir,
                 t_uchar ** install_name,
                 t_uchar ** install_loc,
                 t_uchar * target,
                 t_uchar * mod_loc,
                 t_uchar * id,
                 assoc_table mod_dir_id_of,
                 rel_table file_set_aside_with,
                 rel_table dir_set_aside_with)
{
  t_uchar * basename = 0;
  t_uchar * loc_dir = 0;

  basename = file_name_tail (0, mod_loc);
  loc_dir = file_name_directory_file (0, mod_loc);

  if (!str_cmp (loc_dir, "."))
    {
      *target_has_dir = str_save (0, ".");
      *install_dir = str_save (0, target);
      *install_name = file_name_in_vicinity (0, *install_dir, basename);
      *install_loc = file_name_in_vicinity (0, ".", basename);
    }
  else
    {
      t_uchar * relpath = 0;
      t_uchar * install_loc_dir = 0;

      while (str_cmp (loc_dir, "."))
        {
          t_uchar * dir_id = 0;    /* not allocated */
          t_uchar * loc_dir_in_tgt = 0; /* not allocated */
          t_uchar * dir_tail = 0;
          t_uchar * s;

          dir_id = assoc_ref (mod_dir_id_of, loc_dir);

          if (!dir_id)
            {
              /* A degenerate changeset -- it should include that dir-id but
               * doesn't.   Let's try a guess.
               */
              dir_id = assoc_ref (running->dir_id_of, loc_dir);
            }

          loc_dir_in_tgt = assoc_ref (running->dir_loc_of, dir_id);

          if (loc_dir_in_tgt)
            break;

          dir_tail = file_name_tail (0, loc_dir);
          if (!relpath)
            relpath = str_save (0, dir_tail);
          else
            {
              t_uchar * t = relpath;
              relpath = file_name_in_vicinity (0, dir_tail, relpath);
              lim_free (0, t);
            }


          s = file_name_directory_file (0, loc_dir);
          lim_free (0, loc_dir);
          loc_dir = s;

          lim_free (0, dir_tail);
        }

      *target_has_dir = str_save (0, loc_dir);
      install_loc_dir = file_name_in_vicinity (0, loc_dir, relpath);
      if (!str_cmp (install_loc_dir, "./"))
        *install_dir = str_save (0, target);
      else
        *install_dir = file_name_in_vicinity (0, target, 2 + install_loc_dir);
      *install_name = file_name_in_vicinity (0, *install_dir, basename);
      *install_loc = file_name_in_vicinity (0, install_loc_dir, basename);

      lim_free (0, relpath);
      lim_free (0, install_loc_dir);
    }


  if (is_dir)
    {
      int x;

      assoc_set (&running->dir_loc_of, id, *install_loc);
      assoc_set (&running->dir_id_of, *install_loc, id);

      for (x = 0; x < rel_n_records (file_set_aside_with); ++x)
        {
          int cmp;
          t_uchar * new_loc = 0;
          t_uchar * sub_id;

          cmp = str_cmp (file_set_aside_with[x][0], id);

          if (cmp < 0)
            continue;
          else if (cmp > 0)
            break;

          new_loc = file_name_in_vicinity (0, *install_loc, file_set_aside_with[x][1]);
          sub_id = file_set_aside_with[x][2];

          assoc_set (&running->file_loc_of, sub_id, new_loc);
          assoc_set (&running->file_id_of, new_loc, sub_id);

          lim_free (0, new_loc);
        }

      for (x = 0; x < rel_n_records (dir_set_aside_with); ++x)
        {
          int cmp;
          t_uchar * new_loc = 0;
          t_uchar * sub_id;

          cmp = str_cmp (dir_set_aside_with[x][0], id);

          if (cmp < 0)
            continue;
          else if (cmp > 0)
            break;

          new_loc = file_name_in_vicinity (0, *install_loc, dir_set_aside_with[x][1]);
          sub_id = dir_set_aside_with[x][2];

          assoc_set (&running->dir_loc_of, sub_id, new_loc);
          assoc_set (&running->dir_id_of, new_loc, sub_id);

          lim_free (0, new_loc);
        }
    }
  else
    {
      assoc_set (&running->file_loc_of, id, *install_loc);
      assoc_set (&running->file_id_of, *install_loc, id);
    }

  lim_free (0, basename);
  lim_free (0, loc_dir);
}

static void
ensure_directory_eliminating_conflicts (rel_table * deferred_conflicts,
                                        t_uchar * spew_root,
                                        t_uchar * target_has_path,
                                        t_uchar * dir,
                                        t_uchar * target)
{
  t_uchar * dir_of_dir = 0;

  if (!str_cmp (target_has_path, dir))
    return;

  dir_of_dir = file_name_directory_file (0, dir);
  ensure_directory_eliminating_conflicts (deferred_conflicts, spew_root, target_has_path, dir_of_dir, target);

  if (safe_file_is_directory (dir))
    return;

  if (!safe_access (dir, F_OK))
    {
      t_uchar * loc;
      t_uchar * orig;

      loc = str_alloc_cat (0, "./", dir + str_length (target) + 1);
      orig = str_alloc_cat (0, dir, ".orig");

      deferred_conflict (deferred_conflicts, spew_root, target, loc, 0);
      safe_rename (dir, orig);

      lim_free (0, loc);
      lim_free (0, orig);
    }

  safe_mkdir (dir, 0777);

  lim_free (0, dir_of_dir);
}

static int
run_diff3 (t_uchar * basename,
           t_uchar * mine_path,
           t_uchar * older_path,
           t_uchar * yours_path)
{
  int pid;

  pid = fork ();

  if (pid == -1)
    panic ("unable to fork for diff3");

  if (pid)
    {
      int status;
      int wait_pid;

      wait_pid = waitpid (pid, &status, 0);
      if (wait_pid < 0)
        {
          panic_msg ("error waiting for patch subprocess");
          kill (0, SIGKILL);
          panic ("error waiting for subprocess");
        }
      if (WIFSIGNALED (status))
        {
          safe_printfmt (2, "\n");
          safe_printfmt (2, "arch_apply_changeset: diff3 subprocess killed by signal %d\n", WTERMSIG (status));
          safe_printfmt (2, "\n");
          exit (2);
        }
      else if (!WIFEXITED (status))
        {
          panic_msg ("waitpid returned for a non-exited process");
          kill (0, SIGKILL);
          panic ("waitpid returned for a non-exited process");
        }
      else
        {
          int exit_status;

          exit_status = WEXITSTATUS (status);

          if (exit_status == 1)
            {
              t_uchar * rej_name = str_alloc_cat (0, basename, ".rej");
              int fd;

              if (!safe_access (rej_name, F_OK))
                {
                  t_uchar * setaside_base_name = str_alloc_cat (0, ",,saved-", rej_name);

                  safe_rename (rej_name, setaside_base_name);

                  lim_free (0, setaside_base_name);
                }

              fd = safe_open (rej_name, O_WRONLY | O_CREAT | O_EXCL, 0444);
              safe_printfmt (fd, "Conflicts occured, diff3 conflict markers left in file.\n");
              safe_close (fd);

              lim_free (0, rej_name);
            }
          return exit_status;
        }
    }
  else
    {
      int output_redir_fd;
      int input_redir_fd;
      t_uchar ** argv;

      argv = 0;

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = cfg__gnu_diff3;

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-E";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "--merge";

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-L";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "TREE";

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-L";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "ANCESTOR";

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-L";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "MERGE-SOURCE";

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = mine_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = older_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = yours_path;

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = 0;

      input_redir_fd = safe_open ("/dev/null", O_RDONLY, 0);
      output_redir_fd = safe_open (basename, O_WRONLY | O_CREAT | O_EXCL, 0666);

      safe_move_fd (input_redir_fd, 0);
      safe_move_fd (output_redir_fd, 1);
      output_redir_fd = safe_dup (1);
      safe_move_fd (output_redir_fd, 2);

      arch_util_execvp (cfg__gnu_diff3, argv);
      panic ("arch_apply_changeset: execvp for diff3 returned to caller");
      exit (2);
    }
  panic ("dopatch: not reached (run_diff3)");
  return 2;
}

static int
run_patch  (int apply_in_reverse,
            int forward_opt_to_patch,
            char * patch_path,
            char * tree_file_base,
            char * original_inode_name)
{
  int pid;

  pid = fork ();

  if (pid == -1)
    panic ("unable to fork for patch");

  if (pid)
    {
      int status;
      int wait_pid;

      wait_pid = waitpid (pid, &status, 0);
      if (wait_pid < 0)
        {
          panic_msg ("error waiting for patch subprocess");
          kill (0, SIGKILL);
          panic ("error waiting for subprocess");
        }
      if (WIFSIGNALED (status))
        {
          safe_printfmt (2, "\n");
          safe_printfmt (2, "arch_apply_changeset: patch subprocess killed by signal %d\n", WTERMSIG (status));
          safe_printfmt (2, "\n");
          exit (2);
        }
      else if (!WIFEXITED (status))
        {
          panic_msg ("waitpid returned for a non-exited process");
          kill (0, SIGKILL);
          panic ("waitpid returned for a non-exited process");
        }
      else
        {
          int exit_status;

          exit_status = WEXITSTATUS (status);
          return exit_status;
        }
    }
  else
    {
      int output_redir_fd;
      int input_redir_fd;
      t_uchar ** argv;

      argv = 0;

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = cfg__gnu_patch;

      if (forward_opt_to_patch)
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "--forward";

      if (apply_in_reverse)
        *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "--reverse";

      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-f";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-s";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "--posix";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-i";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = patch_path;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = "-o";
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = tree_file_base;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = original_inode_name;
      *(t_uchar **)ar_push ((void **)&argv, 0, sizeof (char *)) = 0;

      input_redir_fd = safe_open ("/dev/null", O_RDONLY, 0);
      output_redir_fd = safe_open (",,patch-output", O_WRONLY | O_CREAT | O_EXCL, 0666);

      safe_move_fd (input_redir_fd, 0);
      safe_move_fd (output_redir_fd, 1);
      output_redir_fd = safe_dup (1);
      safe_move_fd (output_redir_fd, 2);

      arch_util_execvp (cfg__gnu_patch, argv);
      panic ("arch_apply_changeset: execvp for patch returned to caller");
      exit (2);
    }
  panic ("dopatch: not reached (run_patch)");
  return 2;
}


static void
save_patch_for_missing_file (t_uchar * missing_patch_dir, t_uchar * patch_path, t_uchar * mod_loc)
{
  t_uchar * mod_loc_dir = 0;
  t_uchar * dest_dir = 0;
  t_uchar * patch_tail = 0;
  t_uchar * dest_path = 0;

  mod_loc_dir = file_name_directory_file (0, mod_loc);
  dest_dir = file_name_in_vicinity (0, missing_patch_dir, mod_loc_dir);
  ensure_directory_exists (dest_dir);
  patch_tail = file_name_tail (0, patch_path);
  dest_path = file_name_in_vicinity (0, dest_dir, patch_tail);
  copy_file (patch_path, dest_path);

  lim_free (0, mod_loc_dir);
  lim_free (0, dest_dir);
  lim_free (0, patch_tail);
  lim_free (0, dest_path);
}




/* tag: Tom Lord Thu May 15 17:19:28 2003 (apply-changeset.c)
 */
