/* build-revision.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 "hackerlab/bugs/exception.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/mem/alloc-limits.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/copy-file.h"
#include "libfsutils/dir-listing.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libfsutils/link-tree.h"
#include "libfsutils/ensure-dir.h"
#include "libarch/ancestry.h"
#include "libarch/arch.h"
#include "libarch/namespace.h"
#include "libarch/libraries.h"
#include "libarch/project-tree.h"
#include "libarch/archive.h"
#include "libarch/archives.h"
#include "libarch/apply-changeset.h"
#include "libarch/pristines.h"
#include "libarch/invent.h"
#include "libarch/inode-sig.h"
#include "libarch/chatter.h"
#include "libarch/patch-logs.h"
#include "libarch/library-txn.h"
#include "libarch/build-revision.h"
#include "libarch/debug.h"



static void finalize_library_revision(t_uchar const * const dest_directory,
                               t_uchar * tmp_dir, 
                               arch_patch_id * patch_id);

static int
build_revision (int * patch_list_length,
                t_uchar const * const dest_dir,
                struct arch_archive * arch,
                arch_patch_id * const revision,
                arch_project_tree_t * cache,
                int try_link,
                t_uchar const * const non_sparse_lib);

static void
select_patch_list (ar_patch_id * patch_list, ar_patch_id candidate);

static ar_patch_id
make_patch_list (arch_patch_id * ancestor,
                 arch_patch_id * descendant,
		 assoc_table  contin_list,
		 struct arch_archive *arch);

static int 
patch_direction (ar_patch_id patch_list, arch_patch_id * const revision);

static arch_patch_id *
decrement_revision (struct arch_archive ** my_arch, 
                    arch_patch_id * const current_revision,
                    assoc_table contin_list,
                    struct arch_archive * arch);

static void
find_patchlists (ar_patch_id * loc_patches,
		 ar_patch_id * arch_patches,
                 arch_patch_id * patch_id,
		 struct arch_archive *arch,
		 assoc_table * contin_list,
		 arch_project_tree_t * cache);

static int
direct_descendant (struct arch_archive * arch, 
                   arch_patch_id *revision, 
                   arch_patch_id * candidate_descendant);

static arch_patch_id * 
find_previous_library (arch_patch_id * revision);
static arch_patch_id *
find_previous_pristine (struct arch_archive *arch, arch_patch_id * revision, arch_project_tree_t * cache);

static int
find_multisource (arch_patch_id ** lib_rev, 
                  arch_patch_id ** arch_rev, 
                  arch_patch_id ** continuation, 
                  struct arch_archive * arch, 
                  arch_patch_id * revision,
		  arch_project_tree_t * cache);

static arch_patch_id *
scan_archive (enum arch_revision_type *type,
              int * num_scanned,
              arch_patch_id ** newest_cached, 
	      struct arch_archive *arch, 
              arch_patch_id * start_revision, 
              arch_patch_id * end_revision,
              int max_count_after_cacherev);

static void 
find_prev_revision (assoc_table  * contin_list,
                    arch_patch_id ** local_rev, 
                    arch_patch_id ** arch_rev, 
                    struct arch_archive * arch, 
                    arch_patch_id * start_revision,
                    arch_project_tree_t * cache);

static arch_patch_id *
find_library_forwards (arch_patch_id * revision);

static int 
copy_local_revision (t_uchar const * const dest_dir,  
                     arch_patch_id * revision,
                     arch_project_tree_t * cache,
                     int try_link);

static int 
copy_library_revision (t_uchar const * const dest_dir, 
                       arch_patch_id * revision,
                       int try_link);

static int 
copy_pristine_revision (t_uchar const * const dest_dir,  
                        arch_patch_id * revision,
                        arch_project_tree_t * cache);

static void
move_tmp_revision (t_uchar const * const dest_dir, t_uchar const * const tmpdir);

static int 
copy_archive_revision(t_uchar const * const dest_dir,
                      struct arch_archive *arch,
                      arch_patch_id * revision);

static void 
copy_cached_revision (t_uchar const * const dest_dir, 
                      struct arch_archive * arch, 
                      arch_patch_id * revision);

static void
copy_import_revision (t_uchar const * const dest_dir, 
                      struct arch_archive * arch, 
                      arch_patch_id * const revision);

static arch_patch_id *
find_for_previous_revision (struct arch_archive ** prev_arch, 
                            assoc_table contin_list,
                            struct arch_archive * arch, 
                            arch_patch_id * const revision);

static void
apply_revision_patch (t_uchar const * const dest_tree_root, 
                      struct arch_archive * arch, 
                      arch_patch_id * const revision,
                      int reverse);

static void 
apply_revisions (t_uchar const * const dest_dir, 
                 struct arch_archive * arch, 
                 ar_patch_id patch_list,
                 int apply_direction,
                 t_uchar const * const non_sparse_lib);

static void
add_to_library (t_uchar const * const tree_dir, 
                t_uchar const * const library, 
                arch_patch_id * patch_id);

/* return non-0 if it came from a library.
 */
int
arch_build_revision (t_uchar const * const dest_dir,
                     struct arch_archive * arch,
                     t_uchar const * const archive, 
                     t_uchar const * const revision,
                     arch_project_tree_t * cache)
{
  struct arch_archive * my_arch = talloc_reference (NULL, arch);
  arch_patch_id * revision_patch = arch_patch_id_new_archive (archive, revision);
  int from_library = 0;
  int n_patches;
  if (!my_arch)
    my_arch = arch_archive_connect_branch(archive, NULL);
  
  invariant (my_arch != NULL);

  from_library = build_revision (&n_patches, dest_dir, my_arch, revision_patch, cache, 0, NULL) == 1;
  if (n_patches > 1)
    from_library = 0;
  talloc_free (my_arch);
  talloc_free (revision_patch);
  return from_library;
}

void
arch_build_library_revision(int no_patch_noise, 
                            t_uchar const * const dest_directory,
                            struct arch_archive * arch, 
                            arch_patch_id * patch_id,
                            t_uchar const * const opt_library, 
                            t_uchar const * const opt_same_dev_path,
                            int sparse,
                            arch_apply_changeset_report_callback callback,
                            int escape_classes)
{
  t_uchar * dest_dir_dir = 0;
  t_uchar * tmp_dir = 0;
  t_uchar * my_opt_library = str_save (0, opt_library);
  int n_patches;

  dest_dir_dir = file_name_directory_file (0, dest_directory);
  ensure_directory_exists (dest_dir_dir);
  tmp_dir = talloc_tmp_file_name (talloc_context, dest_dir_dir, ",,new-revision");  
 
  if (my_opt_library == NULL) 
  {
    my_opt_library = arch_library_add_choice (patch_id, my_opt_library, dest_dir_dir);
  }
 

  /* 
   * WARNING: tag patches are not reversible in older 
   * arch archives, so do not consider removing the cross-archive limit.
   */

  safe_mkdir (tmp_dir, 0777);
  build_revision (&n_patches, tmp_dir, arch, patch_id, 0, 1, (sparse) ? NULL : my_opt_library);

  finalize_library_revision (dest_directory, tmp_dir, patch_id);

  lim_free (0, dest_dir_dir);
  talloc_free (tmp_dir);
  lim_free (0, my_opt_library);
}


/* \brief Turn a temp dir containing a revision into a library revision
 *
 * Finish up adding ancillary files to the tmp-dir and then
 * install it in the library.
 *
 * \param dest_directory The directory to store in
 * \param tmp_dir The directory the revision is currently in
 * \param the revision identifier for the revision being produced
 */
void
finalize_library_revision(t_uchar const * const dest_directory,
                               t_uchar * tmp_dir, 
                               arch_patch_id * patch_id)
{
  t_uchar * index_by_name_path = 0;
  t_uchar * index_path = 0;
  int index_by_name_fd = -1;
  int index_fd = -1;
  arch_project_tree_t * tree;
  rel_table index = 0;

  arch_set_tree_version (tmp_dir, patch_id);
  
  tree = arch_project_tree_new (talloc_context, tmp_dir);

  index = arch_source_inventory (tree, 1, 0, 0);
  index_by_name_path = file_name_in_vicinity (0, tmp_dir, ",,index-by-name");
  index_path = file_name_in_vicinity (0, tmp_dir, ",,index");

  rmrf_file (index_by_name_path);
  rmrf_file (index_path);

  index_by_name_fd = safe_open (index_by_name_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
  rel_print_pika_escape_iso8859_1_table (index_by_name_fd, arch_escape_classes, index);
  safe_close (index_by_name_fd);

  rel_sort_table_by_field (0, index, 1);
  index_fd = safe_open (index_path, O_WRONLY | O_CREAT | O_EXCL, 0444);
  rel_print_pika_escape_iso8859_1_table (index_fd, arch_escape_classes, index);
  safe_close (index_fd);

  arch_snap_inode_sig (tree, patch_id);

  rel_free_table (index);
  lim_free (0, index_by_name_path);
  lim_free (0, index_path);
  arch_project_tree_delete (tree);
  safe_rename (tmp_dir, dest_directory);
}


/**
 * \brief Produce a revision, using any available resources
 * \param n_patches The number of patches that were applied is written here
 * \param dest_dir The directory to store the revision in.  It must not exist,
 * but its parent should.
 * \param arch (optional) an archive that may be useful for building the
 * revision
 * \param cache_dir A directory to look for pristine trees in
 * \param try_link If true, attempt to hard-link to a revision library
 * \param non_sparse_lib If non-NULL, a revision library to add intermediate
 * revisions to
 * \return 0 if no revision was built (but usually panics first) 
 * \return 1 if built by copying a revision library
 * \return 2 if built by hard-linking from a revision library
 * \return 3 if built from a pristine
 * \return 4 if built from an import revision
 * \return 5 if built from a cached revision
 *
 * The selection code is built on the following false assumptions:
 * 1 all patches are the same size
 * 2 cached trees take around as long to download as 16 patches
 * 3 throughput is the same for all archives
 * 
 * This is because we don't know the truth.  But
 * 1 on *average*, this is true (by definition).
 * 2 value reached by experimentation w/ a particular tree
 * 3 this a common case.
 */
static int
build_revision (int * n_patches,
                t_uchar const * const dest_dir,
                struct arch_archive * arch,
                arch_patch_id * const revision,
                arch_project_tree_t * cache,
                int try_link,
                t_uchar const * const non_sparse_lib)
{
  struct arch_archive * my_arch = talloc_reference (NULL, arch);
  int from_library = 0;
  int archive_revision_cost = 50;
  assoc_table contin_list = 0;

  ar_patch_id loc_patches = NULL;
  ar_patch_id arch_patches = NULL;
  ar_patch_id patch_list = NULL;
  int status = 0;
  arch_patch_id * my_patch;
  int n_arch_patches;

  find_patchlists (&loc_patches, &arch_patches, revision, arch, &contin_list, cache);

  if (ar_size (loc_patches) == 0 && ar_size (arch_patches) == 0)
    {
      panic ("The requested revision cannot be built.  It is based on archives that are not registered.");
    }
  
  patch_list = loc_patches;
  *n_patches = ar_size (patch_list);
  n_arch_patches = ar_size (arch_patches);
  if (*n_patches == 0 || (n_arch_patches > 0 && (*n_patches > n_arch_patches + archive_revision_cost)))
  {
    patch_list = arch_patches;
    ar_free_patch_id (&loc_patches);
    *n_patches = ar_size (patch_list);
  }
  else
  {
    /* choosing location based answer */
    ar_free_patch_id (&arch_patches);
  }

  if (patch_direction(patch_list, revision) == -1)
    my_patch = patch_list[*n_patches -1];
  else 
    my_patch = patch_list[0];
  if (patch_list == arch_patches)
  {
    struct arch_archive * tmp_arch;
    /* FIXME this should be tmp_arch = arch_archive_connect_branch
     * unconditionally - RBC 20050519
     */
    if (str_cmp (arch->official_name, arch_patch_id_archive (my_patch)))
      {
        tmp_arch = arch_archive_connect_branch (arch_patch_id_patch_id (my_patch), NULL);
        invariant (!!tmp_arch);
      }
    else
      tmp_arch = talloc_reference (NULL, arch);
      
    status = copy_archive_revision (dest_dir, tmp_arch, my_patch);
    talloc_free (tmp_arch);
  }
  else
  {
    status = copy_local_revision (dest_dir, my_patch, cache, try_link);
    invariant (status != 0);
    from_library = (status == 2);
  }
  
  talloc_free (my_arch);

  apply_revisions (dest_dir, arch, patch_list, patch_direction (patch_list, revision), non_sparse_lib);
  ar_free_patch_id (&patch_list);
    {
      arch_project_tree_t * tree;
      
      arch_set_tree_version (dest_dir, revision);

      tree = arch_project_tree_new (talloc_context, dest_dir);
      arch_snap_inode_sig (tree, revision);

      arch_project_tree_delete (tree);
    }
  
  return status;
}

static void
select_patch_list (ar_patch_id * patch_list, ar_patch_id candidate)
{
  int candidate_n = ar_size (candidate);
  int current_n = ar_size (*patch_list);
  if (candidate_n < 1) 
    {
      ar_free_patch_id (&candidate);
      return;
    }
  else if (current_n < 1)
    {
      ar_free_patch_id (patch_list);
      * patch_list = candidate;
    }
  else if (current_n > candidate_n)
    {
      ar_free_patch_id (patch_list);
      * patch_list = candidate;
    }
}

ar_patch_id
make_patch_list (arch_patch_id * ancestor,
                 arch_patch_id * descendant,
		 assoc_table contin_list,
		 struct arch_archive * arch)
{
  ar_patch_id patch_list = NULL;
  arch_patch_id * my_revision = talloc_reference (NULL, descendant);
  struct arch_archive * my_arch = talloc_reference (NULL, arch);

  while (arch_patch_id_cmp (my_revision, ancestor))
    {
      /* loop backwards from descendant until we reach ancestor
       * descendant is passed to decrement revisionas a check for archive closure I think
       */
      ar_push_patch_id (&patch_list, my_revision);
      talloc_reference (ar_base (patch_list), my_revision);
      if (!(my_revision = decrement_revision (&my_arch, my_revision, contin_list, arch)))
        ar_free_patch_id (&patch_list);
    }
  ar_push_patch_id (&patch_list, my_revision);
  talloc_reference (ar_base (patch_list), my_revision);
  talloc_free (my_revision);
  talloc_free (my_arch);

  /* TODO factor into ar_reverse .. */
  {
    int a;
    int b;

    a = 0;
    b = ar_size (patch_list) - 1;

    while (a < b)
    {
      arch_patch_id * tmp;

      tmp = patch_list[a];
      patch_list[a] = patch_list[b];
      patch_list[b] = tmp;

      ++a;
      --b;
    }
  }
  return patch_list;
}


static int 
patch_direction (ar_patch_id patch_list, arch_patch_id * const revision)
{
  int apply_direction = 1;
  if (ar_size (patch_list) > 0)
  {
    if (!arch_patch_id_cmp (revision , patch_list[0]))
      apply_direction = -1;
  }
  return apply_direction;
}

static arch_patch_id *
decrement_revision (struct arch_archive ** my_arch, 
                    arch_patch_id * const current_revision,
                    assoc_table contin_list,
                    struct arch_archive * arch)
{
  struct arch_archive *prev_arch = 0;
  arch_patch_id * prev_revision;

  if (!(prev_revision = find_for_previous_revision (&prev_arch, contin_list, *my_arch, current_revision)))
    return NULL;

  talloc_free (*my_arch);
  *my_arch = prev_arch;

  talloc_free (current_revision);
  return talloc_reference (NULL, prev_revision);
}

static void
find_patchlists (ar_patch_id * loc_patches,
		 ar_patch_id * arch_patches,
                 arch_patch_id * patch_id,
		 struct arch_archive * arch,
		 assoc_table * contin_list,
		 arch_project_tree_t * cache)
{
  arch_patch_id * lib_descendant_revision_patch;
  ar_patch_id loc_ancestor_patches = NULL;
  ar_patch_id descendant_patches = NULL;
  arch_patch_id * loc_ancestor = 0;
  arch_patch_id * arch_ancestor = NULL;
  debug (dbg_builder, 1, "* Scanning for full-tree revision: ");
  find_prev_revision (contin_list, &loc_ancestor, &arch_ancestor, arch, patch_id, cache);
  if (loc_ancestor)
    {
      loc_ancestor_patches = make_patch_list (loc_ancestor, patch_id, *contin_list, arch);
      talloc_free (loc_ancestor);
    }
  if (arch_ancestor)
      *arch_patches = make_patch_list (arch_ancestor, patch_id, *contin_list, arch);
  if ((lib_descendant_revision_patch = find_library_forwards (patch_id)))
    {
      if (direct_descendant (arch, patch_id, lib_descendant_revision_patch))
	descendant_patches = make_patch_list (patch_id, lib_descendant_revision_patch, *contin_list, arch);
      talloc_free (lib_descendant_revision_patch);
    }
  debug (dbg_builder, 1, "\n");
  
  select_patch_list (loc_patches, descendant_patches);
  select_patch_list (loc_patches, loc_ancestor_patches);
  talloc_free (arch_ancestor);
}

/**
 * \brief Determine whether a revision is a descedant of a candidate revision
 * \note Because of in-version tags or imports, a namespace-subsequent revision
 * may not be a descendant.
 * \param arch An archive that may be useful.  May not be NULL
 * \param revision The possible ancestor
 * \param candidate_descendant The possible descendant
 */
static int
direct_descendant (struct arch_archive * arch, 
                   arch_patch_id *revision, 
                   arch_patch_id * candidate_descendant)
{
  enum arch_revision_type type;
  int num_scanned;
  arch_patch_id * last_revision = scan_archive (&type, &num_scanned, NULL, arch, candidate_descendant, revision, -1);
  int answer = arch_patch_id_cmp (last_revision, revision) == 0;
  invariant_str_cmp (arch_patch_id_archive (revision), arch->official_name);
  invariant_str_cmp (arch_patch_id_archive (candidate_descendant), arch->official_name);
  talloc_free (last_revision);
  return answer;
}


/*
 * Note that tag-based branches, the previous revision is not an *ancestor*
 */
static arch_patch_id * 
find_previous_library (arch_patch_id * revision)
{
  int r;
  rel_table revisions = arch_library_revisions (arch_patch_id_archive (revision), arch_patch_id_version (revision), 0);
  arch_patch_id * found_revision = NULL;
  
  for (r = rel_n_records (revisions)-1; r >=0;  r--)
    {
      if (arch_cmp_revision (revisions[r][0], arch_patch_id_patchlevel (revision)) <= 0)
	{
          t_uchar * temp = str_alloc_cat_many (0, arch_patch_id_version (revision), "--", revisions[r][0], str_end);
          found_revision = arch_patch_id_new_archive (arch_patch_id_archive (revision), temp);
          debug (dbg_builder, 3, "* found previous library revision %s\n", arch_patch_id_patch_id (found_revision));
          lim_free (0, temp);
	  break;
	}
    }
  rel_free_table(revisions);
  return found_revision;
}


/*
 * Note that tag-based branches, the previous revision is not an *ancestor*
 */
static arch_patch_id *
find_previous_pristine (struct arch_archive *arch, arch_patch_id * revision, arch_project_tree_t * cache)
{
  arch_patch_id * my_revision;

  if (!cache)
      return NULL;
  
  my_revision = talloc_reference (NULL, revision);
  while (my_revision)
    {
      arch_patch_id * prev_revision;
      /****************************************************************
       * Try the pristine cache.
       */
      arch_project_tree_t * pristine_copy = arch_find_pristine (cache, my_revision, arch_cache_dir_pristine_search);
      if (pristine_copy)
	{
	  arch_project_tree_delete (pristine_copy);
          return my_revision;
	}

      /********************************
       * Find the previous revision
       */
      prev_revision = arch_previous_revision (arch, my_revision);
      talloc_free (my_revision);
      my_revision = prev_revision;
    }

  return NULL;
}


/**
 * \brief Finds a ancestor or descendant full trees for a revision
 * \param lib_rev library revisions
 */
static int
find_multisource (arch_patch_id ** lib_rev, 
                  arch_patch_id ** arch_rev, 
                  arch_patch_id ** continuation, 
                  struct arch_archive * arch, 
                  arch_patch_id * revision,
                  arch_project_tree_t * cache)
{
  int i = 0;
  arch_patch_id * candidate_revision;
  arch_patch_id * pristine_candidate;
  arch_patch_id * last_revision = NULL;
  arch_patch_id * newest_cached = NULL;
  arch_patch_id ** newest_cached_ptr = (arch_rev != NULL) ? &newest_cached : NULL;
  enum arch_revision_type type;
  debug (dbg_builder, 3, "* checking for %s/%s or earlier\n", arch->official_name, revision); 

  if (arch_rev && *arch_rev)
    arch_rev = 0;
  candidate_revision = find_previous_library (revision);
  pristine_candidate = find_previous_pristine (arch, revision, cache);
  if (pristine_candidate)
  {
    int use_pristine = 0;
    debug (dbg_builder, 3, "* found candidate pristine %s\n", arch_patch_id_patch_id (pristine_candidate)); 

    if (!candidate_revision ||
         /* should this be arch_patch_id_cmp () < 0 ? RBC 20050522 */
	(arch_cmp_revision (arch_patch_id_patchlevel (candidate_revision), arch_patch_id_patchlevel (pristine_candidate)) < 0))
	  use_pristine = 1;
      {
	talloc_free (candidate_revision);
	candidate_revision = talloc_reference (NULL, pristine_candidate);
      }
   talloc_free (pristine_candidate);
  }

  last_revision = scan_archive (&type, &i, newest_cached_ptr,
                                arch, revision, candidate_revision, 50);

  if (arch_rev)
    {
      if (newest_cached)
        *arch_rev = newest_cached;
      else if (type == arch_import_revision)
        *arch_rev = talloc_reference (NULL, last_revision);
      if (*arch_rev)
        debug (dbg_builder, 3, "* found archive revision %s\n", arch_patch_id_patch_id (*arch_rev));
    }

  if (candidate_revision && !arch_patch_id_cmp (candidate_revision, last_revision))
    *lib_rev = talloc_reference (NULL, candidate_revision);
  else
    lib_rev = NULL;

  if (type == arch_continuation_revision)
    *continuation = talloc_reference (NULL, last_revision);

  talloc_free (candidate_revision);
  talloc_free (last_revision);
  return i;
}


static void 
find_prev_revision (assoc_table  * contin_list,
                    arch_patch_id ** local_rev, 
                    arch_patch_id ** arch_rev, 
                    struct arch_archive * arch, 
                    arch_patch_id * start_revision,
                    arch_project_tree_t * cache)
{
  struct arch_archive * my_arch = talloc_reference (NULL, arch);
  int patch_n = 0;
  int arch_rev_n = -1;
  arch_patch_id * my_start_revision = talloc_reference (NULL, start_revision);
  *arch_rev = NULL;
  while (1)
    {
      arch_patch_id * prev_revision = 0;
      arch_patch_id * continuation = NULL;
      patch_n += find_multisource (local_rev, *arch_rev ? NULL : arch_rev, &continuation, my_arch, my_start_revision, cache);
      talloc_free (my_start_revision);
      my_start_revision = NULL;
      /* BUG FOR MULTIPLE CACHEREVS */
      if (*arch_rev && arch_rev_n == -1)
      {
	ar_patch_id arch_rev_patches = make_patch_list (*arch_rev, start_revision, *contin_list, arch);
	arch_rev_n = ar_size (arch_rev_patches) - 1;
      }
      if (continuation)
	{
	  prev_revision = arch_get_continuation (my_arch, continuation);
	  if (!arch_valid_package_name (arch_patch_id_patch_id (prev_revision), arch_req_archive, arch_req_patch_level, 0))
	    {
	      safe_printfmt (2, "arch_build_revision: archive contains bogus CONTINUATION file\n  localtion: %s\n  revision: %s\n bogosity: %s\n",
                             arch->location,
			     arch_patch_id_patch_id (continuation), arch_patch_id_patch_id (prev_revision));
	      exit (2);
	    }
	  assoc_set (contin_list, arch_patch_id_patch_id (continuation), arch_patch_id_patch_id (prev_revision) );
	  talloc_free (continuation);
	}

      if (*local_rev)
	  return;

      if (arch_rev_n >= 0 && arch_rev_n + 50 < patch_n)
	return;

      if (prev_revision)
	{
	  my_start_revision = arch_patch_id_copy (prev_revision);
	  if ( str_cmp (my_arch->official_name, arch_patch_id_archive (prev_revision)) )
	    {
              talloc_free (my_arch);
	      my_arch = arch_archive_connect_readonly (arch_patch_id_archive (prev_revision));
	    }
	  if (!my_arch)
	    debug (dbg_builder, 3, "* archive %s is not registered or is inaccessible\n", arch_patch_id_archive (prev_revision));
	}
      if (!my_arch || !my_start_revision)
	break;
    }
  talloc_free (my_arch);
}


arch_patch_id *
scan_archive(enum arch_revision_type * type,
             int * num_scanned,
             arch_patch_id ** newest_cached, 
	     struct arch_archive *arch, 
             arch_patch_id * start_revision,
             arch_patch_id * end_revision,
             int max_count_after_cacherev)
{
  int count_after_cacherev = 0;
  arch_patch_id * my_patch = arch_patch_id_copy (start_revision);
  debug (dbg_builder, 6, "scanning from '%s'\n", arch_patch_id_patch_id (my_patch));

  if (newest_cached != NULL)
    *newest_cached = NULL;

  for (*num_scanned = 0; count_after_cacherev < max_count_after_cacherev || max_count_after_cacherev == -1; ++*num_scanned)
    {
      int is_cached = 0;
      int * is_cached_ptr = &is_cached;
      arch_patch_id * prev_revision = NULL;
      if (newest_cached == NULL || *newest_cached != NULL)
        {
          is_cached_ptr = NULL;
          count_after_cacherev ++;
        }
      
      arch_revision_type (type, is_cached_ptr, NULL, arch, my_patch);
      debug (dbg_builder, 1, ".");
      if (is_cached)
        {
          if (newest_cached != NULL && *newest_cached == 0)
            *newest_cached = arch_patch_id_copy (my_patch);
        }
      if (*type != arch_simple_revision)
        break;
      
      if (end_revision && !arch_patch_id_cmp (my_patch, end_revision))
        break;
      prev_revision = arch_previous_revision (arch, my_patch);
      /* simple revisions are always in the same archive namespace */
      if (prev_revision)
        arch_ancestry_note_ancestor (my_patch, arch_patch_id_patch_id (prev_revision));
      talloc_free (my_patch);
      my_patch = prev_revision;
    }
  return my_patch;
}

/**
 * \brief find a revision library entry forwards from revision
 * \return a patch_id or NULL if nothing was found. 
 */
arch_patch_id *
find_library_forwards (arch_patch_id * revision)
{
  int r;
  arch_patch_id * answer = NULL;
  rel_table revisions = arch_library_revisions (arch_patch_id_archive (revision), arch_patch_id_version (revision), 0);
  
  debug (dbg_builder, 3, "* checking libraries for  %s or later\n", arch_patch_id_patch_id (revision)); 
  for (r = 0; r < rel_n_records (revisions);  r++ )
    {
      if (arch_cmp_revision (revisions[r][0], arch_patch_id_patchlevel (revision)) >= 0)
	{
          t_uchar * f_revision = str_alloc_cat_many (0, arch_patch_id_branch (revision), "--", revisions[r][0], str_end);
	  answer = arch_patch_id_new (f_revision);
          lim_free (0, f_revision);
          debug (dbg_builder, 3, "* found descendant revision %s\n", arch_patch_id_patch_id (answer));
	  break;
	}
    }

  rel_free_table(revisions);

  return answer;
}


static int 
copy_local_revision (t_uchar const * const dest_dir,  
                     arch_patch_id * revision,
                     arch_project_tree_t * cache,
                     int try_link)
{
  if (copy_pristine_revision (dest_dir, revision, cache))
    return 3;
  else 
    return copy_library_revision (dest_dir, revision, try_link);
  return 0; 
}


static int 
copy_library_revision (t_uchar const * const dest_dir, 
                       arch_patch_id * revision, 
                       int try_link)
{
  rel_table index_by_name = 0;
  int status = 0;
  arch_project_tree_t * library_rev_tree;
  
  library_rev_tree = arch_library_find (0, revision, 1);
  if (!library_rev_tree) return status;
  if (try_link)
    {
      struct stat from, to;
      safe_stat (library_rev_tree->root, &from);
      safe_stat (dest_dir, &to);
      if (from.st_dev == to.st_dev)
	{
	  debug (dbg_builder, 1, "* from revision library (linking): %s\n", arch_patch_id_patch_id (revision));
	  safe_rmdir (dest_dir);
	  build_link_tree (library_rev_tree->root, dest_dir);
	  status = 2;
	  goto link_done;
	}
    }
  debug (dbg_builder, 1, "* from revision library: %s\n", arch_patch_id_patch_id (revision));

  index_by_name = arch_library_index (revision);
  rel_sort_table_by_field (0, index_by_name, 0);
  copy_file_list (dest_dir, library_rev_tree->root, index_by_name);

  rel_free_table (index_by_name);
  status = 1;

  link_done:
  arch_project_tree_delete (library_rev_tree);
  return status;
}


static int 
copy_pristine_revision (t_uchar const * const dest_dir,  
                        arch_patch_id * revision,
                        arch_project_tree_t * cache)
{
  arch_project_tree_t * pristine_copy;
  rel_table file_list = 0;
  if (!cache)
      return 0;
  pristine_copy = arch_find_pristine (cache, revision, arch_cache_dir_pristine_search);

  if (!pristine_copy)
      return 0;

  debug (dbg_builder, 1, "* from pristine cache: %s\n", arch_patch_id_patch_id (revision));

  file_list = arch_source_inventory (pristine_copy, 1, 0, 0);
  copy_file_list (dest_dir, pristine_copy->root, file_list);

  rel_free_table (file_list);
  arch_project_tree_delete(pristine_copy);

  return 1;
}

static void
move_tmp_revision (t_uchar const * const dest_dir, t_uchar const * const tmpdir)
{
  int x;
  rel_table files = directory_files (tmpdir);

  for (x = 0; x < rel_n_records (files); ++x)
    {
      if (str_cmp (".", files[x][0]) && str_cmp ("..", files[x][0]))
	{
	  t_uchar * frm = 0;
	  t_uchar * to = 0;

	  frm = file_name_in_vicinity (0, tmpdir, files[x][0]);
	  to = file_name_in_vicinity (0, dest_dir, files[x][0]);

	  safe_rename (frm, to);

	  lim_free (0, frm);
	  lim_free (0, to);
	}
    }

  safe_rmdir (tmpdir);
  rel_free_table (files);
}


static int 
copy_archive_revision(t_uchar const * const dest_dir,
                      struct arch_archive *arch,
                      arch_patch_id * revision)
{
  enum arch_revision_type type;
  int is_cached;
  arch_revision_type (&type, &is_cached, NULL, arch, revision);
  
  /********************************
   * Archive Cached Revision?
   */
  if (is_cached)
  {
    copy_cached_revision (dest_dir, arch, revision);
    /* ensure that inode sigs from the archive are ignored */
    arch_clear_inode_sigs (dest_dir);
    return 5;
  }

  /********************************
   * Import Revision?
   */
  else if (type == arch_import_revision)
  {
    copy_import_revision (dest_dir, arch, revision);
    /* ensure that inode sigs from the archive are ignored */
    arch_clear_inode_sigs (dest_dir);
    return 4;
  }
  
  else return 0;
}


static void
copy_cached_revision(t_uchar const * const dest_dir, 
		     struct arch_archive * arch, 
		     arch_patch_id * revision)
{
  t_uchar * tmpdir = 0;
  debug (dbg_builder, 1, "* from archive cached: %s\n", arch_patch_id_patch_id (revision));

  tmpdir = talloc_tmp_file_name (talloc_context, dest_dir, ",,cached-revision");
  arch_get_cached_revision (arch, revision, tmpdir);
  move_tmp_revision (dest_dir, tmpdir);
  talloc_free (tmpdir);
}


static void 
copy_import_revision (t_uchar const * const dest_dir, 
                      struct arch_archive * arch, 
                      arch_patch_id * const revision)
{
  t_uchar * tmpdir = 0;

  debug (dbg_builder, 1, "* from import revision: %s\n", arch_patch_id_patch_id (revision));

  tmpdir = talloc_tmp_file_name (talloc_context, dest_dir, ",,import-revision");
  arch_get_import_revision (arch, revision, tmpdir);
  move_tmp_revision (dest_dir, tmpdir);
  talloc_free (tmpdir);
}

/**
 * \brief find the predecessor to revision, and return it with an archive connection
 * \param prev_arch the archive handle to set to an archive connection. after use it should be closed
 * \return NULL if there is no predecessor or the archive for it is unable to be connected to
 */
static arch_patch_id *
find_for_previous_revision (struct arch_archive ** prev_arch, 
                            assoc_table contin_list,
                            struct arch_archive * arch, 
                            arch_patch_id * const revision)
{
  arch_patch_id * prev_revision;
  t_uchar * fq_prev_revision = assoc_ref (contin_list, arch_patch_id_patch_id (revision));
  if (!fq_prev_revision)
    {
      prev_revision = arch_previous_revision (arch, revision);
      if (!prev_revision)
        return NULL;
      *prev_arch = talloc_reference (NULL, arch);
      return prev_revision;
    }
  else
    {
      if (!arch_valid_package_name (fq_prev_revision, arch_req_archive, arch_req_patch_level, 0))
	{
	  safe_printfmt (2, "arch_build_revision: continuation list contains bogus entry\n  revision: %s\n bogosity: %s\n",
			 arch_patch_id_patch_id (revision), fq_prev_revision);
	  exit (2);
	}
      prev_revision = arch_patch_id_new (fq_prev_revision);
      *prev_arch = arch_archive_connect_readonly (arch_patch_id_archive (prev_revision));
      if (*prev_arch == NULL)
        {
          /* cannot connect */
          talloc_free (prev_revision);
          return NULL;
        }
      return prev_revision;
    }
}


static void
apply_revision_patch (t_uchar const * const dest_tree_root, 
		      struct arch_archive * arch, 
		      arch_patch_id * const revision,
		      int reverse)
{
  t_uchar * tmppatch = 0;
  struct arch_apply_changeset_report * r;
  arch_project_tree_t * dest_tree;
  
  dest_tree = arch_project_tree_new (talloc_context, dest_tree_root);
  tmppatch = talloc_tmp_file_name (talloc_context, dest_tree->root, ",,next-patch");
  arch_get_patch (arch, revision, tmppatch);

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

  r = arch_apply_changeset (tmppatch, talloc_context, dest_tree, arch_unspecified_id_tagging, arch_inventory_unrecognized, reverse, 0, 0, NULL, NULL);

  if (arch_conflicts_occurred (r))
    panic ("conflict applying patch in arch_build_revision");

  talloc_free (r);

  rmrf_file (tmppatch);
  talloc_free (tmppatch);
  arch_project_tree_delete (dest_tree);
}


/**
 * \brief Apply the changesets for listed revisions to the specified directory
 * 
 * The specified directory must contain a tree which can be patched into
 * the right revision.  If going forwards, it must be the first-listed revision.
 * If going backwards, it must be the last-listed revision.
 *
 * The revision list is actually a list of deltas: for each revision n, the
 * delta between patch_list[n-1] and patchlist[n] will be applied.  However,
 * this implementation requires that n is a direct descendant of n-1.
 *
 * \param dest_dir Directory to apply changesets to
 * \param arch An archive which may be useful for this.  If it is not useful,
 * it will be ignored.  It may also be NULL.
 * \param patch_list The list of deltas to apply, archive in column 0,
 * revision in 1.
 * \param apply_direction +1 to apply from 0 to the end, -1 to apply from end
 * to 0
 * \param non_sparse_lib If non-NULL, a non-sparse library for revisions
 */
static void 
apply_revisions (t_uchar const * const dest_dir, 
		 struct arch_archive * arch, 
		 ar_patch_id patch_list,
		 int apply_direction,
                 t_uchar const * non_sparse_lib)
{
  int patch_index;
  struct arch_archive * my_arch = talloc_reference (NULL, arch);
  int reverse = (apply_direction == 1) ? 0 : 1;
  int start;
  int end;
  t_uchar * reverse_text = (apply_direction < 0)? " (in reverse)" : "";

  if (apply_direction == 1)
  {
     start = 1;
     end = ar_size (patch_list);
  }
  else
  {
     start = ar_size (patch_list) - 1;
     end = 0;
  }
  if (start != end)
    debug (dbg_builder, 1, "* Applying %d revisions%s: ", ar_size (patch_list) - 1, reverse_text);
  for (patch_index=start; patch_index != end; patch_index +=apply_direction)
  {
    /* non-sparse adding should not be done for the final target revision */
    if (patch_index+apply_direction == end)
      non_sparse_lib = NULL;
    if (my_arch == NULL || str_cmp(my_arch->official_name, arch_patch_id_archive (patch_list[patch_index])))
      {
	talloc_free (my_arch);
	my_arch = arch_archive_connect_readonly (arch_patch_id_archive (patch_list[patch_index]));
        if (!my_arch)
          {
            safe_printfmt (2, "Unable to connect to archive for patch %s\n", arch_patch_id_patch_id (patch_list[patch_index]));
            exit (2);
          }
        invariant_str_cmp (my_arch->official_name, arch_patch_id_archive (patch_list[patch_index]));
      }

    debug (dbg_builder, 1, ".");    
    apply_revision_patch (dest_dir, my_arch, patch_list[patch_index], reverse);
    if (non_sparse_lib != NULL)
      {
        arch_patch_id * patch_id; 
        if (reverse)
          patch_id = patch_list[patch_index - 1];
        else
          patch_id = patch_list[patch_index];
        add_to_library (dest_dir, non_sparse_lib, patch_id);
      }
  }
  if (start != end)
    debug (dbg_builder, 1, "\n");

  talloc_free (my_arch);
}

/**
 * \brief Add a reference tree to the library.  
 *
 * The tree is hard-linked, not copied or moved.  Assumed to be on the same 
 * filesystem as the library.
 * \param tree_dir The directory containing the reference tree
 * \param library The library to add the the revision to
 * \param patch_id the revision id of the revision being added
 */
void
add_to_library (t_uchar const * const tree_dir,
                t_uchar const * const library, 
                arch_patch_id * patch_id)
{
  t_uchar * tmp_lib = str_save (0, library);
  t_uchar * dest_directory = arch_library_revision_dir_in_lib (tmp_lib, patch_id);
  t_uchar * dest_dir_dir = file_name_directory_file (0, dest_directory);
  t_uchar * tmp_dir = talloc_tmp_file_name (talloc_context, dest_dir_dir, ",,new-revision");  
  
  ensure_directory_exists (dest_dir_dir);
  build_link_tree(tree_dir, tmp_dir);
  finalize_library_revision (dest_directory, tmp_dir, patch_id);
  
  talloc_free (tmp_dir);
  lim_free (0, dest_dir_dir);
  lim_free (0, dest_directory);
  lim_free (0, tmp_lib);
}




/* tag: Tom Lord Wed May 21 15:59:22 2003 (build-revision.c)
 */


syntax highlighted by Code2HTML, v. 0.9.1