dev-null-is-nul-on-windows.p
[cvsps/4msysgit.git] / cvsps.c
blob529f3c37c76f72a2aff07b6be4df83ebe5f33c40
1 /*
2 * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3 * See COPYING file for license information
4 */
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <limits.h>
10 #include <unistd.h>
11 #ifdef __MINGW32__
12 #include <tsearch/search.h>
13 #define WEXITSTATUS(status) (status)
14 /* WIFSIGNALED is not entierly correct, but almost */
15 #define WIFSIGNALED(status) ((status)==-1)
16 #define DEV_NULL "nul"
17 #else
18 #include <search.h>
19 #define DEV_NULL "/dev/null"
20 #endif
21 #include <time.h>
22 #include <ctype.h>
23 #include <sys/stat.h>
24 #include <sys/types.h>
25 #include <fcntl.h>
26 #include <regex.h>
27 #ifndef __MINGW32__
28 #include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
29 #endif
31 #include <cbtcommon/hash.h>
32 #include <cbtcommon/list.h>
33 #include <cbtcommon/text_util.h>
34 #include <cbtcommon/debug.h>
35 #include <cbtcommon/rcsid.h>
37 #include "cache.h"
38 #include "cvsps_types.h"
39 #include "cvsps.h"
40 #include "util.h"
41 #include "stats.h"
42 #include "cap.h"
43 #include "cvs_direct.h"
44 #include "list_sort.h"
46 RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
48 #define CVS_LOG_BOUNDARY "----------------------------\n"
49 #define CVS_FILE_BOUNDARY "=============================================================================\n"
51 enum
53 NEED_FILE,
54 NEED_SYMS,
55 NEED_EOS,
56 NEED_START_LOG,
57 NEED_REVISION,
58 NEED_DATE_AUTHOR_STATE,
59 NEED_EOM
62 /* true globals */
63 struct hash_table * file_hash;
64 CvsServerCtx * cvs_direct_ctx;
65 char root_path[PATH_MAX];
66 char repository_path[PATH_MAX];
68 const char * tag_flag_descr[] = {
69 "",
70 "**FUNKY**",
71 "**INVALID**",
72 "**INVALID**"
75 const char * fnk_descr[] = {
76 "",
77 "FNK_SHOW_SOME",
78 "FNK_SHOW_ALL",
79 "FNK_HIDE_ALL",
80 "FNK_HIDE_SOME"
83 /* static globals */
84 static int ps_counter;
85 static void * ps_tree;
86 static struct hash_table * global_symbols;
87 static char strip_path[PATH_MAX];
88 static int strip_path_len;
89 static time_t cache_date;
90 static int update_cache;
91 static int ignore_cache;
92 static int do_write_cache;
93 static int statistics;
94 static const char * test_log_file;
95 static struct hash_table * branch_heads;
96 static struct list_head all_patch_sets;
97 static struct list_head collisions;
99 /* settable via options */
100 static int timestamp_fuzz_factor = 300;
101 static int do_diff;
102 static const char * restrict_author;
103 static int have_restrict_log;
104 static regex_t restrict_log;
105 static int have_restrict_file;
106 static regex_t restrict_file;
107 static time_t restrict_date_start;
108 static time_t restrict_date_end;
109 static const char * restrict_branch;
110 static struct list_head show_patch_set_ranges;
111 static int summary_first;
112 static const char * norc = "";
113 static const char * patch_set_dir;
114 static const char * restrict_tag_start;
115 static const char * restrict_tag_end;
116 static int restrict_tag_ps_start;
117 static int restrict_tag_ps_end = INT_MAX;
118 static const char * diff_opts;
119 static int bkcvs;
120 static int no_rlog;
121 static int cvs_direct;
122 static int compress;
123 static char compress_arg[8];
124 static int track_branch_ancestry;
126 static void check_norc(int, char *[]);
127 static int parse_args(int, char *[]);
128 static int parse_rc();
129 static void load_from_cvs();
130 static void init_paths();
131 static CvsFile * parse_file(const char *);
132 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
133 static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
134 static void check_print_patch_set(PatchSet *);
135 static void print_patch_set(PatchSet *);
136 static void assign_patchset_id(PatchSet *);
137 static int compare_rev_strings(const char *, const char *);
138 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
139 static int compare_patch_sets_bk(const void *, const void *);
140 static int compare_patch_sets(const void *, const void *);
141 static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
142 static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
143 static int is_revision_metadata(const char *);
144 static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
145 static int patch_set_affects_branch(PatchSet *, const char *);
146 static void do_cvs_diff(PatchSet *);
147 static PatchSet * create_patch_set();
148 static PatchSetRange * create_patch_set_range();
149 static void parse_sym(CvsFile *, char *);
150 static void resolve_global_symbols();
151 static int revision_affects_branch(CvsFileRevision *, const char *);
152 static int is_vendor_branch(const char *);
153 static void set_psm_initial(PatchSetMember * psm);
154 static int check_rev_funk(PatchSet *, CvsFileRevision *);
155 static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
156 static int before_tag(CvsFileRevision * rev, const char * tag);
157 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
158 static void handle_collisions();
160 int main(int argc, char *argv[])
162 debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
164 INIT_LIST_HEAD(&show_patch_set_ranges);
167 * we want to parse the rc first, so command line can override it
168 * but also, --norc should stop the rc from being processed, so
169 * we look for --norc explicitly first. Note: --norc in the rc
170 * file itself will prevent the cvs rc file from being used.
172 check_norc(argc, argv);
174 if (strlen(norc) == 0 && parse_rc() < 0)
175 exit(1);
177 if (parse_args(argc, argv) < 0)
178 exit(1);
180 if (diff_opts && !cvs_direct && do_diff)
182 debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
183 debug(DEBUG_APPMSG1, " which is usually used to create diffs. 'cvs diff'");
184 debug(DEBUG_APPMSG1, " will be used instead, but the resulting patches ");
185 debug(DEBUG_APPMSG1, " will need to be applied using the '-p0' option");
186 debug(DEBUG_APPMSG1, " to patch(1) (in the working directory), ");
187 debug(DEBUG_APPMSG1, " instead of '-p1'\n");
190 file_hash = create_hash_table(1023);
191 global_symbols = create_hash_table(111);
192 branch_heads = create_hash_table(1023);
193 INIT_LIST_HEAD(&all_patch_sets);
194 INIT_LIST_HEAD(&collisions);
196 /* this parses some of the CVS/ files, and initializes
197 * the repository_path and other variables
199 init_paths();
201 if (!ignore_cache)
203 int save_fuzz_factor = timestamp_fuzz_factor;
205 /* the timestamp fuzz should only be in effect when loading from
206 * CVS, not re-fuzzed when loading from cache. This is a hack
207 * working around bad use of global variables
210 timestamp_fuzz_factor = 0;
212 if ((cache_date = read_cache()) < 0)
213 update_cache = 1;
215 timestamp_fuzz_factor = save_fuzz_factor;
218 if (cvs_direct && (do_diff || (update_cache && !test_log_file))) {
219 cvs_direct_ctx = open_cvs_server(root_path, compress);
220 if (!cvs_direct_ctx) debug(DEBUG_APPERROR, "main: Unable to open cvs server");
223 if (update_cache)
225 load_from_cvs();
226 do_write_cache = 1;
229 //XXX
230 //handle_collisions();
232 list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
234 ps_counter = 0;
235 walk_all_patch_sets(assign_patchset_id);
237 handle_collisions();
239 resolve_global_symbols();
241 if (do_write_cache)
242 write_cache(cache_date);
244 if (statistics)
245 print_statistics(ps_tree);
247 /* check that the '-r' symbols (if specified) were resolved */
248 if (restrict_tag_start && restrict_tag_ps_start == 0 &&
249 strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
251 debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
252 exit(1);
255 if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
257 debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
258 exit(1);
261 walk_all_patch_sets(check_print_patch_set);
263 if (summary_first++)
264 walk_all_patch_sets(check_print_patch_set);
266 if (cvs_direct_ctx)
267 close_cvs_server(cvs_direct_ctx);
269 exit(0);
272 static void load_from_cvs()
274 FILE * cvsfp;
275 char buff[BUFSIZ];
276 int state = NEED_FILE;
277 CvsFile * file = NULL;
278 PatchSetMember * psm = NULL;
279 char datebuff[20];
280 char authbuff[AUTH_STR_MAX];
281 char logbuff[LOG_STR_MAX + 1];
282 int loglen = 0;
283 int have_log = 0;
284 char cmd[BUFSIZ];
285 char date_str[64];
286 char use_rep_buff[PATH_MAX];
287 char * ltype;
289 if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
291 ltype = "rlog";
292 snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
294 else
296 ltype = "log";
297 use_rep_buff[0] = 0;
300 if (cache_date > 0)
302 struct tm * tm = gmtime(&cache_date);
303 strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
305 /* this command asks for logs using two different date
306 * arguments, separated by ';' (see man rlog). The first
307 * gets all revisions more recent than date, the second
308 * gets a single revision no later than date, which combined
309 * get us all revisions that have occurred since last update
310 * and overlaps what we had before by exactly one revision,
311 * which is necessary to fill in the pre_rev stuff for a
312 * PatchSetMember
314 snprintf(cmd, BUFSIZ, "cvs %s %s %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
316 else
318 date_str[0] = 0;
319 snprintf(cmd, BUFSIZ, "cvs %s %s %s %s", compress_arg, norc, ltype, use_rep_buff);
322 debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
324 cache_date = time(NULL);
326 /* FIXME: this is ugly, need to virtualize the accesses away from here */
327 if (test_log_file)
328 cvsfp = fopen(test_log_file, "r");
329 else if (cvs_direct_ctx)
330 cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
331 else
332 cvsfp = popen(cmd, "r");
334 if (!cvsfp)
336 debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
337 exit(1);
340 for (;;)
342 char * tst;
343 if (cvs_direct_ctx)
344 tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
345 else
346 tst = fgets(buff, BUFSIZ, cvsfp);
348 if (!tst)
349 break;
351 debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
353 switch(state)
355 case NEED_FILE:
356 if (strncmp(buff, "RCS file", 8) == 0 && (file = parse_file(buff)))
357 state = NEED_SYMS;
358 break;
359 case NEED_SYMS:
360 if (strncmp(buff, "symbolic names:", 15) == 0)
361 state = NEED_EOS;
362 break;
363 case NEED_EOS:
364 if (!isspace(buff[0]))
366 /* see cvsps_types.h for commentary on have_branches */
367 file->have_branches = 1;
368 state = NEED_START_LOG;
370 else
371 parse_sym(file, buff);
372 break;
373 case NEED_START_LOG:
374 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
375 state = NEED_REVISION;
376 break;
377 case NEED_REVISION:
378 if (strncmp(buff, "revision", 8) == 0)
380 char new_rev[REV_STR_MAX];
381 CvsFileRevision * rev;
383 strcpy(new_rev, buff + 9);
384 chop(new_rev);
387 * rev may already exist (think cvsps -u), in which
388 * case parse_revision is a hash lookup
390 rev = parse_revision(file, new_rev);
393 * in the simple case, we are copying rev to psm->pre_rev
394 * (psm refers to last patch set processed at this point)
395 * since generally speaking the log is reverse chronological.
396 * This breaks down slightly when branches are introduced
399 assign_pre_revision(psm, rev);
402 * if this is a new revision, it will have no post_psm associated.
403 * otherwise we are (probably?) hitting the overlap in cvsps -u
405 if (!rev->post_psm)
407 psm = rev->post_psm = create_patch_set_member();
408 psm->post_rev = rev;
409 psm->file = file;
410 state = NEED_DATE_AUTHOR_STATE;
412 else
414 /* we hit this in cvsps -u mode, we are now up-to-date
415 * w.r.t this particular file. skip all of the rest
416 * of the info (revs and logs) until we hit the next file
418 psm = NULL;
419 state = NEED_EOM;
422 break;
423 case NEED_DATE_AUTHOR_STATE:
424 if (strncmp(buff, "date:", 5) == 0)
426 char * p;
428 strncpy(datebuff, buff + 6, 19);
429 datebuff[19] = 0;
431 strcpy(authbuff, "unknown");
432 p = strstr(buff, "author: ");
433 if (p)
435 char * op;
436 p += 8;
437 op = strchr(p, ';');
438 if (op)
440 strzncpy(authbuff, p, op - p + 1);
444 /* read the 'state' tag to see if this is a dead revision */
445 p = strstr(buff, "state: ");
446 if (p)
448 char * op;
449 p += 7;
450 op = strchr(p, ';');
451 if (op)
452 if (strncmp(p, "dead", MIN(4, op - p)) == 0)
453 psm->post_rev->dead = 1;
456 state = NEED_EOM;
458 break;
459 case NEED_EOM:
460 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
462 if (psm)
464 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
465 patch_set_add_member(ps, psm);
468 logbuff[0] = 0;
469 loglen = 0;
470 have_log = 0;
471 state = NEED_REVISION;
473 else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
475 if (psm)
477 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
478 patch_set_add_member(ps, psm);
479 assign_pre_revision(psm, NULL);
482 logbuff[0] = 0;
483 loglen = 0;
484 have_log = 0;
485 psm = NULL;
486 file = NULL;
487 state = NEED_FILE;
489 else
491 /* other "blahblah: information;" messages can
492 * follow the stuff we pay attention to
494 if (have_log || !is_revision_metadata(buff))
496 /* if the log buffer is full, that's it.
498 * Also, read lines (fgets) always have \n in them
499 * which we count on. So if truncation happens,
500 * be careful to put a \n on.
502 * Buffer has LOG_STR_MAX + 1 for room for \0 if
503 * necessary
505 if (loglen < LOG_STR_MAX)
507 int len = strlen(buff);
509 if (len >= LOG_STR_MAX - loglen)
511 debug(DEBUG_APPMSG1, "WARNING: maximum log length exceeded, truncating log");
512 len = LOG_STR_MAX - loglen;
513 buff[len - 1] = '\n';
516 debug(DEBUG_STATUS, "appending %s to log", buff);
517 memcpy(logbuff + loglen, buff, len);
518 loglen += len;
519 logbuff[loglen] = 0;
520 have_log = 1;
523 else
525 debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
529 break;
533 if (state == NEED_SYMS)
535 debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
536 debug(DEBUG_APPERROR, " Perhaps you should try running with --norc");
537 exit(1);
540 if (state != NEED_FILE)
542 debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state);
543 exit(1);
546 if (test_log_file)
548 fclose(cvsfp);
550 else if (cvs_direct_ctx)
552 cvs_rlog_close(cvs_direct_ctx);
554 else
556 if (pclose(cvsfp) < 0)
558 debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
559 exit(1);
564 static int usage(const char * str1, const char * str2)
566 if (str1)
567 debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
569 debug(DEBUG_APPMSG1, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
570 debug(DEBUG_APPMSG1, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
571 debug(DEBUG_APPMSG1, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
572 debug(DEBUG_APPMSG1, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
573 debug(DEBUG_APPMSG1, " [--test-log <captured cvs log file>] [--bkcvs]");
574 debug(DEBUG_APPMSG1, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
575 debug(DEBUG_APPMSG1, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
576 debug(DEBUG_APPMSG1, " [-q] [-A] [<repository>]");
577 debug(DEBUG_APPMSG1, "");
578 debug(DEBUG_APPMSG1, "Where:");
579 debug(DEBUG_APPMSG1, " -h display this informative message");
580 debug(DEBUG_APPMSG1, " -x ignore (and rebuild) cvsps.cache file");
581 debug(DEBUG_APPMSG1, " -u update cvsps.cache file");
582 debug(DEBUG_APPMSG1, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
583 debug(DEBUG_APPMSG1, " -g generate diffs of the selected patch sets");
584 debug(DEBUG_APPMSG1, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
585 debug(DEBUG_APPMSG1, " -a <author> restrict output to patch sets created by author");
586 debug(DEBUG_APPMSG1, " -f <file> restrict output to patch sets involving file");
587 debug(DEBUG_APPMSG1, " -d <date1> -d <date2> if just one date specified, show");
588 debug(DEBUG_APPMSG1, " revisions newer than date1. If two dates specified,");
589 debug(DEBUG_APPMSG1, " show revisions between two dates.");
590 debug(DEBUG_APPMSG1, " -b <branch> restrict output to patch sets affecting history of branch");
591 debug(DEBUG_APPMSG1, " -l <regex> restrict output to patch sets matching <regex> in log message");
592 debug(DEBUG_APPMSG1, " -r <tag1> -r <tag2> if just one tag specified, show");
593 debug(DEBUG_APPMSG1, " revisions since tag1. If two tags specified, show");
594 debug(DEBUG_APPMSG1, " revisions between the two tags.");
595 debug(DEBUG_APPMSG1, " -p <directory> output patch sets to individual files in <directory>");
596 debug(DEBUG_APPMSG1, " -v show very verbose parsing messages");
597 debug(DEBUG_APPMSG1, " -t show some brief memory usage statistics");
598 debug(DEBUG_APPMSG1, " --norc when invoking cvs, ignore the .cvsrc file");
599 debug(DEBUG_APPMSG1, " --summary-first when multiple patch sets are shown, put all summaries first");
600 debug(DEBUG_APPMSG1, " --test-log <captured cvs log> supply a captured cvs log for testing");
601 debug(DEBUG_APPMSG1, " --diff-opts <option string> supply special set of options to diff");
602 debug(DEBUG_APPMSG1, " --bkcvs special hack for parsing the BK -> CVS log format");
603 debug(DEBUG_APPMSG1, " --no-rlog disable rlog (it's faulty in some setups)");
604 debug(DEBUG_APPMSG1, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
605 debug(DEBUG_APPMSG1, " --debuglvl <bitmask> enable various debug channels.");
606 debug(DEBUG_APPMSG1, " -Z <compression> A value 1-9 which specifies amount of compression");
607 debug(DEBUG_APPMSG1, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
608 debug(DEBUG_APPMSG1, " -q be quiet about warnings");
609 debug(DEBUG_APPMSG1, " -A track and report branch ancestry");
610 debug(DEBUG_APPMSG1, " <repository> apply cvsps to repository. overrides working directory");
611 debug(DEBUG_APPMSG1, "\ncvsps version %s\n", VERSION);
613 return -1;
616 static int parse_args(int argc, char *argv[])
618 int i = 1;
619 while (i < argc)
621 if (strcmp(argv[i], "-z") == 0)
623 if (++i >= argc)
624 return usage("argument to -z missing", "");
626 timestamp_fuzz_factor = atoi(argv[i++]);
627 continue;
630 if (strcmp(argv[i], "-g") == 0)
632 do_diff = 1;
633 i++;
634 continue;
637 if (strcmp(argv[i], "-s") == 0)
639 PatchSetRange * range;
640 char * min_str, * max_str;
642 if (++i >= argc)
643 return usage("argument to -s missing", "");
645 min_str = strtok(argv[i++], ",");
648 range = create_patch_set_range();
650 max_str = strrchr(min_str, '-');
651 if (max_str)
652 *max_str++ = '\0';
653 else
654 max_str = min_str;
656 range->min_counter = atoi(min_str);
658 if (*max_str)
659 range->max_counter = atoi(max_str);
660 else
661 range->max_counter = INT_MAX;
663 list_add(&range->link, show_patch_set_ranges.prev);
665 while ((min_str = strtok(NULL, ",")));
667 continue;
670 if (strcmp(argv[i], "-a") == 0)
672 if (++i >= argc)
673 return usage("argument to -a missing", "");
675 restrict_author = argv[i++];
676 continue;
679 if (strcmp(argv[i], "-l") == 0)
681 int err;
683 if (++i >= argc)
684 return usage("argument to -l missing", "");
686 if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
688 char errbuf[256];
689 regerror(err, &restrict_log, errbuf, 256);
690 return usage("bad regex to -l", errbuf);
693 have_restrict_log = 1;
695 continue;
698 if (strcmp(argv[i], "-f") == 0)
700 int err;
702 if (++i >= argc)
703 return usage("argument to -f missing", "");
705 if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
707 char errbuf[256];
708 regerror(err, &restrict_file, errbuf, 256);
709 return usage("bad regex to -f", errbuf);
712 have_restrict_file = 1;
714 continue;
717 if (strcmp(argv[i], "-d") == 0)
719 time_t *pt;
721 if (++i >= argc)
722 return usage("argument to -d missing", "");
724 pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
725 convert_date(pt, argv[i++]);
726 continue;
729 if (strcmp(argv[i], "-r") == 0)
731 if (++i >= argc)
732 return usage("argument to -r missing", "");
734 if (restrict_tag_start)
735 restrict_tag_end = argv[i];
736 else
737 restrict_tag_start = argv[i];
739 i++;
740 continue;
743 if (strcmp(argv[i], "-u") == 0)
745 update_cache = 1;
746 i++;
747 continue;
750 if (strcmp(argv[i], "-x") == 0)
752 ignore_cache = 1;
753 update_cache = 1;
754 i++;
755 continue;
758 if (strcmp(argv[i], "-b") == 0)
760 if (++i >= argc)
761 return usage("argument to -b missing", "");
763 restrict_branch = argv[i++];
764 /* Warn if the user tries to use TRUNK. Should eventually
765 * go away as TRUNK may be a valid branch within CVS
767 if (strcmp(restrict_branch, "TRUNK") == 0)
768 debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
769 continue;
772 if (strcmp(argv[i], "-p") == 0)
774 if (++i >= argc)
775 return usage("argument to -p missing", "");
777 patch_set_dir = argv[i++];
778 continue;
781 if (strcmp(argv[i], "-v") == 0)
783 debuglvl = ~0;
784 i++;
785 continue;
788 if (strcmp(argv[i], "-t") == 0)
790 statistics = 1;
791 i++;
792 continue;
795 if (strcmp(argv[i], "--summary-first") == 0)
797 summary_first = 1;
798 i++;
799 continue;
802 if (strcmp(argv[i], "-h") == 0)
803 return usage(NULL, NULL);
805 /* see special handling of --norc in main */
806 if (strcmp(argv[i], "--norc") == 0)
808 norc = "-f";
809 i++;
810 continue;
813 if (strcmp(argv[i], "--test-log") == 0)
815 if (++i >= argc)
816 return usage("argument to --test-log missing", "");
818 test_log_file = argv[i++];
819 continue;
822 if (strcmp(argv[i], "--diff-opts") == 0)
824 if (++i >= argc)
825 return usage("argument to --diff-opts missing", "");
827 /* allow diff_opts to be turned off by making empty string
828 * into NULL
830 if (!strlen(argv[i]))
831 diff_opts = NULL;
832 else
833 diff_opts = argv[i];
834 i++;
835 continue;
838 if (strcmp(argv[i], "--bkcvs") == 0)
840 bkcvs = 1;
841 i++;
842 continue;
845 if (strcmp(argv[i], "--no-rlog") == 0)
847 no_rlog = 1;
848 i++;
849 continue;
852 if (strcmp(argv[i], "--cvs-direct") == 0)
854 cvs_direct = 1;
855 i++;
856 continue;
859 if (strcmp(argv[i], "--no-cvs-direct") == 0)
861 cvs_direct = 0;
862 i++;
863 continue;
866 if (strcmp(argv[i], "--debuglvl") == 0)
868 if (++i >= argc)
869 return usage("argument to --debuglvl missing", "");
871 debuglvl = atoi(argv[i++]);
872 continue;
875 if (strcmp(argv[i], "-Z") == 0)
877 if (++i >= argc)
878 return usage("argument to -Z", "");
880 compress = atoi(argv[i++]);
882 if (compress < 0 || compress > 9)
883 return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
885 if (compress == 0)
886 compress_arg[0] = 0;
887 else
888 snprintf(compress_arg, 8, "-z%d", compress);
889 continue;
892 if (strcmp(argv[i], "--root") == 0)
894 if (++i >= argc)
895 return usage("argument to --root missing", "");
897 strcpy(root_path, argv[i++]);
898 continue;
901 if (strcmp(argv[i], "-q") == 0)
903 debuglvl &= ~DEBUG_APPMSG1;
904 i++;
905 continue;
908 if (strcmp(argv[i], "-A") == 0)
910 track_branch_ancestry = 1;
911 i++;
912 continue;
915 if (argv[i][0] == '-')
916 return usage("invalid argument", argv[i]);
918 strcpy(repository_path, argv[i++]);
921 return 0;
924 static int parse_rc()
926 char rcfile[PATH_MAX];
927 FILE * fp;
928 snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
929 if ((fp = fopen(rcfile, "r")))
931 char buff[BUFSIZ];
932 while (fgets(buff, BUFSIZ, fp))
934 char * argv[3], *p;
935 int argc = 2;
937 chop(buff);
939 argv[0] = "garbage";
941 p = strchr(buff, ' ');
942 if (p)
944 *p++ = '\0';
945 argv[2] = xstrdup(p);
946 argc = 3;
949 argv[1] = xstrdup(buff);
951 if (parse_args(argc, argv) < 0)
952 return -1;
954 fclose(fp);
957 return 0;
960 static void init_paths()
962 FILE * fp;
963 char * p;
964 int len;
966 /* determine the CVSROOT. precedence:
967 * 1) command line
968 * 2) working directory (if present)
969 * 3) environment variable CVSROOT
971 if (!root_path[0])
973 if (!(fp = fopen("CVS/Root", "r")))
975 const char * e;
977 debug(DEBUG_STATUS, "Can't open CVS/Root");
978 e = getenv("CVSROOT");
980 if (!e)
982 debug(DEBUG_APPERROR, "cannot determine CVSROOT");
983 exit(1);
986 strcpy(root_path, e);
988 else
990 if (!fgets(root_path, PATH_MAX, fp))
992 debug(DEBUG_APPERROR, "Error reading CVSROOT");
993 exit(1);
996 fclose(fp);
998 /* chop the lf and optional trailing '/' */
999 len = strlen(root_path) - 1;
1000 root_path[len] = 0;
1001 if (root_path[len - 1] == '/')
1002 root_path[--len] = 0;
1006 /* Determine the repository path, precedence:
1007 * 1) command line
1008 * 2) working directory
1011 if (!repository_path[0])
1013 if (!(fp = fopen("CVS/Repository", "r")))
1015 debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1016 exit(1);
1019 if (!fgets(repository_path, PATH_MAX, fp))
1021 debug(DEBUG_APPERROR, "Error reading repository path");
1022 exit(1);
1025 chop(repository_path);
1026 fclose(fp);
1029 /* get the path portion of the root */
1030 p = strrchr(root_path, ':');
1032 if (!p)
1033 p = root_path;
1034 else
1035 p++;
1037 /* some CVS have the CVSROOT string as part of the repository
1038 * string (initial substring). remove it.
1040 len = strlen(p);
1042 if (strncmp(p, repository_path, len) == 0)
1044 int rlen = strlen(repository_path + len + 1);
1045 memmove(repository_path, repository_path + len + 1, rlen + 1);
1048 /* the 'strip_path' will be used whenever the CVS server gives us a
1049 * path to an 'rcs file'. the strip_path portion of these paths is
1050 * stripped off, leaving us with the working file.
1052 * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1053 * (instead of log) it gives the 'real' RCS file path, which can be different
1054 * from the 'nominal' repository path because of symlinks in the server and
1055 * the like. See also the 'parse_file' routine
1057 strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1059 if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1061 debug(DEBUG_APPERROR, "strip_path overflow");
1062 exit(1);
1065 debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1068 static CvsFile * parse_file(const char * buff)
1070 CvsFile * retval;
1071 char fn[PATH_MAX];
1072 int len = strlen(buff + 10);
1073 char * p;
1075 /* once a single file has been parsed ok we set this */
1076 static int path_ok;
1078 /* chop the ",v" string and the "LF" */
1079 len -= 3;
1080 memcpy(fn, buff + 10, len);
1081 fn[len] = 0;
1083 if (strncmp(fn, strip_path, strip_path_len) != 0)
1085 /* if the very first file fails the strip path,
1086 * then maybe we need to try for an alternate.
1087 * this will happen if symlinks are being used
1088 * on the server. our best guess is to look
1089 * for the final occurance of the repository
1090 * path in the filename and use that. it should work
1091 * except in the case where:
1092 * 1) the project has no files in the top-level directory
1093 * 2) the project has a directory with the same name as the project
1094 * 3) that directory sorts alphabetically before any other directory
1095 * in which case, you are scr**ed
1097 if (!path_ok)
1099 char * p = fn, *lastp = NULL;
1101 while ((p = strstr(p, repository_path)))
1102 lastp = p++;
1104 if (lastp)
1106 int len = strlen(repository_path);
1107 memcpy(strip_path, fn, lastp - fn + len + 1);
1108 strip_path_len = lastp - fn + len + 1;
1109 strip_path[strip_path_len] = 0;
1110 debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
1111 goto ok;
1115 /* FIXME: a subdirectory may have a different Repository path
1116 * than it's parent. we'll fail the above test since strip_path
1117 * is global for the entire checked out tree (recursively).
1119 * For now just ignore such files
1121 debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring",
1122 fn, strip_path);
1123 return NULL;
1127 path_ok = 1;
1129 /* remove from beginning the 'strip_path' string */
1130 len -= strip_path_len;
1131 memmove(fn, fn + strip_path_len, len);
1132 fn[len] = 0;
1134 /* check if file is in the 'Attic/' and remove it */
1135 if ((p = strrchr(fn, '/')) &&
1136 p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1138 memmove(p - 5, p + 1, len - (p - fn + 1));
1139 len -= 6;
1140 fn[len] = 0;
1143 debug(DEBUG_STATUS, "stripped filename %s", fn);
1145 retval = (CvsFile*)get_hash_object(file_hash, fn);
1147 if (!retval)
1149 if ((retval = create_cvsfile()))
1151 retval->filename = xstrdup(fn);
1152 put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1154 else
1156 debug(DEBUG_SYSERROR, "malloc failed");
1157 exit(1);
1160 debug(DEBUG_STATUS, "new file: %s", retval->filename);
1162 else
1164 debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1167 return retval;
1170 PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
1172 PatchSet * retval = NULL, **find = NULL;
1173 int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
1175 if (!(retval = create_patch_set()))
1177 debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1178 return NULL;
1181 convert_date(&retval->date, dte);
1182 retval->author = get_string(author);
1183 retval->descr = xstrdup(log);
1184 retval->branch = get_string(branch);
1186 /* we are looking for a patchset suitable for holding this member.
1187 * this means two things:
1188 * 1) a patchset already containing an entry for the file is no good
1189 * 2) for two patchsets with same exact date/time, if they reference
1190 * the same file, we can properly order them. this primarily solves
1191 * the 'cvs import' problem and may not have general usefulness
1192 * because it would only work if the first member we consider is
1193 * present in the existing ps.
1195 if (psm)
1196 list_add(&psm->link, retval->members.prev);
1198 find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
1200 if (psm)
1201 list_del(&psm->link);
1203 if (*find != retval)
1205 debug(DEBUG_STATUS, "found existing patch set");
1207 if (bkcvs && strstr(retval->descr, "BKrev:"))
1209 free((*find)->descr);
1210 (*find)->descr = retval->descr;
1212 else
1214 free(retval->descr);
1217 /* keep the minimum date of any member as the 'actual' date */
1218 if (retval->date < (*find)->date)
1219 (*find)->date = retval->date;
1221 /* expand the min_date/max_date window to help finding other members .
1222 * open the window by an extra margin determined by the fuzz factor
1224 if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1226 (*find)->min_date = retval->date - timestamp_fuzz_factor;
1227 //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1229 else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1230 (*find)->max_date = retval->date + timestamp_fuzz_factor;
1232 free(retval);
1233 retval = *find;
1235 else
1237 debug(DEBUG_STATUS, "new patch set!");
1238 debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
1240 retval->min_date = retval->date - timestamp_fuzz_factor;
1241 retval->max_date = retval->date + timestamp_fuzz_factor;
1243 list_add(&retval->all_link, &all_patch_sets);
1247 return retval;
1250 static int get_branch_ext(char * buff, const char * rev, int * leaf)
1252 char * p;
1253 int len = strlen(rev);
1255 /* allow get_branch(buff, buff) without destroying contents */
1256 memmove(buff, rev, len);
1257 buff[len] = 0;
1259 p = strrchr(buff, '.');
1260 if (!p)
1261 return 0;
1262 *p++ = 0;
1264 if (leaf)
1265 *leaf = atoi(p);
1267 return 1;
1270 static int get_branch(char * buff, const char * rev)
1272 return get_branch_ext(buff, rev, NULL);
1276 * the goal if this function is to determine what revision to assign to
1277 * the psm->pre_rev field. usually, the log file is strictly
1278 * reverse chronological, so rev is direct ancestor to psm,
1280 * This all breaks down at branch points however
1283 static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1285 char pre[REV_STR_MAX], post[REV_STR_MAX];
1287 if (!psm)
1288 return;
1290 if (!rev)
1292 /* if psm was last rev. for file, it's either an
1293 * INITIAL, or first rev of a branch. to test if it's
1294 * the first rev of a branch, do get_branch twice -
1295 * this should be the bp.
1297 if (get_branch(post, psm->post_rev->rev) &&
1298 get_branch(pre, post))
1300 psm->pre_rev = file_get_revision(psm->file, pre);
1301 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1303 else
1305 set_psm_initial(psm);
1307 return;
1311 * is this canditate for 'pre' on the same branch as our 'post'?
1312 * this is the normal case
1314 if (!get_branch(pre, rev->rev))
1316 debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1317 return;
1320 if (!get_branch(post, psm->post_rev->rev))
1322 debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1323 return;
1326 if (strcmp(pre, post) == 0)
1328 psm->pre_rev = rev;
1329 rev->pre_psm = psm;
1330 return;
1333 /* branches don't match. new_psm must be head of branch,
1334 * so psm is oldest rev. on branch. or oldest
1335 * revision overall. if former, derive predecessor.
1336 * use get_branch to chop another rev. off of string.
1338 * FIXME:
1339 * There's also a weird case. it's possible to just re-number
1340 * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1341 * It's not widely used. In those cases of discontinuity,
1342 * we end up stamping the predecessor as 'INITIAL' incorrectly
1345 if (!get_branch(pre, post))
1347 set_psm_initial(psm);
1348 return;
1351 psm->pre_rev = file_get_revision(psm->file, pre);
1352 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1355 static void check_print_patch_set(PatchSet * ps)
1357 if (ps->psid < 0)
1358 return;
1360 /* the funk_factor overrides the restrict_tag_start and end */
1361 if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1362 goto ok;
1364 if (ps->funk_factor == FNK_HIDE_ALL)
1365 return;
1367 if (ps->psid <= restrict_tag_ps_start)
1369 if (ps->psid == restrict_tag_ps_start)
1370 debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1372 return;
1375 if (ps->psid > restrict_tag_ps_end)
1376 return;
1379 if (restrict_date_start > 0 &&
1380 (ps->date < restrict_date_start ||
1381 (restrict_date_end > 0 && ps->date > restrict_date_end)))
1382 return;
1384 if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1385 return;
1387 if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1388 return;
1390 if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1391 return;
1393 if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1394 return;
1396 if (!list_empty(&show_patch_set_ranges))
1398 struct list_head * next = show_patch_set_ranges.next;
1400 while (next != &show_patch_set_ranges)
1402 PatchSetRange *range = list_entry(next, PatchSetRange, link);
1403 if (range->min_counter <= ps->psid &&
1404 ps->psid <= range->max_counter)
1406 break;
1408 next = next->next;
1411 if (next == &show_patch_set_ranges)
1412 return;
1415 if (patch_set_dir)
1417 char path[PATH_MAX];
1419 snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1421 fflush(stdout);
1422 close(1);
1423 if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1425 debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1426 exit(1);
1429 fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1433 * If the summary_first option is in effect, there will be
1434 * two passes through the tree. the first with summary_first == 1
1435 * the second with summary_first == 2. if the option is not
1436 * in effect, there will be one pass with summary_first == 0
1438 * When the -s option is in effect, the show_patch_set_ranges
1439 * list will be non-empty.
1441 if (summary_first <= 1)
1442 print_patch_set(ps);
1443 if (do_diff && summary_first != 1)
1444 do_cvs_diff(ps);
1446 fflush(stdout);
1449 static void print_patch_set(PatchSet * ps)
1451 struct tm *tm;
1452 struct list_head * next;
1453 const char * funk = "";
1455 tm = localtime(&ps->date);
1456 next = ps->members.next;
1458 funk = fnk_descr[ps->funk_factor];
1460 /* this '---...' is different from the 28 hyphens that separate cvs log output */
1461 printf("---------------------\n");
1462 printf("PatchSet %d %s\n", ps->psid, funk);
1463 printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
1464 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
1465 tm->tm_hour, tm->tm_min, tm->tm_sec);
1466 printf("Author: %s\n", ps->author);
1467 printf("Branch: %s\n", ps->branch);
1468 if (ps->ancestor_branch)
1469 printf("Ancestor branch: %s\n", ps->ancestor_branch);
1470 printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
1471 printf("Log:\n%s\n", ps->descr);
1472 printf("Members: \n");
1474 while (next != &ps->members)
1476 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1477 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1478 funk = "(BEFORE START TAG)";
1479 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1480 funk = "(AFTER END TAG)";
1481 else
1482 funk = "";
1484 printf("\t%s:%s->%s%s %s\n",
1485 psm->file->filename,
1486 psm->pre_rev ? psm->pre_rev->rev : "INITIAL",
1487 psm->post_rev->rev,
1488 psm->post_rev->dead ? "(DEAD)": "",
1489 funk);
1491 next = next->next;
1494 printf("\n");
1497 /* walk all the patchsets to assign monotonic psid,
1498 * and to establish branch ancestry
1500 static void assign_patchset_id(PatchSet * ps)
1503 * Ignore the 'BRANCH ADD' patchsets
1505 if (!ps->branch_add)
1507 ps_counter++;
1508 ps->psid = ps_counter;
1510 if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
1512 PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
1513 if (!head_ps)
1515 head_ps = ps;
1516 put_hash_object(branch_heads, ps->branch, head_ps);
1519 determine_branch_ancestor(ps, head_ps);
1522 else
1524 ps->psid = -1;
1528 static int compare_rev_strings(const char * cr1, const char * cr2)
1530 char r1[REV_STR_MAX];
1531 char r2[REV_STR_MAX];
1532 char *s1 = r1, *s2 = r2;
1533 char *p1, *p2;
1534 int n1, n2;
1536 strcpy(s1, cr1);
1537 strcpy(s2, cr2);
1539 for (;;)
1541 p1 = strchr(s1, '.');
1542 p2 = strchr(s2, '.');
1544 if (p1) *p1++ = 0;
1545 if (p2) *p2++ = 0;
1547 n1 = atoi(s1);
1548 n2 = atoi(s2);
1550 if (n1 < n2)
1551 return -1;
1552 if (n1 > n2)
1553 return 1;
1555 if (!p1 && p2)
1556 return -1;
1557 if (p1 && !p2)
1558 return 1;
1559 if (!p1 && !p2)
1560 return 0;
1562 s1 = p1;
1563 s2 = p2;
1567 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1569 struct list_head * i;
1571 for (i = ps1->members.next; i != &ps1->members; i = i->next)
1573 PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1574 struct list_head * j;
1576 for (j = ps2->members.next; j != &ps2->members; j = j->next)
1578 PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1579 if (psm1->file == psm2->file)
1581 int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1582 //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1583 return ret;
1588 return 0;
1591 static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
1593 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1594 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1595 long diff;
1597 diff = ps1->date - ps2->date;
1599 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1602 static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1604 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1605 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1606 long diff;
1607 int ret;
1608 time_t d, min, max;
1610 /* We order by (author, descr, branch, members, date), but because of the fuzz factor
1611 * we treat times within a certain distance as equal IFF the author
1612 * and descr match.
1615 ret = strcmp(ps1->author, ps2->author);
1616 if (ret)
1617 return ret;
1619 ret = strcmp(ps1->descr, ps2->descr);
1620 if (ret)
1621 return ret;
1623 ret = strcmp(ps1->branch, ps2->branch);
1624 if (ret)
1625 return ret;
1627 ret = compare_patch_sets_by_members(ps1, ps2);
1628 if (ret)
1629 return ret;
1632 * one of ps1 or ps2 is new. the other should have the min_date
1633 * and max_date set to a window opened by the fuzz_factor
1635 if (ps1->min_date == 0)
1637 d = ps1->date;
1638 min = ps2->min_date;
1639 max = ps2->max_date;
1641 else if (ps2->min_date == 0)
1643 d = ps2->date;
1644 min = ps1->min_date;
1645 max = ps1->max_date;
1647 else
1649 debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
1650 exit(1);
1653 if (min < d && d < max)
1654 return 0;
1656 diff = ps1->date - ps2->date;
1658 return (diff < 0) ? -1 : 1;
1661 static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
1663 const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
1664 const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
1665 return compare_patch_sets_bytime(ps1, ps2);
1668 static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
1670 long diff;
1671 int ret;
1673 /* When doing a time-ordering of patchsets, we don't need to
1674 * fuzzy-match the time. We've already done fuzzy-matching so we
1675 * know that insertions are unique at this point.
1678 diff = ps1->date - ps2->date;
1679 if (diff)
1680 return (diff < 0) ? -1 : 1;
1682 ret = compare_patch_sets_by_members(ps1, ps2);
1683 if (ret)
1684 return ret;
1686 ret = strcmp(ps1->author, ps2->author);
1687 if (ret)
1688 return ret;
1690 ret = strcmp(ps1->descr, ps2->descr);
1691 if (ret)
1692 return ret;
1694 ret = strcmp(ps1->branch, ps2->branch);
1695 return ret;
1699 static int is_revision_metadata(const char * buff)
1701 char * p1, *p2;
1702 int len;
1704 if (!(p1 = strchr(buff, ':')))
1705 return 0;
1707 p2 = strchr(buff, ' ');
1709 if (p2 && p2 < p1)
1710 return 0;
1712 len = strlen(buff);
1714 /* lines have LF at end */
1715 if (len > 1 && buff[len - 2] == ';')
1716 return 1;
1718 return 0;
1721 static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
1723 struct list_head * next = ps->members.next;
1725 while (next != &ps->members)
1727 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1729 if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
1730 return 1;
1732 next = next->next;
1735 return 0;
1738 static int patch_set_affects_branch(PatchSet * ps, const char * branch)
1740 struct list_head * next;
1742 for (next = ps->members.next; next != &ps->members; next = next->next)
1744 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1747 * slight hack. if -r is specified, and this patchset
1748 * is 'before' the tag, but is FNK_SHOW_SOME, only
1749 * check if the 'after tag' revisions affect
1750 * the branch. this is especially important when
1751 * the tag is a branch point.
1753 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1754 continue;
1756 if (revision_affects_branch(psm->post_rev, branch))
1757 return 1;
1760 return 0;
1763 static void do_cvs_diff(PatchSet * ps)
1765 struct list_head * next;
1766 const char * dtype;
1767 const char * dopts;
1768 const char * utype;
1769 char use_rep_path[PATH_MAX];
1770 char esc_use_rep_path[PATH_MAX];
1772 fflush(stdout);
1773 fflush(stderr);
1776 * if cvs_direct is not in effect, and diff options are specified,
1777 * then we have to use diff instead of rdiff and we'll get a -p0
1778 * diff (instead of -p1) [in a manner of speaking]. So to make sure
1779 * that the add/remove diffs get generated likewise, we need to use
1780 * 'update' instead of 'co'
1782 * cvs_direct will always use diff (not rdiff), but will also always
1783 * generate -p1 diffs.
1785 if (diff_opts == NULL)
1787 dopts = "-u";
1788 dtype = "rdiff";
1789 utype = "co";
1790 sprintf(use_rep_path, "%s/", repository_path);
1791 /* the rep_path may contain characters that the shell will barf on */
1792 escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
1794 else
1796 dopts = diff_opts;
1797 dtype = "diff";
1798 utype = "update";
1799 use_rep_path[0] = 0;
1800 esc_use_rep_path[0] = 0;
1803 for (next = ps->members.next; next != &ps->members; next = next->next)
1805 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1806 char cmdbuff[PATH_MAX * 2+1];
1807 char esc_file[PATH_MAX];
1808 int ret, check_ret = 0;
1810 cmdbuff[0] = 0;
1811 cmdbuff[PATH_MAX*2] = 0;
1813 /* the filename may contain characters that the shell will barf on */
1814 escape_filename(esc_file, PATH_MAX, psm->file->filename);
1817 * Check the patchset funk. we may not want to diff this particular file
1819 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1821 printf("Index: %s\n", psm->file->filename);
1822 printf("===================================================================\n");
1823 printf("*** Member not diffed, before start tag\n");
1824 continue;
1826 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1828 printf("Index: %s\n", psm->file->filename);
1829 printf("===================================================================\n");
1830 printf("*** Member not diffed, after end tag\n");
1831 continue;
1835 * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1836 * or 'cvs update' to get the file, because cvs won't generate these diffs.
1837 * The problem is that this must be piped to diff, and so the resulting
1838 * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1839 * sed is used to replace the '-' with the filename.
1841 * It's possible for pre_rev to be a 'dead' revision. This happens when a file
1842 * is added on a branch. post_rev will be dead dead for remove
1844 if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
1846 int cr;
1847 const char * rev;
1849 if (!psm->pre_rev || psm->pre_rev->dead)
1851 cr = 1;
1852 rev = psm->post_rev->rev;
1854 else
1856 cr = 0;
1857 rev = psm->pre_rev->rev;
1860 if (cvs_direct_ctx)
1862 /* cvs_rupdate does the pipe through diff thing internally */
1863 cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
1865 else
1867 snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s -p -r %s %s%s | diff %s %s %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s%s|g'",
1868 compress_arg, norc, utype, rev, esc_use_rep_path, esc_file,
1869 dopts, cr?DEV_NULL:"-", cr?"-":DEV_NULL,
1870 cr?"2":"1", use_rep_path, psm->file->filename);
1873 else
1875 /* a regular diff */
1876 if (cvs_direct_ctx)
1878 cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
1880 else
1882 /* 'cvs diff' exit status '1' is ok, just means files are different */
1883 if (strcmp(dtype, "diff") == 0)
1884 check_ret = 1;
1886 snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1887 compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev,
1888 esc_use_rep_path, esc_file);
1893 * my_system doesn't block signals the way system does.
1894 * if ctrl-c is pressed while in there, we probably exit
1895 * immediately and hope the shell has sent the signal
1896 * to all of the process group members
1898 if (cmdbuff[0] && (ret = my_system(cmdbuff)))
1900 int stat = WEXITSTATUS(ret);
1903 * cvs diff returns 1 in exit status for 'files are different'
1904 * so use a better method to check for failure
1906 if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
1908 debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
1909 exit(1);
1915 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
1917 char * p;
1919 /* The "revision" log line can include extra information
1920 * including who is locking the file --- strip that out.
1923 p = rev_str;
1924 while (isdigit(*p) || *p == '.')
1925 p++;
1926 *p = 0;
1928 return cvs_file_add_revision(file, rev_str);
1931 CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
1933 CvsFileRevision * rev;
1935 if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
1937 rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
1938 rev->rev = get_string(rev_str);
1939 rev->file = file;
1940 rev->branch = NULL;
1941 rev->present = 0;
1942 rev->pre_psm = NULL;
1943 rev->post_psm = NULL;
1944 INIT_LIST_HEAD(&rev->branch_children);
1945 INIT_LIST_HEAD(&rev->tags);
1947 put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
1949 debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
1951 else
1953 debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
1957 * note: we are guaranteed to get here at least once with 'have_branches' == 1.
1958 * we may pass through once before this, because of symbolic tags, then once
1959 * always when processing the actual revision logs
1961 * rev->branch will always be set to something, maybe "HEAD"
1963 if (!rev->branch && file->have_branches)
1965 char branch_str[REV_STR_MAX];
1967 /* in the cvs cvs repository (ccvs) there are tagged versions
1968 * that don't exist. let's mark every 'known to exist'
1969 * version
1971 rev->present = 1;
1973 /* determine the branch this revision was committed on */
1974 if (!get_branch(branch_str, rev->rev))
1976 debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
1977 exit(1);
1980 rev->branch = (char*)get_hash_object(file->branches, branch_str);
1982 /* if there's no branch and it's not on the trunk, blab */
1983 if (!rev->branch)
1985 if (get_branch(branch_str, branch_str))
1987 debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
1988 rev->branch = "#CVSPS_NO_BRANCH";
1990 else
1992 rev->branch = "HEAD";
1996 debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
1999 return rev;
2002 CvsFile * create_cvsfile()
2004 CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
2005 if (!f)
2006 return NULL;
2008 f->revisions = create_hash_table(53);
2009 f->branches = create_hash_table(13);
2010 f->branches_sym = create_hash_table(13);
2011 f->symbols = create_hash_table(253);
2012 f->have_branches = 0;
2014 if (!f->revisions || !f->branches || !f->branches_sym)
2016 if (f->branches)
2017 destroy_hash_table(f->branches, NULL);
2018 if (f->revisions)
2019 destroy_hash_table(f->revisions, NULL);
2020 free(f);
2021 return NULL;
2024 return f;
2027 static PatchSet * create_patch_set()
2029 PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
2031 if (ps)
2033 INIT_LIST_HEAD(&ps->members);
2034 ps->psid = -1;
2035 ps->date = 0;
2036 ps->min_date = 0;
2037 ps->max_date = 0;
2038 ps->descr = NULL;
2039 ps->author = NULL;
2040 ps->tag = NULL;
2041 ps->tag_flags = 0;
2042 ps->branch_add = 0;
2043 ps->funk_factor = 0;
2044 ps->ancestor_branch = NULL;
2045 CLEAR_LIST_NODE(&ps->collision_link);
2048 return ps;
2051 PatchSetMember * create_patch_set_member()
2053 PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
2054 psm->pre_rev = NULL;
2055 psm->post_rev = NULL;
2056 psm->ps = NULL;
2057 psm->file = NULL;
2058 psm->bad_funk = 0;
2059 return psm;
2062 static PatchSetRange * create_patch_set_range()
2064 PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
2065 return psr;
2068 CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
2070 CvsFileRevision * rev;
2072 if (strcmp(r, "INITIAL") == 0)
2073 return NULL;
2075 rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
2077 if (!rev)
2079 debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
2080 exit(1);
2083 return rev;
2087 * Parse lines in the format:
2089 * <white space>tag_name: <rev>;
2091 * Handles both regular tags (these go into the symbols hash)
2092 * and magic-branch-tags (second to last node of revision is 0)
2093 * which go into branches and branches_sym hashes. Magic-branch
2094 * format is hidden in CVS everwhere except the 'cvs log' output.
2097 static void parse_sym(CvsFile * file, char * sym)
2099 char * tag = sym, *eot;
2100 int leaf, final_branch = -1;
2101 char rev[REV_STR_MAX];
2102 char rev2[REV_STR_MAX];
2104 while (*tag && isspace(*tag))
2105 tag++;
2107 if (!*tag)
2108 return;
2110 eot = strchr(tag, ':');
2112 if (!eot)
2113 return;
2115 *eot = 0;
2116 eot += 2;
2118 if (!get_branch_ext(rev, eot, &leaf))
2120 debug(DEBUG_APPERROR, "malformed revision");
2121 exit(1);
2125 * get_branch_ext will leave final_branch alone
2126 * if there aren't enough '.' in string
2128 get_branch_ext(rev2, rev, &final_branch);
2130 if (final_branch == 0)
2132 snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
2133 debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
2135 cvs_file_add_branch(file, rev, tag);
2137 else
2139 strcpy(rev, eot);
2140 chop(rev);
2142 /* see cvs manual: what is this vendor tag? */
2143 if (is_vendor_branch(rev))
2144 cvs_file_add_branch(file, rev, tag);
2145 else
2146 cvs_file_add_symbol(file, rev, tag);
2150 void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
2152 CvsFileRevision * rev;
2153 GlobalSymbol * sym;
2154 Tag * tag;
2156 /* get a permanent storage string */
2157 char * tag_str = get_string(p_tag_str);
2159 debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
2160 rev = cvs_file_add_revision(file, rev_str);
2161 put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
2164 * check the global_symbols
2166 sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
2167 if (!sym)
2169 sym = (GlobalSymbol*)malloc(sizeof(*sym));
2170 sym->tag = tag_str;
2171 sym->ps = NULL;
2172 INIT_LIST_HEAD(&sym->tags);
2174 put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
2177 tag = (Tag*)malloc(sizeof(*tag));
2178 tag->tag = tag_str;
2179 tag->rev = rev;
2180 tag->sym = sym;
2181 list_add(&tag->global_link, &sym->tags);
2182 list_add(&tag->rev_link, &rev->tags);
2185 char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
2187 char * new_tag;
2188 char * new_rev;
2190 if (get_hash_object(file->branches, rev))
2192 debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s",
2193 rev, tag, file->filename);
2194 return NULL;
2197 /* get permanent storage for the strings */
2198 new_tag = get_string(tag);
2199 new_rev = get_string(rev);
2201 put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
2202 put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
2204 return new_tag;
2208 * Resolve each global symbol to a PatchSet. This is
2209 * not necessarily doable, because tagging isn't
2210 * necessarily done to the project as a whole, and
2211 * it's possible that no tag is valid for all files
2212 * at a single point in time. We check for that
2213 * case though.
2215 * Implementation: the most recent PatchSet containing
2216 * a revision (post_rev) tagged by the symbol is considered
2217 * the 'tagged' PatchSet.
2220 static void resolve_global_symbols()
2222 struct hash_entry * he_sym;
2223 reset_hash_iterator(global_symbols);
2224 while ((he_sym = next_hash_entry(global_symbols)))
2226 GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
2227 PatchSet * ps;
2228 struct list_head * next;
2230 debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
2233 * First pass, determine the most recent PatchSet with a
2234 * revision tagged with the symbolic tag. This is 'the'
2235 * patchset with the tag
2238 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2240 Tag * tag = list_entry(next, Tag, global_link);
2241 CvsFileRevision * rev = tag->rev;
2243 /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
2244 if (!rev->present || !rev->post_psm)
2246 struct list_head *tmp = next->prev;
2247 debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
2248 rev->rev, rev->file->filename);
2249 /* FIXME: memleak */
2250 list_del(next);
2251 next = tmp;
2252 continue;
2255 ps = rev->post_psm->ps;
2257 if (!sym->ps || ps->date > sym->ps->date)
2258 sym->ps = ps;
2261 /* convenience variable */
2262 ps = sym->ps;
2264 if (!ps)
2266 debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
2267 return;
2270 ps->tag = sym->tag;
2272 /* check if this ps is one of the '-r' patchsets */
2273 if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
2274 restrict_tag_ps_start = ps->psid;
2276 /* the second -r implies -b */
2277 if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
2279 restrict_tag_ps_end = ps->psid;
2281 if (restrict_branch)
2283 if (strcmp(ps->branch, restrict_branch) != 0)
2285 debug(DEBUG_APPMSG1,
2286 "WARNING: -b option and second -r have conflicting branches: %s %s",
2287 restrict_branch, ps->branch);
2290 else
2292 debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
2293 restrict_branch = ps->branch;
2298 * Second pass.
2299 * check if this is an invalid patchset,
2300 * check which members are invalid. determine
2301 * the funk factor etc.
2303 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2305 Tag * tag = list_entry(next, Tag, global_link);
2306 CvsFileRevision * rev = tag->rev;
2307 CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
2309 if (!next_rev)
2310 continue;
2313 * we want the 'tagged revision' to be valid until after
2314 * the date of the 'tagged patchset' or else there's something
2315 * funky going on
2317 if (next_rev->post_psm->ps->date < ps->date)
2319 int flag = check_rev_funk(ps, next_rev);
2320 debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
2321 rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
2322 ps->tag_flags |= flag;
2328 static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
2330 /* special case the branch called 'HEAD' */
2331 if (strcmp(branch, "HEAD") == 0)
2333 /* look for only one '.' in rev */
2334 char * p = strchr(rev->rev, '.');
2335 if (p && !strchr(p + 1, '.'))
2336 return 1;
2338 else
2340 char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
2342 if (branch_rev)
2344 char post_rev[REV_STR_MAX];
2345 char branch[REV_STR_MAX];
2346 int file_leaf, branch_leaf;
2348 strcpy(branch, branch_rev);
2350 /* first get the branch the file rev is on */
2351 if (get_branch_ext(post_rev, rev->rev, &file_leaf))
2353 branch_leaf = file_leaf;
2355 /* check against branch and all branch ancestor branches */
2358 debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
2359 if (strcmp(branch, post_rev) == 0)
2360 return (file_leaf <= branch_leaf);
2362 while(get_branch_ext(branch, branch, &branch_leaf));
2367 return 0;
2370 static int count_dots(const char * p)
2372 int dots = 0;
2374 while (*p)
2375 if (*p++ == '.')
2376 dots++;
2378 return dots;
2382 * When importing vendor sources, (apparently people do this)
2383 * the code is added on a 'vendor' branch, which, for some reason
2384 * doesn't use the magic-branch-tag format. Try to detect that now
2386 static int is_vendor_branch(const char * rev)
2388 return !(count_dots(rev)&1);
2391 void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
2393 /* check if a member for the same file already exists, if so
2394 * put this PatchSet on the collisions list
2396 struct list_head * next;
2397 for (next = ps->members.next; next != &ps->members; next = next->next)
2399 PatchSetMember * m = list_entry(next, PatchSetMember, link);
2400 if (m->file == psm->file && ps->collision_link.next == NULL)
2401 list_add(&ps->collision_link, &collisions);
2404 psm->ps = ps;
2405 list_add(&psm->link, ps->members.prev);
2408 static void set_psm_initial(PatchSetMember * psm)
2410 psm->pre_rev = NULL;
2411 if (psm->post_rev->dead)
2414 * we expect a 'file xyz initially added on branch abc' here
2415 * but there can only be one such member in a given patchset
2417 if (psm->ps->branch_add)
2418 debug(DEBUG_APPMSG1, "WARNING: branch_add already set!");
2419 psm->ps->branch_add = 1;
2424 * look at all revisions starting at rev and going forward until
2425 * ps->date and see whether they are invalid or just funky.
2427 static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
2429 int retval = TAG_FUNKY;
2431 while (rev)
2433 PatchSet * next_ps = rev->post_psm->ps;
2434 struct list_head * next;
2436 if (next_ps->date > ps->date)
2437 break;
2439 debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
2440 ps->date, next_ps->date, rev->rev, rev->branch);
2443 * If the ps->tag is one of the two possible '-r' tags
2444 * then the funkyness is even more important.
2446 * In the restrict_tag_start case, this next_ps is chronologically
2447 * before ps, but tagwise after, so set the funk_factor so it will
2448 * be included.
2450 * The restrict_tag_end case is similar, but backwards.
2452 * Start assuming the HIDE/SHOW_ALL case, we will determine
2453 * below if we have a split ps case
2455 if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
2456 next_ps->funk_factor = FNK_SHOW_ALL;
2457 if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
2458 next_ps->funk_factor = FNK_HIDE_ALL;
2461 * if all of the other members of this patchset are also 'after' the tag
2462 * then this is a 'funky' patchset w.r.t. the tag. however, if some are
2463 * before then the patchset is 'invalid' w.r.t. the tag, and we mark
2464 * the members individually with 'bad_funk' ,if this tag is the
2465 * '-r' tag. Then we can actually split the diff on this patchset
2467 for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
2469 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2470 if (before_tag(psm->post_rev, ps->tag))
2472 retval = TAG_INVALID;
2473 /* only set bad_funk for one of the -r tags */
2474 if (next_ps->funk_factor)
2476 psm->bad_funk = 1;
2477 next_ps->funk_factor =
2478 (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
2480 debug(DEBUG_APPMSG1,
2481 "WARNING: Invalid PatchSet %d, Tag %s:\n"
2482 " %s:%s=after, %s:%s=before. Treated as 'before'",
2483 next_ps->psid, ps->tag,
2484 rev->file->filename, rev->rev,
2485 psm->post_rev->file->filename, psm->post_rev->rev);
2489 rev = rev_follow_branch(rev, ps->branch);
2492 return retval;
2495 /* determine if the revision is before the tag */
2496 static int before_tag(CvsFileRevision * rev, const char * tag)
2498 CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
2499 int retval = 0;
2501 if (tagged_rev &&
2502 revision_affects_branch(rev, tagged_rev->branch) &&
2503 rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
2504 retval = 1;
2506 debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d",
2507 rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
2509 return retval;
2512 /* get the next revision from this one following branch if possible */
2513 /* FIXME: not sure if this needs to follow branches leading up to branches? */
2514 static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
2516 struct list_head * next;
2518 /* check for 'main line of inheritance' */
2519 if (strcmp(rev->branch, branch) == 0)
2520 return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
2522 /* look down branches */
2523 for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
2525 CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
2526 //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2527 if (strcmp(next_rev->branch, branch) == 0)
2528 return next_rev;
2531 return NULL;
2534 static void check_norc(int argc, char * argv[])
2536 int i = 1;
2537 while (i < argc)
2539 if (strcmp(argv[i], "--norc") == 0)
2541 norc = "-f";
2542 break;
2544 i++;
2548 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
2550 struct list_head * next;
2551 CvsFileRevision * rev;
2553 /* PatchSet 1 has no ancestor */
2554 if (ps->psid == 1)
2555 return;
2557 /* HEAD branch patchsets have no ancestry, but callers should know that */
2558 if (strcmp(ps->branch, "HEAD") == 0)
2560 debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
2561 return;
2564 for (next = ps->members.next; next != &ps->members; next = next->next)
2566 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2567 rev = psm->pre_rev;
2568 int d1, d2;
2570 /* the reason this is at all complicated has to do with a
2571 * branch off of a branch. it is possible (and indeed
2572 * likely) that some file would not have been modified
2573 * from the initial branch point to the branch-off-branch
2574 * point, and therefore the branch-off-branch point is
2575 * really branch-off-HEAD for that specific member (file).
2576 * in that case, rev->branch will say HEAD but we want
2577 * to know the symbolic name of the first branch
2578 * so we continue to look member after member until we find
2579 * the 'deepest' branching. deepest can actually be determined
2580 * by considering the revision currently indicated by
2581 * ps->ancestor_branch (by symbolic lookup) and rev->rev. the
2582 * one with more dots wins
2584 * also, the first commit in which a branch-off-branch is
2585 * mentioned may ONLY modify files never committed since
2586 * original branch-off-HEAD was created, so we have to keep
2587 * checking, ps after ps to be sure to get the deepest ancestor
2589 * note: rev is the pre-commit revision, not the post-commit
2591 if (!head_ps->ancestor_branch)
2592 d1 = 0;
2593 else if (strcmp(ps->branch, rev->branch) == 0)
2594 continue;
2595 else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
2596 d1 = 1;
2597 else {
2598 /* branch_rev may not exist if the file was added on this branch for example */
2599 const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
2600 d1 = branch_rev ? count_dots(branch_rev) : 1;
2603 /* HACK: we sometimes pretend to derive from the import branch.
2604 * just don't do that. this is the easiest way to prevent...
2606 d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
2608 if (d2 > d1)
2609 head_ps->ancestor_branch = rev->branch;
2611 //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
2615 static void handle_collisions()
2617 struct list_head *next;
2618 for (next = collisions.next; next != &collisions; next = next->next)
2620 PatchSet * ps = list_entry(next, PatchSet, collision_link);
2621 printf("PatchSet %d has collisions\n", ps->psid);
2625 void walk_all_patch_sets(void (*action)(PatchSet *))
2627 struct list_head * next;;
2628 for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
2629 PatchSet * ps = list_entry(next, PatchSet, all_link);
2630 action(ps);