/* merge.c:
*
* vim:smartindent ts=8:sts=2:sta:et:ai:shiftwidth=2
****************************************************************
* Copyright (C) 2003 Tom Lord
* Copyright (C) 2004 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 "config-options.h"
#include "po/gettext.h"
#include "hackerlab/bugs/panic.h"
#include "hackerlab/bugs/exception.h"
#include "hackerlab/arrays/ar.h"
#include "hackerlab/mem/mem.h"
#include "hackerlab/char/str.h"
#include "hackerlab/fs/file-names.h"
#include "hackerlab/vu/safe.h"
#include "libfsutils/file-contents.h"
#include "libfsutils/tmp-files.h"
#include "libfsutils/rmrf.h"
#include "libawk/relassoc.h"
#include "libarch/ancestry.h"
#include "libarch/namespace.h"
#include "libarch/patch-logs.h"
#include "libarch/project-tree.h"
#include "libarch/merge-points.h"
#include "libarch/local-cache.h"
#include "libarch/make-changeset.h"
#include "libarch/apply-changeset.h"
#include "libarch/merge-three-way.h"
#include "libarch/chatter.h"
#include "libarch/inode-sig.h"
#include "libarch/invent.h"
#include "libarch/merge.h"
static void star_merge_callback (void * vfd, char * fmt, va_list ap);
static t_uchar *
arch_nearest_star_ancestor (arch_project_tree_t * from_tree, t_uchar * from_archive, t_uchar * from_revision,
arch_project_tree_t * to_tree, t_uchar * to_archive, t_uchar * to_version, int trace)
{
t_uchar * from_version = 0;
rel_table to_logs_for_from_version = 0;
rel_table from_logs_for_from_version = 0;
rel_table common_from_logs = 0;
t_uchar * latest_common_from_revision = 0;
rel_table from_merges_from_to = 0;
t_uchar * highest_common_to_revision = 0;
t_uchar * from_rev_of_highest_common_to_revision = 0;
t_uchar * answer = 0;
int x;
from_version = arch_parse_package_name (arch_ret_package_version, 0, from_revision);
to_logs_for_from_version = arch_logs (to_tree->root, from_archive, from_version, 0);
from_logs_for_from_version = arch_logs (from_tree->root, from_archive, from_version, 0);
rel_sort_table_by_field (0, to_logs_for_from_version, 0);
rel_sort_table_by_field (0, from_logs_for_from_version, 0);
common_from_logs = rel_join (-1, rel_join_output (1,0, -1), 0, 0, to_logs_for_from_version, from_logs_for_from_version);
arch_sort_table_by_patch_level_field (1, common_from_logs, 0);
if (common_from_logs)
latest_common_from_revision = str_alloc_cat_many (0, from_version, "--", common_from_logs[0][0], str_end);
from_merges_from_to = arch_tree_merge_points (from_tree->root, from_archive, from_version, to_archive, to_version);
arch_sort_table_by_name_field (1, from_merges_from_to, 1);
for (x = 0; !highest_common_to_revision && (x < rel_n_records (from_merges_from_to)); ++x)
{
if (arch_tree_has_log (to_tree->root, to_archive, from_merges_from_to[x][1]))
{
highest_common_to_revision = str_save (0, from_merges_from_to[x][1]);
from_rev_of_highest_common_to_revision = str_alloc_cat_many (0, from_version, "--", from_merges_from_to[x][0], str_end);
}
}
if (!from_rev_of_highest_common_to_revision && !latest_common_from_revision)
{
answer = 0;
}
else if (!from_rev_of_highest_common_to_revision)
{
answer = arch_fully_qualify (from_archive, latest_common_from_revision);
}
else if (!latest_common_from_revision)
{
answer = arch_fully_qualify (to_archive, highest_common_to_revision);
}
else if (0 < arch_names_cmp (from_rev_of_highest_common_to_revision, latest_common_from_revision))
{
answer = arch_fully_qualify (to_archive, highest_common_to_revision);
}
else
{
answer = arch_fully_qualify (from_archive, latest_common_from_revision);
}
lim_free (0, from_version);
rel_free_table (to_logs_for_from_version);
rel_free_table (from_logs_for_from_version);
rel_free_table (common_from_logs);
lim_free (0, latest_common_from_revision);
rel_free_table (from_merges_from_to);
lim_free (0, highest_common_to_revision);
lim_free (0, from_rev_of_highest_common_to_revision);
return answer;
}
struct arch_common_ancestor_search
{
rel_table todo;
assoc_table reached;
rel_table pruned;
int trace;
} ;
static void
arch_common_ancestor_search_prune_warning(struct arch_common_ancestor_search *search, t_uchar *tree_root)
{
if (rel_n_records (search->pruned))
{
int position;
for (position = 0; position < rel_n_records (search->pruned); ++position)
safe_printfmt (2, _("WARNING: patch log %s has been deleted from tree %s.\n"
"The best merge point may not be chosen\n"), search->pruned[position][0], tree_root);
}
rel_free_table (search->pruned);
search->pruned = NULL;
}
/* append return the directly reachable patches from fqrevision to out
* , in no particular order
*/
static void
arch_included_from_patch (struct arch_common_ancestor_search *search, rel_table * out, arch_project_tree_t *tree, t_uchar * fqrevision)
{
t_uchar * log = arch_project_tree_patch_log (tree, fqrevision);
assoc_table headers = 0;
t_uchar * new_patches_header;
rel_table new_patches = 0;
int x;
t_uchar * continuation_header;
if (!log)
{
/* TODO FIXME rbc 20041215 this should be replaced with 'try and get the log, if failed, then warn */
rel_add_records (&search->pruned, rel_make_record (fqrevision, 0), 0);
return;
}
arch_parse_log (0, &headers, 0, log);
new_patches_header = assoc_ref (headers, "new-patches");
continuation_header = assoc_ref (headers, "continuation-of");
if (continuation_header)
{
/* TODO FIXME: get the headers present in the source and do
* a subtraction to get the real added headers.
* then remove fqrevision and *boom* we are done.
*/
rel_add_records (out, rel_make_record (continuation_header, 0), 0);
}
else
{
new_patches = rel_ws_split (new_patches_header);
rel_uniq_by_field (&new_patches, 0);
for (x = 0; x < rel_n_records (new_patches); ++x)
if (str_cmp( fqrevision, new_patches[x][0]))
rel_add_records (out, rel_make_record (new_patches[x][0], 0), 0);
/* add the patch ancestor predecessor */
if (!str_cmp("-0", fqrevision + str_length(fqrevision) - 2))
/* version-0, cannot determine the ancestor from the tree */
{
struct exception * e;
Try
{
arch_patch_id * patch_id = talloc_steal (talloc_context,
arch_patch_id_new (fqrevision));
arch_patch_id * predecessor = arch_patch_ancestor (talloc_context, patch_id, 0);
if (predecessor)
{
if (arch_project_tree_has_patch (tree, arch_patch_id_patch_id (predecessor)))
rel_add_records (out, rel_make_record (arch_patch_id_patch_id (predecessor), 0), 0);
}
/* if the patch ancestry isn't available, we definately cannot build a
* reference tree, so we just don't record it as reachable
*/
talloc_free (patch_id);
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
}
}
else
/* tree ancestry is sufficient for our purposes */
{
t_uchar *predecessor = assoc_ref (tree->local_ancestry, fqrevision);
if (predecessor)
{
if (str_length (predecessor) && str_cmp (predecessor, ARCH_ANCESTRY_PATCH_PARTIAL))
rel_add_records (out, rel_make_record (predecessor, 0), 0);
}
else
{
predecessor = arch_highest_patch_level_before (tree, fqrevision);
if (predecessor)
rel_add_records (out, rel_make_record (predecessor, 0), 0);
lim_free (0, predecessor);
}
}
}
free_assoc_table (headers);
rel_free_table (new_patches);
lim_free (0, log);
}
/* FIXME: this should be a simply adjacency list djikstra implementation. */
/* topologically sort patches according to ancestry */
static rel_table
arch_topological_sort (struct arch_common_ancestor_search *search, rel_table fqrevisions, arch_project_tree_t *tree, int trace)
{
/* generate predecessors */
/* col 0 is the node. cols beyond that are the reachables */
rel_table graph = NULL;
rel_table answer = NULL;
assoc_table found = 0;
int found_count = 0;
int loop;
for (loop=0; loop < rel_n_records (fqrevisions); ++loop)
{
rel_table predecessors = 0;
int fields;
int graph_row;
rel_add_records (&graph, rel_make_record (fqrevisions[loop][0], 0), 0);
arch_included_from_patch (search, &predecessors, tree, fqrevisions[loop][0]);
graph_row = rel_n_records (graph) -1;
for (fields=0; fields < rel_n_records (predecessors); ++fields)
{
rel_add_field (&graph[graph_row], predecessors[fields][0]);
}
rel_free_table (predecessors);
}
if (trace)
{
safe_printfmt (2, "brute force adjacency searching in table\n");
rel_print_table (2, graph);
}
while (found_count < rel_n_records (graph))
{
assoc_table links = 0;
for (loop = 0; loop < rel_n_records (graph); ++ loop)
/* have we done this one already ? */
if (!assoc_ref (found, graph[loop][0]))
{
int field;
for (field = 1; field < rel_n_fields (graph[loop]); ++field)
{
//safe_printfmt(2, "%s->%s\n", graph[loop][0],graph[loop][field]);
assoc_set (&links, graph[loop][field],graph[loop][0]);
}
}
for (loop=0; loop < rel_n_records (graph); ++loop)
{
//safe_printfmt(2, "checking %s\n", graph[loop][0]);
if (!assoc_ref (links, graph[loop][0]) && !assoc_ref (found, graph[loop][0]))
{
//safe_printfmt(2, "unreachable %s\n", graph[loop][0]);
/* unreachable */
++found_count;
rel_add_records (&answer, rel_make_record (graph[loop][0], 0), 0);
assoc_set (&found, graph[loop][0], graph[loop][0]);
}
//else
// safe_printfmt(2, "reachable %s\n", graph[loop][0]);
}
free_assoc_table (links);
}
free_assoc_table (found);
//safe_printfmt (2, "sorted table\n");
//rel_print_table (2, answer);
//safe_printfmt (2, "============\n");
rel_free_table (graph);
return answer;
}
/* add the new patches reachable from fqrevision to out,
* IF log is a continuation, ONLY the logs id itself is added.
* that is, if A-patch-1 merges B patch-1 and 2, and B-2 is not
* a continuation, out will have B-2 and B-1 appended.
*/
/* FIXME: patches in the line of ancestry for search, should not be reachable by any patch
* other than its successor in the search line of ancestry.
* i.e. A0 A1 A2
* \-B1 B2 -/
* (B1 from A-0, B2 merges in A1, and A2 merges in B2, reachable from B2 should list B1 but not
* A1 even though A1 is a 'new patch'
*/
static void
arch_common_ancestor_search_reachable_from_patch (struct arch_common_ancestor_search * search, arch_project_tree_t *tree, t_uchar * fqrevision)
{
rel_table reachable = 0;
if (search->trace)
safe_printfmt (2, "Adding to todo queue as reachable from %s:\n", fqrevision);
arch_included_from_patch (search, &reachable, tree, fqrevision);
if (search->trace)
{
int loop;
for (loop=0; loop < rel_n_records (reachable); ++loop)
safe_printfmt (2, "%s\n", reachable[loop][0]);
}
rel_append_x (&search->todo, reachable);
rel_free_table (reachable);
}
/* add the new patches reachable from fqrevision in tree tree_root,
* to out. This means
* * All patches not present in the archive copy of the tree
* * Patches that are ancestors or the named fqrevision in the tree,
* as per arch_common_ancestor_search_reachable_from_patch.
*/
static void
arch_reachable_from_tree (struct arch_common_ancestor_search *search, arch_project_tree_t *tree, t_uchar * fqrevision,
t_uchar * tmp_dir, arch_project_tree_t * cache)
{
t_uchar * reference_tree_root;
rel_table reachable = 0;
rel_table new_in_tree;
if (search->trace)
safe_printfmt (2, "Adding to todo queue as reachable (from tree) %s:\n", fqrevision);
/* patches in the tree from fqrevision */
arch_included_from_patch (search, &reachable, tree, fqrevision);
reference_tree_root = arch_find_or_make_tmp_local_copy (-1, tmp_dir, tree, cache, 0, tree->archive, tree->revision);
new_in_tree = arch_new_in_tree (tree, reference_tree_root);
/* I'm sure there is a function to do this trivially */
{
int loop;
for (loop=0; loop < rel_n_records (new_in_tree); ++loop)
{
rel_add_records(&reachable, rel_make_record (new_in_tree[loop][0], 0), 0);
}
}
if (search->trace)
{
int loop;
for (loop=0; loop < rel_n_records (reachable); ++loop)
safe_printfmt (2, "%s\n", reachable[loop][0]);
}
rel_append_x (&search->todo, reachable);
rel_free_table (reachable);
rel_free_table (new_in_tree);
/* tmp_dir will be rmed by the caller */
lim_free (0, reference_tree_root);
}
static int
arch_visited (assoc_table *visited, t_uchar * fqrevision, int trace)
{
t_uchar *exists = assoc_ref (*visited, fqrevision);
int result = exists != NULL;
if (!result)
{
if (trace)
safe_printfmt (2, "%s visited\n", fqrevision);
assoc_set (visited, fqrevision, fqrevision);
}
return result;
}
static void
arch_common_ancestor_search_init(struct arch_common_ancestor_search *search)
{
search->todo = 0;
search->trace = 0;
search->reached = 0;
search->pruned = NULL;
}
static void
arch_common_ancestor_search_finalise(struct arch_common_ancestor_search *search)
{
rel_free_table (search->todo);
free_assoc_table (search->reached);
rel_free_table (search->pruned);
}
static void
arch_common_ancestor_search_reach (struct arch_common_ancestor_search *search, arch_project_tree_t *search_tree, int trace)
{
rel_table current=search->todo;
int position;
rel_table filtered = 0;
search->todo = NULL;
for (position=0; position < rel_n_records (current); ++position)
{
if (!assoc_ref (search->reached, current[position][0]))
{
arch_visited (&search->reached, current[position][0], trace);
rel_add_records (&filtered, rel_copy_record (current[position]), 0);
}
else if (trace)
{
safe_printfmt (2, "Skipping %s (already reached on this side)\n", current[position][0]);
}
}
for (position=0; position < rel_n_records (filtered); ++position)
{
arch_common_ancestor_search_reachable_from_patch (search, search_tree, filtered[position][0]);
}
rel_free_table (current);
rel_free_table (filtered);
}
typedef struct arch_common_ancestor_state
{
int trace;
struct arch_common_ancestor_search from;
struct arch_common_ancestor_search to;
} common_ancestor;
static t_uchar *
arch_project_tree_patch_ancestor (arch_project_tree_t *tree, t_uchar const *patch_id)
{
t_uchar * log;
assoc_table headers = 0;
t_uchar * new_patches_header;
rel_table new_patches = 0;
t_uchar * continuation_header;
t_uchar *result;
result = assoc_ref (tree->local_ancestry, patch_id);
if (result)
{
if (str_length (result))
return str_save (0, result);
else
return NULL;
}
log = arch_project_tree_patch_log (tree, patch_id);
if (!log)
{
assoc_set (&tree->local_ancestry, patch_id, ARCH_ANCESTRY_PATCH_PARTIAL);
return str_save (0, ARCH_ANCESTRY_PATCH_PARTIAL);
}
arch_parse_log (0, &headers, 0, log);
new_patches_header = assoc_ref (headers, "new-patches");
continuation_header = assoc_ref (headers, "continuation-of");
if (continuation_header)
{
result = str_save (0, continuation_header);
}
else
/* namespace predecessor - may be wrong in double-import cases
* or if their is a sealed version and any non sealed patch levels
* were removed
*/
{
/* add the patch ancestor predecessor */
if (!str_cmp("-0", patch_id + str_length(patch_id) - 2))
{
/* version-0, cannot determine the ancestor from the tree */
struct exception * e;
Try
{
arch_patch_id * patch_id_ = talloc_steal (talloc_context,
arch_patch_id_new (patch_id));
arch_patch_id * patch_result = arch_patch_ancestor (talloc_context,
patch_id_, 0);
if (patch_result)
result = str_save (0, arch_patch_id_patch_id (patch_result));
else
result = NULL;
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
result = str_save (0, ARCH_ANCESTRY_PATCH_PARTIAL);
}
}
else
/* tree ancestry is sufficient for our purposes */
result = arch_highest_patch_level_before (tree, patch_id);
}
if (!result)
assoc_set (&tree->local_ancestry, patch_id, "");
else
assoc_set (&tree->local_ancestry, patch_id, result);
free_assoc_table (headers);
rel_free_table (new_patches);
lim_free (0, log);
return result;
}
/* determine the ancestry of patch_id that is distinct from the ancestry for tree)
*/
static rel_table
arch_ancestry_unique_to_branch (t_uchar * patch_id, arch_project_tree_t *tree)
{
assoc_table base_ancestry = rel_to_assoc (arch_project_tree_ancestry(tree), 0, 0);
t_uchar *current = str_save (0, patch_id);
rel_table result = NULL;
while (1)
{
/* may hit the archive, thats ok */
t_uchar * next = NULL;
struct exception * e;
Try
{
arch_patch_id * patch_id = talloc_steal (talloc_context,
arch_patch_id_new (current));
arch_patch_id * patch_result = arch_patch_ancestor (talloc_context,
patch_id, 0);
if (patch_result)
next = str_save (0, arch_patch_id_patch_id (patch_result));
else
next = NULL;
}
Catch (e)
{
/* dont mask critical errors */
if (e->code < 0)
Throw (e);
talloc_free (e);
/* no ancestor from ancestry, use local cache */
next = arch_project_tree_patch_ancestor (tree, current);
}
if (!str_cmp (next, ARCH_ANCESTRY_PATCH_PARTIAL))
/* still couldn't */
{
rel_add_records (&result, rel_make_record (ARCH_ANCESTRY_PATCH_PARTIAL, 0), 0);
lim_free (0, next);
break;
}
if (!next)
/* end of the branch - unrelated trees ? */
break;
/* ok have an ancestor */
rel_add_records (&result, rel_make_record (next, 0), 0);
current = str_replace (current, next);
if (assoc_ref (base_ancestry, next))
/* we've met the branch */
break;
}
lim_free (0, current);
free_assoc_table (base_ancestry);
return result;
}
/* determine if patch_id is present in tree via a cherry pick or a merge operation */
static int
arch_patch_is_cherrypicked (t_uchar * patch_id, arch_project_tree_t *tree, int trace)
{
int position;
rel_table unchecked;
int result = 0;
if (trace)
safe_printfmt (2, "Checking for cherrypicked patch %s in tree %s\n", patch_id, tree->root);
/* this was cherry picked if its ancestor, or any of its ancestors up to the point it was
* import / branched from tree are missing.
*/
unchecked = arch_ancestry_unique_to_branch (patch_id, tree);
for (position=0; position < rel_n_records (unchecked); ++position)
{
if (!str_cmp (unchecked[position][0], ARCH_ANCESTRY_PATCH_PARTIAL))
{
if (trace)
safe_printfmt (2, "full ancestry to the branch was not available from tree %s\n", tree->root);
result = -1;
}
else if (!arch_project_tree_has_patch (tree, unchecked[position][0]))
{
if (trace)
safe_printfmt (2, "missing patch %s from tree %s\n", unchecked[position][0], tree->root);
result = -1;
}
}
rel_free_table (unchecked);
return result;
}
static rel_table
arch_ancestry_cherrypicked (rel_table patches, arch_project_tree_t *tree, int trace)
{
rel_table answer = NULL;
int position;
for (position = 0; position < rel_n_records (patches); ++position)
{
if (arch_patch_is_cherrypicked (patches[position][0], tree, trace))
rel_add_records (&answer, rel_copy_record (patches[position]), 0);
}
return answer;
}
/* rel_remove_common: remove all elements of other from me
*/
static void
rel_remove_common (rel_table *me, rel_table other)
{
rel_table result = rel_set_subtract (*me, other);
rel_free_table (*me);
*me = result;
}
/* Find the nearest common ancestor from two revisions.
* NOTE: until --reference is removed from the star-merge interfance, to_version and to_archive MAY DIFFER
* from to_tree->version and to_tree->archive.
* from_tree is presumed to be a reference tree, with no local changes
*/
static t_uchar *
arch_nearest_common_ancestor (arch_project_tree_t * from_tree, t_uchar * from_archive, t_uchar * from_revision,
arch_project_tree_t * to_tree, t_uchar * to_archive, t_uchar * to_version, int trace,
t_uchar *tmp_dir, arch_project_tree_t * cache)
{
common_ancestor state;
t_uchar * from_fqrevision = 0;
t_uchar * to_fqrevision = 0;
rel_table intersection = 0;
t_uchar * result = NULL;
arch_common_ancestor_search_init (&state.from);
arch_common_ancestor_search_init (&state.to);
/* TODO refactor this */
state.trace = trace;
state.from.trace = trace;
state.to.trace = trace;
{
t_uchar * patch_level = arch_highest_patch_level (to_tree->root, to_archive, to_version);
to_fqrevision = str_alloc_cat_many (0, to_archive, "/", to_version, "--", patch_level, str_end);
lim_free (0, patch_level);
}
from_fqrevision = arch_fully_qualify (from_archive, from_revision);
/* FIXME pass in the chatter_fd */
safe_printfmt (1, "* Searching for best merge point: ");
/* from_tree is always a pristine */
arch_common_ancestor_search_reachable_from_patch (&state.from, from_tree, from_fqrevision);
arch_reachable_from_tree (&state.to, to_tree, to_fqrevision, tmp_dir, cache);
arch_visited (&state.from.reached, from_fqrevision, trace);
arch_visited (&state.to.reached, to_fqrevision, trace);
while (!result && ( rel_n_records (state.to.todo) || rel_n_records (state.from.todo)))
{
if (state.trace)
safe_printfmt (2, "Checking next reachable nodes\n");
safe_printfmt (1, ".");
safe_flush (1);
arch_common_ancestor_search_reach (&state.from, from_tree, state.trace);
arch_common_ancestor_search_reach (&state.to, to_tree, state.trace);
intersection = assoc_intersection (state.from.reached, state.to.reached);
if (!intersection)
{
arch_common_ancestor_search_prune_warning(&state.from, from_tree->root);
arch_common_ancestor_search_prune_warning(&state.to,to_tree->root);
}
if (intersection)
{
rel_table from_cherries;
rel_table to_cherries;
if (state.trace)
{
safe_printfmt (2, "Found intersection:\n");
rel_print_table (2, intersection);
}
/* Note: Bad O here: intersection is fully regenerated every time..
* that said, long cherry picked trees are uncommon AFAICT. Robert Collins 20050118
*/
from_cherries = arch_ancestry_cherrypicked (intersection, from_tree, trace);
to_cherries = arch_ancestry_cherrypicked (intersection, to_tree, trace);
rel_remove_common (&intersection, from_cherries);
rel_remove_common (&intersection, to_cherries);
{
rel_table temp_table = arch_topological_sort (&state.from, intersection, from_tree, trace);
rel_free_table (intersection);
intersection = temp_table;
}
/* don't warn on pruned from this sort, they are irrelevant */
if (state.trace)
{
safe_printfmt (2, "Sorted intersection:\n");
rel_print_table (2, intersection);
}
if (rel_n_records (intersection))
result = str_save (0, intersection[0][0]);
rel_free_table (intersection);
intersection = NULL;
rel_free_table (from_cherries);
rel_free_table (to_cherries);
}
}
lim_free (0, from_fqrevision);
lim_free (0, to_fqrevision);
arch_common_ancestor_search_finalise (&state.from);
arch_common_ancestor_search_finalise (&state.to);
safe_printfmt (1, " done\n");
return result;
}
/*
*
* star-merge FROM MY-TREE [MY-VERSION]
*
* from-patch-j is the latest rev from FROM's version in both my tree
* and FROM
*
* from-patch-k is the latest rev in FROM merging
* my-version-m into FROM where my-version-m
* is present in MY-TREE.
*
*
* if j > k
* delta(from-patch-j,from-patch-k)[my-tree]
* else
* delta(my-version-m,from-patch-k)[my-tree]
* fi
*/
int
arch_star_merge (int chatter_fd,
struct arch_archive * from_arch, t_uchar * from_archive, t_uchar * from_revision,
arch_project_tree_t * to_tree, struct arch_archive * to_arch, t_uchar * to_archive, t_uchar * to_version,
arch_project_tree_t * cache, t_uchar * changeset_output, int use_diff3, int forward,
int escape_classes, int show_points_only, int graph_merge, int trace)
{
int answer = 2;
t_uchar * tmp_dir = 0;
t_uchar * changeset = 0;
t_uchar * from_tree_root = 0;
t_uchar * fq_orig_revision = 0;
t_uchar * orig_archive = 0;
t_uchar * orig_revision = 0;
arch_project_tree_t * from_tree;
arch_project_tree_t * orig_tree = NULL;
tmp_dir = talloc_tmp_file_name (talloc_context, to_tree->root, ",,merge");
if (!changeset_output)
changeset = file_name_in_vicinity (0, tmp_dir, ",,changeset");
else
changeset = str_save (0, changeset_output);
rmrf_file (tmp_dir);
safe_mkdir (tmp_dir, 0777);
from_tree_root = arch_find_or_make_tmp_local_copy (chatter_fd, tmp_dir, to_tree, cache, from_arch, from_archive, from_revision);
from_tree = arch_project_tree_new (talloc_context, from_tree_root);
if (graph_merge)
fq_orig_revision = arch_nearest_common_ancestor (from_tree, from_archive, from_revision, to_tree, to_archive, to_version, trace, tmp_dir, cache);
else
fq_orig_revision = arch_nearest_star_ancestor (from_tree, from_archive, from_revision, to_tree, to_archive, to_version, trace);
if (!fq_orig_revision)
{
safe_printfmt (2, "merge: unable to merge unrelated trees.\n");
answer = 2;
}
else
{
t_uchar * orig_tree_root = 0;
struct arch_make_changeset_report make_report = {0, };
struct arch_apply_changeset_report * apply_report = NULL;
assoc_table inode_shortcut = 0;
if (show_points_only)
{
arch_chatter (chatter_fd, "* merge points are:\n");
arch_chatter (chatter_fd, "base: %s\n", fq_orig_revision);
arch_chatter (chatter_fd, "other: %s/%s\n", from_archive, from_revision);
arch_chatter (chatter_fd, "this: %s\n", to_tree->fqrevision);
return 0;
}
arch_chatter (chatter_fd, "* merge by delta(%s,%s/%s)[%s]\n", fq_orig_revision, from_archive, from_revision, to_tree->root);
{
t_uchar *fq_from = str_alloc_cat_many (0, from_archive,"/", from_revision, str_end);
int empty = !str_cmp (fq_from, fq_orig_revision);
lim_free (0, fq_from);
if (empty)
{
arch_chatter (chatter_fd, "* skipping (empty delta)\n");
answer = 0;
goto star_merge_cleanup;
}
}
orig_archive = arch_parse_package_name (arch_ret_archive, 0, fq_orig_revision);
orig_revision = arch_parse_package_name (arch_ret_non_archive, 0, fq_orig_revision);
orig_tree_root = arch_find_or_make_tmp_local_copy (chatter_fd, tmp_dir, to_tree, cache, (str_cmp (orig_archive, from_archive) ? 0 : from_arch), orig_archive, orig_revision);
orig_tree = arch_project_tree_new (talloc_context, orig_tree_root);
if (changeset_output)
{
make_report.callback = star_merge_callback;
make_report.thunk = (void *)(long)chatter_fd;
}
if (use_diff3)
{
arch_chatter (chatter_fd, "* performing merge\n");
answer = arch_merge3 (chatter_fd, to_tree, orig_tree, from_tree, escape_classes, 1);
lim_free (0, orig_tree_root);
goto star_merge_cleanup;
}
arch_read_inode_sig_ids (0, &inode_shortcut, from_tree_root, orig_archive, orig_revision);
arch_make_changeset (&make_report, orig_tree, from_tree, changeset, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, inode_shortcut, 0, escape_classes);
if (changeset_output)
answer = 0;
else
{
assoc_table older_files_table = 0;
assoc_table yours_files_table = 0;
arch_chatter (chatter_fd, "* applying changeset\n");
safe_flush (chatter_fd);
apply_report = arch_apply_changeset (changeset, talloc_context, to_tree, arch_unspecified_id_tagging, arch_inventory_unrecognized, 0, forward, escape_classes, star_merge_callback, (void *)(long)chatter_fd);
if (arch_conflicts_occurred (apply_report))
answer = 1;
else
answer = 0;
free_assoc_table (older_files_table);
free_assoc_table (yours_files_table);
}
arch_free_make_changeset_report_data (&make_report);
talloc_free (apply_report);
lim_free (0, orig_tree_root);
free_assoc_table (inode_shortcut);
}
star_merge_cleanup:
rmrf_file (tmp_dir);
talloc_free (tmp_dir);
lim_free (0, from_tree_root);
lim_free (0, fq_orig_revision);
lim_free (0, orig_archive);
lim_free (0, orig_revision);
lim_free (0, changeset);
arch_project_tree_delete (from_tree);
arch_project_tree_delete (orig_tree);
return answer;
}
static void
star_merge_callback (void * vfd, char * fmt, va_list ap)
{
int fd;
fd = (int)(t_ulong)vfd;
if (fd >= 0)
{
safe_printfmt_va_list (fd, fmt, ap);
safe_flush (fd);
}
}
/* tag: Tom Lord Sat Jun 28 16:40:26 2003 (star-merge.c)
*/
syntax highlighted by Code2HTML, v. 0.9.1