/*
 *  SPDX-FileCopyrightText: Copyright © 2006 Keith Packard <keithp@keithp.com>
 *
 *  SPDX-License-Identifier: GPL-2.0+
 */

/*
 * Build one in-core linked list corresponding to a single CVS
 * master.  Just one entry point, cvs_master_digest(), which takes the
 * structure built by the grammar parse of the master as one of its
 * arguments.
 */
#include "cvs.h"
#include "hash.h"

#ifdef REDBLACK
#include "rbtree.h"
#endif /* REDBLACK */

#ifdef THREADS
#include <pthread.h>
#endif

const master_dir *root_dir = NULL;
#ifdef THREADS
static pthread_mutex_t root_dir_mutex = PTHREAD_MUTEX_INITIALIZER;
#endif

static const char *fileop_name(const char *rectified) {
	size_t rlen = strlen(rectified);

	if (rlen >= 10 && strcmp(rectified + rlen - 10, ".cvsignore") == 0) {
		char path[PATH_MAX];
		strncpy(path, rectified, PATH_MAX - 1);
		path[rlen - 9] = 'g';
		path[rlen - 8] = 'i';
		path[rlen - 7] = 't';
		return atom(path);
	}
	// assume rectified is already an atom
	return rectified;
}

static const char *dir_name(const char *filename) {
	char *slash = strrchr(filename, '/');
	if (slash) {
		char buf[PATH_MAX];
		strncpy(buf, filename, slash - filename);
		buf[slash - filename] = '\0';
		return atom(buf);
	} else {
		return atom("\0");
	}
}

#define DIR_BUCKETS 24593

typedef struct _dir_bucket {
	struct _dir_bucket *next;
	master_dir dir;
} dir_bucket;

static dir_bucket *dir_buckets[DIR_BUCKETS];
#ifdef THREADS
static pthread_mutex_t dir_bucket_mutex;

void atom_dir_init(void) {
	pthread_mutexattr_t attr;

	pthread_mutexattr_init(&attr);
	pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE);
	pthread_mutex_init(&dir_bucket_mutex, &attr);
}
#endif /* THREADS */

/* Extract information about the directory a master is in .
 * atomize the result so all references to the same directory
 * point the the same value.
 * Needs to be called with an atomized string
 */
static const master_dir *atom_dir(const char *dirname) {
	dir_bucket **head = &dir_buckets[HASH_VALUE(dirname) % DIR_BUCKETS];
	dir_bucket *b;

#ifdef THREADS
	if (threads > 1) {
		pthread_mutex_lock(&dir_bucket_mutex);
	}
#endif /* THREADS */
	while ((b = *head)) {
	collision:
		if (b->dir.name == dirname) {
#ifdef THREADS
			if (threads > 1) {
				pthread_mutex_unlock(&dir_bucket_mutex);
			}
#endif /* THREADS */
			return &(b->dir);
		}
		head = &(b->next);
	}
	if ((b = *head)) {
		goto collision;
	}
	b = xmalloc(sizeof(dir_bucket), __func__);
	b->next = NULL;
	b->dir.name = dirname;
	*head = b;
	if (strlen(dirname) > 0) {
		/* recursive mutex use, find parent dir */
		b->dir.parent = atom_dir(dir_name(dirname));
	} else {
		b->dir.parent = NULL;
	}
#ifdef THREADS
	if (threads > 1) {
		pthread_mutex_unlock(&dir_bucket_mutex);
	}
#endif /* THREADS */
	return &(b->dir);
}

/* given a single-file revlist tree, locate the specific version number */
static cvs_commit *cvs_master_find_revision(cvs_master *cm,
                                            const cvs_number *number) {
	rev_ref *h;
	cvs_commit *c;

	for (h = cm->heads; h; h = h->next) {
		if (h->tail) {
			continue;
		}
		for (c = h->commit; c; c = c->parent) {
			if (cvs_number_compare(c->number, number) == 0) {
				return c;
			}
			if (c->tail) {
				break;
			}
		}
	}
	return NULL;
}

static rev_master *build_rev_master(cvs_file *cvs, rev_master *master) {
	master->name = cvs->export_name;
	master->fileop_name = fileop_name(cvs->export_name);
	master->dir = atom_dir(dir_name(master->name));
	master->mode = cvs->mode;
	master->commits =
	    xcalloc(cvs->nversions, sizeof(cvs_commit), "commit slab alloc");
	master->ncommits = 0;
	return master;
}

/* build a list of commit objects representing a branch from deltas on * it */
static cvs_commit *cvs_master_branch_build(cvs_file *cvs, rev_master *master,
                                           const cvs_number *branch) {
	cvs_number n;
	const cvs_number *atom_n;
	cvs_commit *head = NULL;
	cvs_commit *c, *p, *gc;
	node_t *node;
#if CVSDEBUG
	char buf[CVS_MAX_REV_LEN];

	if (cvs->verbose > 0) {
		debugmsg("\tstarting new branch, branch number = %s\n",
		         cvs_number_string(branch, buf, CVS_MAX_REV_LEN));
	}
#endif /* CVSDEBUG */

	memcpy(&n, branch, sizeof(cvs_number));
	n.n[n.c - 1] = -1;
	atom_n = atom_cvs_number(n);
	for (node = cvs_find_version(cvs, atom_n); node; node = node->next) {
		cvs_version *v = node->version;
		cvs_patch *patch = node->patch;
		cvs_commit *commit;
		if (!v) {
			continue;
		}
		commit = master->commits + master->ncommits++;
		commit->dir = master->dir;
		commit->date = v->date;
		commit->commitid = v->commitid;
		commit->author = v->author;
		commit->tail = commit->tailed = false;
		commit->refcount = commit->serial = 0;
		if (patch != NULL) {
			commit->log = patch->log;
		}
		commit->dead = v->dead;
		/* leave this around so the branch merging stuff can find
		 * numbers */
		commit->master = master;
		commit->number = v->number;
		if (!v->dead) {
			node->commit = commit;
		}
		commit->parent = head;
		/* commits are already interned, these hashes build up revdir
		 * hashes */
		commit->hash = HASH_VALUE(c);
		head = commit;
	}

	if (head == NULL) {
		/* coverity[leaked_storage] */
		return NULL;
	}

	/*
	 * Make sure the dates along the branch are well ordered. As we
	 * want to preserve current data, push previous versions back to
	 * align with newer revisions. (The branch is being traversed
	 * in reverse order. p = parent, c = child, gc = grandchild.)
	 */
	for (c = head, gc = NULL; (p = c->parent); gc = c, c = p) {
		if (time_compare(p->date, c->date) > 0) {
			atom_n = NULL;
			/*
			 * Try to catch an odd one out, such as a commit with
			 * the clock set wrong.  Don't push back all commits for
			 * that, just fix up the current commit instead of the
			 * parent.
			 *
			 * This may look like it's doing a bad thing to the
			 * integrity of the input data, but in reality we don't
			 * get here unless some commit times were messed up to
			 * begin with.
			 *
			 * The best thing we can do in this situation is replace
			 * garbage with a well-formed timestamp sequence that is
			 * not too grossly fictional.
			 */
			if (gc && time_compare(p->date, gc->date) <= 0) {
				c->date = p->date;
				atom_n = c->number;
			} else {
				p->date = c->date;
				atom_n = p->number;
			}
			if (!nowarn) {
				warn("warning - %s:", cvs->gen.master_name);
				dump_number_file(logfile, " ", p->number);
				dump_number_file(logfile, " is newer than",
				                 c->number);
				if (atom_n) {
					dump_number_file(logfile, ", adjusting",
					                 atom_n);
				}
				fprintf(logfile, "\n");
			}
		}
	}

#if CVSDEBUG
	if (cvs->verbose > 0) {
		debugmsg("\tnew branch, head number = %s\n",
		         cvs_number_string(head->number, buf, CVS_MAX_REV_LEN));
	}
#endif /* CVSDEBUG */

	/* coverity[leaked_storage] */
	return head;
}

#ifdef __UNUSED__
static void excise_branch(cvs_master *cm, rev_ref *branchtokill) {
	rev_ref *branchhead;
	/* find current vendor branch tip among all the branch tips ... */
	for (branchhead = cm->heads; branchhead;
	     branchhead = branchhead->next) {
		if (branchhead->next == branchtokill) {
			/* ... and excise it */
			branchhead->next = branchtokill->next;
		}
	}
}
#endif /* __UNUSED__ */

/*
 * "Vendor branches" (1.1.x, with odd x) are created by importing
 * sources from an external source. In X.org, this was from XFree86
 * and DRI. When these trees are imported, cvs sets the 'default'
 * branch in each ,v file to point along this branch. This means that
 * tags made between the time the vendor branch is imported and when a
 * new revision is committed to the head branch are placed on the
 * vendor branch In addition, any files without such a commit appear
 * to adopt the vendor branch as 'head'.
 *
 * The original behavior of this code was to fix this by merging the
 * vendor branch into the master branch, as if they were the same.
 * This produced incorrect behavior on repos where there was a
 * vendor-branch revision more recent than the tip of the master
 * branch.
 *
 * If the vendor branch has no 1.2, what we do now is point the "master"
 * named reference at the tip revision of the lowest numbered vendor branch
 * commit, then splice the old tip to the old branch, then delete the
 * vendor branch reference.
 *
 * A side effect of this code is to give a synthetic label to each
 * vendor branch that has not already been named.
 */
static bool cvs_master_patch_vendor_branch(cvs_master *cm) {
	rev_ref *trunk = cm->heads;
	rev_ref *vendor = NULL;
	rev_ref *nvendor = NULL;
	bool found_vendor = false;

	assert(strcmp(trunk->ref_name, "master") == 0);
	/* walk all the list of branch heads */
	for (vendor = cm->heads; vendor; vendor = vendor->next) {
		if (vendor->commit && cvs_is_vendor(vendor->commit->number)) {
			/* found a vendor branch by its numbering scheme
			 * (1.1.{odd}.n) */
			found_vendor = true;
#ifdef DEBUG
			if (enable_debug(DEBUG_CVSDEBUG)) {
				char vrev[CVS_MAX_REV_LEN];
				cvs_number_string(vendor->commit->number, vrev,
				                  sizeof(vrev));
				debugmsg("Vendor branch ending in %s\n", vrev);
			}
#endif /* DEBUG */

			/* stash pointer to newest vendor branch, will need it
			 * later */
			nvendor = vendor;

			if (!vendor->ref_name) {
				/* Vendor branch without a name: invent one */
				char rev[CVS_MAX_REV_LEN];
				char name[PATH_MAX];
				cvs_number branch;
				cvs_commit *vlast;

				/* walk down vendor branch to its initial
				 * commit, 1.1.{odd}.1 */
				for (vlast = vendor->commit; vlast;
				     vlast = vlast->parent) {
					if (!vlast->parent) {
						break;
					}
				}
				memcpy(&branch, vlast->number,
				       sizeof(cvs_number));
				/* reduce 1.1.{odd}.1 to 1.1.{odd}, and
				 * synthesize a name from that */
				branch.c--;
				cvs_number_string(&branch, rev, sizeof(rev));
				snprintf(name, sizeof(name), "import-%s", rev);
				/* attach new name to the vendor branch tip */
				vendor->ref_name = atom(name);
			}
			/* link vendor branch tip to head */
			vendor->parent = trunk;

			/*
			 * Degree used to be set from vlast->number->c;
			 * this should be equivalent, since the branches
			 * have not yet been grafted.
			 */
			vendor->degree = vendor->commit->number->c;
			vendor->number = vendor->commit->number;
		}
	}

	/* if there's a vendor branch and no commit 1.2... */
	if (nvendor != NULL && trunk->commit->parent == NULL) {
		cvs_commit *vlast, *oldtip = trunk->commit;
		trunk->commit = nvendor->commit;
		trunk->degree = nvendor->commit->number->c;
		trunk->number = nvendor->commit->number;
		for (vlast = trunk->commit; vlast; vlast = vlast->parent) {
			if (!vlast->parent) {
				vlast->parent = oldtip;
				break;
			}
		}
		for (vendor = cm->heads; vendor; vendor = vendor->next) {
			if (vendor->next == nvendor) {
				vendor->next = nvendor->next;
			}
		}
	}

	return found_vendor;
}

/* turn disconnected branches into a tree by grafting roots to parents */
static void cvs_master_graft_branches(cvs_master *cm, cvs_file *cvs) {
	rev_ref *h;
	cvs_commit *c;
	cvs_version *cv;
	cvs_branch *cb;

	/*
	 * Glue branches together
	 */
	for (h = cm->heads; h; h = h->next) {
		/*
		 * skip master branch; it "can't" join
		 * any other branches and it may well end with a vendor
		 * branch revision of the file, which will then create
		 * a loop back to the recorded branch point
		 */
		if (h == cm->heads) {
			continue;
		}
		if (h->tail) {
			continue;
		}
		/*
		 * Find last commit on branch
		 */
		for (c = h->commit; c && c->parent; c = c->parent) {
			if (c->tail) {
				c = NULL; /* already been done, skip */
				break;
			}
		}
		if (c) {
			/*
			 * Walk the version tree, looking for the branch
			 * location. Note that in the presense of vendor
			 * branches, the branch location may actually be out on
			 * that vendor branch
			 */
			for (cv = cvs->gen.versions; cv; cv = cv->next) {
				for (cb = cv->branches; cb; cb = cb->next) {
					if (cvs_number_compare(
					        cb->number, c->number) == 0) {
						c->parent =
						    cvs_master_find_revision(
						        cm, cv->number);
						c->tail = true;
						break;
					}
				}
				if (c->parent) {
#if 0
		    /*
		     * check for a parallel vendor branch
		     */
		    for (cb = cv->branches; cb; cb = cb->next) {
			if (cvs_is_vendor(cb->number)) {
			    cvs_number	v_n;
			    cvs_commit	*v_c, *n_v_c;
			    warn("Found merge into vendor branch\n");
			    memcpy(&v_n, cb->number, sizeof(cvs_number));
			    v_c = NULL;
			    /*
			     * Walk to head of vendor branch
			     */
			    while ((n_v_c = cvs_master_find_revision(cm, atom_cve_number(v_n))))
			    {
				/*
				 * Stop if we reach a date after the
				 * branch version date
				 */
				if (time_compare(n_v_c->date, c->date) > 0) {
				      break;
				}
				v_c = n_v_c;
				v_n.n[v_n.c - 1]++;
			    }
			    if (v_c)
			    {
				warn("%s: rewrite branch", cvs->name);
				dump_number_file(logfile, " branch point",
						  v_c->number);
				dump_number_file(logfile, " branch version",
						  c->number);
				fprintf(logfile, "\n");
				c->parent = v_c;
			    }
			}
		    }
#endif
					break;
				}
			}
		}
	}
}

/* look up a revision reference in a revlist by symbol */
static rev_ref *cvs_master_find_branch(cvs_master *cm,
                                       const cvs_number *number) {
	cvs_number n;
	rev_ref *h;

	if (number->c < 2) {
		return NULL;
	}
	memcpy(&n, number, sizeof(cvs_number));
	h = NULL;
	while (n.c >= 2) {
		const cvs_number *k = atom_cvs_number(n);
		for (h = cm->heads; h; h = h->next) {
			if (cvs_same_branch(h->number, k)) {
				break;
			}
		}
		if (h) {
			break;
		}
		n.c -= 2;
	}
	return h;
}

/* create head references or tags for each symbol in the CVS master */
static void cvs_master_set_refs(cvs_master *cm, cvs_file *cvsfile) {
	rev_ref *h, **ph, *h2;
	cvs_symbol *s;

	for (s = cvsfile->symbols; s; s = s->next) {
		cvs_commit *c = NULL;
		/*
		 * Locate a symbolic name for this head
		 */
		if (cvs_is_head(s->number)) {
			for (h = cm->heads; h; h = h->next) {
				if (cvs_same_branch(h->commit->number,
				                    s->number)) {
					break;
				}
			}
			if (h) {
				if (!h->ref_name) {
					h->ref_name = s->symbol_name;
					h->degree =
					    cvs_number_degree(s->number);
				} else {
					h = rev_list_add_head(
					    cm, h->commit, s->symbol_name,
					    cvs_number_degree(s->number));
				}
			} else {
				cvs_number n;

				memcpy(&n, s->number, sizeof(cvs_number));
				while (n.c >= 4) {
					n.c -= 2;
					c = cvs_master_find_revision(
					    cm, atom_cvs_number(n));
					if (c) {
						break;
					}
				}
				if (c) {
					h = rev_list_add_head(
					    cm, c, s->symbol_name,
					    cvs_number_degree(s->number));
				}
			}
			if (h) {
				h->number = s->number;
			}
		} else {
			c = cvs_master_find_revision(cm, s->number);
			if (c) {
				tag_commit(c, s->symbol_name, cvsfile);
			}
		}
	}
	/*
	 * Fix up unnamed heads.  Give each one a synthetic branch tag named
	 * after the branch root.
	 */
	for (h = cm->heads; h; h = h->next) {
		cvs_number n;
		cvs_commit *c;

		if (h->ref_name) {
			continue;
		}
		for (c = h->commit; c; c = c->parent) {
			if (!c->dead) {
				break;
			}
		}
		if (!c) {
			char buf[CVS_MAX_REV_LEN];
			/*
			 * Strange edge case here.  Every revision on the branch
			 * is in state 'dead', and there's no tag pointing to
			 * it. (Yes, this has been seen in the wild.)  The code
			 * used to just do a 'continue' here; this produced
			 * spurious unnumbered-head messages.
			 *
			 * We choose to discard the dead branch on the theory
			 * that these revisions couldn't have been visible in
			 * the archival state of the CVS, either. They might
			 * have been visible at some past time in the evolution
			 * of the repo, but that state is impossible to
			 * reconstruct.
			 *
			 * This is going to leave some allocated storage
			 * hanging.
			 */
			h->number = atom_cvs_number(cvs_zero);
			warn("discarding dead untagged branch %s in %s\n",
			     cvs_number_string(h->commit->number, buf,
			                       sizeof(buf)),
			     cvsfile->export_name);
			continue;
		}
		memcpy(&n, c->number, sizeof(cvs_number));
		/* convert to branch form */
		n.n[n.c - 1] = n.n[n.c - 2];
		n.n[n.c - 2] = 0;
		h->number = atom_cvs_number(n);
		h->degree = cvs_number_degree(&n);
		/* compute name after patching parents */
	}
	/* discard zero-marked heads */
	for (ph = &cm->heads; *ph; ph = &(h2->next)) {
		h2 = *ph;
		if ((*ph)->number == atom_cvs_number(cvs_zero)) {
			*ph = (*ph)->next;
		}
	}

	/*
	 * Link heads together in a tree
	 */
	for (h = cm->heads; h; h = h->next) {
		cvs_number n;

		/* might have been flagged for discard above */
		if (h->number == atom_cvs_number(cvs_zero)) {
			continue;
		}

		/*
		 * keithp: can get unnumbered heads here
		 * not sure what that means
		 *
		 * ESR: I found a bug in the code for patching vendor branches
		 * that produced these. It is likely this is now a can't-happen.
		 * I have re-tagged it as "internal error" but left it in place
		 * just in case.
		 */
		if (!h->number) {
			h->number = atom_cvs_number(cvs_zero);
			if (h->ref_name) {
				warn("internal error - unnumbered head %s in "
				     "%s\n",
				     h->ref_name, cvsfile->export_name);
			} else {
				warn("internal error - unnumbered head in %s\n",
				     cvsfile->export_name);
			}
		}

		if (h->number->c >= 4) {
			memcpy(&n, h->number, sizeof(cvs_number));
			n.c -= 2;
			h->parent =
			    cvs_master_find_branch(cm, atom_cvs_number(n));
			if (!h->parent && !cvs_is_vendor(h->number)) {
				warn("warning - non-vendor %s branch %s has no "
				     "parent\n",
				     cvsfile->gen.master_name, h->ref_name);
			}
		}
		if (h->parent && !h->ref_name) {
			char name[1024];
			char rev[CVS_MAX_REV_LEN];

			cvs_number_string(h->number, rev, sizeof(rev));
			if (h->commit->commitid) {
				sprintf(name, "%s-UNNAMED-BRANCH-%s",
				        h->parent->ref_name,
				        h->commit->commitid);
			} else {
				sprintf(name, "%s-UNNAMED-BRANCH",
				        h->parent->ref_name);
			}
			warn("warning - putting %s rev %s on unnamed branch %s "
			     "off %s\n",
			     cvsfile->gen.master_name, rev, name,
			     h->parent->ref_name);
			h->ref_name = atom(name);
		}
	}
}

#ifdef REDBLACK
/* compare function used for red-black tree lookup */
static int cvs_symbol_name_compare(const void *x, const void *y) {
	if (x < y) {
		return -1;
	} else if (y < x) {
		return 1;
	} else {
		return 0;
	}
}
#endif /* REDBLACK */

/* return the CVS symbol corresponding to a specified name */
static cvs_symbol *cvs_find_symbol(cvs_file *cvs, const char *name) {
#ifdef REDBLACK
	struct rbtree_node *n, **tree;

	tree = &cvs->symbols_by_name;
	if (!(*tree)) {
		cvs_symbol *s;
		for (s = cvs->symbols; s; s = s->next) {
			rbtree_insert(tree, s->symbol_name, s,
			              cvs_symbol_name_compare);
		}
	}

	n = rbtree_lookup(*tree, name, cvs_symbol_name_compare);
	if (n) {
		return (cvs_symbol *)rbtree_value(n);
	}
#else
	cvs_symbol *s;

	for (s = cvs->symbols; s; s = s->next) {
		if (s->symbol_name == name) {
			return s;
		}
	}
#endif /*  REDBLACK */
	return NULL;
}

/* comparison function used for topological sorting */
static int rev_ref_compare(cvs_file *cvs, const rev_ref *r1,
                           const rev_ref *r2) {
	cvs_symbol *s1, *s2;
	s1 = cvs_find_symbol(cvs, r1->ref_name);
	s2 = cvs_find_symbol(cvs, r2->ref_name);
	if (!s1) {
		if (!s2) {
			return 0;
		}
		return -1;
	}
	if (!s2) {
		return 1;
	}
	return cvs_number_compare(s1->number, s2->number);
}

/* sort branch heads so parents are always before children; trunk first. */
static void cvs_master_sort_heads(cvs_master *cm, cvs_file *cvs) {
	rev_ref *p = cm->heads, *q;
	rev_ref *e;
	rev_ref *l = NULL, *lastl = NULL;
	int k = 1;
	int i, psize, qsize;

	/*
	 * Implemented from description at
	 * http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
	 */
	for (;;) {
		int passmerges = 0;

		passmerges = 0;

		while (p) {

			passmerges++;

			q = p;
			qsize = k;
			psize = 0;
			for (i = 0; i < k; i++) {
				if (!q->next) {
					break;
				}
				psize++;
				q = q->next;
			}

			while (psize || (qsize && q)) {
				if (!psize) {
					e = q;
				} else if (!(qsize && q)) {
					e = p;
				} else if (rev_ref_compare(cvs, p, q) > 0) {
					e = q;
				} else {
					e = p;
				}

				/*
				 * If the element ever equals q, it is always
				 * safe to assume it will come from q. The same
				 * is not true for p as p == q when psize == 0
				 */
				if (e == q) {
					e = q;
					q = q->next;
					qsize--;
				} else {
					e = p;
					p = p->next;
					psize--;
				}

				/*
				 * Break the element out of its old list and
				 * append it to the new sorted list
				 */
				e->next = NULL;
				if (l) {
					lastl->next = e;
					lastl = e;
				} else {
					l = lastl = e;
				}
			}
			p = q;
		}

		if (passmerges <= 1) {
			break;
		}

		p = l;
		l = lastl = NULL;
		k = 2 * k;
	}

	cm->heads = l;
#ifdef DEBUG
	if (enable_debug(DEBUG_CVSDEBUG) && cvs->verbose > 0) {
		debugmsg("Sorted heads for %s\n", cvs->gen.master_name);
		for (e = cm->heads; e;) {
			debugmsg("\t");
			// cvs_master_dump_ref_parents(stderr, e->parent);
			dump_number_file(logfile, e->ref_name, e->number);
			debugmsg("\n");
			e = e->next;
		}
	}
#endif /* DEBUG */
}

/* fill out a linked list capturing the CVS master file structure */
cvs_commit *cvs_master_digest(cvs_file *cvs, cvs_master *cm,
                              rev_master *master) {
	const cvs_number *trunk_number;
	cvs_commit *trunk;
	cvs_commit *branch;
	cvs_version *cv;
	cvs_branch *cb;
	cvs_version *ctrunk = NULL;

#ifdef THREADS
	if (threads > 1)
		pthread_mutex_lock(&root_dir_mutex);
#endif
	if (!root_dir) {
		root_dir = atom_dir(atom("\0"));
	}
#ifdef THREADS
	if (threads > 1)
		pthread_mutex_unlock(&root_dir_mutex);
#endif
	build_rev_master(cvs, master);
#if CVSDEBUG
	char buf[CVS_MAX_REV_LEN];
#endif /* CVSDEBUG */

	build_branches(&cvs->gen.nodehash);
	/*
	 * Locate first revision on trunk branch
	 */
	for (cv = cvs->gen.versions; cv; cv = cv->next) {
		if (cvs_is_trunk(cv->number) &&
		    (!ctrunk ||
		     cvs_number_compare(cv->number, ctrunk->number) < 0)) {
			ctrunk = cv;
		}
	}
	/*
	 * Generate trunk branch
	 */
	if (ctrunk) {
		trunk_number = ctrunk->number;
	} else {
		trunk_number = atom_cvs_number(lex_number("1.1"));
	}
	trunk = cvs_master_branch_build(cvs, master, trunk_number);
	if (trunk) {
		rev_ref *t;
		t = rev_list_add_head(cm, trunk, atom("master"), 2);
		t->number = trunk_number;
#if CVSDEBUG
		if (cvs->verbose > 0) {
			debugmsg(
			    "Building trunk branch %s for %s:\n",
			    cvs_number_string(t->number, buf, CVS_MAX_REV_LEN),
			    cvs->gen.master_name);
		}
#endif /* CVSDEBUG */
	} else {
		warn("warning - no master branch generated\n");
		return NULL; /* cannot proceed with this master file */
	}
#ifdef DEBUG
	/*
	 * Search for other branches
	 */
	if (enable_debug(DEBUG_CVSDEBUG) && cvs->verbose > 0) {
		debugmsg("Building non-trunk branches for %s:\n",
		         cvs->gen.master_name);
	}
#endif /* DEBUG */

	for (cv = cvs->gen.versions; cv; cv = cv->next) {
		for (cb = cv->branches; cb; cb = cb->next) {
			branch =
			    cvs_master_branch_build(cvs, master, cb->number);
#ifdef DEBUG
			if (enable_debug(DEBUG_CVSDEBUG) && cvs->verbose > 0) {
				char buf[CVS_MAX_REV_LEN];
				char buf2[CVS_MAX_REV_LEN];
				char buf3[CVS_MAX_REV_LEN];
				debugmsg("\t%s\t->\t%s\t->\t%s\n",
				         cvs_number_string(cv->number, buf,
				                           CVS_MAX_REV_LEN),
				         cvs_number_string(cb->number, buf2,
				                           CVS_MAX_REV_LEN),
				         cvs_number_string(branch->number, buf3,
				                           CVS_MAX_REV_LEN));
			}
#endif /* DEBUG */
			rev_list_add_head(cm, branch, NULL, 0);
		}
	}
	if (cvs_master_patch_vendor_branch(cm)) {
		cvs->vendor_warning = true;
	}
	cvs_master_graft_branches(cm, cvs);
	cvs_master_set_refs(cm, cvs);
	cvs_master_sort_heads(cm, cvs);
	rev_list_set_tail(cm);

#ifdef DEBUG
	if (enable_debug(DEBUG_CVSDEBUG) && cvs->verbose > 0) {
		rev_ref *lh;

		debugmsg("Named heads in %s:\n", cvs->gen.master_name);
		for (lh = cm->heads; lh; lh = lh->next) {
			char buf[CVS_MAX_REV_LEN];

			debugmsg("\tname = %s\tnumber = %s\n", lh->ref_name,
			         cvs_number_string(lh->number, buf,
			                           CVS_MAX_REV_LEN));
		}
	}
#endif /* DEBUG */

	// rev_list_validate(cm);
	return trunk; /* to allow testing for an error in calling function */
}

/* Local Variables:    */
/* mode: c             */
/* c-basic-offset: 8   */
/* indent-tabs-mode: t */
/* End:                */
