/*
 * Copyright (c) 2002, The EROS Group, LLC and Johns Hopkins
 * University. All rights reserved.
 * 
 * This software was developed to support the EROS secure operating
 * system project (http://www.eros-os.org). The latest version of
 * the OpenCM software can be found at http://www.opencm.org.
 * 
 * Redistribution and use in source and binary forms, with or
 * without modification, are permitted provided that the following
 * conditions are met:
 * 
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 
 * 2. Redistributions in binary form must reproduce the above
 *    copyright notice, this list of conditions and the following
 *    disclaimer in the documentation and/or other materials
 *    provided with the distribution.
 * 
 * 3. Neither the name of the The EROS Group, LLC nor the name of
 *    Johns Hopkins University, nor the names of its contributors
 *    may be used to endorse or promote products derived from this
 *    software without specific prior written permission.
 * 
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
 * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include <opencm.h>
#include <repos/Repository.h>
#include "opencmclient.h"
#include "../libgdiff/diffrun.h"

#define BULK_ECACHE_SZ 40000

/* OPENCM always merges into the working arena. This is because there
 * is no guarantee that the client has write permission on ANY
 * repository.
 *
 * A reasonable policy is to want to ensure that the result of a merge
 * doesn't have any "other" random changes. The best approximation to
 * this that we can actually enforce is to insist that the working
 * area have no changes when we begin.
 *
 * Note, however, that this is arguably the *wrong* behavior for the
 * update command. Given this, at the moment, we do not enforce an
 * unmodified arena for either command.
 *
 * The result of the merge is a modified workspace, NOT a modified
 * branch in the repository.
 *
 * For an overview of how merge processing works, consult the
 * opencm-internals texinfo file.
 *
 * The merge strategy proceeds as follows:
 *
 * Any files/object may have been modified. Therefore, for each
 * object there is a (possibly empty) delta.
 *
 * The current state of each object in the workspace is therefore one
 * of
 *
 *       baseEntity + delta
 *       baseEntity + (delta from mergeEntity) + delta
 *
 * All of the merges proceed by finding the nearest common ancestor of
 * the merge version and the current version. It is guaranteed (by
 * construction) that if this ancestor exists it is unique.
 *
 * Since files may have been renamed, we cannot use the filenames as a
 * reliable indicator of relationship. Given this, we (in principle)
 * need to do a common ancestor search for every pair (x,y) where x is
 * some object the inbound merge version and y is some object in the
 * workspace base version. This potentially requires N^2 ancestor
 * searches. Since the ancestor search on a given pair is potentially
 * expensive (in theory, but rarely in practice) we would very much
 * like to avoid unnecessary comparisons.
 *
 * Some things that help:
 *
 * 1. OPENCM doesn't have a "copy" command. Therefore, if there exists a
 *    common ancestor for some pair (a,b) we can remove both a and b
 *    from further consideration.
 *
 * 2. While renames may have happened, they usually haven't. We could
 *    in principle try to do the ancestor search on objects whose
 *    fsName matches. If no ancestor is found we need to do a more
 *    complete search, but if a common ancestor IS found we can remove
 *    the pair from the pool. Eliminating the "low hanging fruit" in
 *    this way still leaves us with an N^2 search to do, but on a much
 *    much smaller pool.
 *
 *    OPENCM currently doesn't do this optimization, because trick
 *    number (3) works just as well and doesn't rely on the absence of
 *    renames.
 *
 * 3. To reduce the size of the search, the "opencm add" command assigns
 *    to each new entity a randomly generated "Family ID". All changes
 *    starting from that version will retain the same family ID. While
 *    family ID's might collide, they dramatically reduce the N in the
 *    N^2 pairs to be compared. Here are the truth statements about
 *    family IDs:
 *
 *      a) if two objects have a common ancestor, they have the same
 *         family ID
 *      b) if two objects have the same family ID, they may or may not
 *         be related at all.
 *
 * As of today (July 16, 2002), the IDs are 160-bit strings generated by the
 * RNG in OpenSSL, so the odds of a collision are astronomically low (presuming
 * OpenSSL's RNG is any good).
 *
 * IS THE FOLLOWING STILL CORRECT?
 *
 * To actually perform the common ancestor search, we proceed as
 * follows:
 *
 *   Sort each pool by family ID.
 *   For each object ow in the working version
 *      for each object om in the merge version
 *          s.t. ow.famID == om.famID
 *             perform the common ancestor check
 *             if one is found
 *                place (om, ow, ancestor) on the ancestor list
 *                   remove om and ow from further consideration.
 */
  
static void
extract_entities(const Change *chg, EntityCache *ec)
{
  unsigned q;

  for (q = 0; q < vec_size(CMGET(chg,entities)); q++) {
    Entity *ent = vec_fetch(CMGET(chg,entities), q, Entity *);
    const char *tn = ser_getTrueName(ent);
    if (!ecache_lookup(ec, tn))
      ecache_insert(ec, tn, (Serializable *)ent);
  }
}

/* This routine can get very expensive.  Since we always call it twice
   (once to see if we have a "parent" and the second time to see if we
   have a "mergeParent") why not combine those two so we only iterate
   over the vector once..? */
#define HAS_PARENT    0x01u
#define HAS_MRGPARENT 0x02u

static unsigned
obvec_contains_tnob(ObVec *vec, const char *parentTN, const char *mrgParentTN)
{
  unsigned ndx;
  unsigned result = 0x0u;

  for (ndx = 0; ndx < vec_size(vec); ndx++) {
    Serializable *s = vec_fetch(vec, ndx, Serializable *);

    if (((result & HAS_PARENT) == 0) && nmequal(ser_getTrueName(s), parentTN))
      result |= HAS_PARENT;

    if (((result & HAS_MRGPARENT) == 0) && 
	nmequal(ser_getTrueName(s), mrgParentTN))
      result |= HAS_MRGPARENT;

    if (result & (HAS_PARENT | HAS_MRGPARENT))
      break;
  }

  return result;
}

/* Expand an existing ancestor vector in place. Return TRUE if the
 * vector was expanded, or FALSE if no elements were added.
 */
static OC_bool
entity_expand_ancestor_vector(Repository *r, const char *mURI, 
			      ObVec *vec,
			      EntityCache *bulkMergeCache)
{
  unsigned len = vec_size(vec);
  unsigned u, q;
  OC_bool result = FALSE;
  
  for (u = 0; u < len; u++) {
    const Entity *e = vec_fetch(vec, u, const Entity*);

    unsigned flag = obvec_contains_tnob(vec, e->parent, e->mergeParent);

    if (e->parent && !(flag & HAS_PARENT)) {
      Entity *parent = 
	(Entity *) ecache_lookup(bulkMergeCache, e->parent);

      assert(e->change_parent);

      if (parent == 0) {
	Change *chg_parent = NULL;

	TRY {
	  chg_parent = (Change *)repos_GetEntity(r, mURI, e->change_parent);

	}
	DEFAULT(AnyException) {
	  chg_parent = NULL;
	}
	END_CATCH;
	if (chg_parent == NULL)
	  return FALSE;

	extract_entities(chg_parent, bulkMergeCache);

	parent = 
	  (Entity *) ecache_lookup(bulkMergeCache, e->parent);

	if (parent == NULL) {
	  /* The single change overran the bulk cache. Do it the hard
	   * way. */
	  for (q = 0; q < vec_size(CMGET(chg_parent,entities)); q++) {
	    Entity *ent = vec_fetch(CMGET(chg_parent,entities), q, Entity *);
	    if (nmequal(ser_getTrueName(ent), e->parent)) {
	      parent = ent;
	      break;
	    }
	  }
	}
      }

      if (parent == NULL)
	return FALSE;

      assert(parent);
      obvec_append(vec, (Serializable *) parent);
      result = TRUE;
    }

    if (e->mergeParent && !(flag & HAS_MRGPARENT)) {
      Entity *mergeParent = 
	(Entity *) ecache_lookup(bulkMergeCache, e->mergeParent);

      assert(e->change_mergeParent);

      if (mergeParent == 0) {
	Change *chg_mrgparent = NULL;

	TRY {
	  chg_mrgparent = (Change *)repos_GetEntity(r, mURI, e->change_mergeParent);
	}
	DEFAULT(AnyException) {
	  chg_mrgparent = NULL;
	}
	END_CATCH;

	if (chg_mrgparent == NULL)
	  return FALSE;

        extract_entities(chg_mrgparent, bulkMergeCache);

	mergeParent = 
	  (Entity *) ecache_lookup(bulkMergeCache, e->mergeParent);

	if (mergeParent == NULL) {
	  /* The single change overran the bulk cache. Do it the hard
	   * way. */
	  for (q = 0; q < vec_size(CMGET(chg_mrgparent,entities)); q++) {
	    Entity *ent = vec_fetch(CMGET(chg_mrgparent,entities), q, Entity *);
	    if (nmequal(ser_getTrueName(ent), e->mergeParent)) {
	      mergeParent = ent;
	      break;
	    }
	  }
	}
      }
      if (mergeParent == NULL)
	return FALSE;

      obvec_append(vec, (Serializable *) mergeParent);
      result = TRUE;
    }
  }

  return result;
}

static OC_bool
entity_isAncestorOf(Repository *r, const char *mURI, 
		    const Entity *parent, const Entity *child,
		    EntityCache *bulkMergeCache)
{
  ObVec *vec = obvec_create();
  unsigned u = 0;
  const char * parentTN = ser_getTrueName(parent);
  
  /* Cherry pick the two most likely cases... */
  if (nmequal(child->parent, parentTN))
    return TRUE;
  if (nmequal(child->mergeParent, parentTN))
    return TRUE;

  obvec_append(vec, child);

  do {
    for (; u < vec_size(vec); u++) {
      const Entity *e = vec_fetch(vec, u, const Entity *);

      if ( nmequal(ser_getTrueName(e), parentTN) )
	return TRUE;
    }
  } while(entity_expand_ancestor_vector(r, mURI, vec, bulkMergeCache));
  
  return FALSE;
}

#define REACH_S1    0x1u
#define REACH_S2    0x2u
#define REACH_BOTH  (REACH_S1|REACH_S2)

/* FIX: This will do an unnecessary re-fetch of c1, c2 */
static const Change *
change_find_common_ancestor(Repository *r,
			    const char *m1URI, const Change *c1,
			    const char *m2URI, const Change *c2,
			    unsigned whichExpand)
{
  unsigned ndx;
  ObVec *avec = obvec_create();

  ObDict *aDict = obdict_create();
  
  const char *tn1 = ser_getTrueName(c1);
  const char *tn2 = ser_getTrueName(c2);

  if (nmequal(tn1, tn2))
    return c1;

  {
    ObDictEntry *ode = ode_create(tn1, 0, REACH_S1);
    obdict_insert(aDict, ode);
    if (whichExpand & REACH_S1) {
      log_trace(DBG_CHANGE_ANCESTOR, "APPEND S1 %s\n", tn1);
      obvec_append(avec, ode);
    }
  }

  {
    ObDictEntry *ode = ode_create(tn2, 0, REACH_S2);
    obdict_insert(aDict, ode);
    if (whichExpand & REACH_S2) {
      log_trace(DBG_CHANGE_ANCESTOR, "APPEND S2 %s\n", tn2);
      obvec_append(avec, ode);
    }
  }

  /* Idea: we are proceeding from 0 to N through avec, appending to it
     as we go. Everything to the left of ndx has already been
     expanded. Everything at positions >= ndx has not been
     explored. */

  for (ndx = 0; ndx < vec_size(avec); ndx++) {
    ObDictEntry *ode = vec_fetch(avec, ndx, ObDictEntry *);
    const char *mURI;
    int i;
    TnVec *parents;

    log_trace(DBG_CHANGE_ANCESTOR, "CONSIDER ndx=%d %s %s\n", 
	    ndx,
	    (ode->w & REACH_S1) ? "S1" : "S2", ode->trueName);

    assert(ode->w == REACH_S1 || ode->w == REACH_S2);

    mURI = (ode->w & REACH_S1) ? m1URI : m2URI;

    parents = repos_GetParents(r, mURI, ode->trueName);

    for (i = 0; parents && i < vec_size(parents); i++) {
      const char *tn = vec_fetch(parents, i, const char *);

      ObDictEntry *aPar = ode_create(tn, 0, ode->w);
      ObDictEntry *found = obdict_lookup(aDict, aPar);

      log_trace(DBG_CHANGE_ANCESTOR, "CONSIDER PARENT %s %s (parent of %s)\n", 
		(ode->w & REACH_S1) ? "S1" : "S2", tn, ode->trueName);

      if (found) {
	assert(nmequal(found->trueName, tn));
	found->w |= aPar->w;
	log_trace(DBG_CHANGE_ANCESTOR, "FOUND: 0x%x\n", found->w);
	if (found->w == REACH_BOTH)
	  return repos_GetEntity(r, mURI, found->trueName);
      }
      else {
	obdict_insert(aDict, aPar);
	log_trace(DBG_CHANGE_ANCESTOR, "APPEND %s %s (parent of %s)\n", 
		(aPar->w & REACH_S1) ? "S1" : "S2", tn, ode->trueName);
	obvec_append(avec, aPar);
      }
    }
  }

  return 0;
}

#if 0
/* Currently not used, but when we implement special cases for
 * multiple merges between commits, we'll need this. */
static OC_bool change_isAncestorOf (Repository *, const char *, const Change *,
				 const Change *,
				 EntityCache *bulkMergeCache)
     __attribute__ ((unused));
#endif

#if 0
static OC_bool
change_isAncestorOf (Repository *r, const char *mURI, const Change *parent, 
		     const Change *child)
{
  /* shap: This is counter-intuitive (translate: I got it wrong the
     first time :-). On the face of things, it might seem that you
     want to go down the child chain verifying that you cannot find
     the parent.

     Which is correct, but if the respective chains are long and they
     ARE related this can take a hell of a long time. This is
     especially true when you are handed parent/child that are
     reversed.

     In practice, we only compute this as a sanity check, and a pretty
     stupid one at that. If you are planning to compute the common
     change ancestor anyway, you are better off doing that and then
     checking that it is not the same as one of the inputs.  */
  const Change *chg = 
    change_find_common_ancestor(r, mURI, child, mURI, parent, REACH_BOTH); 

  if (chg && nmequal(ser_getTrueName(chg), ser_getTrueName(parent)))
    return TRUE;

  return FALSE;
}
#endif

/* NOTE: IT IS IMPERATIVE THAT WE USE THE "bulkMergeCache" ENTITY
   CACHE IN THE CALL TO entity_expand_ancestor_vector IN ORDER TO
   SURVIVE A FATAL BUG IN OUR MERGE ALGORITHM!!  THIS BUG EXISTS IN
   ALL OPENCM VERSIONS UP TO ALPHA-15 !! THIS FATAL BUG IS IN THE
   PROCESS OF BEING FIXED. */
static const Entity *
entity_do_find_common_ancestor(Repository *r,
			       const char *mURI1, ObVec *v1, 
			       const char *mURI2, ObVec *v2,
			       EntityCache *bulkMergeCache)
{
  do {
    unsigned u1, u2;

    for (u1 = 0; u1 < vec_size(v1); u1++) {
      const Entity *e1 = vec_fetch(v1, u1, const Entity *);
      for (u2 = 0; u2 < vec_size(v2); u2++) {
	const Entity *e2 = vec_fetch(v2, u2, const Entity *);
	if (nmequal(ser_getTrueName(e1), ser_getTrueName(e2)))
	  return e1;
      }
    }
  } while (entity_expand_ancestor_vector(r, mURI1, v1, bulkMergeCache) ||
	   entity_expand_ancestor_vector(r, mURI2, v2, bulkMergeCache));

  return 0;
}

static const Entity *
entity_find_common_ancestor(Repository *r,
			    const char *m1URI, const Entity *e1,
			    const char *m2URI, const Entity *e2,
			    EntityCache *bulkMergeCache)
{
  ObVec *v1 = obvec_create();
  ObVec *v2 = obvec_create();
  obvec_append(v1, e1);
  obvec_append(v2, e2);

  return entity_do_find_common_ancestor(r, m1URI, v1, 
					m2URI, v2, bulkMergeCache);
}

/* Construct a merge plan for merging the Change described by /other/
 * into the current workspace.
 *
 * The original design for this worked on entity sets rather than on
 * Changes. That was a mistake, as without having a common ancestor
 * Change it is impossible to figure out what has been added and
 * deleted correctly. The current design records ancestry in both
 * entities and Change objects.
 *
 * The revised algorithm proceeds as follows:
 *
 * 1. It finds the nearest common ancestor Change by expanding the
 *    ancestor lists until a common ancestor is found.
 *
 *    For the active entity set, we use the top change from which the
 *    workspace was derived.
 *
 * 2. It extracts the entity lists from base, merge, and ancestor
 *    Changes.
 *
 *    For the workspace, this entity set consists of synthetic
 *    entities that have /baseEntity/ (if present) /mergeEntity/ as
 *    it's predecessors, but has the current file name and type (as
 *    recorded in the WsEntity structure).
 *
 * 3. It sorts these entity lists by familyNID so that we can match
 *    them up more easily.
 *
 * 4. Build a set of triples consisting of (base entity, ancestor
 *    entity, merge entity)
 *
 * 5. Return this triple set to the caller.
 */
typedef struct MergeTuple MergeTuple;
struct MergeTuple {
  const WsEntity *ws;		
  const WsEntity *merge;
  const WsEntity *common;
  WsEntity *result;		/* output entity */
  unsigned ndx;
  const char *mergeInputName;
  const char *commonInputName;

  const char *mergeOutputName;
  const char *delScratchName;	/* temporary name for delete */
  const char *finalName;	/* final name */

  OC_bool  needRename;		/* true if merge output must be
				 * renamed to final name */

  OC_bool  needDelete;		/* true if current version in
                                 * workspace should be deleted in
                                 * pass 2 */

  OC_bool  wsUnchanged;		/* workspace object will not be changed */
};

static MergeTuple *
mergetuple_create()
{
  char *tmpname;
  
  static unsigned mergeCount = 0;

  MergeTuple *tup = (MergeTuple *) GC_MALLOC(sizeof(MergeTuple));

  tup->ndx = mergeCount++;
  
  /* FIX: This strategy only works if there is a lock on the workspace
   * file or some similar mutex!
   */
  tmpname = xstrdup("scratch/mergeoutput-xxxxxxxxxxxX");
  sprintf(tmpname, "scratch/mergeoutput-%u", tup->ndx);
  tup->mergeOutputName = path_join(CM_CONFIG_DIR,tmpname);
  onthrow_remove(tup->mergeOutputName);

  tmpname = xstrdup("scratch/del-xxxxxxxxxxxX");
  sprintf(tmpname, "scratch/del-%u", tup->ndx);
  tup->delScratchName = path_join(CM_CONFIG_DIR,tmpname);
  onthrow_remove(tup->delScratchName);

  tmpname = xstrdup("scratch/input-xxxxxxxxxxxX");
  sprintf(tmpname, "scratch/input-%u", tup->ndx);
  tup->mergeInputName = path_join(CM_CONFIG_DIR,tmpname);
  onthrow_remove(tup->mergeInputName);

  tmpname = xstrdup("scratch/common-xxxxxxxxxxxX");
  sprintf(tmpname, "scratch/common-%u", tup->ndx);
  tup->commonInputName = path_join(CM_CONFIG_DIR,tmpname);
  onthrow_remove(tup->commonInputName);

  assert(tup->result == 0);

  return tup;
}

/* Forward declaration (prototype). Returns true if there were conflicts */
OC_bool
merge3(Repository *r, const char *base_mURI, const char *merge_mURI, 
       const MergeTuple *mt);

static ObVec *
build_merge_plan(PendingChange *pc, Repository *r, 
		 const char *merge_mURI, Change *merge,
		 EntityCache *bulkMergeCache)
{
  unsigned u;
  const Change *ws_chg;
  const Change *common;
  ObVec *triples = obvec_create();
  
  rbtree *commonEnts = rbtree_create(rbtree_s_cmp, rbtree_s_cmpkey, TRUE);
  rbtree *mergeEnts = rbtree_create(rbtree_s_cmp, rbtree_s_cmpkey, TRUE);
  rbtree *wsEnts = rbtree_create(rbtree_s_cmp, rbtree_s_cmpkey, TRUE);
  
  /* Currently cannot build a merge plan for an empty branch yet. It
   * should be simple, I just haven't thought through the algorithms
   * involved. */
  assert(pc->nRevisions != 0);
  assert(pc->baseChangeName != 0);

  log_trace(DBG_MERGE, "Calling repos_GetEntity() for building merge plan... ");
  ws_chg = (Change *)repos_GetEntity(r, pc->branchURI, pc->baseChangeName);
  log_trace(DBG_MERGE, "done.\n");

  assert(GETTYPE(ws_chg) == TY_Change);
  
  /* Not yet dealing with the case where there is an existing merge. */
  assert(pc->mergedChange == 0);

  log_trace(DBG_CHANGE_ANCESTOR, "Finding common change ancestor\n");
  common = 
    change_find_common_ancestor(r, pc->branchURI, ws_chg,
				merge_mURI, merge, REACH_BOTH);

  log_trace(DBG_CHANGE_ANCESTOR, "Got common change ancestor\n");
  if (!common)
    THROW(ExNoObject, "No common ancestor from which to merge");

  /* See if the common ancestor is identical to the merge
     configuration. If so, the user is trying to merge an old state
     back into a later revision of the same line of development. This
     is a no-no. */

  log_trace(DBG_MERGE, "Checking if merge is ancestor of current...\n");
  if (nmequal(ser_getTrueName(common), ser_getTrueName(merge)))
    THROW(ExBadValue, "Merged version is ancestor of current workspace");

  /* Precache those entities that are already known to us via our existing
     PendingChange object and/or the common ancestor and/or the merge
     Change object. In the update case, it will turn out in practice
     that the nearest entitywise predecessor in any given merge tuple
     will be a member of one of these. By precaching these entities in
     the bulk merge cache, we can avoid fetching their originating
     Change objects altogether. */
  for (u = 0; u < vec_size(pc->entSet); u++) {
    const WsEntity *wse = vec_fetch(pc->entSet, u, const WsEntity *);
    const Entity *e = wse->old;
    if (e) {
      const char *tn = ser_getTrueName(e);
      ecache_insert(bulkMergeCache, tn, (Serializable *)e);
    }
  }

  for (u = 0; u < vec_size(CMGET(merge,entities)); u++) {
    Entity *e = vec_fetch(CMGET(merge,entities), u, Entity *);
    const char *tn = ser_getTrueName(e);
    ecache_insert(bulkMergeCache, tn, (Serializable *)e);
  }

  for (u = 0; u < vec_size(CMGET(common,entities)); u++) {
    Entity *e = vec_fetch(CMGET(common,entities), u, Entity *);
    const char *tn = ser_getTrueName(e);
    ecache_insert(bulkMergeCache, tn, (Serializable *)e);
  }
  

  log_trace(DBG_MERGE, "Done precacheing\n");

  /* Now we have to extract the entity vectors and make up a bunch of
   * triples.
   */

  {
    /* Fetch the entities from the inbound merge */

    for (u = 0; u < vec_size(CMGET(merge,entities)); u++) {
      Entity *e = vec_fetch(CMGET(merge,entities), u, Entity *);
      WsEntity *wse = wsentity_fromEntity(e);

      assert(GETTYPE(e) == TY_Entity);

      rbtree_insert(mergeEnts, 
		    rbnode_create(wse->familyNID, wse->famNidHash, wse));
    }
  }

  {
    /* Fetch the entities from the common ancestor Change */

    for (u = 0; u < vec_size(CMGET(common,entities)); u++) {
      Entity *e = vec_fetch(CMGET(common,entities), u, Entity *);
      WsEntity *wse = wsentity_fromEntity(e);
 
      assert(GETTYPE(e) == TY_Entity);

      rbtree_insert(commonEnts, 
		    rbnode_create(wse->familyNID, wse->famNidHash, wse));
    }
  }

  /* Use the WsEntity objects directly: */
  {
    for (u = 0; u < vec_size(pc->entSet); u++) {
      const WsEntity *wse = vec_fetch(pc->entSet, u, const WsEntity *);
      rbtree_insert(wsEnts, 
		    rbnode_create(wse->familyNID, wse->famNidHash, wse));
    }
  }

  log_trace(DBG_MERGE, "Build the merge plan\n");

#define until(x) while (! (x) )

  until (rbtree_isEmpty(wsEnts) 
	 && rbtree_isEmpty(commonEnts)
	 && rbtree_isEmpty(mergeEnts))
  {
    MergeTuple *tup = mergetuple_create();
    tup->common = 0;
    tup->ws = 0;
    tup->merge = 0;

    if (!rbtree_isEmpty(commonEnts)) {
      rbnode *ceNode = commonEnts->root;
      rbnode *peer;
      const WsEntity *commonEnt = ceNode->data;

      rbtree_remove(commonEnts, ceNode);

      tup->common = commonEnt;

      /* First, see if it has a peer in the workspace: */
      if ((peer = rbtree_find(wsEnts, &ceNode->value)) != TREE_NIL) {
	/* this COULD be a match, but might not be. */

	while (peer && !tup->ws) {
	  WsEntity *wsEnt = (WsEntity *) peer->data;

	  if (entity_isAncestorOf(r, pc->branchURI, commonEnt->old, 
				  wsEnt->old, bulkMergeCache)) {
	    tup->ws = wsEnt;
	    rbtree_remove(wsEnts, peer);
	    break;
	  }

	  peer = rbtree_succ(peer);

	  /* might have fallen off of the family: */
	  if (rbtree_compare_node_to_key(wsEnts, peer, &ceNode->value) != 0)
	    peer = 0;
	}
      }

      /* Then see if it has a peer in the merge space: */
      if ((peer = rbtree_find(mergeEnts, &ceNode->value)) != TREE_NIL) {
	/* this COULD be a match, but might not be. */

	while (peer && !tup->merge) {
	  WsEntity *mergeEnt = (WsEntity *) peer->data;

	  if (entity_isAncestorOf(r, pc->branchURI, commonEnt->old, 
				  mergeEnt->old, bulkMergeCache)) {
	    tup->merge = mergeEnt;
	    rbtree_remove(mergeEnts, peer);
	  }

	  peer = rbtree_succ(peer);

	  /* might have fallen off of the family: */
	  if (rbtree_compare_node_to_key(wsEnts, peer, &ceNode->value) != 0)
	    peer = 0;
	}
      }

      /* Fall out to the rename check below */
    }
    else if (!rbtree_isEmpty(wsEnts)) {
      rbnode *wsNode = wsEnts->root;
      rbnode *peer;

      const WsEntity *wsEnt = (WsEntity *) wsNode->data;

      rbtree_remove(wsEnts, wsNode);

      tup->ws = wsEnt;

      /* This case is only run when the common set is empty, so we
	 need not check for a common peer. See if it has a peer in the
	 merge space: */ 
      if ((peer = rbtree_find(mergeEnts, &wsNode->value)) != TREE_NIL) {
	/* this COULD be a match, but might not be. */

	while (peer && !tup->merge) {

	  WsEntity *mergeEnt = (WsEntity *) peer->data;

	  const Entity *tempent = 
	    entity_find_common_ancestor(r, 
					pc->branchURI, wsEnt->old, 
					merge_mURI, mergeEnt->old,
					bulkMergeCache);

	  /* In this case, the common entity ancestor might be in a
             different Change than the common change ancestor.  Ensure
             that there is a common entity ancestor and store it in
             the tup->common field. */
	  if (tempent) {
	    tup->common = wsentity_fromEntity(tempent);
	    tup->merge = mergeEnt;
	    rbtree_remove(mergeEnts, peer);
	  }

	  peer = rbtree_succ(peer);

	  /* might have fallen off of the family: */
	  if (rbtree_compare_node_to_key(wsEnts, peer, &wsNode->value) != 0)
	    peer = 0;
	}
      }

      /* Fall out to the rename check below */
    }
    else { /* mergeEnts is not empty */
      /* This case is only run when both the common set and the
	 workspace set are empty, so we need not check for peers at
	 all. Just insert this as an orphan from the merge space. */

      rbnode *mergeNode = mergeEnts->root;
      tup->merge = mergeNode->data;

      rbtree_remove(mergeEnts, mergeNode);
    }

    /* RENAME CASE ANALYSIS:
     *
     * The behavior of rename is orthogonal to anything
     * else. Possibilities:
     *
     *   Workspace    Merge      Resolution
     *   no rename    no rename  don't change it :-)
     *   rename       no rename  use workspace name
     *   no rename    rename     use merge name
     *   rename       rename     CONFLICT: use workspace name
     *
     * In the conflict case, we add a note to the workspace notes
     * string.
     *
     * Within the code below, we make a note of the planned final
     * name.
     */
       
    /* Keep the current name unless there has been a rename in the
     * merge configuration and NOT in the workspace configuration */
    if (tup->ws) {
      tup->finalName = tup->ws->cur_fsName;

      if (tup->merge)
	assert(tup->common);
      
      if (tup->common && tup->merge &&
	  nmequal(tup->ws->cur_fsName, tup->common->cur_fsName) &&
	  !nmequal(tup->ws->cur_fsName, tup->merge->cur_fsName))
	tup->finalName = tup->merge->cur_fsName;
    }
    else if (tup->merge)
      tup->finalName = tup->merge->cur_fsName;

    /* Make sure this Entity exists in merge or base: */
    if (tup->ws || tup->merge)
      obvec_append(triples, tup);
  }
  
  return triples;
}

static OC_bool
process_merge_tuple(WorkSpace *ws, const char *merge_mURI, Change *chg, 
		    MergeTuple *mt, OC_bool isUpdate)
{
#define ADD_TO_PENDINGCHANGE TRUE
#define REMOVE_FROM_PENDINGCHANGE FALSE 
  void *obj;

  assert(mt->common || mt->ws || mt->merge);
    
  assert(mt->result == NULL);

  if (mt->ws)
    mt->result = wsentity_ShallowCopy(mt->ws);
  
  /* Also, right off the bat we're going to change any NEF_DELETED
     status flags to NEF_CONDDEL if this is a 'merge'.  This ensures
     that any modifications to Workspace files (as a result of a
     merge) that have been marked for deletion are brought to the
     attention of the user. (Of course, only try this if there is a
     valid mt->result (which means a valid mt->ws).) */
  if (!isUpdate && mt->result) {
    if (mt->result->flags & NEF_DELETED) {
      mt->result->flags &= ~NEF_DELETED;
      mt->result->flags |= NEF_CONDDEL;
    }
  }

  /* ORPHAN CASES */
  if (mt->common == 0 && mt->ws) {
    /* This is an orphan in the workspace -- probably newly added.
       Leave the file alone in the workspace. */

    assert(mt->merge == 0);
      
    report(0, "Orphan \"%s\" in workspace unchanged.\n",
	   mt->ws->cur_fsName);

    mt->needRename = FALSE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = TRUE;

    return ADD_TO_PENDINGCHANGE;
  }
  else if (mt->common == 0 && mt->merge) {
    Buffer *buf;
      
    mt->needRename = TRUE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = FALSE;

    /* Adopt the merge entity into the workspace */
    mt->result = wsentity_ShallowCopy(mt->merge);
 
    /* For the 'merge' command, new files are unconditionally added
     * and we set NEF_MERGED and NEF_JADD .For the 'update'
     * command, new files are unconditionally added and the
     * mergeParent field is NOT updated (remains NULL) */
    if (!isUpdate)
      mt->result->flags |= (NEF_JADD | NEF_MERGED);

    report(0, "Planning to add \"%s\"\n", mt->result->cur_fsName);

    assert(mt->ws == 0);

    obj = repos_GetEntity(ws->r, merge_mURI, mt->result->old->contentTrueName);

    buf = (Buffer *)obj;

    assert(GETTYPE(buf) == TY_Buffer);

    buffer_ToFile(buf, mt->mergeOutputName, mt->result->cur_entityType);

    report(1, "Orphan \"%s\" in mergespace written to \"%s\"\n",
	   mt->merge->cur_fsName, mt->mergeOutputName);

    return ADD_TO_PENDINGCHANGE;
  }

  assert(mt->common);
    
  /* Have now covered all the cases where there was no common
   * entity. We can now rely on mt->common != 0. It is still
   * possible that no merge is required! */

  if (mt->ws && !mt->merge) {
    /* This file was deleted in the merge version, but not the
       Workspace.  If this is a 'merge', mark it NEF_DELETED (after
       cancelling NEF_CONDDEL if needed).  If this is an 'update' mark
       the file for deletion in the 'delete' pass.  If 'update' wants
       to delete a file that has NEF_MODIFIED set, we will remove the
       entity from PendingChange but leave the file in the workspace in
       case the user has important changes they don't want to lose. */

    mt->needRename = FALSE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = TRUE;	/* file in WS is not going away */

    /* For 'update', Workspace file is deleted from the workspace UNLESS
       user has modified the content (in which case WsEntity is
       removed from PendingChange but the file is left in the
       workspace in case the changes were something they don't want to
       lose. */
    if (isUpdate) {
      if (path_exists(mt->ws->cur_fsName) && 
	  (mt->ws->flags & NEF_MODIFIED) == 0) {
	mt->needDelete = TRUE;	/* don't actually remove it yet! */
	mt->wsUnchanged = FALSE;
      }

      return REMOVE_FROM_PENDINGCHANGE;
    }
    else {
      /* If CONDDEL, cancel CONDDEL first */
      if (mt->result->flags & NEF_CONDDEL)
	mt->result->flags &= ~ NEF_CONDDEL;

      /* Now mark it NEF_MERGED and NEF_DELETED */
      mt->result->flags = mt->ws->flags | NEF_MERGED | NEF_DELETED;

      report(0, "%s was deleted from merge version but "
	     "not from workspace.\n", mt->ws->cur_fsName);
    }
      
    return ADD_TO_PENDINGCHANGE;
  }
  else if (!mt->ws && mt->merge) {
    report(0, "%s was deleted in workspace version but not merge version\n",
	   mt->merge->cur_fsName);
      
    {
      Buffer *buf;

      mt->needRename = TRUE;
      mt->needDelete = FALSE;
      mt->wsUnchanged = FALSE;

      /* Adopt the merge object into the workspace: */
      mt->result = wsentity_ShallowCopy(mt->merge);

      if (!isUpdate)
	mt->result->flags |= (NEF_JADD | NEF_MERGED);

      obj = repos_GetEntity(ws->r, merge_mURI, 
			    mt->result->old->contentTrueName);

      buf = (Buffer *)obj;

      assert(GETTYPE(buf) == TY_Buffer);

      buffer_ToFile(buf, mt->mergeOutputName, mt->result->cur_entityType);
    }

    return ADD_TO_PENDINGCHANGE;
  }

  /* We are now doing the three-way case, but we might very well get
   * lucky and not need to do any work. */
  assert(mt->ws && mt->merge);
    
  if (nmequal(ser_getTrueName(mt->merge), 
	      ser_getTrueName(mt->common))) {
    /* Object did not change on the merge path -- keep the base
     * version and any renames. Note that doing this test before the
     * next one also captures the case of "unchanged in either
     * space" by keeping the active one in the workspace. */

    report(2, "Keeping \"%s\" -- unchanged from common in mergespace\n",
	   mt->ws->cur_fsName);

    mt->needRename = FALSE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = TRUE;	/* TRICKY CASE! */

    /* No need to shallowcopy mt->result, as we did so above. */

    /* However, if merge exec bit is set, we need to make sure it's
       set in the workspace version ! So, only carry over the
       entityPerms field if it is SET. */
    if (mt->merge->cur_entityPerms & EPRM_EXEC)
      mt->result->cur_entityPerms = EPRM_EXEC;

    return ADD_TO_PENDINGCHANGE;
  }

  /* Check if Workspace checkout state is same as common. */
  else if (nmequal(ser_getTrueName(mt->ws->old), 
		   ser_getTrueName(mt->common->old))) {
    unsigned flags = mt->ws->flags;

    /* Accept the merged entity into the workspace. (???)*/

    mt->needRename = TRUE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = FALSE;

    mt->result = wsentity_ShallowCopy(isUpdate ? mt->merge : mt->ws);

    if (!isUpdate) {
      mt->result->flags |= NEF_MERGED;

      mt->result->cur_mergeParent = ser_getTrueName(mt->merge->old);
      mt->result->cur_mergeParentChange = ser_getTrueName(chg);

      /* If the merge caused a permissions change, propagate that into
	 the workspace: */
      if ((mt->merge->cur_entityPerms & EPRM_EXEC) && 
	  (mt->ws->cur_entityPerms != mt->merge->cur_entityPerms))
	mt->result->cur_entityPerms = mt->merge->cur_entityPerms;

      /* Force status recomputation: */
      mt->result->lk_contentTrueName = 0;
      mt->result->lk_length = 0;
    }

    /* Deal with the content: */
    if (flags & NEF_MODIFIED) {
      const char *conflictMsg = "";
      if (merge3(ws->r, ws->pc->branchURI, merge_mURI, mt))
	conflictMsg = "Conflicts ";

      report(0, "%sUpdating \"%s\".\n", conflictMsg, mt->ws->cur_fsName);
    }
    else {
      Buffer *buf;

      obj = repos_GetEntity(ws->r, merge_mURI, mt->merge->old->contentTrueName);
      buf = (Buffer *)obj;

      buffer_ToFile(buf, mt->mergeOutputName, mt->result->cur_entityType);

      report(0, "Updating \"%s\".\n", mt->ws->cur_fsName);
    }


    /* If renamed in workspace, preserve workspace name. */
    if (flags & NEF_RENAMED)
      mt->result->cur_fsName = mt->ws->cur_fsName;

    /* If permissions were changed in the workspace, preserve them: */
    if (flags & NEF_PERMS)
      mt->result->cur_entityPerms = mt->ws->cur_entityPerms;

    goto decide_result_name;
  }
  else  {
    /* This is the yucky merge case :-) */
    unsigned flags = mt->ws->flags;

    mt->result = wsentity_ShallowCopy(isUpdate ? mt->merge : mt->ws);

    assert((mt->ws->flags & NEF_ADDED) == 0);
      
    mt->needRename = TRUE;
    mt->needDelete = FALSE;
    mt->wsUnchanged = FALSE;

    /* Note that we have NOT changed the output entity here -- we
     * have merged something into it! */
    if (!isUpdate) {
      mt->result->flags |= NEF_MERGED;

      mt->result->cur_mergeParent = ser_getTrueName(mt->merge->old);
      mt->result->cur_mergeParentChange = ser_getTrueName(chg);

      /* If the merge caused a permissions change, mark it such: */
      if ((mt->merge->cur_entityPerms & EPRM_EXEC) && 
	  (mt->ws->cur_entityPerms != mt->merge->cur_entityPerms))
	mt->result->cur_entityPerms = mt->merge->cur_entityPerms;

      /* Force status recomputation: */
      mt->result->lk_contentTrueName = 0;
      mt->result->lk_length = 0;
    }

    report(2, "\"%s\" Requires 3-way merge\n", mt->ws->cur_fsName);

    {
      const char *conflictMsg = "";
      if (merge3(ws->r, ws->pc->branchURI, merge_mURI, mt))
	conflictMsg = "Conflicts ";

      if (isUpdate)
	report(0, "%sUpdating \"%s\".\n", conflictMsg, mt->ws->cur_fsName);
      else
	report(0, "%sMerging \"%s\".\n", conflictMsg, mt->ws->cur_fsName);

    }
 
    if (flags & NEF_RENAMED)
      mt->result->cur_fsName = mt->ws->cur_fsName;
    
    if (flags & NEF_PERMS)
      mt->result->cur_entityPerms = mt->ws->cur_entityPerms;

    goto decide_result_name;
  }

 decide_result_name:
  /* File name resolution: 
   *
   * If renamed in the workspace, keep that name as the final
   * name. Otherwise take whatever name is active in the merge version
   * as final name. If uncommitted rename in workspace, keep that
   * rename. Else update result fsName too.  */
  {
    const char *name = mt->ws->cur_fsName;
	
    /* If did not change in base branch and changed in merge
     * branch, rename:
     */
    if (nmequal(name, mt->common->cur_fsName)  &&
	!nmequal(name, mt->merge->cur_fsName)) {
      name = mt->merge->cur_fsName;
      mt->needDelete = TRUE;	/* We need to remove the ws name */
    }

    if (!nmequal(mt->ws->cur_fsName, mt->common->cur_fsName)  &&
	!nmequal(mt->ws->cur_fsName, mt->merge->cur_fsName))
      mt->result->flags |= NEF_RENAMEALERT;

    /* If renamed by user, our rename doesn't count, but the
     * selected name must be updated in the new entity: */
    if ((mt->ws->flags & NEF_RENAMED) == 0) {
      if (!nmequal(mt->result->cur_fsName, name))
	mt->result->flags |= NEF_RENAMED;
      mt->result->cur_fsName = name;
    }
  }

  return ADD_TO_PENDINGCHANGE;
}

static void
check_name_collisions(ObVec *tuples, StrVec *names)
{
  unsigned u = 0;
  OC_bool collisions = FALSE;	/* until proven otherwise */

  /* Make sure names is sorted, since we use bsearch... */
  strvec_sort(names);

  /* Make a simulated deletion pass of the fsnames that are either
     going to disappear entirely (due to being deleted or renamed) or
     the fsnames that will be clobbered when we rename the scratch
     files (that are the result of a 'diff3') back into the ws.  We
     distinguish the former group by the 'needDelete' flag and the
     latter by 'needRename'.  NOTE: 'needRename' does NOT have
     anything to do with the NEF_RENAMED flag in this context! */
  for (u = 0; u < vec_size(tuples); u++) {
    const MergeTuple *mt = vec_fetch(tuples, u, const MergeTuple *);

    if (mt->needDelete == FALSE && mt->needRename == FALSE)
      continue;

    if (mt->ws == NULL)
      continue;

    assert(mt->result);
    assert(mt->ws->cur_fsName);
    assert(mt->finalName);
    {
      int ndx = strvec_bsearch(names, mt->ws->cur_fsName);
      if (ndx >= 0)
	vec_remove(names, ndx);
    }
  }

  /* Note that the names vector is still sorted, as we have only
     performed deletion in the previous pass. */

  /* Make a hypothetical collision check. */
  for (u = 0; u < vec_size(tuples); u++) {
    MergeTuple *mt = vec_fetch(tuples, u, MergeTuple *);

    if (mt->wsUnchanged)
      continue;

    assert(mt->result);
    assert(mt->finalName);

    if (strvec_bsearch(names, mt->finalName) >= 0) {
      collisions = TRUE;
      report(0,"Merge/Update would clobber \"%s\""
	      " -- move it out of the way.\n", mt->finalName);
    }
  }

  /* Wait until all collisions are reported before throwing the
     exception.  This allows the user to resolve all of them at
     once. */
  if (collisions)
    THROW(ExObjectExists, "Merge/Update name collisions!");
}

static void
delete_files(ObVec *tuples)
{
  unsigned u;

  for (u = 0; u < vec_size(tuples); u++) {
    const MergeTuple *mt = vec_fetch(tuples, u, const MergeTuple *);

    if (!mt->needDelete)
      continue;

    assert(mt->ws);
    assert(mt->result);

    if (path_exists(mt->ws->cur_fsName)) {
      report(0, "Deleting \"%s\"\n", mt->ws->cur_fsName);
      path_remove(mt->ws->cur_fsName);
    }
  }
}

static void
rename_files(ObVec *tuples)
{
  unsigned u;
  
  for (u = 0; u < vec_size(tuples); u++) {
    const MergeTuple *mt = vec_fetch(tuples, u, const MergeTuple *);

    if (!mt->needRename)
      continue;
    
    assert(mt->result);

    log_trace(DBG_MERGE, "Renaming \"%s\" => \"%s\"\n",
	      mt->mergeOutputName, mt->finalName);

    /* Make sure intermediate subdirs exist */
    path_smkdir(path_dirname(mt->finalName));

    /* Atomic operation: this will bash the current version of
       mt->finalName, if it exists. */
    path_rename(mt->mergeOutputName, mt->finalName);

  }
}

static void
set_executable_bits(ObVec *tuples, Change *chg)
{
  unsigned u;

  for (u = 0; u < vec_size(tuples); u++) {
    const MergeTuple *mt = vec_fetch(tuples, u, const MergeTuple *);

    /* If mt->finalName doesn't exist, it's because this file was
       permanently removed from workspace via 'update'. Skip
       those cases. */
    if (!path_exists(mt->finalName))
      continue;

    /* If there's no merge and no workspace, skip it */
    if (!mt->merge && !mt->ws)
      continue;
	
    assert(mt->result);

    /* Actually bash the bit based on the current setting in
       mt->result, which should have been set appropriately in
       'process_merge_tuple' */
    path_mkexecutable(mt->finalName, 
		      mt->result->cur_entityPerms ? TRUE : FALSE);

  }
}

/* Merge the Change described by /chg/ into the current
 * workspace.
 *
 * 1. Extract the respective entity sets. For /chg/ this is the
 *    entity set associated with the change. For the workspace this is
 *    the entity set of the workspace, which we will call /ws/. This
 *    is a hypothetical (uncommitted) successor to the configuration
 *    that was at the top of the branch at the time we checked the
 *    workspace out.
 *
 *    The WsEntity structures are derivatives of the Entity
 *    structures, so they can be used directly.
 *
 * 2. For each entity e in /ws/, extract from /ws/ and /chg/ the set
 *    of all entities whose familyNID matches the familyNID of
 *    /e/. This creates a partition on familyNID, within which we will
 *    have to match up the appropriate objects.
 *
 * 3. Perform a merge within that partition.
 *
 * COMPLICATIONS
 *
 * This whole thing is considerably complicated by the possibility of
 * simultaneous renames, and also by collisions between deletes and
 * adds. For renames, the most obvious problem arises when somebody
 * does:
 *
 *    opencm checkout
 *    opencm mv x tmp
 *    opencm mv y x
 *    opencm mv tmp y
 *    opencm commit
 *
 * For adds/deletes, the bad case is
 *
 *    one space      the other space
 *    ----------     ---------------
 *    X deleted      X added or Y renamed to X
 *
 * The add/delete issue can happen where either space is the
 * workspace.
 *
 * We don't want to mess with the workspace until we absolutely know
 * that the merge is done and we are down to rename operations. We
 * might still conceivably crash somewhere during the renames, but at
 * that point we at least know that we aren't going to run out of
 * space.
 *
 * The strategy adopted goes as follows:
 *
 * 1. Perform all of the updates and fetches necessary. Put the output
 *    into the .merge files (which serve us as temporary files). If
 *    this runs to completion, then you know that you are done
 *    allocating disk space and have now reduced the problem to
 *    renames.
 *
 * 2. Now run through renaming all of the files that are to be deleted
 *    and all of the files that will be replaced by something we just
 *    merged. We rename these to .opencm/.del-xxx files so that we can
 *    do crash recovery. We do not currently actually DO crash
 *    recovery.
 *
 * 3. Run through the merge plan renaming files to their final output
 *    names. It is possible that this will collide, either by rename
 *    or by merge/delete.
 *
 * Name collisions currently are not handled correctly, but at least
 * after I do the above I'll have reduced the issue to addressing the
 * name collision problem.
 *
 * We *do* try to take advantage of the fact that many things don't
 * change in the course of an update by avoiding renaming them.
 *
 * COLLISION HANDLING
 *
 * For pure updates, in the absence of changes in the workspace, all
 * collisions can be handled without consultation. Logically, we
 * simply check the whole thing out again.
 *
 * The following cases can also be resolved automatically:
 *
 * 1. [Single, non-colliding rename] If a rename has been done in
 *    /chg/ or in /ws/, but not in both, and if the resulting name
 *    does not collide, there is no need to consult the user -- just
 *    apply the rename.
 *
 * 2. [Single add] If an object has been added in /chg/ or /ws/, but
 *    and the name does not collide, simply add it. Colliding adds in
 *    the end look just like colliding renames.
 *
 * 3. [Delete in both] If an object has been deleted in both spaces,
 *    it stays deleted. :-)
 *
 * 4. [Mergeable Merge] Even if there is a conflict, we need only make a
 *    note of it. If it's a binary merge, we may find that we need to
 *    fall back on a user merge.
 *
 * 5. [Unmergeable Merge] (e.g. binary) There just isn't a good
 *    solution. Choices are keep current and rename /chg/ or vice
 *    versa. We bias in favor of 'keep current'. Take the new version
 *    into a new name and make the user deal with it.
 *
 * 6. At the moment, we inquire about all other cases.
 */

OC_bool
ws_mergeFrom(WorkSpace *ws, Repository *r, const char *merge_mURI, 
	     Change *chg, OC_bool isUpdate)
{
  unsigned u;
  void *obj;
  ObVec *tuples;
  StrVec *names;

  EntityCache *bulkMergeCache = ecache_create(BULK_ECACHE_SZ);

  /* Since we are doing a complex iteration, build up a new vector of
     objects that will completely replace the current PendingChange
     entSet. */
  ObVec *wsVec = obvec_create();

  Change *curBaseConfig;

  obj = repos_GetEntity(ws->r, ws->pc->branchURI, ws->pc->baseChangeName);
  curBaseConfig = (Change *)obj;

  assert(GETTYPE(curBaseConfig) == TY_Change);

  if (!isUpdate) {
    if (ws->pc->mergedChange) {
      THROW(ExObjectExists, "A previous merge has not been committed");

      /* Eventually, we want to support the following logic, but there is an
       * 'assert' in build_merge_plan() that checks for
       * ws->pc->mergeChange. */
#if 0
      Change *curMergeConfig;
      obj = repos_GetEntity(r, ws->pc->branchURI, ws->pc->mergedChange);

      curMergeConfig = (Change *)obj;

      assert(GETTYPE(curMergeConfig) == TY_Change);

      if (change_isAncestorOf(r, merge_mURI, curMergeConfig, chg) == 0)
	THROW(ExObjectExists, "A previous merge has not been committed");
#endif
    }

    /* Possible that the merge is attempting to merge the same
       configuration. Don't allow that. */
    if (nmequal(ser_getTrueName(curBaseConfig), ser_getTrueName(chg))) {
      report(0, "Workspace is up to date.\n");
      return TRUE;
    }

    /* NOTE: We used to do a sanity check here to make sure that the
       in-merging change is not an ancestor of the workspace. This is
       a stupid thing to do, because if it ISN'T you end up
       back-walking a potentially very long change history over a
       wire. We learned this the hard way with the EROS code, which
       has several thousand changes. In practice, the user doing the
       merge already should know that there is a common ancestry
       relationship, and this is an unlikely outcome. Given which, we
       now go ahead and compute the common change ancestor and then
       test to see if it is the same as the merge version before
       proceeding. */
  }

  /* Now that we've checked preconditions, proceed: */
  tuples = build_merge_plan(ws->pc, r, merge_mURI, chg, bulkMergeCache);

  log_trace(DBG_MERGE, "Merge plan built\nNow executing merge plan\n");

  /* PASS 1: BUILD THE MERGE RESULT */

  for (u = 0; u < vec_size(tuples); u++) {
    MergeTuple *mt = vec_fetch(tuples, u, MergeTuple *);

    if (process_merge_tuple(ws, merge_mURI, chg, mt, isUpdate))
      obvec_append(wsVec, mt->result);
  }

  log_trace(DBG_MERGE, "Merge plan executed\n"
	    "    Now checking for name collisions\n");

  /* PASS 1.5 -- enumerate the current workspace and process the
     deletes so that we can detect name collisions before we do
     anything fatally stupid to the user's workspace. */

  /* FIX: There is a rather bad bug here, which is that a delete,
     merge, undelete can lead to serious problems. This needs to be
     resolved. If the user hasn't actually committed the delete, we
     shouldn't be processing it, but if the delete is the result of
     the merge, we probably should. YUCK. We REALLY need to normalize
     all of this. */

  /* Abort immediately if there are any name collisions detected.  We
     have to do this after executing the merge plan, because the
     'merge' or 'update' command may add new files to the Workspace
     and/or may rename current Workspace files. */
  names = strvec_create();

  /* Enumerate the filesystem space, getting everything.  Then do a
     name collision check against that list. */
  ws_EnumeratePath(ws, names, path_curdir(), WSE_UNFILTERED, 0);
  check_name_collisions(tuples, names);

  log_trace(DBG_MERGE, "No name collisions\n"
	    "    Now deleting and renaming files\n");

  /* PASS 2: Delete the things that are supposed to get deleted. NOTE:
     This is for filenames that would otherwise be dangling, not
     filenames that are going to get (properly) clobbered by updated
     versions. */
  delete_files(tuples);

  /* PASS 3: Perform the desired renames.  NOTE: This means renaming
     necessary scratch files into the Workspace as opposed to
     processing the NEF_RENAMED flag! */
  rename_files(tuples);

  log_trace(DBG_MERGE, "Now setting executable bits\n");

  /* PASS 4:  Set executable bit appropriately */
  set_executable_bits(tuples, chg);

  /* Now update the user's workspace with the state of affairs after
   * the merge. */
  ws->pc->entSet = wsVec;
  
  if (!isUpdate) {  
    report(0, "Merge is complete. Please run 'commit' to save any changes.\n");

    /* This is the 'flag' that indicates a 'merge' was performed.
     * Multiple 'merge's are currently not allowed.  User must 'commit'
     * to clear this field. */
    ws->pc->mergedChange = ser_getTrueName(chg);
  }
  else {
    report(0, "Update is complete.\n");
    ws->pc->baseChangeName = ser_getTrueName(chg);
    ws->pc->baseCmtInfoName = CMGET(chg,commitInfoTrueName);
  }

  return TRUE;
}

OC_bool
merge3(Repository *r, const char *base_mURI, const char *merge_mURI,
       const MergeTuple *mt)
{
  /* This now handles only the text merge itself */

  Buffer *commonBuf;
  Buffer *mergeBuf;
  void *obj1;
  void *obj2;

  obj1 = repos_GetEntity(r, base_mURI, mt->common->old->contentTrueName);
  obj2 = repos_GetEntity(r, merge_mURI, mt->merge->old->contentTrueName);

  commonBuf = (Buffer *)obj1;
  mergeBuf =  (Buffer *)obj2;

  buffer_ToFile(mergeBuf, mt->mergeInputName, mt->merge->cur_entityType);
  buffer_ToFile(commonBuf, mt->commonInputName, mt->common->cur_entityType);
  
  {
    int conflicts = 0;
#define argc 6
    char *argv[argc+1]; /* +1 for the terminating NULL */

    /* diff3 manpage specifies order of args is:  diff3 <mine> <older> <yours>
    * It looks to me like <mine> should be <base>, <older> should be <common>, and <yours>
    * should be <merge>.  So, need to fix the order here:   Vanderburgh (11/05/2001):
    argv[0] = "diff3";
    argv[1] = "-E";
    argv[2] = "-am";
    argv[3] = (char *) mt->ws->fsName;
    argv[4] = (char *) mt->mergeInputName;
    argv[5] = (char *) mt->commonInputName;
    argv[6] = 0;
    */

    argv[0] = "diff3";
    argv[1] = "-E";
    argv[2] = "-am";
    argv[3] = (char *) mt->ws->cur_fsName;
    argv[4] = (char *) mt->commonInputName;
    argv[5] = (char *) mt->mergeInputName;
    argv[6] = 0;
    
#if 0
    {
      int i = 0;
      xprintf("Calling");
      for (i = 0; i < argc; i++)
	xprintf(" %s", argv[i]);

      xprintf("\n");
    }
#endif
    
    conflicts =
      diff3_run(argc, argv, (char *) mt->mergeOutputName, 0);
#undef argc

    return conflicts ? TRUE : FALSE;
  }
}
