PageRenderTime 101ms CodeModel.GetById 4ms app.highlight 75ms RepoModel.GetById 1ms app.codeStats 1ms

/cvsps.c

https://github.com/jamesblackburn/cvsps
C | 2697 lines | 1894 code | 418 blank | 385 comment | 472 complexity | 59115bef20254ad0543839ec9ac4a915 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
   3 * See COPYING file for license information 
   4 */
   5
   6#include <stdio.h>
   7#include <stdlib.h>
   8#include <string.h>
   9#include <limits.h>
  10#include <unistd.h>
  11#include <search.h>
  12#include <time.h>
  13#include <ctype.h>
  14#include <sys/stat.h>
  15#include <sys/types.h>
  16#include <fcntl.h>
  17#include <regex.h>
  18#include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
  19
  20#include <cbtcommon/hash.h>
  21#include <cbtcommon/list.h>
  22#include <cbtcommon/text_util.h>
  23#include <cbtcommon/debug.h>
  24#include <cbtcommon/rcsid.h>
  25
  26#include "cache.h"
  27#include "cvsps_types.h"
  28#include "cvsps.h"
  29#include "util.h"
  30#include "stats.h"
  31#include "cap.h"
  32#include "cvs_direct.h"
  33#include "list_sort.h"
  34
  35RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
  36
  37#define CVS_LOG_BOUNDARY "----------------------------\n"
  38#define CVS_FILE_BOUNDARY "=============================================================================\n"
  39
  40enum
  41{
  42    NEED_RCS_FILE,
  43    NEED_WORKING_FILE,
  44    NEED_SYMS,
  45    NEED_EOS,
  46    NEED_START_LOG,
  47    NEED_REVISION,
  48    NEED_DATE_AUTHOR_STATE,
  49    NEED_EOM
  50};
  51
  52/* true globals */
  53struct hash_table * file_hash;
  54CvsServerCtx * cvs_direct_ctx;
  55char root_path[PATH_MAX];
  56char repository_path[PATH_MAX];
  57
  58const char * tag_flag_descr[] = {
  59    "",
  60    "**FUNKY**",
  61    "**INVALID**",
  62    "**INVALID**"
  63};
  64
  65const char * fnk_descr[] = {
  66    "",
  67    "FNK_SHOW_SOME",
  68    "FNK_SHOW_ALL",
  69    "FNK_HIDE_ALL",
  70    "FNK_HIDE_SOME"
  71};
  72
  73/* static globals */
  74static int ps_counter;
  75static void * ps_tree;
  76static struct hash_table * global_symbols;
  77static char strip_path[PATH_MAX];
  78static int strip_path_len;
  79static time_t cache_date;
  80static int update_cache;
  81static int ignore_cache;
  82static int do_write_cache;
  83static int statistics;
  84static const char * test_log_file;
  85static struct hash_table * branch_heads;
  86static struct list_head all_patch_sets;
  87static struct list_head collisions;
  88
  89/* settable via options */
  90static int timestamp_fuzz_factor = 300;
  91static int do_diff;
  92static const char * restrict_author;
  93static int have_restrict_log;
  94static regex_t restrict_log;
  95static int have_restrict_file;
  96static regex_t restrict_file;
  97static time_t restrict_date_start;
  98static time_t restrict_date_end;
  99static const char * restrict_branch;
 100static struct list_head show_patch_set_ranges;
 101static int summary_first;
 102static const char * norc = "";
 103static const char * patch_set_dir;
 104static const char * restrict_tag_start;
 105static const char * restrict_tag_end;
 106static int restrict_tag_ps_start;
 107static int restrict_tag_ps_end = INT_MAX;
 108static const char * diff_opts;
 109static int bkcvs;
 110static int no_rlog;
 111static int cvs_direct;
 112static int compress;
 113static char compress_arg[8];
 114static int track_branch_ancestry;
 115
 116static void check_norc(int, char *[]);
 117static int parse_args(int, char *[]);
 118static int parse_rc();
 119static void load_from_cvs();
 120static void init_paths();
 121static CvsFile * build_file_by_name(const char *);
 122static CvsFile * parse_rcs_file(const char *);
 123static CvsFile * parse_working_file(const char *);
 124static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
 125static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
 126static void check_print_patch_set(PatchSet *);
 127static void print_patch_set(PatchSet *);
 128static void assign_patchset_id(PatchSet *);
 129static int compare_rev_strings(const char *, const char *);
 130static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
 131static int compare_patch_sets_bk(const void *, const void *);
 132static int compare_patch_sets(const void *, const void *);
 133static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
 134static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
 135static int is_revision_metadata(const char *);
 136static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
 137static int patch_set_affects_branch(PatchSet *, const char *);
 138static void do_cvs_diff(PatchSet *);
 139static PatchSet * create_patch_set();
 140static PatchSetRange * create_patch_set_range();
 141static void parse_sym(CvsFile *, char *);
 142static void resolve_global_symbols();
 143static int revision_affects_branch(CvsFileRevision *, const char *) __attribute__ ((noinline));;
 144static int is_vendor_branch(const char *);
 145static void set_psm_initial(PatchSetMember * psm);
 146static int check_rev_funk(PatchSet *, CvsFileRevision *);
 147static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
 148static int before_tag(CvsFileRevision * rev, const char * tag) __attribute__ ((noinline));;
 149static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
 150static void handle_collisions();
 151
 152int main(int argc, char *argv[])
 153{
 154    debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
 155
 156    INIT_LIST_HEAD(&show_patch_set_ranges);
 157
 158    /*
 159     * we want to parse the rc first, so command line can override it
 160     * but also, --norc should stop the rc from being processed, so
 161     * we look for --norc explicitly first.  Note: --norc in the rc 
 162     * file itself will prevent the cvs rc file from being used.
 163     */
 164    check_norc(argc, argv);
 165
 166    if (strlen(norc) == 0 && parse_rc() < 0)
 167	exit(1);
 168
 169    if (parse_args(argc, argv) < 0)
 170	exit(1);
 171
 172    if (diff_opts && !cvs_direct && do_diff)
 173    {
 174	debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
 175	debug(DEBUG_APPMSG1, "         which is usually used to create diffs.  'cvs diff'");
 176	debug(DEBUG_APPMSG1, "         will be used instead, but the resulting patches ");
 177	debug(DEBUG_APPMSG1, "         will need to be applied using the '-p0' option");
 178	debug(DEBUG_APPMSG1, "         to patch(1) (in the working directory), ");
 179	debug(DEBUG_APPMSG1, "         instead of '-p1'\n");
 180    }
 181
 182    file_hash = create_hash_table(1023);
 183    global_symbols = create_hash_table(111);
 184    branch_heads = create_hash_table(1023);
 185    INIT_LIST_HEAD(&all_patch_sets);
 186    INIT_LIST_HEAD(&collisions);
 187
 188    /* this parses some of the CVS/ files, and initializes
 189     * the repository_path and other variables 
 190     */
 191    init_paths();
 192
 193    if (!ignore_cache)
 194    {
 195	int save_fuzz_factor = timestamp_fuzz_factor;
 196
 197	/* the timestamp fuzz should only be in effect when loading from
 198	 * CVS, not re-fuzzed when loading from cache.  This is a hack
 199	 * working around bad use of global variables
 200	 */
 201
 202	timestamp_fuzz_factor = 0;
 203
 204	if ((cache_date = read_cache()) < 0)
 205	    update_cache = 1;
 206
 207	timestamp_fuzz_factor = save_fuzz_factor;
 208    }
 209
 210    if (cvs_direct && (do_diff || (update_cache && !test_log_file)))
 211	cvs_direct_ctx = open_cvs_server(root_path, compress);
 212
 213    if (update_cache)
 214    {
 215	load_from_cvs();
 216	do_write_cache = 1;
 217    }
 218
 219    //XXX
 220    //handle_collisions();
 221
 222    list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
 223
 224    ps_counter = 0;
 225    walk_all_patch_sets(assign_patchset_id);
 226
 227    handle_collisions();
 228
 229    resolve_global_symbols();
 230
 231    if (do_write_cache)
 232	write_cache(cache_date);
 233
 234    if (statistics)
 235	print_statistics(ps_tree);
 236
 237    /* check that the '-r' symbols (if specified) were resolved */
 238    if (restrict_tag_start && restrict_tag_ps_start == 0 && 
 239	strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
 240    {
 241	debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
 242	exit(1);
 243    }
 244
 245    if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
 246    {
 247	debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
 248	exit(1);
 249    }
 250
 251    walk_all_patch_sets(check_print_patch_set);
 252
 253    if (summary_first++)
 254	walk_all_patch_sets(check_print_patch_set);
 255
 256    if (cvs_direct_ctx)
 257	close_cvs_server(cvs_direct_ctx);
 258
 259    exit(0);
 260}
 261
 262static void load_from_cvs()
 263{
 264    FILE * cvsfp;
 265    char buff[BUFSIZ];
 266    int state = NEED_RCS_FILE;
 267    CvsFile * file = NULL;
 268    PatchSetMember * psm = NULL;
 269    char datebuff[20];
 270    char authbuff[AUTH_STR_MAX];
 271    int logbufflen = LOG_STR_MAX + 1;
 272    char * logbuff = malloc(logbufflen);
 273    int loglen = 0;
 274    int have_log = 0;
 275    char cmd[BUFSIZ];
 276    char date_str[64];
 277    char use_rep_buff[PATH_MAX];
 278    char * ltype;
 279
 280    if (logbuff == NULL)
 281    {
 282	debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen);
 283	exit(1);
 284    }
 285
 286    if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
 287    {
 288	ltype = "rlog";
 289	snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
 290    }
 291    else
 292    {
 293	ltype = "log";
 294	use_rep_buff[0] = 0;
 295    }
 296
 297    if (cache_date > 0)
 298    {
 299	struct tm * tm = gmtime(&cache_date);
 300	strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
 301
 302	/* this command asks for logs using two different date
 303	 * arguments, separated by ';' (see man rlog).  The first
 304	 * gets all revisions more recent than date, the second 
 305	 * gets a single revision no later than date, which combined
 306	 * get us all revisions that have occurred since last update
 307	 * and overlaps what we had before by exactly one revision,
 308	 * which is necessary to fill in the pre_rev stuff for a 
 309	 * PatchSetMember
 310	 */
 311	snprintf(cmd, BUFSIZ, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
 312    }
 313    else
 314    {
 315	date_str[0] = 0;
 316	snprintf(cmd, BUFSIZ, "cvs %s %s -q %s %s", compress_arg, norc, ltype, use_rep_buff);
 317    }
 318    
 319    debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
 320
 321    cache_date = time(NULL);
 322
 323    /* FIXME: this is ugly, need to virtualize the accesses away from here */
 324    if (test_log_file)
 325	cvsfp = fopen(test_log_file, "r");
 326    else if (cvs_direct_ctx)
 327	cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
 328    else
 329	cvsfp = popen(cmd, "r");
 330
 331    if (!cvsfp)
 332    {
 333	debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
 334	exit(1);
 335    }
 336
 337    for (;;)
 338    {
 339	char * tst;
 340	if (cvs_direct_ctx)
 341	    tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
 342	else
 343	    tst = fgets(buff, BUFSIZ, cvsfp);
 344
 345	if (!tst)
 346	    break;
 347
 348	debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
 349
 350	switch(state)
 351	{
 352	case NEED_RCS_FILE:
 353	    if (strncmp(buff, "RCS file", 8) == 0) {
 354              if ((file = parse_rcs_file(buff)) != NULL)
 355		state = NEED_SYMS;
 356              else
 357                state = NEED_WORKING_FILE;
 358            }
 359	    break;
 360	case NEED_WORKING_FILE:
 361	    if (strncmp(buff, "Working file", 12) == 0) {
 362              if ((file = parse_working_file(buff)))
 363		state = NEED_SYMS;
 364              else
 365                state = NEED_RCS_FILE;
 366		break;
 367	    } else {
 368              // Working file come just after RCS file. So reset state if it was not found
 369              state = NEED_RCS_FILE;
 370            }
 371            break;
 372	case NEED_SYMS:
 373	    if (strncmp(buff, "symbolic names:", 15) == 0)
 374		state = NEED_EOS;
 375	    break;
 376	case NEED_EOS:
 377	    if (!isspace(buff[0]))
 378	    {
 379		/* see cvsps_types.h for commentary on have_branches */
 380		file->have_branches = 1;
 381		state = NEED_START_LOG;
 382	    }
 383	    else
 384		parse_sym(file, buff);
 385	    break;
 386	case NEED_START_LOG:
 387	    if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
 388		state = NEED_REVISION;
 389	    break;
 390	case NEED_REVISION:
 391	    if (strncmp(buff, "revision", 8) == 0)
 392	    {
 393		char new_rev[REV_STR_MAX];
 394		CvsFileRevision * rev;
 395
 396		strcpy(new_rev, buff + 9);
 397		chop(new_rev);
 398
 399		/* 
 400		 * rev may already exist (think cvsps -u), in which
 401		 * case parse_revision is a hash lookup
 402		 */
 403		rev = parse_revision(file, new_rev);
 404
 405		/* 
 406		 * in the simple case, we are copying rev to psm->pre_rev
 407		 * (psm refers to last patch set processed at this point)
 408		 * since generally speaking the log is reverse chronological.
 409		 * This breaks down slightly when branches are introduced 
 410		 */
 411
 412		assign_pre_revision(psm, rev);
 413
 414		/*
 415		 * if this is a new revision, it will have no post_psm associated.
 416		 * otherwise we are (probably?) hitting the overlap in cvsps -u 
 417		 */
 418		if (!rev->post_psm)
 419		{
 420		    psm = rev->post_psm = create_patch_set_member();
 421		    psm->post_rev = rev;
 422		    psm->file = file;
 423		    state = NEED_DATE_AUTHOR_STATE;
 424		}
 425		else
 426		{
 427		    /* we hit this in cvsps -u mode, we are now up-to-date
 428		     * w.r.t this particular file. skip all of the rest 
 429		     * of the info (revs and logs) until we hit the next file
 430		     */
 431		    psm = NULL;
 432		    state = NEED_EOM;
 433		}
 434	    }
 435	    break;
 436	case NEED_DATE_AUTHOR_STATE:
 437	    if (strncmp(buff, "date:", 5) == 0)
 438	    {
 439		char * p;
 440
 441		strncpy(datebuff, buff + 6, 19);
 442		datebuff[19] = 0;
 443
 444		strcpy(authbuff, "unknown");
 445		p = strstr(buff, "author: ");
 446		if (p)
 447		{
 448		    char * op;
 449		    p += 8;
 450		    op = strchr(p, ';');
 451		    if (op)
 452		    {
 453			strzncpy(authbuff, p, op - p + 1);
 454		    }
 455		}
 456		
 457		/* read the 'state' tag to see if this is a dead revision */
 458		p = strstr(buff, "state: ");
 459		if (p)
 460		{
 461		    char * op;
 462		    p += 7;
 463		    op = strchr(p, ';');
 464		    if (op)
 465			if (strncmp(p, "dead", MIN(4, op - p)) == 0)
 466			    psm->post_rev->dead = 1;
 467		}
 468
 469		state = NEED_EOM;
 470	    }
 471	    break;
 472	case NEED_EOM:
 473	    if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
 474	    {
 475		if (psm)
 476		{
 477		    PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
 478		    patch_set_add_member(ps, psm);
 479		}
 480
 481		logbuff[0] = 0;
 482		loglen = 0;
 483		have_log = 0;
 484		state = NEED_REVISION;
 485	    }
 486	    else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
 487	    {
 488		if (psm)
 489		{
 490		    PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
 491		    patch_set_add_member(ps, psm);
 492		    assign_pre_revision(psm, NULL);
 493		}
 494
 495		logbuff[0] = 0;
 496		loglen = 0;
 497		have_log = 0;
 498		psm = NULL;
 499		file = NULL;
 500		state = NEED_RCS_FILE;
 501	    }
 502	    else
 503	    {
 504		/* other "blahblah: information;" messages can 
 505		 * follow the stuff we pay attention to
 506		 */
 507		if (have_log || !is_revision_metadata(buff))
 508		{
 509		    /* If the log buffer is full, try to reallocate more. */
 510		    if (loglen < logbufflen)
 511		    {
 512			int len = strlen(buff);
 513			
 514			if (len >= logbufflen - loglen)
 515			{
 516			    debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename);
 517			    logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX);
 518			    char * newlogbuff = realloc(logbuff, logbufflen);
 519			    if (newlogbuff == NULL)
 520			    {
 521				debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen);
 522				exit(1);
 523			    }
 524			    logbuff = newlogbuff;
 525			}
 526
 527			debug(DEBUG_STATUS, "appending %s to log", buff);
 528			memcpy(logbuff + loglen, buff, len);
 529			loglen += len;
 530			logbuff[loglen] = 0;
 531			have_log = 1;
 532		    }
 533		}
 534		else 
 535		{
 536		    debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
 537		}
 538	    }
 539
 540	    break;
 541	}
 542    }
 543
 544    if (state == NEED_SYMS)
 545    {
 546	debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
 547	debug(DEBUG_APPERROR, "       Perhaps you should try running with --norc");
 548	exit(1);
 549    }
 550
 551    if (state != NEED_RCS_FILE)
 552    {
 553	debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d)  Use -v to debug", state);
 554	exit(1);
 555    }
 556    
 557    if (test_log_file)
 558    {
 559	fclose(cvsfp);
 560    }
 561    else if (cvs_direct_ctx)
 562    {
 563	cvs_rlog_close(cvs_direct_ctx);
 564    }
 565    else
 566    {
 567	if (pclose(cvsfp) < 0)
 568	{
 569	    debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
 570	    exit(1);
 571	}
 572    }
 573}
 574
 575static int usage(const char * str1, const char * str2)
 576{
 577    if (str1)
 578	debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
 579
 580    debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]]  ");
 581    debug(DEBUG_APPERROR, "             [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
 582    debug(DEBUG_APPERROR, "             [-b <branch>]  [-l <regex>] [-r <tag> [-r <tag>]] ");
 583    debug(DEBUG_APPERROR, "             [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
 584    debug(DEBUG_APPERROR, "             [--test-log <captured cvs log file>] [--bkcvs]");
 585    debug(DEBUG_APPERROR, "             [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
 586    debug(DEBUG_APPERROR, "             [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
 587    debug(DEBUG_APPERROR, "             [-q] [-A] [<repository>]");
 588    debug(DEBUG_APPERROR, "");
 589    debug(DEBUG_APPERROR, "Where:");
 590    debug(DEBUG_APPERROR, "  -h display this informative message");
 591    debug(DEBUG_APPERROR, "  -x ignore (and rebuild) cvsps.cache file");
 592    debug(DEBUG_APPERROR, "  -u update cvsps.cache file");
 593    debug(DEBUG_APPERROR, "  -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
 594    debug(DEBUG_APPERROR, "  -g generate diffs of the selected patch sets");
 595    debug(DEBUG_APPERROR, "  -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
 596    debug(DEBUG_APPERROR, "  -a <author> restrict output to patch sets created by author");
 597    debug(DEBUG_APPERROR, "  -f <file> restrict output to patch sets involving file");
 598    debug(DEBUG_APPERROR, "  -d <date1> -d <date2> if just one date specified, show");
 599    debug(DEBUG_APPERROR, "     revisions newer than date1.  If two dates specified,");
 600    debug(DEBUG_APPERROR, "     show revisions between two dates.");
 601    debug(DEBUG_APPERROR, "  -b <branch> restrict output to patch sets affecting history of branch");
 602    debug(DEBUG_APPERROR, "  -l <regex> restrict output to patch sets matching <regex> in log message");
 603    debug(DEBUG_APPERROR, "  -r <tag1> -r <tag2> if just one tag specified, show");
 604    debug(DEBUG_APPERROR, "     revisions since tag1. If two tags specified, show");
 605    debug(DEBUG_APPERROR, "     revisions between the two tags.");
 606    debug(DEBUG_APPERROR, "  -p <directory> output patch sets to individual files in <directory>");
 607    debug(DEBUG_APPERROR, "  -v show very verbose parsing messages");
 608    debug(DEBUG_APPERROR, "  -t show some brief memory usage statistics");
 609    debug(DEBUG_APPERROR, "  --norc when invoking cvs, ignore the .cvsrc file");
 610    debug(DEBUG_APPERROR, "  --summary-first when multiple patch sets are shown, put all summaries first");
 611    debug(DEBUG_APPERROR, "  --test-log <captured cvs log> supply a captured cvs log for testing");
 612    debug(DEBUG_APPERROR, "  --diff-opts <option string> supply special set of options to diff");
 613    debug(DEBUG_APPERROR, "  --bkcvs special hack for parsing the BK -> CVS log format");
 614    debug(DEBUG_APPERROR, "  --no-rlog disable rlog (it's faulty in some setups)");
 615    debug(DEBUG_APPERROR, "  --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
 616    debug(DEBUG_APPERROR, "  --debuglvl <bitmask> enable various debug channels.");
 617    debug(DEBUG_APPERROR, "  -Z <compression> A value 1-9 which specifies amount of compression");
 618    debug(DEBUG_APPERROR, "  --root <cvsroot> specify cvsroot.  overrides env. and working directory (cvs-direct only)");
 619    debug(DEBUG_APPERROR, "  -q be quiet about warnings");
 620    debug(DEBUG_APPERROR, "  -A track and report branch ancestry");
 621    debug(DEBUG_APPERROR, "  <repository> apply cvsps to repository.  overrides working directory");
 622    debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
 623
 624    return -1;
 625}
 626
 627static int parse_args(int argc, char *argv[])
 628{
 629    int i = 1;
 630    while (i < argc)
 631    {
 632	if (strcmp(argv[i], "-z") == 0)
 633	{
 634	    if (++i >= argc)
 635		return usage("argument to -z missing", "");
 636
 637	    timestamp_fuzz_factor = atoi(argv[i++]);
 638	    continue;
 639	}
 640	
 641	if (strcmp(argv[i], "-g") == 0)
 642	{
 643	    do_diff = 1;
 644	    i++;
 645	    continue;
 646	}
 647	
 648	if (strcmp(argv[i], "-s") == 0)
 649	{
 650	    PatchSetRange * range;
 651	    char * min_str, * max_str;
 652
 653	    if (++i >= argc)
 654		return usage("argument to -s missing", "");
 655
 656	    min_str = strtok(argv[i++], ",");
 657	    do
 658	    {
 659		range = create_patch_set_range();
 660
 661		max_str = strrchr(min_str, '-');
 662		if (max_str)
 663		    *max_str++ = '\0';
 664		else
 665		    max_str = min_str;
 666
 667		range->min_counter = atoi(min_str);
 668
 669		if (*max_str)
 670		    range->max_counter = atoi(max_str);
 671		else
 672		    range->max_counter = INT_MAX;
 673
 674		list_add(&range->link, show_patch_set_ranges.prev);
 675	    }
 676	    while ((min_str = strtok(NULL, ",")));
 677
 678	    continue;
 679	}
 680	
 681	if (strcmp(argv[i], "-a") == 0)
 682	{
 683	    if (++i >= argc)
 684		return usage("argument to -a missing", "");
 685
 686	    restrict_author = argv[i++];
 687	    continue;
 688	}
 689
 690	if (strcmp(argv[i], "-l") == 0)
 691	{
 692	    int err;
 693
 694	    if (++i >= argc)
 695		return usage("argument to -l missing", "");
 696
 697	    if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
 698	    {
 699		char errbuf[256];
 700		regerror(err, &restrict_log, errbuf, 256);
 701		return usage("bad regex to -l", errbuf);
 702	    }
 703
 704	    have_restrict_log = 1;
 705
 706	    continue;
 707	}
 708
 709	if (strcmp(argv[i], "-f") == 0)
 710	{
 711	    int err;
 712
 713	    if (++i >= argc)
 714		return usage("argument to -f missing", "");
 715
 716	    if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
 717	    {
 718		char errbuf[256];
 719		regerror(err, &restrict_file, errbuf, 256);
 720		return usage("bad regex to -f", errbuf);
 721	    }
 722
 723	    have_restrict_file = 1;
 724
 725	    continue;
 726	}
 727	
 728	if (strcmp(argv[i], "-d") == 0)
 729	{
 730	    time_t *pt;
 731
 732	    if (++i >= argc)
 733		return usage("argument to -d missing", "");
 734
 735	    pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
 736	    convert_date(pt, argv[i++]);
 737	    continue;
 738	}
 739
 740	if (strcmp(argv[i], "-r") == 0)
 741	{
 742	    if (++i >= argc)
 743		return usage("argument to -r missing", "");
 744
 745	    if (restrict_tag_start)
 746		restrict_tag_end = argv[i];
 747	    else
 748		restrict_tag_start = argv[i];
 749
 750	    i++;
 751	    continue;
 752	}
 753
 754	if (strcmp(argv[i], "-u") == 0)
 755	{
 756	    update_cache = 1;
 757	    i++;
 758	    continue;
 759	}
 760	
 761	if (strcmp(argv[i], "-x") == 0)
 762	{
 763	    ignore_cache = 1;
 764	    update_cache = 1;
 765	    i++;
 766	    continue;
 767	}
 768
 769	if (strcmp(argv[i], "-b") == 0)
 770	{
 771	    if (++i >= argc)
 772		return usage("argument to -b missing", "");
 773
 774	    restrict_branch = argv[i++];
 775	    /* Warn if the user tries to use TRUNK. Should eventually
 776	     * go away as TRUNK may be a valid branch within CVS
 777	     */
 778	    if (strcmp(restrict_branch, "TRUNK") == 0)
 779		debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
 780	    continue;
 781	}
 782
 783	if (strcmp(argv[i], "-p") == 0)
 784	{
 785	    if (++i >= argc)
 786		return usage("argument to -p missing", "");
 787	    
 788	    patch_set_dir = argv[i++];
 789	    continue;
 790	}
 791
 792	if (strcmp(argv[i], "-v") == 0)
 793	{
 794	    debuglvl = ~0;
 795	    i++;
 796	    continue;
 797	}
 798	
 799	if (strcmp(argv[i], "-t") == 0)
 800	{
 801	    statistics = 1;
 802	    i++;
 803	    continue;
 804	}
 805
 806	if (strcmp(argv[i], "--summary-first") == 0)
 807	{
 808	    summary_first = 1;
 809	    i++;
 810	    continue;
 811	}
 812
 813	if (strcmp(argv[i], "-h") == 0)
 814	    return usage(NULL, NULL);
 815
 816	/* see special handling of --norc in main */
 817	if (strcmp(argv[i], "--norc") == 0)
 818	{
 819	    norc = "-f";
 820	    i++;
 821	    continue;
 822	}
 823
 824	if (strcmp(argv[i], "--test-log") == 0)
 825	{
 826	    if (++i >= argc)
 827		return usage("argument to --test-log missing", "");
 828
 829	    test_log_file = argv[i++];
 830	    continue;
 831	}
 832
 833	if (strcmp(argv[i], "--diff-opts") == 0)
 834	{
 835	    if (++i >= argc)
 836		return usage("argument to --diff-opts missing", "");
 837
 838	    /* allow diff_opts to be turned off by making empty string
 839	     * into NULL
 840	     */
 841	    if (!strlen(argv[i]))
 842		diff_opts = NULL;
 843	    else
 844		diff_opts = argv[i];
 845	    i++;
 846	    continue;
 847	}
 848
 849	if (strcmp(argv[i], "--bkcvs") == 0)
 850	{
 851	    bkcvs = 1;
 852	    i++;
 853	    continue;
 854	}
 855
 856	if (strcmp(argv[i], "--no-rlog") == 0)
 857	{
 858	    no_rlog = 1;
 859	    i++;
 860	    continue;
 861	}
 862
 863	if (strcmp(argv[i], "--cvs-direct") == 0)
 864	{
 865	    cvs_direct = 1;
 866	    i++;
 867	    continue;
 868	}
 869
 870	if (strcmp(argv[i], "--no-cvs-direct") == 0)
 871	{
 872	    cvs_direct = 0;
 873	    i++;
 874	    continue;
 875	}
 876
 877	if (strcmp(argv[i], "--debuglvl") == 0)
 878	{
 879	    if (++i >= argc)
 880		return usage("argument to --debuglvl missing", "");
 881
 882	    debuglvl = atoi(argv[i++]);
 883	    continue;
 884	}
 885
 886	if (strcmp(argv[i], "-Z") == 0)
 887	{
 888	    if (++i >= argc)
 889		return usage("argument to -Z", "");
 890
 891	    compress = atoi(argv[i++]);
 892
 893	    if (compress < 0 || compress > 9)
 894		return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
 895
 896	    if (compress == 0)
 897		compress_arg[0] = 0;
 898	    else
 899		snprintf(compress_arg, 8, "-z%d", compress);
 900	    continue;
 901	}
 902	
 903	if (strcmp(argv[i], "--root") == 0)
 904	{
 905	    if (++i >= argc)
 906		return usage("argument to --root missing", "");
 907
 908	    strcpy(root_path, argv[i++]);
 909	    continue;
 910	}
 911
 912	if (strcmp(argv[i], "-q") == 0)
 913	{
 914	    debuglvl &= ~DEBUG_APPMSG1;
 915	    i++;
 916	    continue;
 917	}
 918
 919	if (strcmp(argv[i], "-A") == 0)
 920	{
 921	    track_branch_ancestry = 1;
 922	    i++;
 923	    continue;
 924	}
 925
 926	if (argv[i][0] == '-')
 927	    return usage("invalid argument", argv[i]);
 928	
 929	strcpy(repository_path, argv[i++]);
 930    }
 931
 932    return 0;
 933}
 934
 935static int parse_rc()
 936{
 937    char rcfile[PATH_MAX];
 938    FILE * fp;
 939    snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
 940    if ((fp = fopen(rcfile, "r")))
 941    {
 942	char buff[BUFSIZ];
 943	while (fgets(buff, BUFSIZ, fp))
 944	{
 945	    char * argv[3], *p;
 946	    int argc = 2;
 947
 948	    chop(buff);
 949
 950	    argv[0] = "garbage";
 951
 952	    p = strchr(buff, ' ');
 953	    if (p)
 954	    {
 955		*p++ = '\0';
 956		argv[2] = xstrdup(p);
 957		argc = 3;
 958	    }
 959
 960	    argv[1] = xstrdup(buff);
 961
 962	    if (parse_args(argc, argv) < 0)
 963		return -1;
 964	}
 965	fclose(fp);
 966    }
 967
 968    return 0;
 969}
 970
 971static void init_paths()
 972{
 973    FILE * fp;
 974    char * p;
 975    int len;
 976
 977    /* determine the CVSROOT. precedence:
 978     * 1) command line
 979     * 2) working directory (if present)
 980     * 3) environment variable CVSROOT
 981     */
 982    if (!root_path[0])
 983    {
 984	if (!(fp = fopen("CVS/Root", "r")))
 985	{
 986	    const char * e;
 987
 988	    debug(DEBUG_STATUS, "Can't open CVS/Root");
 989	    e = getenv("CVSROOT");
 990
 991	    if (!e)
 992	    {
 993		debug(DEBUG_APPERROR, "cannot determine CVSROOT");
 994		exit(1);
 995	    }
 996	    
 997	    strcpy(root_path, e);
 998	}
 999	else
1000	{
1001	    if (!fgets(root_path, PATH_MAX, fp))
1002	    {
1003		debug(DEBUG_APPERROR, "Error reading CVSROOT");
1004		exit(1);
1005	    }
1006	    
1007	    fclose(fp);
1008	    
1009	    /* chop the lf and optional trailing '/' */
1010	    len = strlen(root_path) - 1;
1011	    root_path[len] = 0;
1012	    if (root_path[len - 1] == '/')
1013		root_path[--len] = 0;
1014	}
1015    }
1016
1017    /* Determine the repository path, precedence:
1018     * 1) command line
1019     * 2) working directory
1020     */
1021      
1022    if (!repository_path[0])
1023    {
1024	if (!(fp = fopen("CVS/Repository", "r")))
1025	{
1026	    debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1027	    exit(1);
1028	}
1029	
1030	if (!fgets(repository_path, PATH_MAX, fp))
1031	{
1032	    debug(DEBUG_APPERROR, "Error reading repository path");
1033	    exit(1);
1034	}
1035	
1036	chop(repository_path);
1037	fclose(fp);
1038    }
1039
1040    /* get the path portion of the root */
1041    p = strrchr(root_path, ':');
1042
1043    if (!p)
1044	p = root_path;
1045    else 
1046	p++;
1047
1048    /* some CVS have the CVSROOT string as part of the repository
1049     * string (initial substring).  remove it.
1050     */
1051    len = strlen(p);
1052
1053    if (strncmp(p, repository_path, len) == 0)
1054    {
1055	int rlen = strlen(repository_path + len + 1);
1056	memmove(repository_path, repository_path + len + 1, rlen + 1);
1057    }
1058
1059    /* the 'strip_path' will be used whenever the CVS server gives us a
1060     * path to an 'rcs file'.  the strip_path portion of these paths is
1061     * stripped off, leaving us with the working file.
1062     *
1063     * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1064     * (instead of log) it gives the 'real' RCS file path, which can be different
1065     * from the 'nominal' repository path because of symlinks in the server and
1066     * the like.  See also the 'parse_rcs_file' routine
1067     */
1068    strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1069
1070    if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1071    {
1072	debug(DEBUG_APPERROR, "strip_path overflow");
1073	exit(1);
1074    }
1075
1076    debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1077}
1078
1079static CvsFile * parse_rcs_file(const char * buff)
1080{
1081    char fn[PATH_MAX];
1082    int len = strlen(buff + 10);
1083    char * p;
1084
1085    /* once a single file has been parsed ok we set this */
1086    static int path_ok;
1087    
1088    /* chop the ",v" string and the "LF" */
1089    len -= 3;
1090    memcpy(fn, buff + 10, len);
1091    fn[len] = 0;
1092    
1093    if (strncmp(fn, strip_path, strip_path_len) != 0)
1094    {
1095	/* if the very first file fails the strip path,
1096	 * then maybe we need to try for an alternate.
1097	 * this will happen if symlinks are being used
1098	 * on the server.  our best guess is to look
1099	 * for the final occurance of the repository
1100	 * path in the filename and use that.  it should work
1101	 * except in the case where:
1102	 * 1) the project has no files in the top-level directory
1103	 * 2) the project has a directory with the same name as the project
1104	 * 3) that directory sorts alphabetically before any other directory
1105	 * in which case, you are scr**ed
1106	 */
1107	if (!path_ok)
1108	{
1109	    char * p = fn, *lastp = NULL;
1110
1111	    while ((p = strstr(p, repository_path)))
1112		lastp = p++;
1113      
1114	    if (lastp)
1115	    {
1116		int len = strlen(repository_path);
1117		memcpy(strip_path, fn, lastp - fn + len + 1);
1118		strip_path_len = lastp - fn + len + 1;
1119		strip_path[strip_path_len] = 0;
1120		debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
1121		goto ok;
1122	    }
1123	}
1124
1125	/* FIXME: a subdirectory may have a different Repository path
1126	 * than it's parent.  we'll fail the above test since strip_path
1127	 * is global for the entire checked out tree (recursively).
1128	 *
1129	 * For now just ignore such files
1130	 */
1131	debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring", 
1132	      fn, strip_path);
1133	return NULL;
1134    }
1135
1136 ok:
1137    path_ok = 1;
1138
1139    /* remove from beginning the 'strip_path' string */
1140    len -= strip_path_len;
1141    memmove(fn, fn + strip_path_len, len);
1142    fn[len] = 0;
1143
1144    /* check if file is in the 'Attic/' and remove it */
1145    if ((p = strrchr(fn, '/')) &&
1146	p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1147    {
1148	memmove(p - 5, p + 1, len - (p - fn + 1));
1149	len -= 6;
1150	fn[len] = 0;
1151    }
1152
1153    debug(DEBUG_STATUS, "stripped filename %s", fn);
1154
1155    return build_file_by_name(fn);
1156}
1157
1158static CvsFile * parse_working_file(const char * buff)
1159{
1160    char fn[PATH_MAX];
1161    int len = strlen(buff + 14);
1162
1163    /* chop the "LF" */
1164    len -= 1;
1165    memcpy(fn, buff + 14, len);
1166    fn[len] = 0;
1167
1168    debug(DEBUG_STATUS, "working filename %s", fn);
1169
1170    return build_file_by_name(fn);
1171}
1172
1173static CvsFile * build_file_by_name(const char * fn)
1174{
1175    CvsFile * retval;
1176
1177    retval = (CvsFile*)get_hash_object(file_hash, fn);
1178
1179    if (!retval)
1180    {
1181	if ((retval = create_cvsfile()))
1182	{
1183	    retval->filename = xstrdup(fn);
1184	    put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1185	}
1186	else
1187	{
1188	    debug(DEBUG_SYSERROR, "malloc failed");
1189	    exit(1);
1190	}
1191
1192	debug(DEBUG_STATUS, "new file: %s", retval->filename);
1193    }
1194    else
1195    {
1196	debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1197    }
1198
1199    return retval;
1200}
1201
1202PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
1203{
1204    PatchSet * retval = NULL, **find = NULL;
1205    int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
1206
1207    if (!(retval = create_patch_set()))
1208    {
1209	debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1210	return NULL;
1211    }
1212
1213    convert_date(&retval->date, dte);
1214    retval->author = get_string(author);
1215    retval->descr = xstrdup(log);
1216    retval->branch = get_string(branch);
1217    
1218    /* we are looking for a patchset suitable for holding this member.
1219     * this means two things:
1220     * 1) a patchset already containing an entry for the file is no good
1221     * 2) for two patchsets with same exact date/time, if they reference 
1222     *    the same file, we can properly order them.  this primarily solves
1223     *    the 'cvs import' problem and may not have general usefulness
1224     *    because it would only work if the first member we consider is
1225     *    present in the existing ps.
1226     */
1227    if (psm)
1228	list_add(&psm->link, retval->members.prev);
1229
1230    find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
1231
1232    if (psm)
1233	list_del(&psm->link);
1234
1235    if (*find != retval)
1236    {
1237	debug(DEBUG_STATUS, "found existing patch set");
1238
1239	if (bkcvs && strstr(retval->descr, "BKrev:"))
1240	{
1241	    free((*find)->descr);
1242	    (*find)->descr = retval->descr;
1243	}
1244	else
1245	{
1246	    free(retval->descr);
1247	}
1248
1249	/* keep the minimum date of any member as the 'actual' date */
1250	if (retval->date < (*find)->date)
1251	    (*find)->date = retval->date;
1252
1253	/* expand the min_date/max_date window to help finding other members .
1254	 * open the window by an extra margin determined by the fuzz factor 
1255	 */
1256	if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1257	{
1258	    (*find)->min_date = retval->date - timestamp_fuzz_factor;
1259	    //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1260	}
1261	else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1262	    (*find)->max_date = retval->date + timestamp_fuzz_factor;
1263
1264	free(retval);
1265	retval = *find;
1266    }
1267    else
1268    {
1269	debug(DEBUG_STATUS, "new patch set!");
1270	debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
1271
1272	retval->min_date = retval->date - timestamp_fuzz_factor;
1273	retval->max_date = retval->date + timestamp_fuzz_factor;
1274
1275	list_add(&retval->all_link, &all_patch_sets);
1276    }
1277
1278
1279    return retval;
1280}
1281
1282static int get_branch_ext(char * buff, const char * rev, int * leaf)
1283{
1284    char * p;
1285    int len = strlen(rev);
1286
1287    /* allow get_branch(buff, buff) without destroying contents */
1288    memmove(buff, rev, len);
1289    buff[len] = 0;
1290
1291    p = strrchr(buff, '.');
1292    if (!p)
1293	return 0;
1294    *p++ = 0;
1295
1296    if (leaf)
1297	*leaf = atoi(p);
1298
1299    return 1;
1300}
1301
1302static int get_branch(char * buff, const char * rev)
1303{
1304    return get_branch_ext(buff, rev, NULL);
1305}
1306
1307/* 
1308 * the goal if this function is to determine what revision to assign to
1309 * the psm->pre_rev field.  usually, the log file is strictly 
1310 * reverse chronological, so rev is direct ancestor to psm, 
1311 * 
1312 * This all breaks down at branch points however
1313 */
1314
1315static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1316{
1317    char pre[REV_STR_MAX], post[REV_STR_MAX];
1318
1319    if (!psm)
1320	return;
1321    
1322    if (!rev)
1323    {
1324	/* if psm was last rev. for file, it's either an 
1325	 * INITIAL, or first rev of a branch.  to test if it's 
1326	 * the first rev of a branch, do get_branch twice - 
1327	 * this should be the bp.
1328	 */
1329	if (get_branch(post, psm->post_rev->rev) && 
1330	    get_branch(pre, post))
1331	{
1332	    psm->pre_rev = file_get_revision(psm->file, pre);
1333	    list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1334	}
1335	else
1336	{
1337	    set_psm_initial(psm);
1338	}
1339	return;
1340    }
1341
1342    /* 
1343     * is this canditate for 'pre' on the same branch as our 'post'? 
1344     * this is the normal case
1345     */
1346    if (!get_branch(pre, rev->rev))
1347    {
1348	debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1349	return;
1350    }
1351
1352    if (!get_branch(post, psm->post_rev->rev))
1353    {
1354	debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1355	return;
1356    }
1357
1358    if (strcmp(pre, post) == 0)
1359    {
1360	psm->pre_rev = rev;
1361	rev->pre_psm = psm;
1362	return;
1363    }
1364    
1365    /* branches don't match. new_psm must be head of branch,
1366     * so psm is oldest rev. on branch. or oldest
1367     * revision overall.  if former, derive predecessor.  
1368     * use get_branch to chop another rev. off of string.
1369     *
1370     * FIXME:
1371     * There's also a weird case.  it's possible to just re-number
1372     * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1373     * It's not widely used.  In those cases of discontinuity,
1374     * we end up stamping the predecessor as 'INITIAL' incorrectly
1375     *
1376     */
1377    if (!get_branch(pre, post))
1378    {
1379	set_psm_initial(psm);
1380	return;
1381    }
1382    
1383    psm->pre_rev = file_get_revision(psm->file, pre);
1384    list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1385}
1386
1387static void check_print_patch_set(PatchSet * ps)
1388{
1389    if (ps->psid < 0)
1390	return;
1391
1392    /* the funk_factor overrides the restrict_tag_start and end */
1393    if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1394	goto ok;
1395
1396    if (ps->funk_factor == FNK_HIDE_ALL)
1397	return;
1398
1399    if (ps->psid <= restrict_tag_ps_start)
1400    {
1401	if (ps->psid == restrict_tag_ps_start)
1402	    debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1403	
1404	return;
1405    }
1406    
1407    if (ps->psid > restrict_tag_ps_end)
1408	return;
1409
1410 ok:
1411    if (restrict_date_start > 0 &&
1412	(ps->date < restrict_date_start ||
1413	 (restrict_date_end > 0 && ps->date > restrict_date_end)))
1414	return;
1415
1416    if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1417	return;
1418
1419    if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1420	return;
1421
1422    if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1423	return;
1424
1425    if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1426	return;
1427    
1428    if (!list_empty(&show_patch_set_ranges))
1429    {
1430	struct list_head * next = show_patch_set_ranges.next;
1431	
1432	while (next != &show_patch_set_ranges)
1433	{
1434	    PatchSetRange *range = list_entry(next, PatchSetRange, link);
1435	    if (range->min_counter <= ps->psid &&
1436		ps->psid <= range->max_counter)
1437	    {
1438		break;
1439	    }
1440	    next = next->next;
1441	}
1442	
1443	if (next == &show_patch_set_ranges)
1444	    return;
1445    }
1446
1447    if (patch_set_dir)
1448    {
1449	char path[PATH_MAX];
1450
1451	snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1452
1453	fflush(stdout);
1454	close(1);
1455	if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1456	{
1457	    debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1458	    exit(1);
1459	}
1460
1461	fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1462    }
1463
1464    /*
1465     * If the summary_first option is in effect, there will be 
1466     * two passes through the tree.  the first with summary_first == 1
1467     * the second with summary_first == 2.  if the option is not
1468     * in effect, there will be one pass with summary_first == 0
1469     *
1470     * When the -s option is in effect, the show_patch_set_ranges
1471     * list will be non-empty.
1472     */
1473    if (summary_first <= 1)
1474	print_patch_set(ps);
1475    if (do_diff && summary_first != 1)
1476	do_cvs_diff(ps);
1477
1478    fflush(stdout);
1479}
1480
1481static void print_patch_set(PatchSet * ps)
1482{
1483    struct tm *tm;
1484    struct list_head * next;
1485    const char * funk = "";
1486
1487    tm = localtime(&ps->date);
1488    next = ps->members.next;
1489    
1490    funk = fnk_descr[ps->funk_factor];
1491    
1492    /* this '---...' is different from the 28 hyphens that separate cvs log output */
1493    printf("---------------------\n");
1494    printf("PatchSet %d %s\n", ps->psid, funk);
1495    printf("Date: %d/%02d/%02d %02d:%02d:%02d\n", 
1496	   1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, 
1497	   tm->tm_hour, tm->tm_min, tm->tm_sec);
1498    printf("Author: %s\n", ps->author);
1499    printf("Branch: %s\n", ps->branch);
1500    if (ps->ancestor_branch)
1501	printf("Ancestor branch: %s\n", ps->ancestor_branch);
1502    printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
1503    printf("Log:\n%s\n", ps->descr);
1504    printf("Members: \n");
1505
1506    while (next != &ps->members)
1507    {
1508	PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1509	if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1510	    funk = "(BEFORE START TAG)";
1511	else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1512	    funk = "(AFTER END TAG)";
1513	else
1514	    funk = "";
1515
1516	printf("\t%s:%s->%s%s %s\n", 
1517	       psm->file->filename, 
1518	       psm->pre_rev ? psm->pre_rev->rev : "INITIAL", 
1519	       psm->post_rev->rev, 
1520	       psm->post_rev->dead ? "(DEAD)": "",
1521	       funk);
1522
1523	next = next->next;
1524    }
1525    
1526    printf("\n");
1527}
1528
1529/* walk all the patchsets to assign monotonic psid, 
1530 * and to establish  branch ancestry
1531 */
1532static void assign_patchset_id(PatchSet * ps)
1533{
1534    /*
1535     * Ignore the 'BRANCH ADD' patchsets 
1536     */
1537    if (!ps->branch_add)
1538    {
1539	ps_counter++;
1540	ps->psid = ps_counter;
1541	
1542	if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
1543	{
1544	    PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
1545	    if (!head_ps) 
1546	    {
1547		head_ps = ps;
1548		put_hash_object(branch_heads, ps->branch, head_ps);
1549	    }
1550	    
1551	    determine_branch_ancestor(ps, head_ps);
1552	}
1553    }
1554    else
1555    {
1556	ps->psid = -1;
1557    }
1558}
1559
1560static int compare_rev_strings(const char * cr1, const char * cr2)
1561{
1562    char r1[REV_STR_MAX];
1563    char r2[REV_STR_MAX];
1564    char *s1 = r1, *s2 = r2;
1565    char *p1, *p2;
1566    int n1, n2;
1567
1568    strcpy(s1, cr1);
1569    strcpy(s2, cr2);
1570
1571    for (;;) 
1572    {
1573	p1 = strchr(s1, '.');
1574	p2 = strchr(s2, '.');
1575
1576	if (p1) *p1++ = 0;
1577	if (p2) *p2++ = 0;
1578	
1579	n1 = atoi(s1);
1580	n2 = atoi(s2);
1581	
1582	if (n1 < n2)
1583	    return -1;
1584	if (n1 > n2)
1585	    return 1;
1586
1587	if (!p1 && p2)
1588	    return -1;
1589	if (p1 && !p2)
1590	    return 1;
1591	if (!p1 && !p2)
1592	    return 0;
1593
1594	s1 = p1;
1595	s2 = p2;
1596    }
1597}
1598
1599static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1600{
1601    struct list_head * i;
1602
1603    for (i = ps1->members.next; i != &ps1->members; i = i->next)
1604    {
1605	PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1606	struct list_head * j;
1607
1608	for (j = ps2->members.next; j != &ps2->members; j = j->next)
1609	{
1610	    PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1611	    if (psm1->file == psm2->file) 
1612	    {
1613		int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1614		//debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1615		return ret;
1616	    }
1617	}
1618    }
1619    
1620    return 0;
1621}
1622
1623static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
1624{
1625    const PatchSet * ps1 = (const PatchSet *)v_ps1;
1626    const PatchSet * ps2 = (const PatchSet *)v_ps2;
1627    long diff;
1628
1629    diff = ps1->date - ps2->date;
1630
1631    return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1632}
1633
1634static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1635{
1636    const PatchSet * ps1 = (const PatchSet *)v_ps1;
1637    const PatchSet * ps2 = (const PatchSet *)v_ps2;
1638    long diff;
1639    int ret;
1640    time_t d, min, max;
1641
1642    /* We order by (author, descr, branch, members, date), but because of the fuzz factor
1643     * we treat times within a certain distance as equal IFF the author
1644     * and descr match.
1645     */
1646
1647    ret = strcmp(ps1->author, ps2->author);
1648    if (ret)
1649	    return ret;
1650
1651    ret = strcmp(ps1->descr, ps2->descr);
1652    if (ret)
1653	    return ret;
1654
1655    ret = strcmp(ps1->branch, ps2->branch);
1656    if (ret)
1657	return ret;
1658
1659    ret = compare_patch_sets_by_members(ps1, ps2);
1660    if (ret)
1661	return ret;
1662
1663    /* 
1664     * one of ps1 or ps2 is new.  the other should have the min_date
1665     * and max_date set to a window opened by the fuzz_factor
1666     */
1667    if (ps1->min_date == 0)
1668    {
1669	d = ps1->date;
1670	min = ps2->min_date;
1671	max = ps2->max_date;
1672    } 
1673    else if (ps2->min_date == 0)
1674    {
1675	d = ps2->date;
1676	min = ps1->min_date;
1677	max = ps1->max_date;
1678    }
1679    else
1680    {
1681	debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
1682	exit(1);
1683    }
1684
1685    if (min < d && d < max)
1686	return 0;
1687
1688    diff = ps1->date - ps2->date;
1689
1690    return (diff < 0) ? -1 : 1;
1691}
1692
1693static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
1694{
1695    const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
1696    const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
1697    return compare_patch_sets_bytime(ps1, ps2);
1698}
1699
1700static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
1701{
1702    long diff;
1703    int ret;
1704
1705    /* When doing a time-ordering of patchsets, we don't need to
1706     * fuzzy-match the time.  We've already done fuzzy-matching so we
1707     * know that insertions are unique at this point.
1708     */
1709
1710    diff = ps1->date - ps2->date;
1711    if (diff)
1712	return (diff < 0) ? -1 : 1;
1713
1714    ret = compare_patch_sets_by_members(ps1, ps2);
1715    if (ret)
1716	return ret;
1717
1718    ret = strcmp(ps1->author, ps2->author);
1719    if (ret)
1720	return ret;
1721
1722    ret = strcmp(ps1->descr, ps2->descr);
1723    if (ret)
1724	return ret;
1725
1726    ret = strcmp(ps1->branch, ps2->branch);
1727    return ret;
1728}
1729
1730
1731static int is_revision_metadata(const char * buff)
1732{
1733    char * p1, *p2;
1734    int len;
1735
1736    if (!(p1 = strchr(buff, ':')))
1737	return 0;
1738
1739    p2 = strchr(buff, ' ');
1740    
1741    if (p2 && p2 < p1)
1742	return 0;
1743
1744    len = strlen(buff);
1745
1746    /* lines have LF at end */
1747    if (len > 1 && buff[len - 2] == ';')
1748	return 1;
1749
1750    return 0;
1751}
1752
1753static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
1754{
1755    struct list_head * next = ps->members.next;
1756
1757    while (next != &ps->members)
1758    {
1759	PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1760	
1761	if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
1762	    return 1;
1763
1764	next = next->next;
1765    }
1766
1767    return 0;
1768}
1769
1770static int patch_set_affects_branch(PatchSet * ps, const char * branch)
1771{
1772    struct list_head * next;
1773
1774    for (next = ps->members.next; next != &ps->members; next = next->next)
1775    {
1776	PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1777
1778	/*
1779	 * slight hack. if -r is specified, and this patchset
1780	 * is 'before' the tag, but is FNK_SHOW_SOME, only
1781	 * check if the 'after tag' revisions affect
1782	 * the branch.  this is especially important when
1783	 * the tag is a branch point.
1784	 */
1785	if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1786	    continue;
1787
1788	if (revision_affects_branch(psm->post_rev, branch))
1789	    return 1;
1790    }
1791
1792    return 0;
1793}
1794
1795static void do_cvs_diff(PatchSet * ps)
1796{
1797    struct list_head * next;
1798    const char * dtype;
1799    const char * dopts;
1800    const char * utype;
1801    char use_rep_path[PATH_MAX];
1802    char esc_use_rep_path[PATH_MAX];
1803
1804    fflush(stdout);
1805    fflush(stderr);
1806
1807    /* 
1808     * if cvs_direct is not in effect, and diff options are specified,
1809     * then we have to use diff instead of rdiff and we'll get a -p0 
1810     * diff (instead of -p1) [in a manner of speaking].  So to make sure
1811     * that the add/remove diffs get generated likewise, we need to use
1812     * 'update' instead of 'co' 
1813     *
1814     * cvs_direct will always use diff (not rdiff), but will also always
1815     * generate -p1 diffs.
1816     */
1817    if (diff_opts == NULL) 
1818    {
1819	dopts = "-u";
1820	dtype = "rdiff";
1821	utype = "co";
1822	sprintf(use_rep_path, "%s/", repository_path);
1823	/* the rep_path may contain characters that the shell will barf on */
1824	escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
1825    }
1826    else
1827    {
1828	dopts = diff_opts;
1829	dtype = "diff";
1830	utype = "update";
1831	use_rep_path[0] = 0;
1832	esc_use_rep_path[0] = 0;
1833    }
1834
1835    for (next = ps->members.next; next != &ps->members; next = next->next)
1836    {
1837	PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1838	char cmdbuff[PATH_MAX * 2+1];
1839	char esc_file[PATH_MAX];
1840	int ret, check_ret = 0;
1841
1842	cmdbuff[0] = 0;
1843	cmdbuff[PATH_MAX*2] = 0;
1844
1845	/* the filename may contain characters that the shell will barf on */
1846	escape_filename(esc_file, PATH_MAX, psm->file->filename);
1847
1848	/*
1849	 * Check the patchset funk. we may not want to diff this particular file 
1850	 */
1851	if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1852	{
1853	    printf("Index: %s\n", psm->file->filename);
1854	    printf("===================================================================\n");
1855	    printf("*** Member not diffed, before start tag\n");
1856	    continue;
1857	}
1858	else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1859	{
1860	    printf("Index: %s\n", psm->file->filename);
1861	    printf("===================================================================\n");
1862	    printf("*** Member not diffed, after end tag\n");
1863	    continue;
1864	}
1865
1866	/* 
1867	 * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1868	 * or 'cvs update' to get the file, because cvs won't generate these diffs.
1869	 * The problem is that this must be piped to diff, and so the resulting
1870	 * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1871	 * sed is used to replace the '-' with the filename. 
1872	 *
1873	 * It's possible for pre_rev to be a 'dead' revision. This happens when a file 
1874	 * is added on a branch. post_rev will be dead dead for remove
1875	 */
1876	if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
1877	{
1878	    int cr;
1879	    const char * rev;
1880
1881	    if (!psm->pre_rev || psm->pre_rev->dead)
1882	    {
1883		cr = 1;
1884		rev = psm->post_rev->rev;
1885	    }
1886	    else
1887	    {
1888		cr = 0;
1889		rev = psm->pre_rev->rev;
1890	    }
1891
1892	    if (cvs_direct_ctx)
1893	    {
1894		/* cvs_rupdate does the pipe through diff thing internally */
1895		cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
1896	    }
1897	    else
1898	    {
1899		snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s -p -r %s %s%s | diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s%s|g'",
1900			 compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
1901			 cr?"":"-",cr?"-":"", cr?"2":"1",
1902			 use_rep_path, psm->file->filename);
1903	    }
1904	}
1905	else
1906	{
1907	    /* a regular diff */
1908	    if (cvs_direct_ctx)
1909	    {
1910		cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
1911	    }
1912	    else
1913	    {
1914		/* 'cvs diff' exit status '1' is ok, just means files are different */
1915		if (strcmp(dtype, "diff") == 0)
1916		    check_ret = 1;
1917
1918		snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1919			 compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev, 
1920			 esc_use_rep_path, esc_file);
1921	    }
1922	}
1923
1924	/*
1925	 * my_system doesn't block signals the way system does.
1926	 * if ctrl-c is pressed while in there, we probably exit
1927	 * immediately and hope the shell has sent the signal
1928	 * to all of the process group members
1929	 */
1930	if (cmdbuff[0] && (ret = my_system(cmdbuff)))
1931	{
1932	    int stat = WEXITSTATUS(ret);
1933	    
1934	    /* 
1935	     * cvs diff returns 1 in exit status for 'files are different'
1936	     * so use a better method to check for failure
1937	     */
1938	    if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
1939	    {
1940		debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
1941		exit(1);
1942	    }
1943	}
1944    }
1945}
1946
1947static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
1948{
1949    char * p;
1950
1951    /* The "revision" log line can include extra information 
1952     * including who is locking the file --- strip that out.
1953     */
1954    
1955    p = rev_str;
1956    while (isdigit(*p) || *p == '.')
1957	    p++;
1958  

Large files files are truncated, but you can click here to view the full file