/* apply-changeset.c:
 *
 * vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
 ****************************************************************
 * 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/exception.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/mem/talloc.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 "libfsutils/link-target.h"
#include "libfsutils/read-line.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/ensure-dir.h"
#include "libfsutils/copy-file.h"
#include "libarch/diffs.h"
#include "libarch/proj-tree-lint.h"
#include "libawk/associative.h"
#include "libawk/relassoc.h"
#include "libawk/numbers.h"
#include "libarch/changelogs.h"
#include "libarch/conflict-handling.h"
#include "libarch/debug.h"
#include "libarch/project-tree.h"
#include "libarch/exec.h"
#include "libarch/apply-changeset.h"



typedef struct apply_changeset_
{
    arch_apply_changeset_report_callback callback;
    void * thunk;
    int escape_classes;

    struct arch_apply_changeset_report * report;
    int here_fd;
    struct arch_changeset_report changeset;
    assoc_table mod_dir_id_of;
    assoc_table new_dir_perms;
    t_uchar * removed_patch_conflict_files_path;
    rel_table deferred_conflicts; /* [0] final target location */
    struct running_inventory_assocs running;
    arch_project_tree_t * target;

} apply_changeset_t;



/* __STDC__ prototypes for static functions */
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 int dir_depth_cmp (t_uchar * a, t_uchar * b);
static int run_diff3 (t_uchar * basename,
                      t_uchar * mine_path,
                      t_uchar * older_path,
                      t_uchar * yours_path);
static void invoke_apply_changeset_callback (apply_changeset_t * apply, t_uchar * fmt, ...);
static void one_path_callback (apply_changeset_t * apply, t_uchar * fmt, t_uchar *path);
static void two_path_callback (apply_changeset_t * apply, t_uchar * fmt, t_uchar *path1, t_uchar *path2);
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 int
analyze_install (struct running_inventory_assocs * running,
                 int is_dir,
                 int is_rename,
                 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 (apply_changeset_t * apply,
                                                    t_uchar * target_has_path,
                                                    t_uchar * dir);
static int deferred_conflict (apply_changeset_t * apply,
                              t_uchar * loc, t_uchar * orig_copy,
                              struct running_inventory_assocs *inventory);
static t_uchar * rename_removed_files(void * context,
                                      arch_project_tree_t * tree,
                                      apply_changeset_t * apply, 
                                      struct running_inventory_assocs * running);
static rel_table find_target_equivalent (rel_table source, rel_table target);
static rel_table find_target_missing(rel_table source, rel_table target);
static int arch_free_apply_changeset_report_data (void * data);
static void identify_removed_and_missing_removed_files ( struct arch_changeset_report * changeset,
                                            struct arch_changeset_inventory * inventory,
                                            struct arch_apply_changeset_report * r);
static void report_sort_and_unique (struct arch_apply_changeset_report * r);
static apply_changeset_t * arch_apply_changeset_new (void * context,
                                                     arch_apply_changeset_report_callback callback,
                                                     void * thunk,
                                                     int escape_classes,
                                                     arch_project_tree_t * target);
static int apply_changeset_destructor (void * data);
static void read_changeset (apply_changeset_t * apply, t_uchar const * changeset_spec);
static void preserve_old_patch_spew (t_uchar * dest_root, arch_project_tree_t * target, t_uchar * loc, struct running_inventory_assocs *inventory);
static void setup_running (apply_changeset_t * apply, struct arch_changeset_inventory * inventory);



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



int
arch_free_apply_changeset_report_data (void * data)
{
  struct arch_apply_changeset_report * r = talloc_get_type (data, struct arch_apply_changeset_report);
  invariant (!!r);
  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);
  return 0;
}

struct arch_apply_changeset_report *
arch_apply_changeset (t_uchar * changeset_spec, void * context, arch_project_tree_t * real_target_tree,
                      enum arch_id_tagging_method method,
                      enum arch_inventory_category untagged_source_category,
                      int reverse, int forward,
                      int escape_classes,
                      arch_apply_changeset_report_callback callback,
                      void * thunk)
{
  apply_changeset_t * apply = arch_apply_changeset_new (talloc_context, callback, thunk, escape_classes, real_target_tree);
  struct arch_apply_changeset_report * result;
  int x;

  t_uchar * missing_patch_dir = 0;
  struct arch_changeset_inventory inventory;
  struct arch_changeset_inventory inventory_by_name;

  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 */

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

  rel_table added_files_and_symlinks = 0;

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

  read_changeset (apply, changeset_spec);

  missing_patch_dir = tmp_seq_file (apply->target->root, "++patches-missing-files");

  /****************************************************************
   * Study the changeset.
   */
  if (reverse)
    {
      arch_reverse_changeset (&apply->changeset);
    }
  apply->mod_dir_id_of = rel_to_assoc (apply->changeset.mod_dirs_index, 0, 1);
  apply->new_dir_perms = rel_to_assoc (apply->changeset.added_dirs, 0, 2);


  /****************************************************************
   * Inventory the target tree.
   */
  mem_set0 ((t_uchar *)&inventory, sizeof (inventory));
  arch_changeset_inventory (&inventory, apply->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);

  setup_running (apply, &inventory);

  /****************************************************************
   * Set aside and delete removed files.
   */
  identify_removed_and_missing_removed_files (&apply->changeset, &inventory, apply->report);

  /* FIXME: apply->report->missing_removed_files are conflicts and should be marked as such */

  tmp_removed_files_root = rename_removed_files(talloc_context, apply->target, apply, &apply->running);

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

  renamed_files_index = rel_copy_table (apply->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);
  apply->report->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 (apply->report->missing_renamed_files); ++x)
      two_path_callback (apply, "?r  %s\n     => %s\n", apply->report->missing_renamed_files[x][0], apply->report->missing_renamed_files[x][1]);

  rel_sort_table_by_field (0, apply->report->missing_removed_files, 0);

  tmp_renamed_files_root = talloc_tmp_file_name (tmp_removed_files_root, apply->target->root, ",,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, apply->target->root, 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 (apply->running.file_id_of, target_loc);
      assoc_del (apply->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 (apply->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);

  apply->report->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, apply->report->missing_renamed_dirs, 0);
  for (x = 0; x < rel_n_records (apply->report->missing_renamed_dirs); ++x)
      two_path_callback (apply, "?r/ %s\n     => %s\n", apply->report->missing_renamed_dirs[x][0], apply->report->missing_renamed_dirs[x][1]);

  removed_dirs_index = rel_copy_table (apply->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);

  apply->report->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, apply->report->missing_removed_dirs, 0);

  tmp_shuffled_dirs_root = talloc_tmp_file_name (tmp_removed_files_root, apply->target->root, ",,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
          {
            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;

            one_path_callback (apply, "D/  %s\n", target_loc);

            rel_add_records (&apply->report->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, &apply->running, apply->target->root, &inventory);
        ++seq;
        rel_add_field (dest_record, tmp_name);
        lim_free (0, tmp_name);
      }

    rel_sort_table_by_field (0, apply->report->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
   */
  apply->removed_patch_conflict_files_path = talloc_tmp_file_name (apply, apply->target->root, "+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 (apply->changeset.added_dirs); ++x)
    {
      rel_add_records (&install_dirs_plan, rel_make_record (apply->changeset.added_dirs[x][0], "", apply->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;

      /* FIXME: trigger a conflict here on non-zero */
      analyze_install (&apply->running, 1, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                       apply->target->root, mod_loc, id, apply->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, apply->target->root);
      else
        target_has_path = file_name_in_vicinity (0, apply->target->root, 2 + target_has_dir); /* over "./" */

      ensure_directory_eliminating_conflicts (apply,
                                              target_has_path, install_dir);

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

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

              if (!deferred_conflict (apply, install_loc, 0, &apply->running))
                {
                  one_path_callback (apply, "A/  %s\n", install_loc);
                  rel_add_records (&apply->report->new_dirs, rel_make_record (install_loc, id, 0), 0);
                }
              else
                {
                  one_path_callback (apply, "CA/ %s\n", install_loc);
                  rel_add_records (&apply->report->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
                }

              safe_mkdir (install_name, perms);
              if (!trust_umask)
                safe_chmod (install_name, perms);
            }
        }
      else
        {
          t_uchar * oldtgtloc;

          oldtgtloc = install_dirs_plan[x][3];

          if (!deferred_conflict (apply, install_loc, 0, &apply->running))
            {
              two_path_callback (apply, "/>  %s\t%s\n", oldtgtloc, install_loc);
              rel_add_records (&apply->report->renamed_dirs, rel_make_record (oldtgtloc, install_loc, id, 0), 0);
            }
          else
            {
              two_path_callback (apply, "C/> %s\t%s\n", oldtgtloc, install_loc);
              rel_add_records (&apply->report->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
            }

          safe_rename (take_from, install_name);
        }

      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, apply->report->new_dirs, 0);
  rel_sort_table_by_field (0, apply->report->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;

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

      /* renames cannot conflict on changed-paths */
      invariant (!analyze_install (&apply->running, 0, 1, &target_has_dir, &install_dir, &install_name, &install_loc,
                                   apply->target->root, mod_loc, id, apply->mod_dir_id_of, 0, 0));

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

      ensure_directory_eliminating_conflicts (apply,
                                              target_has_path, install_dir);

      if (!deferred_conflict (apply, install_loc, 0, &apply->running))
        {
          two_path_callback (apply, "=>  %s\t%s\n", old_target_loc, install_loc);
          rel_add_records (&apply->report->renamed_files, rel_make_record (old_target_loc, install_loc, id, 0), 0);
        }
      else
        {
          two_path_callback (apply, "C=> %s\t%s\n", old_target_loc, install_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (install_loc, id, 0), 0);
        }

      safe_rename (take_from, install_name);

      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 (apply->changeset.added_files);
  rel_append_x (&added_files_and_symlinks, apply->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;
      int path_conflict;

      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;

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

      path_conflict = analyze_install (&apply->running, 0, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                                       apply->target->root, mod_loc, id, apply->mod_dir_id_of, 0, 0);

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

      ensure_directory_eliminating_conflicts (apply,
                                              target_has_path, install_dir);

      if (path_conflict)
        {
          one_path_callback (apply, "CA  %s (id present already)\n", install_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (install_loc, id, 0), 0);
          rel_add_records (&apply->report->conflict_files, rel_make_record (assoc_ref (apply->running.file_loc_of, id), id, 0), 0);
        }
      else if (!deferred_conflict (apply, install_loc, take_from, &apply->running))
        {
          one_path_callback (apply, "A   %s\n", install_loc);
          rel_add_records (&apply->report->new_files, rel_make_record (install_loc, id, 0), 0);
        }
      else
        {
          one_path_callback (apply, "CA  %s\n", install_loc);
          rel_add_records (&apply->report->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, 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, apply->report->renamed_files, 0);


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

  for (x = 0; x < rel_n_records (apply->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;
      struct stat target_stat;

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, patch_path, mod_loc);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (patch_path, rej_name);
          copy_permissions (patch_path, rej_name);

          one_path_callback (apply, "C-> %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);
        }
      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 = talloc_tmp_file_name (talloc_context, ".", 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);
                }

              patch_stat = run_patch (reverse, forward, patch_path, basename, original_inode_tmpname);

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

                  one_path_callback (apply, "M   %s\n", target_loc);
                  rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
                }
              else if (patch_stat == 1)
                {
                  t_uchar * orig_name;

                  copy_permissions (original_inode_tmpname, basename);
                  conflict_filenames (apply->target->root, target_path, &orig_name, NULL, &apply->running);
                  safe_rename (original_inode_tmpname, orig_name);

                  one_path_callback (apply, "C   %s\n", target_loc);
                  rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

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

                  safe_printfmt (2, "arch_apply_changeset: internal error (patch returned odd status)\n");
                  safe_printfmt (2, "   patch exit status: %d\n", patch_stat);
                  safe_printfmt (2, "         target file: %s\n", target_path);
                  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 (apply->here_fd);

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

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

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

  for (x = 0; x < rel_n_records (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

      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:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          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");

          one_path_callback (apply, "C-> %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }
          else
            {
              patch_stat = 1;
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "M-> %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

        binary_conflict:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          one_path_callback (apply, "Cb  %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context,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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "Mb  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

        symlink_to_file_conflict:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          one_path_callback (apply, "Cch %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "ch  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

      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:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          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);

          one_path_callback (apply, "Cch %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "ch  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?-- %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

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

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_spew_loc, &apply->running);

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

          one_path_callback (apply, "C-- %s\n", target_loc);
          rel_add_records (&apply->report->metadata_conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (target_path_tmp);
            }
          one_path_callback (apply, "--  %s\n", target_loc);
          rel_add_records (&apply->report->meta_modified_files, rel_make_record (target_loc, id, 0), 0);
        }

      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 (apply->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;
      struct stat target_stat;

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

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

      if (!target_loc)
        {
          one_path_callback (apply, "?-/ %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

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

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_spew_loc, &apply->running);

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

        one_path_callback (apply, "--/ %s\n", target_loc);
        rel_add_records (&apply->report->meta_modified_dirs, rel_make_record (target_loc, id, 0), 0);
      }

      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;
      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 = talloc_tmp_file_name (talloc_context, 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, apply->target, 0, 0, 0, 0, archive, version);
      safe_close (out_fd);
      safe_rename (target_tmp, target_path);

      one_path_callback (apply, "cl  %s\n", target_loc);
      rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);

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


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

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

      target_path = file_name_in_vicinity (0, apply->target->root, apply->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);
    }

  report_sort_and_unique (apply->report);
  /* FIXME: here is where we note conflicts */

  arch_tree_note_conflicts (apply->target, apply->report);

  arch_project_tree_mutated (apply->target);
  
  /****************************************************************
   * cleanup
   */

  lim_free (0, missing_patch_dir);
  arch_free_changeset_inventory_data (&inventory);
  arch_free_changeset_inventory_data (&inventory_by_name);
  
  rmrf_file (tmp_removed_files_root);
  rmrf_file (tmp_renamed_files_root);
  rmrf_file (tmp_shuffled_dirs_root);
  talloc_free (tmp_removed_files_root);

  rel_free_table (renamed_files_index);
  rel_free_table (present_renamed_files_index);
  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);
  rel_free_table (file_set_aside_with_dir_id);
  rel_free_table (dir_set_aside_with_dir_id);
  rel_free_table (install_dirs_plan);
  rel_free_table (added_files_and_symlinks);
  rel_free_table (patched_changelogs);
  result = talloc_steal (context, apply->report);
  talloc_free (apply);
  return result;
}

/**
 * \brief create a new changeset application object
 */
apply_changeset_t *
arch_apply_changeset_new (void * context,
                           arch_apply_changeset_report_callback callback,
                           void * thunk,
                           int escape_classes,
                           arch_project_tree_t * target)
{
    apply_changeset_t *result = talloc (context, apply_changeset_t);

    result->callback = callback;
    result->thunk = thunk;
    result->escape_classes = escape_classes;

    result->report = talloc_zero (result, struct arch_apply_changeset_report);
    talloc_set_destructor (result->report, arch_free_apply_changeset_report_data);
  
    result->here_fd = safe_open (".", O_RDONLY, 0);

    result->mod_dir_id_of = NULL;
    result->new_dir_perms = NULL;

    result->removed_patch_conflict_files_path = NULL;
    result->deferred_conflicts = NULL;
    
    result->running.dir_loc_of = NULL;
    result->running.dir_id_of = NULL;
    result->running.file_loc_of = NULL;
    result->running.file_id_of = NULL;

    result->target = target;

    talloc_set_destructor (result, apply_changeset_destructor);

    return result;
}

/**
 * \brief free non talloc managed resources
 */
int
apply_changeset_destructor (void * data)
{
    apply_changeset_t * apply = talloc_get_type (data, apply_changeset_t);
    safe_close (apply->here_fd);
    arch_free_changeset_report_data (&apply->changeset);
    free_assoc_table (apply->mod_dir_id_of);
    free_assoc_table (apply->new_dir_perms);
    free_assoc_table (apply->running.dir_loc_of);
    free_assoc_table (apply->running.dir_id_of);
    free_assoc_table (apply->running.file_loc_of);
    free_assoc_table (apply->running.file_id_of);
    return 0;
}

struct arch_apply_changeset_report *
arch_merge_from_changeset (t_uchar * changeset_spec,
                           void * context,
                           arch_project_tree_t * real_target_tree,
                           enum arch_id_tagging_method method,
                           enum arch_inventory_category untagged_source_category,
                           assoc_table older_table,
                           assoc_table yours_table,
                           int escape_classes,
                           int show_noops,
                           arch_apply_changeset_report_callback callback,
                           void * thunk)
{
  apply_changeset_t * apply = arch_apply_changeset_new (talloc_context, callback, thunk, escape_classes, real_target_tree);
  struct arch_apply_changeset_report * result;
  int x;

  t_uchar * missing_patch_dir = 0;
  struct arch_changeset_inventory inventory;

  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 */

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

  rel_table added_files_and_symlinks = 0;

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

  read_changeset (apply, changeset_spec);
  missing_patch_dir = tmp_seq_file (apply->target->root, "++patches-missing-files");

  
  /****************************************************************
   * Study the changeset.
   */
  apply->mod_dir_id_of = rel_to_assoc (apply->changeset.mod_dirs_index, 0, 1);
  apply->new_dir_perms = rel_to_assoc (apply->changeset.added_dirs, 0, 2);


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

  setup_running (apply, &inventory);

  /****************************************************************
   * Set aside and delete removed files.
   */
  identify_removed_and_missing_removed_files (&apply->changeset, &inventory, apply->report);

  if (show_noops)
    for (x = 0; x < rel_n_records (apply->report->missing_removed_files); ++x)
      {
          one_path_callback (apply, "=D  %s\n", apply->report->missing_removed_files[x][0]);
      }
  tmp_removed_files_root = rename_removed_files(talloc_context, apply->target, apply, &apply->running);

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

  renamed_files_index = rel_copy_table (apply->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);
  apply->report->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 (apply->report->missing_renamed_files); ++x)
      two_path_callback (apply, "?r  %s\n     => %s\n", apply->report->missing_renamed_files[x][0], apply->report->missing_renamed_files[x][1]);

  rel_sort_table_by_field (0, apply->report->missing_removed_files, 0);

  tmp_renamed_files_root = talloc_tmp_file_name (tmp_removed_files_root, apply->target->root, ",,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, apply->target->root, 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 (apply->running.file_id_of, target_loc);
      assoc_del (apply->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 (apply->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);

  apply->report->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, apply->report->missing_renamed_dirs, 0);
  for (x = 0; x < rel_n_records (apply->report->missing_renamed_dirs); ++x)
    {
      two_path_callback (apply, "?r/ %s\n     => %s\n", apply->report->missing_renamed_dirs[x][0], apply->report->missing_renamed_dirs[x][1]);
    }

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

  present_removed_dirs_index = find_target_equivalent (removed_dirs_index, inventory.dirs);
  rel_sort_table_by_field (1, present_removed_dirs_index, 0);

  apply->report->missing_removed_dirs = find_target_missing(removed_dirs_index, inventory.dirs);

  tmp_shuffled_dirs_root = talloc_tmp_file_name (tmp_removed_files_root, apply->target->root, ",,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
          {
            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;

            one_path_callback (apply, "D/  %s\n", target_loc);

            rel_add_records (&apply->report->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, &apply->running, apply->target->root, &inventory);
        ++seq;
        rel_add_field (dest_record, tmp_name);
        lim_free (0, tmp_name);
      }

    rel_sort_table_by_field (0, apply->report->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);
  }

  if (show_noops)
    for (x = 0; x < rel_n_records (apply->report->missing_removed_dirs); ++x)
      {
          one_path_callback (apply, "=D/ %s\n", apply->report->missing_removed_dirs[x][0]);
      }

  /****************************************************************
   * Make a name for a dir in which to stash removed .rej and .orig files
   */
  apply->removed_patch_conflict_files_path = talloc_tmp_file_name (apply, apply->target->root, "+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.
   */

    {
      rel_cut_spec spec = rel_cut_list (2, 4, 3, 0, -1);
      install_dirs_plan = rel_cut (spec, present_renamed_dirs_index);
      rel_cut_spec_finalise (&spec);
    }
  for (x = 0; x < rel_n_records (apply->changeset.added_dirs); ++x)
    {
      rel_add_records (&install_dirs_plan, rel_make_record (apply->changeset.added_dirs[x][0], "", apply->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;

      /* FIXME: trigger a conflict here on non-zero */
      analyze_install (&apply->running, 1, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                       apply->target->root, mod_loc, id, apply->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, apply->target->root);
      else
        target_has_path = file_name_in_vicinity (0, apply->target->root, 2 + target_has_dir); /* over "./" */

      ensure_directory_eliminating_conflicts (apply, target_has_path, install_dir);

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

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

              if (!deferred_conflict (apply, install_loc, 0, &apply->running))
                {
                  one_path_callback (apply, "A/  %s\n", install_loc);
                  rel_add_records (&apply->report->new_dirs, rel_make_record (install_loc, id, 0), 0);
                }
              else
                {
                  one_path_callback (apply, "CA/ %s\n", install_loc);
                  rel_add_records (&apply->report->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
                }

              safe_mkdir (install_name, perms);
              if (!trust_umask)
                safe_chmod (install_name, perms);
            }
        }
      else
        {
          t_uchar * oldtgtloc;

          oldtgtloc = install_dirs_plan[x][3];

          if (!deferred_conflict (apply, install_loc, 0, &apply->running))
            {
              two_path_callback (apply, "/>  %s\t%s\n", oldtgtloc, install_loc);
              rel_add_records (&apply->report->renamed_dirs, rel_make_record (oldtgtloc, install_loc, id, 0), 0);
            }
          else
            {
              two_path_callback (apply, "C/> %s\t%s\n", oldtgtloc, install_loc);
              rel_add_records (&apply->report->conflict_dirs, rel_make_record (install_loc, id, 0), 0);
            }

          safe_rename (take_from, install_name);
        }

      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, apply->report->new_dirs, 0);
  rel_sort_table_by_field (0, apply->report->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;


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

      /* renames cannot conflict on changed-paths */
      invariant (!analyze_install (&apply->running, 0, 1, &target_has_dir, &install_dir, &install_name, &install_loc,
                                   apply->target->root, mod_loc, id, apply->mod_dir_id_of, 0, 0));

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

      ensure_directory_eliminating_conflicts (apply, target_has_path, install_dir);

      /* deferred conflicts are conflicts we need to do a rename on later */
      if (!deferred_conflict (apply, install_loc, 0, &apply->running))
        {
          two_path_callback (apply, "=>  %s\t%s\n", old_target_loc, install_loc);
          rel_add_records (&apply->report->renamed_files, rel_make_record (old_target_loc, install_loc, id, 0), 0);
        }
      else
        {
          two_path_callback (apply, "C=> %s\t%s\n", old_target_loc, install_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (install_loc, id, 0), 0);
        }

      safe_rename (take_from, install_name);

      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 (apply->changeset.added_files);
  rel_append_x (&added_files_and_symlinks, apply->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;
      int path_conflict;

      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;

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

      path_conflict = analyze_install (&apply->running, 0, 0, &target_has_dir, &install_dir, &install_name, &install_loc,
                                       apply->target->root, mod_loc, id, apply->mod_dir_id_of, 0, 0);

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

      ensure_directory_eliminating_conflicts (apply, target_has_path, install_dir);

      if (path_conflict)
        {
          one_path_callback (apply, "CA  %s (id present already)\n", install_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (install_loc, id, 0), 0);
          rel_add_records (&apply->report->conflict_files, rel_make_record (assoc_ref (apply->running.file_loc_of, id), id, 0), 0);
        }
      else if (!deferred_conflict (apply, install_loc, take_from, &apply->running))
        {
          one_path_callback (apply, "A   %s\n", install_loc);
          rel_add_records (&apply->report->new_files, rel_make_record (install_loc, id, 0), 0);
        }
      else
        {
          one_path_callback (apply, "CA  %s\n", install_loc);
          rel_add_records (&apply->report->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, 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, apply->report->renamed_files, 0);


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

  for (x = 0; x < rel_n_records (apply->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;
      struct stat target_stat;

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

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->missing_file_for_patch, rel_make_record (mod_loc, id, 0), 0);
          save_patch_for_missing_file (missing_patch_dir, patch_path, mod_loc);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (patch_path, rej_name);
          copy_permissions (patch_path, rej_name);

          one_path_callback (apply, "C-> %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);
        }
      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 = talloc_tmp_file_name (talloc_context, ".", 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);
                }

              {
              t_uchar * older_path = assoc_ref (older_table, id);
              t_uchar * yours_path = assoc_ref (yours_table, id);

              patch_stat = run_diff3 (basename, original_inode_tmpname, older_path, yours_path);
              }

              if (patch_stat == 0)
                {
                  int was_noop = !arch_binary_files_differ (original_inode_tmpname, basename, 0, 0);
                  copy_permissions (original_inode_tmpname, basename);
                  safe_unlink (original_inode_tmpname);
                  
                  if (was_noop)
                  {
                    if (show_noops)
                      one_path_callback (apply, "=M  %s\n", target_loc);
                  }
                  else
                    one_path_callback (apply, "M   %s\n", target_loc);
                  rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
                }
              else if (patch_stat == 1)
                {
                  t_uchar * orig_name;

                  copy_permissions (original_inode_tmpname, basename);
                  conflict_filenames (apply->target->root, target_path, &orig_name, NULL, &apply->running);
                  safe_rename (original_inode_tmpname, orig_name);

                  one_path_callback (apply, "C   %s\n", 
                    target_loc);
                  rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

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

                  safe_printfmt (2, "arch_apply_changeset: internal error (patch returned odd status)\n");
                  safe_printfmt (2, "   patch exit status: %d\n", patch_stat);
                  safe_printfmt (2, "         target file: %s\n", target_path);
                  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 (apply->here_fd);

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

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

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

  for (x = 0; x < rel_n_records (apply->changeset.patched_symlinks); ++x)
    {
      t_uchar * target_loc;
      t_uchar * target_path = 0;
      struct stat target_stat;

      t_uchar * orig_patch_path = str_alloc_cat (0, apply->changeset.patched_symlinks[x][2], ".link-orig");
      t_uchar * mod_patch_path = str_alloc_cat (0, apply->changeset.patched_symlinks[x][2], ".link-mod");

      t_uchar * mod_loc = apply->changeset.patched_symlinks[x][0];
      t_uchar * id = apply->changeset.patched_symlinks[x][1];
      target_loc = assoc_ref (apply->running.file_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

      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:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          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");

          one_path_callback (apply, "C-> %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }
          else
            {
              patch_stat = 1;
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "M-> %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->changeset.patched_binaries); ++x)
    {
      t_uchar * target_path = 0;
      struct stat target_stat;

      t_uchar * orig_patch_path = str_alloc_cat (0, apply->changeset.patched_binaries[x][2], ".original");
      t_uchar * mod_patch_path = str_alloc_cat (0, apply->changeset.patched_binaries[x][2], ".modified");
      t_uchar * mod_loc = apply->changeset.patched_binaries[x][0];
      t_uchar * id = apply->changeset.patched_binaries[x][1];
      t_uchar * target_loc = assoc_ref (apply->running.file_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

        binary_conflict:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          one_path_callback (apply, "Cb  %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "Mb  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->changeset.symlink_to_file); ++x)
    {
      t_uchar * orig_patch_path = 0;
      t_uchar * mod_patch_path = 0;
      t_uchar * target_path = 0;
      t_uchar * mod_loc;
      t_uchar * id;
      t_uchar * target_loc;
      struct stat target_stat;

      orig_patch_path = str_alloc_cat (0, apply->changeset.symlink_to_file[x][2], ".link-orig");
      mod_patch_path = str_alloc_cat (0, apply->changeset.symlink_to_file[x][2], ".modified");
      mod_loc = apply->changeset.symlink_to_file[x][0];
      id = apply->changeset.symlink_to_file[x][1];
      target_loc = assoc_ref (apply->running.file_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

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

        symlink_to_file_conflict:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          safe_rename (target_path, orig_name);
          copy_file (mod_patch_path, rej_name);
          copy_permissions (mod_patch_path, rej_name);

          one_path_callback (apply, "Cch %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "ch  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->changeset.file_to_symlink); ++x)
    {
      t_uchar * target_path = 0;
      struct stat target_stat;

      t_uchar * orig_patch_path = str_alloc_cat (0, apply->changeset.file_to_symlink[x][2], ".original");
      t_uchar * mod_patch_path = str_alloc_cat (0, apply->changeset.file_to_symlink[x][2], ".link-mod");
      t_uchar * mod_loc = apply->changeset.file_to_symlink[x][0];
      t_uchar * id = apply->changeset.file_to_symlink[x][1];
      t_uchar * target_loc = assoc_ref (apply->running.file_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?M  %s\n",mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

      target_path = file_name_in_vicinity (0, apply->target->root, target_loc);

      safe_lstat (target_path, &target_stat);

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_loc, &apply->running);

      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:

          conflict_filenames (apply->target->root, target_path, &orig_name, &rej_name, &apply->running);
          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);

          one_path_callback (apply, "Cch %s\n", target_loc);
          rel_add_records (&apply->report->conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (tmp_file_path);
            }

          if (patch_stat == 0)
            {
              one_path_callback (apply, "ch  %s\n", target_loc);
              rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);
            }
          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 (apply->changeset.file_metadata_changed); ++x)
    {
      t_uchar * target_path = 0;
      t_uchar * target_spew_loc = 0;
      t_uchar * target_spew_path = 0;
      struct stat target_stat;

      t_uchar * orig_patch_path = str_alloc_cat (0, apply->changeset.file_metadata_changed[x][2], ".meta-orig");
      t_uchar * mod_patch_path = str_alloc_cat (0, apply->changeset.file_metadata_changed[x][2], ".meta-mod");
      t_uchar * mod_loc = apply->changeset.file_metadata_changed[x][0];
      t_uchar * id = apply->changeset.file_metadata_changed[x][1];
      t_uchar * target_loc = assoc_ref (apply->running.file_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?-- %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

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

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_spew_loc, &apply->running);

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

          one_path_callback (apply, "C-- %s\n", target_loc);
          rel_add_records (&apply->report->metadata_conflict_files, rel_make_record (target_loc, id, 0), 0);

          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 = talloc_tmp_file_name (talloc_context, 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);
              talloc_free (target_path_tmp);
            }
          one_path_callback (apply, "--  %s\n", target_loc);
          rel_add_records (&apply->report->meta_modified_files, rel_make_record (target_loc, id, 0), 0);
        }

      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 (apply->changeset.dir_metadata_changed); ++x)
    {
      t_uchar * target_loc;
      t_uchar * target_path = 0;
      t_uchar * target_spew_loc = 0;
      t_uchar * target_spew_path = 0;
      struct stat target_stat;

      t_uchar * orig_patch_path = file_name_in_vicinity (0, apply->changeset.dir_metadata_changed[x][2], "=dir-meta-orig");
      t_uchar * mod_patch_path = file_name_in_vicinity (0, apply->changeset.dir_metadata_changed[x][2], "=dir-meta-mod");
      t_uchar * mod_loc = apply->changeset.dir_metadata_changed[x][0];
      t_uchar * id = apply->changeset.dir_metadata_changed[x][1];
      target_loc = assoc_ref (apply->running.dir_loc_of, id);

      if (!target_loc)
        {
          one_path_callback (apply, "?-/ %s\n", mod_loc);
          rel_add_records (&apply->report->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);
          continue;
        }

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

      preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, target_spew_loc, &apply->running);

      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);
          }
        one_path_callback (apply, "--/ %s\n", target_loc);
        rel_add_records (&apply->report->meta_modified_dirs, rel_make_record (target_loc, id, 0), 0);
      }

      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;
      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 = talloc_tmp_file_name (talloc_context, 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, apply->target, 0, 0, 0, 0, archive, version);
      safe_close (out_fd);
      safe_rename (target_tmp, target_path);

      one_path_callback (apply, "cl  %s\n", target_loc);
      rel_add_records (&apply->report->modified_files, rel_make_record (target_loc, id, 0), 0);

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


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

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

      target_path = file_name_in_vicinity (0, apply->target->root, apply->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);
    }

  report_sort_and_unique (apply->report);

  arch_tree_note_conflicts (apply->target, apply->report);

  arch_project_tree_mutated (apply->target);
  
  /****************************************************************
   * cleanup
   */

  lim_free (0, missing_patch_dir);
  arch_free_changeset_inventory_data (&inventory);
  
  rmrf_file (tmp_removed_files_root);
  rmrf_file (tmp_renamed_files_root);
  rmrf_file (tmp_shuffled_dirs_root);
  talloc_free (tmp_removed_files_root);

  rel_free_table (renamed_files_index);
  rel_free_table (present_renamed_files_index);
  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);
  rel_free_table (file_set_aside_with_dir_id);
  rel_free_table (dir_set_aside_with_dir_id);
  rel_free_table (install_dirs_plan);
  rel_free_table (added_files_and_symlinks);
  rel_free_table (patched_changelogs);
  result = talloc_steal (context, apply->report);
  talloc_free (apply);
  return result;
}




static void
invoke_apply_changeset_callback (apply_changeset_t * apply, t_uchar * fmt, ...)
{
  va_list ap;

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

  
void
one_path_callback (apply_changeset_t * apply, t_uchar * fmt, t_uchar *path)
{
  if (apply->callback)
    {
      t_uchar * escaped_path = pika_save_escape_iso8859_1 (0, 0, apply->escape_classes, path);
      invoke_apply_changeset_callback (apply, fmt, no_dot (escaped_path));
      lim_free (0, escaped_path);
    }
}


void
two_path_callback (apply_changeset_t * apply, t_uchar * fmt, t_uchar *path1, t_uchar *path2)
{
  if (apply->callback)
    {
      t_uchar * escaped_path1 = pika_save_escape_iso8859_1 (0, 0, apply->escape_classes, path1);
      t_uchar * escaped_path2 = pika_save_escape_iso8859_1 (0, 0, apply->escape_classes, path2);
      invoke_apply_changeset_callback (apply, fmt, no_dot (escaped_path1), no_dot (escaped_path2));
      lim_free (0, escaped_path1);
      lim_free (0, escaped_path2);
    }
}


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


void
preserve_old_patch_spew (t_uchar * dest_root, arch_project_tree_t * target, t_uchar * loc, struct running_inventory_assocs *inventory)
{
  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->root, loc);
  conflict_filenames (target->root, target_path, &orig_path, &rej_path, inventory);

  dest_path = file_name_in_vicinity (0, dest_root, loc);
  dest_dir = file_name_directory_file (0, dest_path);
  conflict_filenames (dest_root, dest_path, &orig_dest, &rej_dest, inventory);

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

/**
 * \brief detect whether a new file is going to overwrite an old one, and if so is the content different
 *
 */
static int
deferred_conflict (apply_changeset_t * apply, t_uchar * loc, t_uchar * orig_copy, struct running_inventory_assocs *inventory)
{
  t_uchar * path = 0;
  t_uchar * orig_path = 0;
  int conflict_detected;
  struct stat target_stat;
  int ign;

  path = file_name_in_vicinity (0, apply->target->root, loc);
  conflict_filenames (apply->target->root, path, &orig_path, NULL, inventory);
  conflict_detected = 0;

  if (!vu_lstat(&ign, path, &target_stat))
    {
      struct stat orig_stat;

      if (orig_copy)
        {
          safe_lstat (orig_copy, &orig_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 if (S_ISLNK (orig_stat.st_mode) && S_ISLNK (target_stat.st_mode) && !arch_symlinks_differ (orig_copy, path))
            {
              safe_unlink (path);
            }
          else
            goto conflict;
        }
      else
        {
        conflict:
          preserve_old_patch_spew (apply->removed_patch_conflict_files_path, apply->target, loc, inventory);
          rel_add_records (&apply->deferred_conflicts, rel_make_record (loc, 0), 0);
          talloc_steal (apply, ar_base (apply->deferred_conflicts));

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

/**
 * \brief figure out whats involved in installing a new file, and update the running inventory
 * FIXME (Hard): we need to figure out what to do with conflict adds to different paths
 * i.e. add BAR, when FOO exists, with the same id - the running inventory isn't sufficient
 * \return non-zero if there is a path-id conflict with the install
 */
static int
analyze_install (struct running_inventory_assocs * running,
                 int is_dir,
                 int is_rename,
                 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;
  int result = 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;

      if (!is_rename)
        {
          t_uchar *old_loc = assoc_ref (running->dir_loc_of, id);
          if (old_loc && str_cmp (old_loc, *install_loc))
            {
              debug (dbg_apply, 6, "conflict installing directory id '%s', old path: '%s', new path '%s'\n", id, old_loc, *install_loc);
              result = -1;
              goto done;
            }
        }

      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
    {
      if (!is_rename)
        {
          t_uchar *old_loc = assoc_ref (running->file_loc_of, id);
          if (old_loc && str_cmp (old_loc, *install_loc))
            {
              debug (dbg_apply, 6, "conflict installing file id '%s', old path: '%s', new path '%s'\n", id, old_loc, *install_loc);
              result = -1;
              goto done;
            }
        }

      assoc_set (&running->file_loc_of, id, *install_loc);
      assoc_set (&running->file_id_of, *install_loc, id);
    }

done:
  lim_free (0, basename);
  lim_free (0, loc_dir);
  return result;
}

/**
 * \brief ensure a directory exists, turning fiels in its place into conflicts
 * NOTE: this appears to ignore the desired id of the dir
 */
static void
ensure_directory_eliminating_conflicts (apply_changeset_t * apply,
                                        t_uchar * target_has_path,
                                        t_uchar * dir)
{
  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 (apply, target_has_path, dir_of_dir);

  /* dir exists, we're done */
  if (safe_file_is_directory (dir))
    {
      lim_free (0, dir_of_dir);
      return;
    }

  /* directory path is a file - make it a conflict */
  if (!safe_access (dir, F_OK))
    {
      t_uchar * loc;
      t_uchar * orig;

      loc = str_alloc_cat (0, "./", dir + str_length (apply->target->root) + 1);
      conflict_filenames (apply->target->root, dir, &orig, NULL, &apply->running);

      deferred_conflict (apply, loc, 0, &apply->running);
      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 occurred, 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;

      ar_push_uchar_star (&argv, cfg__gnu_diff3);

      ar_push_uchar_star (&argv, "-E");
      ar_push_uchar_star (&argv, "--merge");

      ar_push_uchar_star (&argv, "-L");
      ar_push_uchar_star (&argv, "TREE");

      ar_push_uchar_star (&argv, "-L");
      ar_push_uchar_star (&argv, "ANCESTOR");

      ar_push_uchar_star (&argv, "-L");
      ar_push_uchar_star (&argv, "MERGE-SOURCE");

      ar_push_uchar_star (&argv, mine_path);
      ar_push_uchar_star (&argv, older_path);
      ar_push_uchar_star (&argv, yours_path);

      ar_push_uchar_star (&argv, 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;

      ar_push_uchar_star (&argv, cfg__gnu_patch);

      if (forward_opt_to_patch)
        ar_push_uchar_star (&argv, "--forward");

      if (apply_in_reverse)
        ar_push_uchar_star (&argv, "--reverse");

      ar_push_uchar_star (&argv, "--binary");

      ar_push_uchar_star (&argv, "-f");
      ar_push_uchar_star (&argv, "-s");
      ar_push_uchar_star (&argv, "--posix");
      ar_push_uchar_star (&argv, "-i");
      ar_push_uchar_star (&argv, patch_path);
      ar_push_uchar_star (&argv, "-o");
      ar_push_uchar_star (&argv, tree_file_base);
      ar_push_uchar_star (&argv, original_inode_name);
      ar_push_uchar_star (&argv, 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;
}


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


/**
 * \brief Rename files listed in removed_files into a temp dir
 *
 * The changeset_report callback is invoked for each file removed, and
 * associated data is updated.
 * \param tree The tree that the files are being removed from and the temp location will be rooted in.
 * \param removed_files The files to move to a temp dir
 * \param r The changeset_report to write to
 * \param running The data to update
 * \param escape_classes The set of character to pika-escape in output
 * \return The path to the temp directory
 */
static t_uchar * 
rename_removed_files(void * context,
                     arch_project_tree_t * tree,
                     apply_changeset_t * apply,
                     struct running_inventory_assocs * running)
{
  int x;
  t_uchar * tmp_removed_files_root = talloc_tmp_file_name (context, tree->root, ",,tmp-removed-files");
  rmrf_file (tmp_removed_files_root);
  safe_mkdir (tmp_removed_files_root, 0777);

  for (x = 0; x < rel_n_records (apply->report->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;

      target_loc = apply->report->removed_files[x][0];
      target_id = apply->report->removed_files[x][1];
      target_path = file_name_in_vicinity (0, tree->root, target_loc);
      dest_path = file_name_in_vicinity (0, tmp_removed_files_root, target_loc);
      dest_dir = file_name_directory_file (0, dest_path);

      one_path_callback (apply, "D   %s\n", target_loc);
      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);
    }
  return tmp_removed_files_root;
}


/**
 * \brief Generate a table listing id and name of all ids listed in source and
 * target
 *
 * Both tables must be sorted by id and have id at col 1, file location at col 0
 * \param source A sorted table containing all ids to find
 * \param target A table containing a superset of all rows to print
 */
static rel_table 
find_target_equivalent (rel_table source, rel_table target)
{
  rel_table table = rel_join (-1, rel_join_output (2,0, 2,1, -1), 1, 1, source, target);
  rel_sort_table_by_field (0, table, 0);
  return table;
}


/**
 * \brief Generate a table listing id and name of all ids listed in source but
 * not in target
 *
 * Both tables must be sorted by id and have id at col 1, file path at col 0
 * The returned table will list 
 * \param source A sorted table containing all ids to find
 * \param target A table to compare to source
 * \return a table with id at col 0, col 1
 */
static rel_table 
find_target_missing(rel_table source, rel_table target)
{
  rel_table table = rel_join (1, rel_join_output (1,0, 1,1, -1), 1, 1, source, target);
  rel_sort_table_by_field (0, table, 0);
  return table;
}

/**
 * set *orig and *reject to appropriate paths, 
 * using ,.orig if .orig is in the inventory.
 */
void
conflict_filenames(t_uchar const * const output_root, t_uchar const *full_path, t_uchar **orig, t_uchar **reject, struct running_inventory_assocs *inventory)
{
    /* this may be a little inefficient, TODO optimise inventory mgmt */
    int use_alternate = 0;
    if (inventory)
      {
        /* this is 'basename foo + .orig' */
        t_uchar *tmp_orig = str_alloc_cat(0, full_path + 1 + str_length (output_root), ".orig");
        
        if (assoc_ref (inventory->dir_id_of, tmp_orig))
            use_alternate = -1;
        else if (assoc_ref (inventory->file_id_of, tmp_orig))
            use_alternate = -1;
        lim_free (0, tmp_orig);
      }
    if (use_alternate)
      {
        if (orig)
            *orig = str_alloc_cat (0, full_path, ",.orig");
        if (reject)
            *reject = str_alloc_cat (0, full_path, ",.rej");
      }
    else
      {
        if (orig)
            *orig = str_alloc_cat (0, full_path, ".orig");
        if (reject)
            *reject = str_alloc_cat (0, full_path, ".rej");
      }
}

void
apply_print_callback (void * vfd, char * fmt, va_list ap)
{
  int fd;

  fd = (int)(t_ulong)vfd;
  safe_printfmt_va_list (fd, fmt, ap);
  safe_flush (fd);
}

/** 
 * \brief identify removed and missing removed files
 * \param changeset the changeset we are applying
 * \param inventory the inventory of the changeset
 * \param r the application report we are creating
 */
void
identify_removed_and_missing_removed_files (struct arch_changeset_report * changeset,
                                            struct arch_changeset_inventory * inventory,
                                            struct arch_apply_changeset_report * r)
{
  rel_table 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 = find_target_equivalent (removed_files_index, inventory->files);
  r->missing_removed_files = find_target_missing(removed_files_index, inventory->files);
  rel_free_table (removed_files_index);
}

/**
 * \brief prety up the report for nice iteration etc
 * \param report the report
 */
void
report_sort_and_unique (struct arch_apply_changeset_report * r)
{
  /****************************************************************
   * Sort and Uniq Report Fields
   */

  rel_sort_table_by_field (0, r->removed_files, 0);
  rel_uniq_by_field (&r->removed_files, 0);
  talloc_steal (r, ar_base (r->removed_files));
  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);
}


void
read_changeset (apply_changeset_t * apply, t_uchar const * changeset_spec)
{
  t_uchar * changeset_path;
  safe_chdir (changeset_spec);
  changeset_path = safe_current_working_directory ();
  safe_fchdir (apply->here_fd);
  arch_changeset_report_init (&apply->changeset);
  arch_evaluate_changeset (&apply->changeset, changeset_path);
  lim_free (0, changeset_path);
}

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

  assoc_set (&apply->running.dir_id_of, ".", "?_.");
  assoc_set (&apply->running.dir_loc_of, "?_.", ".");
}


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


syntax highlighted by Code2HTML, v. 0.9.1