Take commitid into account
[cvsps-yd/commitid.git] / cvsps.c
blob80a81e1be0f18fb0551ea03fb486a4a01ab4a127
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 #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) */
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>
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"
35 RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
37 #define CVS_LOG_BOUNDARY "----------------------------\n"
38 #define CVS_FILE_BOUNDARY "=============================================================================\n"
40 enum
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
52 /* true globals */
53 struct hash_table * file_hash;
54 CvsServerCtx * cvs_direct_ctx;
55 char root_path[PATH_MAX];
56 char repository_path[PATH_MAX];
58 const char * tag_flag_descr[] = {
59 "",
60 "**FUNKY**",
61 "**INVALID**",
62 "**INVALID**"
65 const char * fnk_descr[] = {
66 "",
67 "FNK_SHOW_SOME",
68 "FNK_SHOW_ALL",
69 "FNK_HIDE_ALL",
70 "FNK_HIDE_SOME"
73 /* static globals */
74 static int ps_counter;
75 static void * ps_tree;
76 static struct hash_table * global_symbols;
77 static char strip_path[PATH_MAX];
78 static int strip_path_len;
79 static time_t cache_date;
80 static int update_cache;
81 static int ignore_cache;
82 static int do_write_cache;
83 static int statistics;
84 static const char * test_log_file;
85 static struct hash_table * branch_heads;
86 static struct list_head all_patch_sets;
87 static struct list_head collisions;
89 /* settable via options */
90 static int timestamp_fuzz_factor = 300;
91 static int do_diff;
92 static const char * restrict_author;
93 static int have_restrict_log;
94 static regex_t restrict_log;
95 static int have_restrict_file;
96 static regex_t restrict_file;
97 static time_t restrict_date_start;
98 static time_t restrict_date_end;
99 static const char * restrict_branch;
100 static struct list_head show_patch_set_ranges;
101 static int summary_first;
102 static const char * norc = "";
103 static const char * patch_set_dir;
104 static const char * restrict_tag_start;
105 static const char * restrict_tag_end;
106 static int restrict_tag_ps_start;
107 static int restrict_tag_ps_end = INT_MAX;
108 static const char * diff_opts;
109 static int bkcvs;
110 static int no_rlog;
111 static int cvs_direct;
112 static int compress;
113 static char compress_arg[8];
114 static int track_branch_ancestry;
116 static void check_norc(int, char *[]);
117 static int parse_args(int, char *[]);
118 static int parse_rc();
119 static void load_from_cvs();
120 static void init_paths();
121 static CvsFile * build_file_by_name(const char *);
122 static CvsFile * parse_rcs_file(const char *);
123 static CvsFile * parse_working_file(const char *);
124 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
125 static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
126 static void check_print_patch_set(PatchSet *);
127 static void print_patch_set(PatchSet *);
128 static void assign_patchset_id(PatchSet *);
129 static int compare_rev_strings(const char *, const char *);
130 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
131 static int compare_patch_sets_bk(const void *, const void *);
132 static int compare_patch_sets(const void *, const void *);
133 static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
134 static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
135 static int is_revision_metadata(const char *);
136 static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
137 static int patch_set_affects_branch(PatchSet *, const char *);
138 static void do_cvs_diff(PatchSet *);
139 static PatchSet * create_patch_set();
140 static PatchSetRange * create_patch_set_range();
141 static void parse_sym(CvsFile *, char *);
142 static void resolve_global_symbols();
143 static int revision_affects_branch(CvsFileRevision *, const char *);
144 static int is_vendor_branch(const char *);
145 static void set_psm_initial(PatchSetMember * psm);
146 static int check_rev_funk(PatchSet *, CvsFileRevision *);
147 static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
148 static int before_tag(CvsFileRevision * rev, const char * tag);
149 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
150 static void handle_collisions();
152 int main(int argc, char *argv[])
154 debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
156 INIT_LIST_HEAD(&show_patch_set_ranges);
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.
164 check_norc(argc, argv);
166 if (strlen(norc) == 0 && parse_rc() < 0)
167 exit(1);
169 if (parse_args(argc, argv) < 0)
170 exit(1);
172 if (diff_opts && !cvs_direct && do_diff)
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");
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);
188 /* this parses some of the CVS/ files, and initializes
189 * the repository_path and other variables
191 init_paths();
193 if (!ignore_cache)
195 int save_fuzz_factor = timestamp_fuzz_factor;
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
202 timestamp_fuzz_factor = 0;
204 if ((cache_date = read_cache()) < 0)
205 update_cache = 1;
207 timestamp_fuzz_factor = save_fuzz_factor;
210 if (cvs_direct && (do_diff || (update_cache && !test_log_file)))
211 cvs_direct_ctx = open_cvs_server(root_path, compress);
213 if (update_cache)
215 load_from_cvs();
216 do_write_cache = 1;
219 //XXX
220 //handle_collisions();
222 list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
224 ps_counter = 0;
225 walk_all_patch_sets(assign_patchset_id);
227 handle_collisions();
229 resolve_global_symbols();
231 if (do_write_cache)
232 write_cache(cache_date);
234 if (statistics)
235 print_statistics(ps_tree);
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)
241 debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
242 exit(1);
245 if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
247 debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
248 exit(1);
251 walk_all_patch_sets(check_print_patch_set);
253 if (summary_first++)
254 walk_all_patch_sets(check_print_patch_set);
256 if (cvs_direct_ctx)
257 close_cvs_server(cvs_direct_ctx);
259 exit(0);
262 static void load_from_cvs()
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 char cidbuff[CID_STR_MAX];
272 int logbufflen = LOG_STR_MAX + 1;
273 char * logbuff = malloc(logbufflen);
274 int loglen = 0;
275 int have_log = 0;
276 char cmd[BUFSIZ];
277 char date_str[64];
278 char use_rep_buff[PATH_MAX];
279 char * ltype;
281 if (logbuff == NULL)
283 debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen);
284 exit(1);
287 if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
289 ltype = "rlog";
290 snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
292 else
294 ltype = "log";
295 use_rep_buff[0] = 0;
298 if (cache_date > 0)
300 struct tm * tm = gmtime(&cache_date);
301 strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
303 /* this command asks for logs using two different date
304 * arguments, separated by ';' (see man rlog). The first
305 * gets all revisions more recent than date, the second
306 * gets a single revision no later than date, which combined
307 * get us all revisions that have occurred since last update
308 * and overlaps what we had before by exactly one revision,
309 * which is necessary to fill in the pre_rev stuff for a
310 * PatchSetMember
312 snprintf(cmd, BUFSIZ, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
314 else
316 date_str[0] = 0;
317 snprintf(cmd, BUFSIZ, "cvs %s %s -q %s %s", compress_arg, norc, ltype, use_rep_buff);
320 debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
322 cache_date = time(NULL);
324 /* FIXME: this is ugly, need to virtualize the accesses away from here */
325 if (test_log_file)
326 cvsfp = fopen(test_log_file, "r");
327 else if (cvs_direct_ctx)
328 cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
329 else
330 cvsfp = popen(cmd, "r");
332 if (!cvsfp)
334 debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
335 exit(1);
338 for (;;)
340 char * tst;
341 if (cvs_direct_ctx)
342 tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
343 else
344 tst = fgets(buff, BUFSIZ, cvsfp);
346 if (!tst)
347 break;
349 debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
351 switch(state)
353 case NEED_RCS_FILE:
354 if (strncmp(buff, "RCS file", 8) == 0) {
355 if ((file = parse_rcs_file(buff)) != NULL)
356 state = NEED_SYMS;
357 else
358 state = NEED_WORKING_FILE;
360 break;
361 case NEED_WORKING_FILE:
362 if (strncmp(buff, "Working file", 12) == 0) {
363 if ((file = parse_working_file(buff)))
364 state = NEED_SYMS;
365 else
366 state = NEED_RCS_FILE;
367 break;
368 } else {
369 // Working file come just after RCS file. So reset state if it was not found
370 state = NEED_RCS_FILE;
372 break;
373 case NEED_SYMS:
374 if (strncmp(buff, "symbolic names:", 15) == 0)
375 state = NEED_EOS;
376 break;
377 case NEED_EOS:
378 if (!isspace(buff[0]))
380 /* see cvsps_types.h for commentary on have_branches */
381 file->have_branches = 1;
382 state = NEED_START_LOG;
384 else
385 parse_sym(file, buff);
386 break;
387 case NEED_START_LOG:
388 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
389 state = NEED_REVISION;
390 break;
391 case NEED_REVISION:
392 if (strncmp(buff, "revision", 8) == 0)
394 char new_rev[REV_STR_MAX];
395 CvsFileRevision * rev;
397 strcpy(new_rev, buff + 9);
398 chop(new_rev);
401 * rev may already exist (think cvsps -u), in which
402 * case parse_revision is a hash lookup
404 rev = parse_revision(file, new_rev);
407 * in the simple case, we are copying rev to psm->pre_rev
408 * (psm refers to last patch set processed at this point)
409 * since generally speaking the log is reverse chronological.
410 * This breaks down slightly when branches are introduced
413 assign_pre_revision(psm, rev);
416 * if this is a new revision, it will have no post_psm associated.
417 * otherwise we are (probably?) hitting the overlap in cvsps -u
419 if (!rev->post_psm)
421 psm = rev->post_psm = create_patch_set_member();
422 psm->post_rev = rev;
423 psm->file = file;
424 state = NEED_DATE_AUTHOR_STATE;
426 else
428 /* we hit this in cvsps -u mode, we are now up-to-date
429 * w.r.t this particular file. skip all of the rest
430 * of the info (revs and logs) until we hit the next file
432 psm = NULL;
433 state = NEED_EOM;
436 break;
437 case NEED_DATE_AUTHOR_STATE:
438 if (strncmp(buff, "date:", 5) == 0)
440 char * p;
442 strncpy(datebuff, buff + 6, 19);
443 datebuff[19] = 0;
445 strcpy(authbuff, "unknown");
446 p = strstr(buff, "author: ");
447 if (p)
449 char * op;
450 p += 8;
451 op = strchr(p, ';');
452 if (op)
454 strzncpy(authbuff, p, op - p + 1);
458 /* read the 'state' tag to see if this is a dead revision */
459 p = strstr(buff, "state: ");
460 if (p)
462 char * op;
463 p += 7;
464 op = strchr(p, ';');
465 if (op)
466 if (strncmp(p, "dead", MIN(4, op - p)) == 0)
467 psm->post_rev->dead = 1;
470 cidbuff[0] = 0;
471 p = strstr(buff, "commitid: ");
472 if (p)
474 char * op;
475 p += 10;
476 op = strchr(p, ';');
477 if (op)
479 strzncpy(cidbuff, p, op - p + 1);
483 state = NEED_EOM;
485 break;
486 case NEED_EOM:
487 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
489 if (psm)
491 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, cidbuff, psm);
492 patch_set_add_member(ps, psm);
495 logbuff[0] = 0;
496 loglen = 0;
497 have_log = 0;
498 state = NEED_REVISION;
500 else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
502 if (psm)
504 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, cidbuff, psm);
505 patch_set_add_member(ps, psm);
506 assign_pre_revision(psm, NULL);
509 logbuff[0] = 0;
510 loglen = 0;
511 have_log = 0;
512 psm = NULL;
513 file = NULL;
514 state = NEED_RCS_FILE;
516 else
518 /* other "blahblah: information;" messages can
519 * follow the stuff we pay attention to
521 if (have_log || !is_revision_metadata(buff))
523 /* If the log buffer is full, try to reallocate more. */
524 if (loglen < logbufflen)
526 int len = strlen(buff);
528 if (len >= logbufflen - loglen)
530 debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename);
531 logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX);
532 char * newlogbuff = realloc(logbuff, logbufflen);
533 if (newlogbuff == NULL)
535 debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen);
536 exit(1);
538 logbuff = newlogbuff;
541 debug(DEBUG_STATUS, "appending %s to log", buff);
542 memcpy(logbuff + loglen, buff, len);
543 loglen += len;
544 logbuff[loglen] = 0;
545 have_log = 1;
548 else
550 debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
554 break;
558 if (state == NEED_SYMS)
560 debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
561 debug(DEBUG_APPERROR, " Perhaps you should try running with --norc");
562 exit(1);
565 if (state != NEED_RCS_FILE)
567 debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state);
568 exit(1);
571 if (test_log_file)
573 fclose(cvsfp);
575 else if (cvs_direct_ctx)
577 cvs_rlog_close(cvs_direct_ctx);
579 else
581 if (pclose(cvsfp) < 0)
583 debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
584 exit(1);
589 static int usage(const char * str1, const char * str2)
591 if (str1)
592 debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
594 debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
595 debug(DEBUG_APPERROR, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
596 debug(DEBUG_APPERROR, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
597 debug(DEBUG_APPERROR, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
598 debug(DEBUG_APPERROR, " [--test-log <captured cvs log file>] [--bkcvs]");
599 debug(DEBUG_APPERROR, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
600 debug(DEBUG_APPERROR, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
601 debug(DEBUG_APPERROR, " [-q] [-A] [<repository>]");
602 debug(DEBUG_APPERROR, "");
603 debug(DEBUG_APPERROR, "Where:");
604 debug(DEBUG_APPERROR, " -h display this informative message");
605 debug(DEBUG_APPERROR, " -x ignore (and rebuild) cvsps.cache file");
606 debug(DEBUG_APPERROR, " -u update cvsps.cache file");
607 debug(DEBUG_APPERROR, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
608 debug(DEBUG_APPERROR, " -g generate diffs of the selected patch sets");
609 debug(DEBUG_APPERROR, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
610 debug(DEBUG_APPERROR, " -a <author> restrict output to patch sets created by author");
611 debug(DEBUG_APPERROR, " -f <file> restrict output to patch sets involving file");
612 debug(DEBUG_APPERROR, " -d <date1> -d <date2> if just one date specified, show");
613 debug(DEBUG_APPERROR, " revisions newer than date1. If two dates specified,");
614 debug(DEBUG_APPERROR, " show revisions between two dates.");
615 debug(DEBUG_APPERROR, " -b <branch> restrict output to patch sets affecting history of branch");
616 debug(DEBUG_APPERROR, " -l <regex> restrict output to patch sets matching <regex> in log message");
617 debug(DEBUG_APPERROR, " -r <tag1> -r <tag2> if just one tag specified, show");
618 debug(DEBUG_APPERROR, " revisions since tag1. If two tags specified, show");
619 debug(DEBUG_APPERROR, " revisions between the two tags.");
620 debug(DEBUG_APPERROR, " -p <directory> output patch sets to individual files in <directory>");
621 debug(DEBUG_APPERROR, " -v show very verbose parsing messages");
622 debug(DEBUG_APPERROR, " -t show some brief memory usage statistics");
623 debug(DEBUG_APPERROR, " --norc when invoking cvs, ignore the .cvsrc file");
624 debug(DEBUG_APPERROR, " --summary-first when multiple patch sets are shown, put all summaries first");
625 debug(DEBUG_APPERROR, " --test-log <captured cvs log> supply a captured cvs log for testing");
626 debug(DEBUG_APPERROR, " --diff-opts <option string> supply special set of options to diff");
627 debug(DEBUG_APPERROR, " --bkcvs special hack for parsing the BK -> CVS log format");
628 debug(DEBUG_APPERROR, " --no-rlog disable rlog (it's faulty in some setups)");
629 debug(DEBUG_APPERROR, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
630 debug(DEBUG_APPERROR, " --debuglvl <bitmask> enable various debug channels.");
631 debug(DEBUG_APPERROR, " -Z <compression> A value 1-9 which specifies amount of compression");
632 debug(DEBUG_APPERROR, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
633 debug(DEBUG_APPERROR, " -q be quiet about warnings");
634 debug(DEBUG_APPERROR, " -A track and report branch ancestry");
635 debug(DEBUG_APPERROR, " <repository> apply cvsps to repository. overrides working directory");
636 debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
638 return -1;
641 static int parse_args(int argc, char *argv[])
643 int i = 1;
644 while (i < argc)
646 if (strcmp(argv[i], "-z") == 0)
648 if (++i >= argc)
649 return usage("argument to -z missing", "");
651 timestamp_fuzz_factor = atoi(argv[i++]);
652 continue;
655 if (strcmp(argv[i], "-g") == 0)
657 do_diff = 1;
658 i++;
659 continue;
662 if (strcmp(argv[i], "-s") == 0)
664 PatchSetRange * range;
665 char * min_str, * max_str;
667 if (++i >= argc)
668 return usage("argument to -s missing", "");
670 min_str = strtok(argv[i++], ",");
673 range = create_patch_set_range();
675 max_str = strrchr(min_str, '-');
676 if (max_str)
677 *max_str++ = '\0';
678 else
679 max_str = min_str;
681 range->min_counter = atoi(min_str);
683 if (*max_str)
684 range->max_counter = atoi(max_str);
685 else
686 range->max_counter = INT_MAX;
688 list_add(&range->link, show_patch_set_ranges.prev);
690 while ((min_str = strtok(NULL, ",")));
692 continue;
695 if (strcmp(argv[i], "-a") == 0)
697 if (++i >= argc)
698 return usage("argument to -a missing", "");
700 restrict_author = argv[i++];
701 continue;
704 if (strcmp(argv[i], "-l") == 0)
706 int err;
708 if (++i >= argc)
709 return usage("argument to -l missing", "");
711 if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
713 char errbuf[256];
714 regerror(err, &restrict_log, errbuf, 256);
715 return usage("bad regex to -l", errbuf);
718 have_restrict_log = 1;
720 continue;
723 if (strcmp(argv[i], "-f") == 0)
725 int err;
727 if (++i >= argc)
728 return usage("argument to -f missing", "");
730 if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
732 char errbuf[256];
733 regerror(err, &restrict_file, errbuf, 256);
734 return usage("bad regex to -f", errbuf);
737 have_restrict_file = 1;
739 continue;
742 if (strcmp(argv[i], "-d") == 0)
744 time_t *pt;
746 if (++i >= argc)
747 return usage("argument to -d missing", "");
749 pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
750 convert_date(pt, argv[i++]);
751 continue;
754 if (strcmp(argv[i], "-r") == 0)
756 if (++i >= argc)
757 return usage("argument to -r missing", "");
759 if (restrict_tag_start)
760 restrict_tag_end = argv[i];
761 else
762 restrict_tag_start = argv[i];
764 i++;
765 continue;
768 if (strcmp(argv[i], "-u") == 0)
770 update_cache = 1;
771 i++;
772 continue;
775 if (strcmp(argv[i], "-x") == 0)
777 ignore_cache = 1;
778 update_cache = 1;
779 i++;
780 continue;
783 if (strcmp(argv[i], "-b") == 0)
785 if (++i >= argc)
786 return usage("argument to -b missing", "");
788 restrict_branch = argv[i++];
789 /* Warn if the user tries to use TRUNK. Should eventually
790 * go away as TRUNK may be a valid branch within CVS
792 if (strcmp(restrict_branch, "TRUNK") == 0)
793 debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
794 continue;
797 if (strcmp(argv[i], "-p") == 0)
799 if (++i >= argc)
800 return usage("argument to -p missing", "");
802 patch_set_dir = argv[i++];
803 continue;
806 if (strcmp(argv[i], "-v") == 0)
808 debuglvl = ~0;
809 i++;
810 continue;
813 if (strcmp(argv[i], "-t") == 0)
815 statistics = 1;
816 i++;
817 continue;
820 if (strcmp(argv[i], "--summary-first") == 0)
822 summary_first = 1;
823 i++;
824 continue;
827 if (strcmp(argv[i], "-h") == 0)
828 return usage(NULL, NULL);
830 /* see special handling of --norc in main */
831 if (strcmp(argv[i], "--norc") == 0)
833 norc = "-f";
834 i++;
835 continue;
838 if (strcmp(argv[i], "--test-log") == 0)
840 if (++i >= argc)
841 return usage("argument to --test-log missing", "");
843 test_log_file = argv[i++];
844 continue;
847 if (strcmp(argv[i], "--diff-opts") == 0)
849 if (++i >= argc)
850 return usage("argument to --diff-opts missing", "");
852 /* allow diff_opts to be turned off by making empty string
853 * into NULL
855 if (!strlen(argv[i]))
856 diff_opts = NULL;
857 else
858 diff_opts = argv[i];
859 i++;
860 continue;
863 if (strcmp(argv[i], "--bkcvs") == 0)
865 bkcvs = 1;
866 i++;
867 continue;
870 if (strcmp(argv[i], "--no-rlog") == 0)
872 no_rlog = 1;
873 i++;
874 continue;
877 if (strcmp(argv[i], "--cvs-direct") == 0)
879 cvs_direct = 1;
880 i++;
881 continue;
884 if (strcmp(argv[i], "--no-cvs-direct") == 0)
886 cvs_direct = 0;
887 i++;
888 continue;
891 if (strcmp(argv[i], "--debuglvl") == 0)
893 if (++i >= argc)
894 return usage("argument to --debuglvl missing", "");
896 debuglvl = atoi(argv[i++]);
897 continue;
900 if (strcmp(argv[i], "-Z") == 0)
902 if (++i >= argc)
903 return usage("argument to -Z", "");
905 compress = atoi(argv[i++]);
907 if (compress < 0 || compress > 9)
908 return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
910 if (compress == 0)
911 compress_arg[0] = 0;
912 else
913 snprintf(compress_arg, 8, "-z%d", compress);
914 continue;
917 if (strcmp(argv[i], "--root") == 0)
919 if (++i >= argc)
920 return usage("argument to --root missing", "");
922 strcpy(root_path, argv[i++]);
923 continue;
926 if (strcmp(argv[i], "-q") == 0)
928 debuglvl &= ~DEBUG_APPMSG1;
929 i++;
930 continue;
933 if (strcmp(argv[i], "-A") == 0)
935 track_branch_ancestry = 1;
936 i++;
937 continue;
940 if (argv[i][0] == '-')
941 return usage("invalid argument", argv[i]);
943 strcpy(repository_path, argv[i++]);
946 return 0;
949 static int parse_rc()
951 char rcfile[PATH_MAX];
952 FILE * fp;
953 snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
954 if ((fp = fopen(rcfile, "r")))
956 char buff[BUFSIZ];
957 while (fgets(buff, BUFSIZ, fp))
959 char * argv[3], *p;
960 int argc = 2;
962 chop(buff);
964 argv[0] = "garbage";
966 p = strchr(buff, ' ');
967 if (p)
969 *p++ = '\0';
970 argv[2] = xstrdup(p);
971 argc = 3;
974 argv[1] = xstrdup(buff);
976 if (parse_args(argc, argv) < 0)
977 return -1;
979 fclose(fp);
982 return 0;
985 static void init_paths()
987 FILE * fp;
988 char * p;
989 int len;
991 /* determine the CVSROOT. precedence:
992 * 1) command line
993 * 2) working directory (if present)
994 * 3) environment variable CVSROOT
996 if (!root_path[0])
998 if (!(fp = fopen("CVS/Root", "r")))
1000 const char * e;
1002 debug(DEBUG_STATUS, "Can't open CVS/Root");
1003 e = getenv("CVSROOT");
1005 if (!e)
1007 debug(DEBUG_APPERROR, "cannot determine CVSROOT");
1008 exit(1);
1011 strcpy(root_path, e);
1013 else
1015 if (!fgets(root_path, PATH_MAX, fp))
1017 debug(DEBUG_APPERROR, "Error reading CVSROOT");
1018 exit(1);
1021 fclose(fp);
1023 /* chop the lf and optional trailing '/' */
1024 len = strlen(root_path) - 1;
1025 root_path[len] = 0;
1026 if (root_path[len - 1] == '/')
1027 root_path[--len] = 0;
1031 /* Determine the repository path, precedence:
1032 * 1) command line
1033 * 2) working directory
1036 if (!repository_path[0])
1038 if (!(fp = fopen("CVS/Repository", "r")))
1040 debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1041 exit(1);
1044 if (!fgets(repository_path, PATH_MAX, fp))
1046 debug(DEBUG_APPERROR, "Error reading repository path");
1047 exit(1);
1050 chop(repository_path);
1051 fclose(fp);
1054 /* get the path portion of the root */
1055 p = strrchr(root_path, ':');
1057 if (!p)
1058 p = root_path;
1059 else
1060 p++;
1062 /* some CVS have the CVSROOT string as part of the repository
1063 * string (initial substring). remove it.
1065 len = strlen(p);
1067 if (strncmp(p, repository_path, len) == 0)
1069 int rlen = strlen(repository_path + len + 1);
1070 memmove(repository_path, repository_path + len + 1, rlen + 1);
1073 /* the 'strip_path' will be used whenever the CVS server gives us a
1074 * path to an 'rcs file'. the strip_path portion of these paths is
1075 * stripped off, leaving us with the working file.
1077 * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1078 * (instead of log) it gives the 'real' RCS file path, which can be different
1079 * from the 'nominal' repository path because of symlinks in the server and
1080 * the like. See also the 'parse_rcs_file' routine
1082 strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1084 if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1086 debug(DEBUG_APPERROR, "strip_path overflow");
1087 exit(1);
1090 debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1093 static CvsFile * parse_rcs_file(const char * buff)
1095 char fn[PATH_MAX];
1096 int len = strlen(buff + 10);
1097 char * p;
1099 /* once a single file has been parsed ok we set this */
1100 static int path_ok;
1102 /* chop the ",v" string and the "LF" */
1103 len -= 3;
1104 memcpy(fn, buff + 10, len);
1105 fn[len] = 0;
1107 if (strncmp(fn, strip_path, strip_path_len) != 0)
1109 /* if the very first file fails the strip path,
1110 * then maybe we need to try for an alternate.
1111 * this will happen if symlinks are being used
1112 * on the server. our best guess is to look
1113 * for the final occurance of the repository
1114 * path in the filename and use that. it should work
1115 * except in the case where:
1116 * 1) the project has no files in the top-level directory
1117 * 2) the project has a directory with the same name as the project
1118 * 3) that directory sorts alphabetically before any other directory
1119 * in which case, you are scr**ed
1121 if (!path_ok)
1123 char * p = fn, *lastp = NULL;
1125 while ((p = strstr(p, repository_path)))
1126 lastp = p++;
1128 if (lastp)
1130 int len = strlen(repository_path);
1131 memcpy(strip_path, fn, lastp - fn + len + 1);
1132 strip_path_len = lastp - fn + len + 1;
1133 strip_path[strip_path_len] = 0;
1134 debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
1135 goto ok;
1139 /* FIXME: a subdirectory may have a different Repository path
1140 * than it's parent. we'll fail the above test since strip_path
1141 * is global for the entire checked out tree (recursively).
1143 * For now just ignore such files
1145 debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring",
1146 fn, strip_path);
1147 return NULL;
1151 path_ok = 1;
1153 /* remove from beginning the 'strip_path' string */
1154 len -= strip_path_len;
1155 memmove(fn, fn + strip_path_len, len);
1156 fn[len] = 0;
1158 /* check if file is in the 'Attic/' and remove it */
1159 if ((p = strrchr(fn, '/')) &&
1160 p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1162 memmove(p - 5, p + 1, len - (p - fn + 1));
1163 len -= 6;
1164 fn[len] = 0;
1167 debug(DEBUG_STATUS, "stripped filename %s", fn);
1169 return build_file_by_name(fn);
1172 static CvsFile * parse_working_file(const char * buff)
1174 char fn[PATH_MAX];
1175 int len = strlen(buff + 14);
1177 /* chop the "LF" */
1178 len -= 1;
1179 memcpy(fn, buff + 14, len);
1180 fn[len] = 0;
1182 debug(DEBUG_STATUS, "working filename %s", fn);
1184 return build_file_by_name(fn);
1187 static CvsFile * build_file_by_name(const char * fn)
1189 CvsFile * retval;
1191 retval = (CvsFile*)get_hash_object(file_hash, fn);
1193 if (!retval)
1195 if ((retval = create_cvsfile()))
1197 retval->filename = xstrdup(fn);
1198 put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1200 else
1202 debug(DEBUG_SYSERROR, "malloc failed");
1203 exit(1);
1206 debug(DEBUG_STATUS, "new file: %s", retval->filename);
1208 else
1210 debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1213 return retval;
1216 PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, const char *commitid, PatchSetMember * psm)
1218 PatchSet * retval = NULL, **find = NULL;
1219 int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
1221 if (!(retval = create_patch_set()))
1223 debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1224 return NULL;
1227 convert_date(&retval->date, dte);
1228 retval->author = get_string(author);
1229 retval->commitid = get_string(commitid);
1230 retval->descr = xstrdup(log);
1231 retval->branch = get_string(branch);
1233 /* we are looking for a patchset suitable for holding this member.
1234 * this means two things:
1235 * 1) a patchset already containing an entry for the file is no good
1236 * 2) for two patchsets with same exact date/time, if they reference
1237 * the same file, we can properly order them. this primarily solves
1238 * the 'cvs import' problem and may not have general usefulness
1239 * because it would only work if the first member we consider is
1240 * present in the existing ps.
1242 if (psm)
1243 list_add(&psm->link, retval->members.prev);
1245 find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
1247 if (psm)
1248 list_del(&psm->link);
1250 if (*find != retval)
1252 debug(DEBUG_STATUS, "found existing patch set");
1254 if (bkcvs && strstr(retval->descr, "BKrev:"))
1256 free((*find)->descr);
1257 (*find)->descr = retval->descr;
1259 else
1261 free(retval->descr);
1264 /* keep the minimum date of any member as the 'actual' date */
1265 if (retval->date < (*find)->date)
1266 (*find)->date = retval->date;
1268 /* expand the min_date/max_date window to help finding other members .
1269 * open the window by an extra margin determined by the fuzz factor
1271 if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1273 (*find)->min_date = retval->date - timestamp_fuzz_factor;
1274 //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1276 else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1277 (*find)->max_date = retval->date + timestamp_fuzz_factor;
1279 free(retval);
1280 retval = *find;
1282 else
1284 debug(DEBUG_STATUS, "new patch set!");
1285 debug(DEBUG_STATUS, "%s %s %s %s", retval->author, retval->descr, retval->commitid, dte);
1287 retval->min_date = retval->date - timestamp_fuzz_factor;
1288 retval->max_date = retval->date + timestamp_fuzz_factor;
1290 list_add(&retval->all_link, &all_patch_sets);
1294 return retval;
1297 static int get_branch_ext(char * buff, const char * rev, int * leaf)
1299 char * p;
1300 int len = strlen(rev);
1302 /* allow get_branch(buff, buff) without destroying contents */
1303 memmove(buff, rev, len);
1304 buff[len] = 0;
1306 p = strrchr(buff, '.');
1307 if (!p)
1308 return 0;
1309 *p++ = 0;
1311 if (leaf)
1312 *leaf = atoi(p);
1314 return 1;
1317 static int get_branch(char * buff, const char * rev)
1319 return get_branch_ext(buff, rev, NULL);
1323 * the goal if this function is to determine what revision to assign to
1324 * the psm->pre_rev field. usually, the log file is strictly
1325 * reverse chronological, so rev is direct ancestor to psm,
1327 * This all breaks down at branch points however
1330 static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1332 char pre[REV_STR_MAX], post[REV_STR_MAX];
1334 if (!psm)
1335 return;
1337 if (!rev)
1339 /* if psm was last rev. for file, it's either an
1340 * INITIAL, or first rev of a branch. to test if it's
1341 * the first rev of a branch, do get_branch twice -
1342 * this should be the bp.
1344 if (get_branch(post, psm->post_rev->rev) &&
1345 get_branch(pre, post))
1347 psm->pre_rev = file_get_revision(psm->file, pre);
1348 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1350 else
1352 set_psm_initial(psm);
1354 return;
1358 * is this canditate for 'pre' on the same branch as our 'post'?
1359 * this is the normal case
1361 if (!get_branch(pre, rev->rev))
1363 debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1364 return;
1367 if (!get_branch(post, psm->post_rev->rev))
1369 debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1370 return;
1373 if (strcmp(pre, post) == 0)
1375 psm->pre_rev = rev;
1376 rev->pre_psm = psm;
1377 return;
1380 /* branches don't match. new_psm must be head of branch,
1381 * so psm is oldest rev. on branch. or oldest
1382 * revision overall. if former, derive predecessor.
1383 * use get_branch to chop another rev. off of string.
1385 * FIXME:
1386 * There's also a weird case. it's possible to just re-number
1387 * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1388 * It's not widely used. In those cases of discontinuity,
1389 * we end up stamping the predecessor as 'INITIAL' incorrectly
1392 if (!get_branch(pre, post))
1394 set_psm_initial(psm);
1395 return;
1398 psm->pre_rev = file_get_revision(psm->file, pre);
1399 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1402 static void check_print_patch_set(PatchSet * ps)
1404 if (ps->psid < 0)
1405 return;
1407 /* the funk_factor overrides the restrict_tag_start and end */
1408 if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1409 goto ok;
1411 if (ps->funk_factor == FNK_HIDE_ALL)
1412 return;
1414 if (ps->psid <= restrict_tag_ps_start)
1416 if (ps->psid == restrict_tag_ps_start)
1417 debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1419 return;
1422 if (ps->psid > restrict_tag_ps_end)
1423 return;
1426 if (restrict_date_start > 0 &&
1427 (ps->date < restrict_date_start ||
1428 (restrict_date_end > 0 && ps->date > restrict_date_end)))
1429 return;
1431 if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1432 return;
1434 if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1435 return;
1437 if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1438 return;
1440 if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1441 return;
1443 if (!list_empty(&show_patch_set_ranges))
1445 struct list_head * next = show_patch_set_ranges.next;
1447 while (next != &show_patch_set_ranges)
1449 PatchSetRange *range = list_entry(next, PatchSetRange, link);
1450 if (range->min_counter <= ps->psid &&
1451 ps->psid <= range->max_counter)
1453 break;
1455 next = next->next;
1458 if (next == &show_patch_set_ranges)
1459 return;
1462 if (patch_set_dir)
1464 char path[PATH_MAX];
1466 snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1468 fflush(stdout);
1469 close(1);
1470 if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1472 debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1473 exit(1);
1476 fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1480 * If the summary_first option is in effect, there will be
1481 * two passes through the tree. the first with summary_first == 1
1482 * the second with summary_first == 2. if the option is not
1483 * in effect, there will be one pass with summary_first == 0
1485 * When the -s option is in effect, the show_patch_set_ranges
1486 * list will be non-empty.
1488 if (summary_first <= 1)
1489 print_patch_set(ps);
1490 if (do_diff && summary_first != 1)
1491 do_cvs_diff(ps);
1493 fflush(stdout);
1496 static void print_patch_set(PatchSet * ps)
1498 struct tm *tm;
1499 struct list_head * next;
1500 const char * funk = "";
1502 tm = localtime(&ps->date);
1503 next = ps->members.next;
1505 funk = fnk_descr[ps->funk_factor];
1507 /* this '---...' is different from the 28 hyphens that separate cvs log output */
1508 printf("---------------------\n");
1509 printf("PatchSet %d %s\n", ps->psid, funk);
1510 printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
1511 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
1512 tm->tm_hour, tm->tm_min, tm->tm_sec);
1513 printf("Author: %s\n", ps->author);
1514 printf("Branch: %s\n", ps->branch);
1515 if (ps->ancestor_branch)
1516 printf("Ancestor branch: %s\n", ps->ancestor_branch);
1517 printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
1518 printf("Log:\n%s\n", ps->descr);
1519 printf("Members: \n");
1521 while (next != &ps->members)
1523 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1524 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1525 funk = "(BEFORE START TAG)";
1526 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1527 funk = "(AFTER END TAG)";
1528 else
1529 funk = "";
1531 printf("\t%s:%s->%s%s %s\n",
1532 psm->file->filename,
1533 psm->pre_rev ? psm->pre_rev->rev : "INITIAL",
1534 psm->post_rev->rev,
1535 psm->post_rev->dead ? "(DEAD)": "",
1536 funk);
1538 next = next->next;
1541 printf("\n");
1544 /* walk all the patchsets to assign monotonic psid,
1545 * and to establish branch ancestry
1547 static void assign_patchset_id(PatchSet * ps)
1550 * Ignore the 'BRANCH ADD' patchsets
1552 if (!ps->branch_add)
1554 ps_counter++;
1555 ps->psid = ps_counter;
1557 if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
1559 PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
1560 if (!head_ps)
1562 head_ps = ps;
1563 put_hash_object(branch_heads, ps->branch, head_ps);
1566 determine_branch_ancestor(ps, head_ps);
1569 else
1571 ps->psid = -1;
1575 static int compare_rev_strings(const char * cr1, const char * cr2)
1577 char r1[REV_STR_MAX];
1578 char r2[REV_STR_MAX];
1579 char *s1 = r1, *s2 = r2;
1580 char *p1, *p2;
1581 int n1, n2;
1583 strcpy(s1, cr1);
1584 strcpy(s2, cr2);
1586 for (;;)
1588 p1 = strchr(s1, '.');
1589 p2 = strchr(s2, '.');
1591 if (p1) *p1++ = 0;
1592 if (p2) *p2++ = 0;
1594 n1 = atoi(s1);
1595 n2 = atoi(s2);
1597 if (n1 < n2)
1598 return -1;
1599 if (n1 > n2)
1600 return 1;
1602 if (!p1 && p2)
1603 return -1;
1604 if (p1 && !p2)
1605 return 1;
1606 if (!p1 && !p2)
1607 return 0;
1609 s1 = p1;
1610 s2 = p2;
1614 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1616 struct list_head * i;
1618 for (i = ps1->members.next; i != &ps1->members; i = i->next)
1620 PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1621 struct list_head * j;
1623 for (j = ps2->members.next; j != &ps2->members; j = j->next)
1625 PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1626 if (psm1->file == psm2->file)
1628 int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1629 //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1630 return ret;
1635 return 0;
1638 static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
1640 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1641 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1642 long diff;
1644 diff = ps1->date - ps2->date;
1646 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1649 static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1651 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1652 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1653 long diff;
1654 int ret;
1655 time_t d, min, max;
1657 /* We order by (author, descr, branch, commitid, members, date), but because of the fuzz factor
1658 * we treat times within a certain distance as equal IFF the author
1659 * and descr match.
1662 ret = strcmp(ps1->author, ps2->author);
1663 if (ret)
1664 return ret;
1666 ret = strcmp(ps1->descr, ps2->descr);
1667 if (ret)
1668 return ret;
1670 ret = strcmp(ps1->branch, ps2->branch);
1671 if (ret)
1672 return ret;
1674 ret = strcmp(ps1->commitid, ps2->commitid);
1675 if (ret)
1676 return ret;
1678 ret = compare_patch_sets_by_members(ps1, ps2);
1679 if (ret)
1680 return ret;
1683 * one of ps1 or ps2 is new. the other should have the min_date
1684 * and max_date set to a window opened by the fuzz_factor
1686 if (ps1->min_date == 0)
1688 d = ps1->date;
1689 min = ps2->min_date;
1690 max = ps2->max_date;
1692 else if (ps2->min_date == 0)
1694 d = ps2->date;
1695 min = ps1->min_date;
1696 max = ps1->max_date;
1698 else
1700 debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
1701 exit(1);
1704 if (min < d && d < max)
1705 return 0;
1707 diff = ps1->date - ps2->date;
1709 return (diff < 0) ? -1 : 1;
1712 static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
1714 const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
1715 const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
1716 return compare_patch_sets_bytime(ps1, ps2);
1719 static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
1721 long diff;
1722 int ret;
1724 /* When doing a time-ordering of patchsets, we don't need to
1725 * fuzzy-match the time. We've already done fuzzy-matching so we
1726 * know that insertions are unique at this point.
1729 diff = ps1->date - ps2->date;
1730 if (diff)
1731 return (diff < 0) ? -1 : 1;
1733 ret = compare_patch_sets_by_members(ps1, ps2);
1734 if (ret)
1735 return ret;
1737 ret = strcmp(ps1->author, ps2->author);
1738 if (ret)
1739 return ret;
1741 ret = strcmp(ps1->descr, ps2->descr);
1742 if (ret)
1743 return ret;
1745 ret = strcmp(ps1->branch, ps2->branch);
1746 if (ret)
1747 return ret;
1749 ret = strcmp(ps1->commitid, ps2->commitid);
1750 return ret;
1754 static int is_revision_metadata(const char * buff)
1756 char * p1, *p2;
1757 int len;
1759 if (!(p1 = strchr(buff, ':')))
1760 return 0;
1762 p2 = strchr(buff, ' ');
1764 if (p2 && p2 < p1)
1765 return 0;
1767 len = strlen(buff);
1769 /* lines have LF at end */
1770 if (len > 1 && buff[len - 2] == ';')
1771 return 1;
1773 return 0;
1776 static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
1778 struct list_head * next = ps->members.next;
1780 while (next != &ps->members)
1782 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1784 if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
1785 return 1;
1787 next = next->next;
1790 return 0;
1793 static int patch_set_affects_branch(PatchSet * ps, const char * branch)
1795 struct list_head * next;
1797 for (next = ps->members.next; next != &ps->members; next = next->next)
1799 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1802 * slight hack. if -r is specified, and this patchset
1803 * is 'before' the tag, but is FNK_SHOW_SOME, only
1804 * check if the 'after tag' revisions affect
1805 * the branch. this is especially important when
1806 * the tag is a branch point.
1808 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1809 continue;
1811 if (revision_affects_branch(psm->post_rev, branch))
1812 return 1;
1815 return 0;
1818 static void do_cvs_diff(PatchSet * ps)
1820 struct list_head * next;
1821 const char * dtype;
1822 const char * dopts;
1823 const char * utype;
1824 char use_rep_path[PATH_MAX];
1825 char esc_use_rep_path[PATH_MAX];
1827 fflush(stdout);
1828 fflush(stderr);
1831 * if cvs_direct is not in effect, and diff options are specified,
1832 * then we have to use diff instead of rdiff and we'll get a -p0
1833 * diff (instead of -p1) [in a manner of speaking]. So to make sure
1834 * that the add/remove diffs get generated likewise, we need to use
1835 * 'update' instead of 'co'
1837 * cvs_direct will always use diff (not rdiff), but will also always
1838 * generate -p1 diffs.
1840 if (diff_opts == NULL)
1842 dopts = "-u";
1843 dtype = "rdiff";
1844 utype = "co";
1845 sprintf(use_rep_path, "%s/", repository_path);
1846 /* the rep_path may contain characters that the shell will barf on */
1847 escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
1849 else
1851 dopts = diff_opts;
1852 dtype = "diff";
1853 utype = "update";
1854 use_rep_path[0] = 0;
1855 esc_use_rep_path[0] = 0;
1858 for (next = ps->members.next; next != &ps->members; next = next->next)
1860 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1861 char cmdbuff[PATH_MAX * 2+1];
1862 char esc_file[PATH_MAX];
1863 int ret, check_ret = 0;
1865 cmdbuff[0] = 0;
1866 cmdbuff[PATH_MAX*2] = 0;
1868 /* the filename may contain characters that the shell will barf on */
1869 escape_filename(esc_file, PATH_MAX, psm->file->filename);
1872 * Check the patchset funk. we may not want to diff this particular file
1874 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1876 printf("Index: %s\n", psm->file->filename);
1877 printf("===================================================================\n");
1878 printf("*** Member not diffed, before start tag\n");
1879 continue;
1881 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1883 printf("Index: %s\n", psm->file->filename);
1884 printf("===================================================================\n");
1885 printf("*** Member not diffed, after end tag\n");
1886 continue;
1890 * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1891 * or 'cvs update' to get the file, because cvs won't generate these diffs.
1892 * The problem is that this must be piped to diff, and so the resulting
1893 * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1894 * sed is used to replace the '-' with the filename.
1896 * It's possible for pre_rev to be a 'dead' revision. This happens when a file
1897 * is added on a branch. post_rev will be dead dead for remove
1899 if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
1901 int cr;
1902 const char * rev;
1904 if (!psm->pre_rev || psm->pre_rev->dead)
1906 cr = 1;
1907 rev = psm->post_rev->rev;
1909 else
1911 cr = 0;
1912 rev = psm->pre_rev->rev;
1915 if (cvs_direct_ctx)
1917 /* cvs_rupdate does the pipe through diff thing internally */
1918 cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
1920 else
1922 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'",
1923 compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
1924 cr?"":"-",cr?"-":"", cr?"2":"1",
1925 use_rep_path, psm->file->filename);
1928 else
1930 /* a regular diff */
1931 if (cvs_direct_ctx)
1933 cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
1935 else
1937 /* 'cvs diff' exit status '1' is ok, just means files are different */
1938 if (strcmp(dtype, "diff") == 0)
1939 check_ret = 1;
1941 snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1942 compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev,
1943 esc_use_rep_path, esc_file);
1948 * my_system doesn't block signals the way system does.
1949 * if ctrl-c is pressed while in there, we probably exit
1950 * immediately and hope the shell has sent the signal
1951 * to all of the process group members
1953 if (cmdbuff[0] && (ret = my_system(cmdbuff)))
1955 int stat = WEXITSTATUS(ret);
1958 * cvs diff returns 1 in exit status for 'files are different'
1959 * so use a better method to check for failure
1961 if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
1963 debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
1964 exit(1);
1970 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
1972 char * p;
1974 /* The "revision" log line can include extra information
1975 * including who is locking the file --- strip that out.
1978 p = rev_str;
1979 while (isdigit(*p) || *p == '.')
1980 p++;
1981 *p = 0;
1983 return cvs_file_add_revision(file, rev_str);
1986 CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
1988 CvsFileRevision * rev;
1990 if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
1992 rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
1993 rev->rev = get_string(rev_str);
1994 rev->file = file;
1995 rev->branch = NULL;
1996 rev->present = 0;
1997 rev->pre_psm = NULL;
1998 rev->post_psm = NULL;
1999 INIT_LIST_HEAD(&rev->branch_children);
2000 INIT_LIST_HEAD(&rev->tags);
2002 put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
2004 debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
2006 else
2008 debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
2012 * note: we are guaranteed to get here at least once with 'have_branches' == 1.
2013 * we may pass through once before this, because of symbolic tags, then once
2014 * always when processing the actual revision logs
2016 * rev->branch will always be set to something, maybe "HEAD"
2018 if (!rev->branch && file->have_branches)
2020 char branch_str[REV_STR_MAX];
2022 /* in the cvs cvs repository (ccvs) there are tagged versions
2023 * that don't exist. let's mark every 'known to exist'
2024 * version
2026 rev->present = 1;
2028 /* determine the branch this revision was committed on */
2029 if (!get_branch(branch_str, rev->rev))
2031 debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
2032 exit(1);
2035 rev->branch = (char*)get_hash_object(file->branches, branch_str);
2037 /* if there's no branch and it's not on the trunk, blab */
2038 if (!rev->branch)
2040 if (get_branch(branch_str, branch_str))
2042 debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
2043 rev->branch = "#CVSPS_NO_BRANCH";
2045 else
2047 rev->branch = "HEAD";
2051 debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
2054 return rev;
2057 CvsFile * create_cvsfile()
2059 CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
2060 if (!f)
2061 return NULL;
2063 f->revisions = create_hash_table(53);
2064 f->branches = create_hash_table(13);
2065 f->branches_sym = create_hash_table(13);
2066 f->symbols = create_hash_table(253);
2067 f->have_branches = 0;
2069 if (!f->revisions || !f->branches || !f->branches_sym)
2071 if (f->branches)
2072 destroy_hash_table(f->branches, NULL);
2073 if (f->revisions)
2074 destroy_hash_table(f->revisions, NULL);
2075 free(f);
2076 return NULL;
2079 return f;
2082 static PatchSet * create_patch_set()
2084 PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
2086 if (ps)
2088 INIT_LIST_HEAD(&ps->members);
2089 ps->psid = -1;
2090 ps->date = 0;
2091 ps->min_date = 0;
2092 ps->max_date = 0;
2093 ps->descr = NULL;
2094 ps->author = NULL;
2095 ps->tag = NULL;
2096 ps->tag_flags = 0;
2097 ps->branch_add = 0;
2098 ps->commitid = "";
2099 ps->funk_factor = 0;
2100 ps->ancestor_branch = NULL;
2101 CLEAR_LIST_NODE(&ps->collision_link);
2104 return ps;
2107 PatchSetMember * create_patch_set_member()
2109 PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
2110 psm->pre_rev = NULL;
2111 psm->post_rev = NULL;
2112 psm->ps = NULL;
2113 psm->file = NULL;
2114 psm->bad_funk = 0;
2115 return psm;
2118 static PatchSetRange * create_patch_set_range()
2120 PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
2121 return psr;
2124 CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
2126 CvsFileRevision * rev;
2128 if (strcmp(r, "INITIAL") == 0)
2129 return NULL;
2131 rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
2133 if (!rev)
2135 debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
2136 exit(1);
2139 return rev;
2143 * Parse lines in the format:
2145 * <white space>tag_name: <rev>;
2147 * Handles both regular tags (these go into the symbols hash)
2148 * and magic-branch-tags (second to last node of revision is 0)
2149 * which go into branches and branches_sym hashes. Magic-branch
2150 * format is hidden in CVS everwhere except the 'cvs log' output.
2153 static void parse_sym(CvsFile * file, char * sym)
2155 char * tag = sym, *eot;
2156 int leaf, final_branch = -1;
2157 char rev[REV_STR_MAX];
2158 char rev2[REV_STR_MAX];
2160 while (*tag && isspace(*tag))
2161 tag++;
2163 if (!*tag)
2164 return;
2166 eot = strchr(tag, ':');
2168 if (!eot)
2169 return;
2171 *eot = 0;
2172 eot += 2;
2174 if (!get_branch_ext(rev, eot, &leaf))
2176 if (strcmp(tag, "TRUNK") == 0)
2178 debug(DEBUG_STATUS, "ignoring the TRUNK branch/tag");
2179 return;
2181 debug(DEBUG_APPERROR, "malformed revision");
2182 exit(1);
2186 * get_branch_ext will leave final_branch alone
2187 * if there aren't enough '.' in string
2189 get_branch_ext(rev2, rev, &final_branch);
2191 if (final_branch == 0)
2193 snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
2194 debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
2196 cvs_file_add_branch(file, rev, tag);
2198 else
2200 strcpy(rev, eot);
2201 chop(rev);
2203 /* see cvs manual: what is this vendor tag? */
2204 if (is_vendor_branch(rev))
2205 cvs_file_add_branch(file, rev, tag);
2206 else
2207 cvs_file_add_symbol(file, rev, tag);
2211 void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
2213 CvsFileRevision * rev;
2214 GlobalSymbol * sym;
2215 Tag * tag;
2217 /* get a permanent storage string */
2218 char * tag_str = get_string(p_tag_str);
2220 debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
2221 rev = cvs_file_add_revision(file, rev_str);
2222 put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
2225 * check the global_symbols
2227 sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
2228 if (!sym)
2230 sym = (GlobalSymbol*)malloc(sizeof(*sym));
2231 sym->tag = tag_str;
2232 sym->ps = NULL;
2233 INIT_LIST_HEAD(&sym->tags);
2235 put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
2238 tag = (Tag*)malloc(sizeof(*tag));
2239 tag->tag = tag_str;
2240 tag->rev = rev;
2241 tag->sym = sym;
2242 list_add(&tag->global_link, &sym->tags);
2243 list_add(&tag->rev_link, &rev->tags);
2246 char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
2248 char * new_tag;
2249 char * new_rev;
2251 if (get_hash_object(file->branches, rev))
2253 debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s",
2254 rev, tag, file->filename);
2255 return NULL;
2258 /* get permanent storage for the strings */
2259 new_tag = get_string(tag);
2260 new_rev = get_string(rev);
2262 put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
2263 put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
2265 return new_tag;
2269 * Resolve each global symbol to a PatchSet. This is
2270 * not necessarily doable, because tagging isn't
2271 * necessarily done to the project as a whole, and
2272 * it's possible that no tag is valid for all files
2273 * at a single point in time. We check for that
2274 * case though.
2276 * Implementation: the most recent PatchSet containing
2277 * a revision (post_rev) tagged by the symbol is considered
2278 * the 'tagged' PatchSet.
2281 static void resolve_global_symbols()
2283 struct hash_entry * he_sym;
2284 reset_hash_iterator(global_symbols);
2285 while ((he_sym = next_hash_entry(global_symbols)))
2287 GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
2288 PatchSet * ps;
2289 struct list_head * next;
2291 debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
2294 * First pass, determine the most recent PatchSet with a
2295 * revision tagged with the symbolic tag. This is 'the'
2296 * patchset with the tag
2299 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2301 Tag * tag = list_entry(next, Tag, global_link);
2302 CvsFileRevision * rev = tag->rev;
2304 /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
2305 if (!rev->present || !rev->post_psm)
2307 struct list_head *tmp = next->prev;
2308 debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
2309 rev->rev, rev->file->filename);
2310 /* FIXME: memleak */
2311 list_del(next);
2312 next = tmp;
2313 continue;
2316 ps = rev->post_psm->ps;
2318 if (!sym->ps || ps->date > sym->ps->date)
2319 sym->ps = ps;
2322 /* convenience variable */
2323 ps = sym->ps;
2325 if (!ps)
2327 debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
2328 return;
2331 ps->tag = sym->tag;
2333 /* check if this ps is one of the '-r' patchsets */
2334 if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
2335 restrict_tag_ps_start = ps->psid;
2337 /* the second -r implies -b */
2338 if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
2340 restrict_tag_ps_end = ps->psid;
2342 if (restrict_branch)
2344 if (strcmp(ps->branch, restrict_branch) != 0)
2346 debug(DEBUG_APPMSG1,
2347 "WARNING: -b option and second -r have conflicting branches: %s %s",
2348 restrict_branch, ps->branch);
2351 else
2353 debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
2354 restrict_branch = ps->branch;
2359 * Second pass.
2360 * check if this is an invalid patchset,
2361 * check which members are invalid. determine
2362 * the funk factor etc.
2364 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2366 Tag * tag = list_entry(next, Tag, global_link);
2367 CvsFileRevision * rev = tag->rev;
2368 CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
2370 if (!next_rev)
2371 continue;
2374 * we want the 'tagged revision' to be valid until after
2375 * the date of the 'tagged patchset' or else there's something
2376 * funky going on
2378 if (next_rev->post_psm->ps->date < ps->date)
2380 int flag = check_rev_funk(ps, next_rev);
2381 debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
2382 rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
2383 ps->tag_flags |= flag;
2389 static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
2391 /* special case the branch called 'HEAD' */
2392 if (strcmp(branch, "HEAD") == 0)
2394 /* look for only one '.' in rev */
2395 char * p = strchr(rev->rev, '.');
2396 if (p && !strchr(p + 1, '.'))
2397 return 1;
2399 else
2401 char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
2403 if (branch_rev)
2405 char post_rev[REV_STR_MAX];
2406 char branch[REV_STR_MAX];
2407 int file_leaf, branch_leaf;
2409 strcpy(branch, branch_rev);
2411 /* first get the branch the file rev is on */
2412 if (get_branch_ext(post_rev, rev->rev, &file_leaf))
2414 branch_leaf = file_leaf;
2416 /* check against branch and all branch ancestor branches */
2419 debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
2420 if (strcmp(branch, post_rev) == 0)
2421 return (file_leaf <= branch_leaf);
2423 while(get_branch_ext(branch, branch, &branch_leaf));
2428 return 0;
2431 static int count_dots(const char * p)
2433 int dots = 0;
2435 while (*p)
2436 if (*p++ == '.')
2437 dots++;
2439 return dots;
2443 * When importing vendor sources, (apparently people do this)
2444 * the code is added on a 'vendor' branch, which, for some reason
2445 * doesn't use the magic-branch-tag format. Try to detect that now
2447 static int is_vendor_branch(const char * rev)
2449 return !(count_dots(rev)&1);
2452 void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
2454 /* check if a member for the same file already exists, if so
2455 * put this PatchSet on the collisions list
2457 struct list_head * next;
2458 for (next = ps->members.next; next != &ps->members; next = next->next)
2460 PatchSetMember * m = list_entry(next, PatchSetMember, link);
2461 if (m->file == psm->file) {
2462 int order = compare_rev_strings(psm->post_rev->rev, m->post_rev->rev);
2465 * Same revision too? Add it to the collision list
2466 * if it isn't already.
2468 if (!order) {
2469 if (ps->collision_link.next == NULL)
2470 list_add(&ps->collision_link, &collisions);
2471 return;
2475 * If this is an older revision than the one we already have
2476 * in this patchset, just ignore it
2478 if (order < 0)
2479 return;
2482 * This is a newer one, remove the old one
2484 list_del(&m->link);
2488 psm->ps = ps;
2489 list_add(&psm->link, ps->members.prev);
2492 static void set_psm_initial(PatchSetMember * psm)
2494 psm->pre_rev = NULL;
2495 if (psm->post_rev->dead)
2498 * We expect a 'file xyz initially added on branch abc' here.
2499 * There can only be several such member in a given patchset,
2500 * since cvs only includes the file basename in the log message.
2502 psm->ps->branch_add = 1;
2507 * look at all revisions starting at rev and going forward until
2508 * ps->date and see whether they are invalid or just funky.
2510 static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
2512 int retval = TAG_FUNKY;
2514 while (rev)
2516 PatchSet * next_ps = rev->post_psm->ps;
2517 struct list_head * next;
2519 if (next_ps->date > ps->date)
2520 break;
2522 debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
2523 ps->date, next_ps->date, rev->rev, rev->branch);
2526 * If the ps->tag is one of the two possible '-r' tags
2527 * then the funkyness is even more important.
2529 * In the restrict_tag_start case, this next_ps is chronologically
2530 * before ps, but tagwise after, so set the funk_factor so it will
2531 * be included.
2533 * The restrict_tag_end case is similar, but backwards.
2535 * Start assuming the HIDE/SHOW_ALL case, we will determine
2536 * below if we have a split ps case
2538 if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
2539 next_ps->funk_factor = FNK_SHOW_ALL;
2540 if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
2541 next_ps->funk_factor = FNK_HIDE_ALL;
2544 * if all of the other members of this patchset are also 'after' the tag
2545 * then this is a 'funky' patchset w.r.t. the tag. however, if some are
2546 * before then the patchset is 'invalid' w.r.t. the tag, and we mark
2547 * the members individually with 'bad_funk' ,if this tag is the
2548 * '-r' tag. Then we can actually split the diff on this patchset
2550 for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
2552 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2553 if (before_tag(psm->post_rev, ps->tag))
2555 retval = TAG_INVALID;
2556 /* only set bad_funk for one of the -r tags */
2557 if (next_ps->funk_factor)
2559 psm->bad_funk = 1;
2560 next_ps->funk_factor =
2561 (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
2563 debug(DEBUG_APPMSG1,
2564 "WARNING: Invalid PatchSet %d, Tag %s:\n"
2565 " %s:%s=after, %s:%s=before. Treated as 'before'",
2566 next_ps->psid, ps->tag,
2567 rev->file->filename, rev->rev,
2568 psm->post_rev->file->filename, psm->post_rev->rev);
2572 rev = rev_follow_branch(rev, ps->branch);
2575 return retval;
2578 /* determine if the revision is before the tag */
2579 static int before_tag(CvsFileRevision * rev, const char * tag)
2581 CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
2582 int retval = 0;
2584 if (tagged_rev &&
2585 revision_affects_branch(rev, tagged_rev->branch) &&
2586 rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
2587 retval = 1;
2589 debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d",
2590 rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
2592 return retval;
2595 /* get the next revision from this one following branch if possible */
2596 /* FIXME: not sure if this needs to follow branches leading up to branches? */
2597 static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
2599 struct list_head * next;
2601 /* check for 'main line of inheritance' */
2602 if (strcmp(rev->branch, branch) == 0)
2603 return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
2605 /* look down branches */
2606 for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
2608 CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
2609 //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2610 if (strcmp(next_rev->branch, branch) == 0)
2611 return next_rev;
2614 return NULL;
2617 static void check_norc(int argc, char * argv[])
2619 int i = 1;
2620 while (i < argc)
2622 if (strcmp(argv[i], "--norc") == 0)
2624 norc = "-f";
2625 break;
2627 i++;
2631 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
2633 struct list_head * next;
2634 CvsFileRevision * rev;
2636 /* PatchSet 1 has no ancestor */
2637 if (ps->psid == 1)
2638 return;
2640 /* HEAD branch patchsets have no ancestry, but callers should know that */
2641 if (strcmp(ps->branch, "HEAD") == 0)
2643 debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
2644 return;
2647 for (next = ps->members.next; next != &ps->members; next = next->next)
2649 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2650 rev = psm->pre_rev;
2651 int d1, d2;
2653 /* the reason this is at all complicated has to do with a
2654 * branch off of a branch. it is possible (and indeed
2655 * likely) that some file would not have been modified
2656 * from the initial branch point to the branch-off-branch
2657 * point, and therefore the branch-off-branch point is
2658 * really branch-off-HEAD for that specific member (file).
2659 * in that case, rev->branch will say HEAD but we want
2660 * to know the symbolic name of the first branch
2661 * so we continue to look member after member until we find
2662 * the 'deepest' branching. deepest can actually be determined
2663 * by considering the revision currently indicated by
2664 * ps->ancestor_branch (by symbolic lookup) and rev->rev. the
2665 * one with more dots wins
2667 * also, the first commit in which a branch-off-branch is
2668 * mentioned may ONLY modify files never committed since
2669 * original branch-off-HEAD was created, so we have to keep
2670 * checking, ps after ps to be sure to get the deepest ancestor
2672 * note: rev is the pre-commit revision, not the post-commit
2674 if (!head_ps->ancestor_branch)
2675 d1 = -1;
2676 else if (strcmp(ps->branch, rev->branch) == 0)
2677 continue;
2678 else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
2679 d1 = 1;
2680 else {
2681 /* branch_rev may not exist if the file was added on this branch for example */
2682 const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
2683 d1 = branch_rev ? count_dots(branch_rev) : 1;
2686 /* HACK: we sometimes pretend to derive from the import branch.
2687 * just don't do that. this is the easiest way to prevent...
2689 d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
2691 if (d2 > d1)
2692 head_ps->ancestor_branch = rev->branch;
2694 //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
2698 static void handle_collisions()
2700 struct list_head *next;
2701 for (next = collisions.next; next != &collisions; next = next->next)
2703 PatchSet * ps = list_entry(next, PatchSet, collision_link);
2704 printf("PatchSet %d has collisions\n", ps->psid);
2708 void walk_all_patch_sets(void (*action)(PatchSet *))
2710 struct list_head * next;;
2711 for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
2712 PatchSet * ps = list_entry(next, PatchSet, all_link);
2713 action(ps);