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