/* ancestry.c:
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2003 Tom Lord
* Copyright (C) 2005 Canonical Limited
* Authors: Robert Collins <robert.collins@canonical.com>
*
* 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/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/os/errno.h"
#include "hackerlab/os/sys/wait.h"
#include "hackerlab/vu/safe.h"
#include "libawk/associative.h"
#include "libawk/trim.h"
#include "libfsutils/file-contents.h"
#include "libfsutils/tmp-files.h"
#include "libarch/archives.h"
#include "libarch/arch-cache.h"
#include "libarch/cached-archive.h"
#include "libarch/libraries.h"
#include "libarch/namespace.h"
#include "libarch/merge-points.h"
#include "libarch/ancestry.h"
static arch_patch_id * arch_ancestry_from_revision (void * context, arch_patch_id * patch_id, int soft_errors);
rel_table
arch_trace_ancestry (void * context, struct arch_archive * arch, arch_patch_id * starting_patch_id, int merges)
{
rel_table answer = 0;
int done = 0;
arch_archive_connection_cache cache;
arch_patch_id * patch_id = arch_patch_id_copy (starting_patch_id);
arch_archive_connection_cache_init (&cache);
if (arch)
arch_archive_connection_cache_add (&cache, arch);
while (!done)
{
struct arch_archive * this_arch = 0;
this_arch = arch_archive_connection_cache_find_or_maybe_connect (&cache, arch_patch_id_archive (patch_id), 0);
if (!this_arch)
{
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id(patch_id), "???", 0), 0);
done = 1;
}
else
{
arch_patch_id * ancestor = NULL;
if (!merges)
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id(patch_id), arch_patch_id_patch_id(patch_id), 0), 0);
else
{
enum arch_revision_type type;
/* FIXME: grab ancestry here if it exists */
arch_revision_type (&type, 0, NULL, this_arch, patch_id);
if (type == arch_continuation_revision)
{
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id(patch_id), arch_patch_id_patch_id(patch_id), 0), 0);
}
else
{
rel_table merges = 0;
int x;
merges = arch_archive_merge_points (this_arch, arch_patch_id_revision (patch_id), 0, 0, 0);
for (x = 0; x < rel_n_records (merges); ++x)
{
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id(patch_id), merges[x][1], 0), 0);
}
rel_free_table (merges);
}
}
ancestor = arch_patch_ancestor (talloc_context, patch_id, 0);
if (!ancestor)
{
done = 1;
}
else
{
talloc_free (patch_id);
patch_id = ancestor;
}
}
}
arch_archive_connection_cache_finalise (&cache, arch);
talloc_steal (context, ar_base(answer));
return answer;
}
static int
ancestry_parse_baz_1_is_patch_id (t_uchar const *record)
{
if (!str_cmp_prefix (ARCH_ANCESTRY_PATCH_PREFIX, record ))
return -1;
if (!str_cmp (ARCH_ANCESTRY_PATCH_PARTIAL, record ))
return -1;
return 0;
}
static t_uchar *
ancestry_parse_baz_1_patch_id (t_uchar const *record)
{
if (!str_cmp_prefix (ARCH_ANCESTRY_PATCH_PREFIX, record ))
return str_save (0, record + str_length (ARCH_ANCESTRY_PATCH_PREFIX));
if (!str_cmp (ARCH_ANCESTRY_PATCH_PARTIAL, record ))
return str_save (0, record);
return NULL;
}
static rel_table
ancestry_parse_baz_1 (rel_table raw, int start_from)
{
rel_table result = NULL;
int current_line;
for (current_line = start_from; current_line < rel_n_records(raw); ++current_line)
{
if (ancestry_parse_baz_1_is_patch_id (raw[current_line][0]))
{
t_uchar *patch_id = ancestry_parse_baz_1_patch_id (raw[current_line][0]);
rel_add_records(&result, rel_make_record (patch_id, 0), 0);
lim_free (0, patch_id);
}
}
return result;
}
static rel_table
ancestry_parse(t_uchar const *content)
{
/* TODO: if it looks like its gpg, run it through gpg */
rel_table temp_table = rel_nl_split ((t_uchar *)content);
rel_table answer = NULL;
int start_record;
for (start_record = 0; start_record < rel_n_records (temp_table); ++start_record)
if (!str_cmp(ARCH_ANCESTRY_VERSION_BAZ_1, temp_table[start_record][0]))
{
answer = ancestry_parse_baz_1 (temp_table, start_record + 1);
break;
}
rel_free_table (temp_table);
return answer;
}
static rel_table
partial_ancestry_from_full(rel_table full, arch_patch_id * patch_id)
{
rel_table result = NULL;
int row = 0;
while (row < rel_n_records (full) && str_cmp (arch_patch_id_patch_id (patch_id), full[row][0]))
++row;
for (; row < rel_n_records (full); ++row)
{
rel_add_records (&result, rel_copy_record (full[row]), 0);
}
return result;
}
static assoc_table arch_ancestors = NULL;
static arch_archive_connection_cache global_cache = { NULL };
/**
* \brief get a cached patch ancestor.
* \throw ENOENT if there is no cached entry
* NULL for terminal revisions
* a patch id for a specific ancestor.
*/
static arch_patch_id *
arch_cached_patch_ancestor (void * context, arch_patch_id * patch_id)
{
t_uchar *result = assoc_ref (arch_ancestors, arch_patch_id_patch_id (patch_id));
invariant (str_cmp (arch_patch_id_patch_id (patch_id), ARCH_ANCESTRY_PATCH_PARTIAL));
if (result && str_cmp (result, ARCH_ANCESTRY_PATCH_PARTIAL))
{
if (!str_length (result))
{
/* penultimate ancestor - "" in the hash */
return NULL;
}
return talloc_steal (context, arch_patch_id_new (result));
}
/* memory cache miss */
{
t_uchar * ancestor_query = arch_cache_query_new (patch_id, "ancestor");
t_uchar * temp_result = NULL;
if (ancestor_query && arch_cache_has_answer (ancestor_query))
{
temp_result = arch_cache_get_line (ancestor_query);
invariant (temp_result != NULL);
result = trim_surrounding_ws (temp_result);
}
lim_free (0, ancestor_query);
if (!result || !str_cmp (result, ARCH_ANCESTRY_PATCH_PARTIAL))
{
lim_free (0, temp_result);
/* not there either, return partial */
Throw (exception (ENOENT, "No cache entry"));
}
else
{
arch_patch_id * answer;
if (!str_length (result))
answer = NULL;
else
answer = arch_patch_id_new (result);
lim_free (0, temp_result);
return talloc_steal (context, answer);
}
}
}
/**
* \brief find the ancestory for patch_id
*
* find the ancestor for patch_id from:
* - known ancestry data
* - the archive
*
* maintains the in-core ancestry cache.
* \throw ENOENT if the ancestor cannot be found, or any unexpected child
* function exceptions raised during its operation.
* \param context the talloc context to put results in
* \param patch_id the patch to get the ancestor of
* \param cached_only - only look in the baz cache.
* \return the patch id. NULL is returned if there is no ancestor.
*/
arch_patch_id *
arch_patch_ancestor (void * context, arch_patch_id * patch_id, int cached_only)
{
/* cache lookup */
arch_patch_id * answer = NULL;
t_uchar *result = NULL;
struct arch_archive *arch;
int found = -1;
struct exception * e;
Try
{
answer = arch_cached_patch_ancestor (context, patch_id);
found = 1;
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
if (cached_only)
Throw (exception (ENOENT, "Could not retrieve ancestor"));
found = 0;
}
if (found)
return talloc_steal (context, answer);
/* cache miss */
Try
{
answer = arch_ancestry_from_revision (context, patch_id, 1);
found = 1;
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
found = 0;
}
if (found)
return talloc_steal (context, answer);
/* old api .. */
arch = arch_archive_connection_cache_find_or_maybe_connect (&global_cache, arch_patch_id_archive (patch_id), 1);
if (arch)
{
/* FIXME: teach arch_ancestor_revision about soft errors */
result = arch_ancestor_revision (arch, arch_patch_id_revision (patch_id));
if (!result)
{
/* penultimate ancestor */
arch_ancestry_note_ancestor (patch_id, "");
return NULL;
}
else /* when ancestor_revision does soft errors..
if (str_cmp (result, ARCH_ANCESTRY_PATCH_PARTIAL)) */
{
arch_ancestry_note_ancestor (patch_id, result);
answer = talloc_steal (context, arch_patch_id_new (result));
lim_free (0, result);
return answer;
}
}
Throw (exception (ENOENT, "Could not retrieve ancestor"));
}
/* we've observed the ancestor patch_id:
* "" for patch_id is the import rev.
* ARCH_ANCESTRY_PATCH_PARTIAL for we are noting that we couldn't find the ancestor
* $other_patch_id for a specific ancestor
*/
void
arch_ancestry_note_ancestor (arch_patch_id * patch_id, t_uchar const *ancestor)
{
struct exception * e;
invariant (ancestor != NULL);
invariant (str_cmp (arch_patch_id_patch_id (patch_id), ARCH_ANCESTRY_PATCH_PARTIAL));
Try
arch_cached_patch_ancestor (talloc_context, patch_id);
Catch (e)
{
t_uchar * ancestor_query;
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
ancestor_query = arch_cache_query_new (patch_id, "ancestor");
arch_cache_put_line (ancestor_query, ancestor);
assoc_set (&arch_ancestors, arch_patch_id_patch_id (patch_id), (t_uchar *)ancestor);
lim_free (0, ancestor_query);
}
}
static void
arch_ancestry_insert_ancestry (rel_table revisions)
{
int row;
if (!revisions)
return;
for (row=0; row < rel_n_records (revisions) - 1; ++row)
{
arch_patch_id * temp = arch_patch_id_new (revisions[row][0]);
arch_ancestry_note_ancestor (temp, revisions[row + 1][0]);
talloc_free (temp);
}
if (str_cmp(ARCH_ANCESTRY_PATCH_PARTIAL, revisions[rel_n_records (revisions) - 1][0]))
{
arch_patch_id * temp = arch_patch_id_new (revisions[rel_n_records (revisions) - 1][0]);
arch_ancestry_note_ancestor (temp, "");
talloc_free (temp);
}
}
/* append locally available ancestry data
* precondition: there is at least one non-PARTIAL row in current_ancestry
*/
static rel_table
ancestry_append_available (rel_table current_ancestry)
{
rel_table answer = NULL;
t_uchar * fq_current;
arch_patch_id * current;
int position = 0;
if (!rel_n_records(current_ancestry))
return NULL;
fq_current = current_ancestry[position][0];
while (fq_current && str_cmp (ARCH_ANCESTRY_PATCH_PARTIAL, fq_current))
{
rel_add_records (&answer, rel_copy_record (current_ancestry[position]), 0);
++position;
if (rel_n_records (current_ancestry) > position)
fq_current = current_ancestry[position][0];
else
fq_current = NULL;
}
{
struct exception * e;
Try
{
current = talloc_steal (talloc_context, arch_patch_id_new (answer[rel_n_records(answer) - 1][0]));
while (current && str_cmp (arch_patch_id_patch_id (current), ARCH_ANCESTRY_PATCH_PARTIAL))
{
arch_patch_id *next = arch_patch_ancestor (talloc_context, current, 1);
if (next)
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id (next), 0), 0);
talloc_free (current);
current = next;
}
talloc_free (current);
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
}
}
return answer;
}
/* scan the network for the ancestry relating to patch_id.
* stop when minimum_depth is reached, or if we join with
* maybe_ancestor.
*/
static rel_table
ancestry_scan (t_uchar const * patch_id, rel_table maybe_ancestor, int minimum_depth)
{
arch_patch_id * current = arch_patch_id_new (patch_id);
rel_table answer = NULL;
rel_table final = NULL;
int depth = 0;
arch_ancestry_insert_ancestry (maybe_ancestor);
rel_add_records (&answer, rel_make_record ((t_uchar *)patch_id, 0), 0);
while (minimum_depth == -1 || depth < minimum_depth)
{
/* pre-condition:
*
* current tell use the patch_id to append to the result.
*/
int partial = 0;
arch_patch_id * next_revision = NULL;
struct exception * e;
Try
{
next_revision = arch_patch_ancestor (NULL, current, 0);
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
partial = -1;
rel_add_records(&answer, rel_make_record (ARCH_ANCESTRY_PATCH_PARTIAL, 0), 0);
}
talloc_free (current);
if (partial || !next_revision)
current = NULL;
else
/* normal ancestor */
{
rel_add_records (&answer, rel_make_record (arch_patch_id_patch_id (next_revision), 0), 0);
current = next_revision;
}
++depth;
if (!current || partial)
break;
}
talloc_free (current);
final = ancestry_append_available (answer);
rel_free_table (answer);
arch_archive_connection_cache_finalise (&global_cache, NULL);
return final;
}
static void
ancestry_snap (struct arch_project_tree *tree, rel_table answer)
{
int ignore;
int outfd;
t_uchar * ancestry_file = file_name_in_vicinity (0, tree->root, ARCH_PROJECT_TREE_ANCESTRY_FILE);
vu_unlink (&ignore, ancestry_file);
outfd = safe_open (ancestry_file, (O_WRONLY | O_CREAT | O_EXCL), 0444);
lim_free (0, ancestry_file);
ancestry_write (answer, outfd, -1);
safe_close (outfd);
}
void
ancestry_write (rel_table answer, int outfd, int maximum_depth)
{
int position;
if (maximum_depth < 1 || maximum_depth > rel_n_records(answer))
{
maximum_depth = rel_n_records (answer);
}
safe_printfmt (outfd, ARCH_ANCESTRY_VERSION_BAZ_1"\n");
for (position = 0; position < maximum_depth ; ++position)
{
if (str_cmp (ARCH_ANCESTRY_PATCH_PARTIAL, answer[position][0]))
safe_printfmt (outfd, ARCH_ANCESTRY_PATCH_PREFIX"%s\n", answer[position][0]);
else
safe_printfmt (outfd, "%s\n", answer[position][0]);
}
if (maximum_depth < rel_n_records (answer))
safe_printfmt (outfd, "%s\n", ARCH_ANCESTRY_PATCH_PARTIAL);
}
/* retrieve the patch ancestry for for patch_id from tree tree */
static rel_table
patch_ancestry_from_tree (struct arch_project_tree *tree, arch_patch_id * patch_id, int minimum_depth)
{
rel_table answer = NULL;
if (arch_project_tree_file_exists(tree, ARCH_PROJECT_TREE_ANCESTRY_FILE))
{
t_uchar *candidate_text = arch_project_tree_file_contents(tree, ARCH_PROJECT_TREE_ANCESTRY_FILE);
rel_table candidate = ancestry_parse(candidate_text);
lim_free (0, candidate_text);
answer = partial_ancestry_from_full (candidate, patch_id);
rel_free_table (candidate);
}
return answer;
}
/* patch_ancestry:
* retrieve the ancestry for the patch patch_id, looking for
* a minimum depth as supplied. If there are more revisions
* than minimum_depth trivially available, they will be supplied
* to the caller. -1 means retrieve the entire ancestry.
* 0 means retrieve as much as is immediately available.
*
* The ancestry is returned as a rel_table. it has one field
* the patch-id for that element of the ancestry. The most
* recent revisions are at the lowest entries in the
* returned table.
*
* Once the ancestry is calculated, it is automatically
* memoised to {arch}/+ancestry.
* TODO: teach commit to update +ancestry in the archive
* upon commit
*
* See http://wiki.gnuarch.org/moin.cgi/AncestryFileFormat
* for the general format of these files.
*
* If soft_errors is 1, any failure encountered will result in
* NULL for the result
* If it is 2, failures will result in the current known history
* being returned.
*
* history is established by the following lookup priority:
* 1) If there is a {arch}/+ancestry file it is processed.
*
* 2) If there is a pristine tree available for patch_id,
* it is consulted for a +ancestry file.
*
* 3) The file ancestry is looked for in the archive that the
* patch belongs too - if it has such a archive, and its
* registered.
*
* 4) The log message for the request revision is consulted
* for its immediate ancestor, and then the search resumes
* from there.
*/
/**
* \brief get the revision-history for a specific patch.
* \throw E* if no ancestry is available.
* network access to revisions is presumed flakey and recoverable
* errors are caught. unknown errors are propogated up.
* \param optional_tree a tree to gather ancestry from
* \param patch_id the patch to gather ancestry for
* \param minimum depth the minimum amount of history to attempt to
* retrieve.
*/
rel_table
patch_ancestry (void * context, struct arch_project_tree *optional_tree, arch_patch_id * patch_id, int minimum_depth)
{
rel_table answer = NULL;
/* look in the local tree */
if (optional_tree && arch_project_tree_file_exists(optional_tree, ARCH_PROJECT_TREE_ANCESTRY_FILE))
{
int do_snap = 0;
t_uchar *candidate_text = arch_project_tree_file_contents(optional_tree, ARCH_PROJECT_TREE_ANCESTRY_FILE);
rel_table candidate = ancestry_parse(candidate_text);
lim_free (0, candidate_text);
answer = partial_ancestry_from_full (candidate, patch_id);
if (!answer)
{
answer = ancestry_scan (arch_patch_id_patch_id (patch_id), candidate, minimum_depth);
do_snap = -1;
}
rel_free_table (candidate);
if (!answer)
Throw (exception (ENOENT, "Empty or invalid cached ancestry file"));
}
else
/* look in a reference tree */
{
arch_project_tree_t * pristine_tree;
pristine_tree = arch_library_find (NULL, patch_id, 1);
if (pristine_tree)
{
answer = patch_ancestry_from_tree (pristine_tree, patch_id, minimum_depth);
arch_project_tree_delete(pristine_tree);
}
}
/* scan the network */
answer = ancestry_scan (arch_patch_id_patch_id (patch_id), NULL, minimum_depth);
if (!answer)
Throw (exception (ENOENT, "Empty or invalid cached ancestry file"));
if (optional_tree && answer)
ancestry_snap (optional_tree, answer);
talloc_steal (context, ar_base (answer));
return answer;
}
/* insert a ancestry file to a revision
* -1 depth means all known,
* 0 means just the revisions data*/
void
ancestry_upload_patch (struct arch_archive * arch, arch_patch_id * patch_id, int max_depth)
{
int infd = -1;
/* working file */
t_uchar *tmp_file = talloc_tmp_file_name (talloc_context, ".", ",,ancestry");
t_uchar *tmp_gz_file = talloc_tmp_file_name (tmp_file, ".", ",,ancestry");
/* generate ancestry data */
rel_table ancestry = patch_ancestry (tmp_file, NULL, patch_id, max_depth);
invariant (rel_n_records (ancestry) > 0);
/* create a ancestry file */
{
int outfd = safe_open (tmp_file, (O_WRONLY | O_CREAT | O_EXCL), 0444);
ancestry_write (ancestry, outfd, max_depth);
safe_close (outfd);
}
/* gzip it */
/* FIXME: magic etc etc config detection for gzip */
{
/* FIXME: write a system replacement using ar */
t_uchar * command = str_alloc_cat_many (0, "gzip -c '", tmp_file, "' > '", tmp_gz_file, "'", str_end);
int status = system (command);
if (status == -1)
{
safe_printfmt (2, "Fork failed when running gzip\n");
exit (2);
}
if (!WIFEXITED (status))
{
safe_printfmt (2, "Child process did not exit - caught a signal or something unexpected happened, status is (%d)\n", status);
exit (2);
}
if (WEXITSTATUS (status))
{
safe_printfmt (2, "gzipping ancestry file for upload to the archive failed, status is (%d)\n", WEXITSTATUS(status));
exit (2);
}
}
/* unlink the old file */
safe_unlink (tmp_file);
/* open the zip */
infd = safe_open (tmp_gz_file, O_RDONLY, 0);
/* upload */
{
t_uchar *error = NULL;
arch_archive_put_ancestry (&error, arch, arch_patch_id_revision (patch_id), infd);
}
safe_close (infd);
safe_unlink (tmp_gz_file);
talloc_free (tmp_file);
}
/**
* \brief get the ancestry data cache from a revision in the archive
* \throw system errors only
* \param patch_id the patch to get ancestors for
* \param soft errors exit / return PARTIAL on error
*/
arch_patch_id *
arch_ancestry_from_revision (void * context, arch_patch_id * patch_id, int soft_errors)
{
/* retrieve tarball */
struct arch_archive *arch = arch_archive_connection_cache_find_or_maybe_connect (&global_cache, arch_patch_id_archive (patch_id), soft_errors);
t_uchar *tmp_gz_file = talloc_tmp_file_name (talloc_context, ".", ",,ancestry");
t_uchar *tmp_file = talloc_tmp_file_name (tmp_gz_file, ".", ",,ancestry");
if (arch)
{
int ignore;
int outfd;
int has_ancestry;
arch_revision_type (NULL, NULL, &has_ancestry, arch, patch_id);
if (!has_ancestry)
{
goto finished;
}
vu_unlink (&ignore, tmp_gz_file);
outfd = safe_open (tmp_gz_file, (O_WRONLY | O_CREAT | O_EXCL), 0444);
arch_archive_get_ancestry (outfd, arch, arch_patch_id_revision (patch_id));
safe_close (outfd);
}
else
goto finished;
/* ungzip it */
/* FIXME: magic etc etc config detection for gzip */
{
/* FIXME: write a system replacement using ar and pipe the result */
t_uchar * command = str_alloc_cat_many (0, "gunzip -c '", tmp_gz_file, "' > '", tmp_file, "'", str_end);
int status = system (command);
if (status == -1)
{
safe_printfmt (2, "Fork failed when running gunzip\n");
exit (2);
}
if (!WIFEXITED (status))
{
safe_printfmt (2, "Child process did not exit - caught a signal or something unexpected happened, status is (%d)\n", status);
exit (2);
}
if (WEXITSTATUS (status))
{
safe_printfmt (2, "un gzipping ancestry file from archive failed, status is (%d)\n", WEXITSTATUS(status));
exit (2);
}
}
/* unlink the old file */
safe_unlink (tmp_gz_file);
{
t_uchar *ancestry_text = file_contents(tmp_file);
rel_table ancestry = ancestry_parse(ancestry_text);
arch_ancestry_insert_ancestry (ancestry);
rel_free_table (ancestry);
lim_free (0, ancestry_text);
}
safe_unlink (tmp_file);
finished:
talloc_free (tmp_gz_file);
return arch_cached_patch_ancestor (context, patch_id);
}
/* tag: Tom Lord Thu Jun 26 18:41:53 2003 (ancestry.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1