fix-help-msg.p
[cvsps/4msysgit.git] / cvsps.c
blob0bb1ae292134ebabaa934798d9e14dbf2ff0f030
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 #else
17 #include <search.h>
18 #endif
19 #include <time.h>
20 #include <ctype.h>
21 #include <sys/stat.h>
22 #include <sys/types.h>
23 #include <fcntl.h>
24 #include <regex.h>
25 #ifndef __MINGW32__
26 #include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
27 #endif
29 #include <cbtcommon/hash.h>
30 #include <cbtcommon/list.h>
31 #include <cbtcommon/text_util.h>
32 #include <cbtcommon/debug.h>
33 #include <cbtcommon/rcsid.h>
35 #include "cache.h"
36 #include "cvsps_types.h"
37 #include "cvsps.h"
38 #include "util.h"
39 #include "stats.h"
40 #include "cap.h"
41 #include "cvs_direct.h"
42 #include "list_sort.h"
44 RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
46 #define CVS_LOG_BOUNDARY "----------------------------\n"
47 #define CVS_FILE_BOUNDARY "=============================================================================\n"
49 enum
51 NEED_FILE,
52 NEED_SYMS,
53 NEED_EOS,
54 NEED_START_LOG,
55 NEED_REVISION,
56 NEED_DATE_AUTHOR_STATE,
57 NEED_EOM
60 /* true globals */
61 struct hash_table * file_hash;
62 CvsServerCtx * cvs_direct_ctx;
63 char root_path[PATH_MAX];
64 char repository_path[PATH_MAX];
66 const char * tag_flag_descr[] = {
67 "",
68 "**FUNKY**",
69 "**INVALID**",
70 "**INVALID**"
73 const char * fnk_descr[] = {
74 "",
75 "FNK_SHOW_SOME",
76 "FNK_SHOW_ALL",
77 "FNK_HIDE_ALL",
78 "FNK_HIDE_SOME"
81 /* static globals */
82 static int ps_counter;
83 static void * ps_tree;
84 static struct hash_table * global_symbols;
85 static char strip_path[PATH_MAX];
86 static int strip_path_len;
87 static time_t cache_date;
88 static int update_cache;
89 static int ignore_cache;
90 static int do_write_cache;
91 static int statistics;
92 static const char * test_log_file;
93 static struct hash_table * branch_heads;
94 static struct list_head all_patch_sets;
95 static struct list_head collisions;
97 /* settable via options */
98 static int timestamp_fuzz_factor = 300;
99 static int do_diff;
100 static const char * restrict_author;
101 static int have_restrict_log;
102 static regex_t restrict_log;
103 static int have_restrict_file;
104 static regex_t restrict_file;
105 static time_t restrict_date_start;
106 static time_t restrict_date_end;
107 static const char * restrict_branch;
108 static struct list_head show_patch_set_ranges;
109 static int summary_first;
110 static const char * norc = "";
111 static const char * patch_set_dir;
112 static const char * restrict_tag_start;
113 static const char * restrict_tag_end;
114 static int restrict_tag_ps_start;
115 static int restrict_tag_ps_end = INT_MAX;
116 static const char * diff_opts;
117 static int bkcvs;
118 static int no_rlog;
119 static int cvs_direct;
120 static int compress;
121 static char compress_arg[8];
122 static int track_branch_ancestry;
124 static void check_norc(int, char *[]);
125 static int parse_args(int, char *[]);
126 static int parse_rc();
127 static void load_from_cvs();
128 static void init_paths();
129 static CvsFile * parse_file(const char *);
130 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
131 static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
132 static void check_print_patch_set(PatchSet *);
133 static void print_patch_set(PatchSet *);
134 static void assign_patchset_id(PatchSet *);
135 static int compare_rev_strings(const char *, const char *);
136 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
137 static int compare_patch_sets_bk(const void *, const void *);
138 static int compare_patch_sets(const void *, const void *);
139 static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
140 static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
141 static int is_revision_metadata(const char *);
142 static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
143 static int patch_set_affects_branch(PatchSet *, const char *);
144 static void do_cvs_diff(PatchSet *);
145 static PatchSet * create_patch_set();
146 static PatchSetRange * create_patch_set_range();
147 static void parse_sym(CvsFile *, char *);
148 static void resolve_global_symbols();
149 static int revision_affects_branch(CvsFileRevision *, const char *);
150 static int is_vendor_branch(const char *);
151 static void set_psm_initial(PatchSetMember * psm);
152 static int check_rev_funk(PatchSet *, CvsFileRevision *);
153 static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
154 static int before_tag(CvsFileRevision * rev, const char * tag);
155 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
156 static void handle_collisions();
158 int main(int argc, char *argv[])
160 debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
162 INIT_LIST_HEAD(&show_patch_set_ranges);
165 * we want to parse the rc first, so command line can override it
166 * but also, --norc should stop the rc from being processed, so
167 * we look for --norc explicitly first. Note: --norc in the rc
168 * file itself will prevent the cvs rc file from being used.
170 check_norc(argc, argv);
172 if (strlen(norc) == 0 && parse_rc() < 0)
173 exit(1);
175 if (parse_args(argc, argv) < 0)
176 exit(1);
178 if (diff_opts && !cvs_direct && do_diff)
180 debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
181 debug(DEBUG_APPMSG1, " which is usually used to create diffs. 'cvs diff'");
182 debug(DEBUG_APPMSG1, " will be used instead, but the resulting patches ");
183 debug(DEBUG_APPMSG1, " will need to be applied using the '-p0' option");
184 debug(DEBUG_APPMSG1, " to patch(1) (in the working directory), ");
185 debug(DEBUG_APPMSG1, " instead of '-p1'\n");
188 file_hash = create_hash_table(1023);
189 global_symbols = create_hash_table(111);
190 branch_heads = create_hash_table(1023);
191 INIT_LIST_HEAD(&all_patch_sets);
192 INIT_LIST_HEAD(&collisions);
194 /* this parses some of the CVS/ files, and initializes
195 * the repository_path and other variables
197 init_paths();
199 if (!ignore_cache)
201 int save_fuzz_factor = timestamp_fuzz_factor;
203 /* the timestamp fuzz should only be in effect when loading from
204 * CVS, not re-fuzzed when loading from cache. This is a hack
205 * working around bad use of global variables
208 timestamp_fuzz_factor = 0;
210 if ((cache_date = read_cache()) < 0)
211 update_cache = 1;
213 timestamp_fuzz_factor = save_fuzz_factor;
216 if (cvs_direct && (do_diff || (update_cache && !test_log_file))) {
217 cvs_direct_ctx = open_cvs_server(root_path, compress);
218 if (!cvs_direct_ctx) debug(DEBUG_APPERROR, "main: Unable to open cvs server");
221 if (update_cache)
223 load_from_cvs();
224 do_write_cache = 1;
227 //XXX
228 //handle_collisions();
230 list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
232 ps_counter = 0;
233 walk_all_patch_sets(assign_patchset_id);
235 handle_collisions();
237 resolve_global_symbols();
239 if (do_write_cache)
240 write_cache(cache_date);
242 if (statistics)
243 print_statistics(ps_tree);
245 /* check that the '-r' symbols (if specified) were resolved */
246 if (restrict_tag_start && restrict_tag_ps_start == 0 &&
247 strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
249 debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
250 exit(1);
253 if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
255 debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
256 exit(1);
259 walk_all_patch_sets(check_print_patch_set);
261 if (summary_first++)
262 walk_all_patch_sets(check_print_patch_set);
264 if (cvs_direct_ctx)
265 close_cvs_server(cvs_direct_ctx);
267 exit(0);
270 static void load_from_cvs()
272 FILE * cvsfp;
273 char buff[BUFSIZ];
274 int state = NEED_FILE;
275 CvsFile * file = NULL;
276 PatchSetMember * psm = NULL;
277 char datebuff[20];
278 char authbuff[AUTH_STR_MAX];
279 char logbuff[LOG_STR_MAX + 1];
280 int loglen = 0;
281 int have_log = 0;
282 char cmd[BUFSIZ];
283 char date_str[64];
284 char use_rep_buff[PATH_MAX];
285 char * ltype;
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 %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 %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_FILE:
354 if (strncmp(buff, "RCS file", 8) == 0 && (file = parse_file(buff)))
355 state = NEED_SYMS;
356 break;
357 case NEED_SYMS:
358 if (strncmp(buff, "symbolic names:", 15) == 0)
359 state = NEED_EOS;
360 break;
361 case NEED_EOS:
362 if (!isspace(buff[0]))
364 /* see cvsps_types.h for commentary on have_branches */
365 file->have_branches = 1;
366 state = NEED_START_LOG;
368 else
369 parse_sym(file, buff);
370 break;
371 case NEED_START_LOG:
372 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
373 state = NEED_REVISION;
374 break;
375 case NEED_REVISION:
376 if (strncmp(buff, "revision", 8) == 0)
378 char new_rev[REV_STR_MAX];
379 CvsFileRevision * rev;
381 strcpy(new_rev, buff + 9);
382 chop(new_rev);
385 * rev may already exist (think cvsps -u), in which
386 * case parse_revision is a hash lookup
388 rev = parse_revision(file, new_rev);
391 * in the simple case, we are copying rev to psm->pre_rev
392 * (psm refers to last patch set processed at this point)
393 * since generally speaking the log is reverse chronological.
394 * This breaks down slightly when branches are introduced
397 assign_pre_revision(psm, rev);
400 * if this is a new revision, it will have no post_psm associated.
401 * otherwise we are (probably?) hitting the overlap in cvsps -u
403 if (!rev->post_psm)
405 psm = rev->post_psm = create_patch_set_member();
406 psm->post_rev = rev;
407 psm->file = file;
408 state = NEED_DATE_AUTHOR_STATE;
410 else
412 /* we hit this in cvsps -u mode, we are now up-to-date
413 * w.r.t this particular file. skip all of the rest
414 * of the info (revs and logs) until we hit the next file
416 psm = NULL;
417 state = NEED_EOM;
420 break;
421 case NEED_DATE_AUTHOR_STATE:
422 if (strncmp(buff, "date:", 5) == 0)
424 char * p;
426 strncpy(datebuff, buff + 6, 19);
427 datebuff[19] = 0;
429 strcpy(authbuff, "unknown");
430 p = strstr(buff, "author: ");
431 if (p)
433 char * op;
434 p += 8;
435 op = strchr(p, ';');
436 if (op)
438 strzncpy(authbuff, p, op - p + 1);
442 /* read the 'state' tag to see if this is a dead revision */
443 p = strstr(buff, "state: ");
444 if (p)
446 char * op;
447 p += 7;
448 op = strchr(p, ';');
449 if (op)
450 if (strncmp(p, "dead", MIN(4, op - p)) == 0)
451 psm->post_rev->dead = 1;
454 state = NEED_EOM;
456 break;
457 case NEED_EOM:
458 if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
460 if (psm)
462 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
463 patch_set_add_member(ps, psm);
466 logbuff[0] = 0;
467 loglen = 0;
468 have_log = 0;
469 state = NEED_REVISION;
471 else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
473 if (psm)
475 PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
476 patch_set_add_member(ps, psm);
477 assign_pre_revision(psm, NULL);
480 logbuff[0] = 0;
481 loglen = 0;
482 have_log = 0;
483 psm = NULL;
484 file = NULL;
485 state = NEED_FILE;
487 else
489 /* other "blahblah: information;" messages can
490 * follow the stuff we pay attention to
492 if (have_log || !is_revision_metadata(buff))
494 /* if the log buffer is full, that's it.
496 * Also, read lines (fgets) always have \n in them
497 * which we count on. So if truncation happens,
498 * be careful to put a \n on.
500 * Buffer has LOG_STR_MAX + 1 for room for \0 if
501 * necessary
503 if (loglen < LOG_STR_MAX)
505 int len = strlen(buff);
507 if (len >= LOG_STR_MAX - loglen)
509 debug(DEBUG_APPMSG1, "WARNING: maximum log length exceeded, truncating log");
510 len = LOG_STR_MAX - loglen;
511 buff[len - 1] = '\n';
514 debug(DEBUG_STATUS, "appending %s to log", buff);
515 memcpy(logbuff + loglen, buff, len);
516 loglen += len;
517 logbuff[loglen] = 0;
518 have_log = 1;
521 else
523 debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
527 break;
531 if (state == NEED_SYMS)
533 debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
534 debug(DEBUG_APPERROR, " Perhaps you should try running with --norc");
535 exit(1);
538 if (state != NEED_FILE)
540 debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state);
541 exit(1);
544 if (test_log_file)
546 fclose(cvsfp);
548 else if (cvs_direct_ctx)
550 cvs_rlog_close(cvs_direct_ctx);
552 else
554 if (pclose(cvsfp) < 0)
556 debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
557 exit(1);
562 static int usage(const char * str1, const char * str2)
564 if (str1)
565 debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
567 debug(DEBUG_APPMSG1, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
568 debug(DEBUG_APPMSG1, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
569 debug(DEBUG_APPMSG1, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
570 debug(DEBUG_APPMSG1, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
571 debug(DEBUG_APPMSG1, " [--test-log <captured cvs log file>] [--bkcvs]");
572 debug(DEBUG_APPMSG1, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
573 debug(DEBUG_APPMSG1, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
574 debug(DEBUG_APPMSG1, " [-q] [-A] [<repository>]");
575 debug(DEBUG_APPMSG1, "");
576 debug(DEBUG_APPMSG1, "Where:");
577 debug(DEBUG_APPMSG1, " -h display this informative message");
578 debug(DEBUG_APPMSG1, " -x ignore (and rebuild) cvsps.cache file");
579 debug(DEBUG_APPMSG1, " -u update cvsps.cache file");
580 debug(DEBUG_APPMSG1, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
581 debug(DEBUG_APPMSG1, " -g generate diffs of the selected patch sets");
582 debug(DEBUG_APPMSG1, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
583 debug(DEBUG_APPMSG1, " -a <author> restrict output to patch sets created by author");
584 debug(DEBUG_APPMSG1, " -f <file> restrict output to patch sets involving file");
585 debug(DEBUG_APPMSG1, " -d <date1> -d <date2> if just one date specified, show");
586 debug(DEBUG_APPMSG1, " revisions newer than date1. If two dates specified,");
587 debug(DEBUG_APPMSG1, " show revisions between two dates.");
588 debug(DEBUG_APPMSG1, " -b <branch> restrict output to patch sets affecting history of branch");
589 debug(DEBUG_APPMSG1, " -l <regex> restrict output to patch sets matching <regex> in log message");
590 debug(DEBUG_APPMSG1, " -r <tag1> -r <tag2> if just one tag specified, show");
591 debug(DEBUG_APPMSG1, " revisions since tag1. If two tags specified, show");
592 debug(DEBUG_APPMSG1, " revisions between the two tags.");
593 debug(DEBUG_APPMSG1, " -p <directory> output patch sets to individual files in <directory>");
594 debug(DEBUG_APPMSG1, " -v show very verbose parsing messages");
595 debug(DEBUG_APPMSG1, " -t show some brief memory usage statistics");
596 debug(DEBUG_APPMSG1, " --norc when invoking cvs, ignore the .cvsrc file");
597 debug(DEBUG_APPMSG1, " --summary-first when multiple patch sets are shown, put all summaries first");
598 debug(DEBUG_APPMSG1, " --test-log <captured cvs log> supply a captured cvs log for testing");
599 debug(DEBUG_APPMSG1, " --diff-opts <option string> supply special set of options to diff");
600 debug(DEBUG_APPMSG1, " --bkcvs special hack for parsing the BK -> CVS log format");
601 debug(DEBUG_APPMSG1, " --no-rlog disable rlog (it's faulty in some setups)");
602 debug(DEBUG_APPMSG1, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
603 debug(DEBUG_APPMSG1, " --debuglvl <bitmask> enable various debug channels.");
604 debug(DEBUG_APPMSG1, " -Z <compression> A value 1-9 which specifies amount of compression");
605 debug(DEBUG_APPMSG1, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
606 debug(DEBUG_APPMSG1, " -q be quiet about warnings");
607 debug(DEBUG_APPMSG1, " -A track and report branch ancestry");
608 debug(DEBUG_APPMSG1, " <repository> apply cvsps to repository. overrides working directory");
609 debug(DEBUG_APPMSG1, "\ncvsps version %s\n", VERSION);
611 return -1;
614 static int parse_args(int argc, char *argv[])
616 int i = 1;
617 while (i < argc)
619 if (strcmp(argv[i], "-z") == 0)
621 if (++i >= argc)
622 return usage("argument to -z missing", "");
624 timestamp_fuzz_factor = atoi(argv[i++]);
625 continue;
628 if (strcmp(argv[i], "-g") == 0)
630 do_diff = 1;
631 i++;
632 continue;
635 if (strcmp(argv[i], "-s") == 0)
637 PatchSetRange * range;
638 char * min_str, * max_str;
640 if (++i >= argc)
641 return usage("argument to -s missing", "");
643 min_str = strtok(argv[i++], ",");
646 range = create_patch_set_range();
648 max_str = strrchr(min_str, '-');
649 if (max_str)
650 *max_str++ = '\0';
651 else
652 max_str = min_str;
654 range->min_counter = atoi(min_str);
656 if (*max_str)
657 range->max_counter = atoi(max_str);
658 else
659 range->max_counter = INT_MAX;
661 list_add(&range->link, show_patch_set_ranges.prev);
663 while ((min_str = strtok(NULL, ",")));
665 continue;
668 if (strcmp(argv[i], "-a") == 0)
670 if (++i >= argc)
671 return usage("argument to -a missing", "");
673 restrict_author = argv[i++];
674 continue;
677 if (strcmp(argv[i], "-l") == 0)
679 int err;
681 if (++i >= argc)
682 return usage("argument to -l missing", "");
684 if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
686 char errbuf[256];
687 regerror(err, &restrict_log, errbuf, 256);
688 return usage("bad regex to -l", errbuf);
691 have_restrict_log = 1;
693 continue;
696 if (strcmp(argv[i], "-f") == 0)
698 int err;
700 if (++i >= argc)
701 return usage("argument to -f missing", "");
703 if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
705 char errbuf[256];
706 regerror(err, &restrict_file, errbuf, 256);
707 return usage("bad regex to -f", errbuf);
710 have_restrict_file = 1;
712 continue;
715 if (strcmp(argv[i], "-d") == 0)
717 time_t *pt;
719 if (++i >= argc)
720 return usage("argument to -d missing", "");
722 pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
723 convert_date(pt, argv[i++]);
724 continue;
727 if (strcmp(argv[i], "-r") == 0)
729 if (++i >= argc)
730 return usage("argument to -r missing", "");
732 if (restrict_tag_start)
733 restrict_tag_end = argv[i];
734 else
735 restrict_tag_start = argv[i];
737 i++;
738 continue;
741 if (strcmp(argv[i], "-u") == 0)
743 update_cache = 1;
744 i++;
745 continue;
748 if (strcmp(argv[i], "-x") == 0)
750 ignore_cache = 1;
751 update_cache = 1;
752 i++;
753 continue;
756 if (strcmp(argv[i], "-b") == 0)
758 if (++i >= argc)
759 return usage("argument to -b missing", "");
761 restrict_branch = argv[i++];
762 /* Warn if the user tries to use TRUNK. Should eventually
763 * go away as TRUNK may be a valid branch within CVS
765 if (strcmp(restrict_branch, "TRUNK") == 0)
766 debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
767 continue;
770 if (strcmp(argv[i], "-p") == 0)
772 if (++i >= argc)
773 return usage("argument to -p missing", "");
775 patch_set_dir = argv[i++];
776 continue;
779 if (strcmp(argv[i], "-v") == 0)
781 debuglvl = ~0;
782 i++;
783 continue;
786 if (strcmp(argv[i], "-t") == 0)
788 statistics = 1;
789 i++;
790 continue;
793 if (strcmp(argv[i], "--summary-first") == 0)
795 summary_first = 1;
796 i++;
797 continue;
800 if (strcmp(argv[i], "-h") == 0)
801 return usage(NULL, NULL);
803 /* see special handling of --norc in main */
804 if (strcmp(argv[i], "--norc") == 0)
806 norc = "-f";
807 i++;
808 continue;
811 if (strcmp(argv[i], "--test-log") == 0)
813 if (++i >= argc)
814 return usage("argument to --test-log missing", "");
816 test_log_file = argv[i++];
817 continue;
820 if (strcmp(argv[i], "--diff-opts") == 0)
822 if (++i >= argc)
823 return usage("argument to --diff-opts missing", "");
825 /* allow diff_opts to be turned off by making empty string
826 * into NULL
828 if (!strlen(argv[i]))
829 diff_opts = NULL;
830 else
831 diff_opts = argv[i];
832 i++;
833 continue;
836 if (strcmp(argv[i], "--bkcvs") == 0)
838 bkcvs = 1;
839 i++;
840 continue;
843 if (strcmp(argv[i], "--no-rlog") == 0)
845 no_rlog = 1;
846 i++;
847 continue;
850 if (strcmp(argv[i], "--cvs-direct") == 0)
852 cvs_direct = 1;
853 i++;
854 continue;
857 if (strcmp(argv[i], "--no-cvs-direct") == 0)
859 cvs_direct = 0;
860 i++;
861 continue;
864 if (strcmp(argv[i], "--debuglvl") == 0)
866 if (++i >= argc)
867 return usage("argument to --debuglvl missing", "");
869 debuglvl = atoi(argv[i++]);
870 continue;
873 if (strcmp(argv[i], "-Z") == 0)
875 if (++i >= argc)
876 return usage("argument to -Z", "");
878 compress = atoi(argv[i++]);
880 if (compress < 0 || compress > 9)
881 return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
883 if (compress == 0)
884 compress_arg[0] = 0;
885 else
886 snprintf(compress_arg, 8, "-z%d", compress);
887 continue;
890 if (strcmp(argv[i], "--root") == 0)
892 if (++i >= argc)
893 return usage("argument to --root missing", "");
895 strcpy(root_path, argv[i++]);
896 continue;
899 if (strcmp(argv[i], "-q") == 0)
901 debuglvl &= ~DEBUG_APPMSG1;
902 i++;
903 continue;
906 if (strcmp(argv[i], "-A") == 0)
908 track_branch_ancestry = 1;
909 i++;
910 continue;
913 if (argv[i][0] == '-')
914 return usage("invalid argument", argv[i]);
916 strcpy(repository_path, argv[i++]);
919 return 0;
922 static int parse_rc()
924 char rcfile[PATH_MAX];
925 FILE * fp;
926 snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
927 if ((fp = fopen(rcfile, "r")))
929 char buff[BUFSIZ];
930 while (fgets(buff, BUFSIZ, fp))
932 char * argv[3], *p;
933 int argc = 2;
935 chop(buff);
937 argv[0] = "garbage";
939 p = strchr(buff, ' ');
940 if (p)
942 *p++ = '\0';
943 argv[2] = xstrdup(p);
944 argc = 3;
947 argv[1] = xstrdup(buff);
949 if (parse_args(argc, argv) < 0)
950 return -1;
952 fclose(fp);
955 return 0;
958 static void init_paths()
960 FILE * fp;
961 char * p;
962 int len;
964 /* determine the CVSROOT. precedence:
965 * 1) command line
966 * 2) working directory (if present)
967 * 3) environment variable CVSROOT
969 if (!root_path[0])
971 if (!(fp = fopen("CVS/Root", "r")))
973 const char * e;
975 debug(DEBUG_STATUS, "Can't open CVS/Root");
976 e = getenv("CVSROOT");
978 if (!e)
980 debug(DEBUG_APPERROR, "cannot determine CVSROOT");
981 exit(1);
984 strcpy(root_path, e);
986 else
988 if (!fgets(root_path, PATH_MAX, fp))
990 debug(DEBUG_APPERROR, "Error reading CVSROOT");
991 exit(1);
994 fclose(fp);
996 /* chop the lf and optional trailing '/' */
997 len = strlen(root_path) - 1;
998 root_path[len] = 0;
999 if (root_path[len - 1] == '/')
1000 root_path[--len] = 0;
1004 /* Determine the repository path, precedence:
1005 * 1) command line
1006 * 2) working directory
1009 if (!repository_path[0])
1011 if (!(fp = fopen("CVS/Repository", "r")))
1013 debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1014 exit(1);
1017 if (!fgets(repository_path, PATH_MAX, fp))
1019 debug(DEBUG_APPERROR, "Error reading repository path");
1020 exit(1);
1023 chop(repository_path);
1024 fclose(fp);
1027 /* get the path portion of the root */
1028 p = strrchr(root_path, ':');
1030 if (!p)
1031 p = root_path;
1032 else
1033 p++;
1035 /* some CVS have the CVSROOT string as part of the repository
1036 * string (initial substring). remove it.
1038 len = strlen(p);
1040 if (strncmp(p, repository_path, len) == 0)
1042 int rlen = strlen(repository_path + len + 1);
1043 memmove(repository_path, repository_path + len + 1, rlen + 1);
1046 /* the 'strip_path' will be used whenever the CVS server gives us a
1047 * path to an 'rcs file'. the strip_path portion of these paths is
1048 * stripped off, leaving us with the working file.
1050 * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1051 * (instead of log) it gives the 'real' RCS file path, which can be different
1052 * from the 'nominal' repository path because of symlinks in the server and
1053 * the like. See also the 'parse_file' routine
1055 strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1057 if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1059 debug(DEBUG_APPERROR, "strip_path overflow");
1060 exit(1);
1063 debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1066 static CvsFile * parse_file(const char * buff)
1068 CvsFile * retval;
1069 char fn[PATH_MAX];
1070 int len = strlen(buff + 10);
1071 char * p;
1073 /* once a single file has been parsed ok we set this */
1074 static int path_ok;
1076 /* chop the ",v" string and the "LF" */
1077 len -= 3;
1078 memcpy(fn, buff + 10, len);
1079 fn[len] = 0;
1081 if (strncmp(fn, strip_path, strip_path_len) != 0)
1083 /* if the very first file fails the strip path,
1084 * then maybe we need to try for an alternate.
1085 * this will happen if symlinks are being used
1086 * on the server. our best guess is to look
1087 * for the final occurance of the repository
1088 * path in the filename and use that. it should work
1089 * except in the case where:
1090 * 1) the project has no files in the top-level directory
1091 * 2) the project has a directory with the same name as the project
1092 * 3) that directory sorts alphabetically before any other directory
1093 * in which case, you are scr**ed
1095 if (!path_ok)
1097 char * p = fn, *lastp = NULL;
1099 while ((p = strstr(p, repository_path)))
1100 lastp = p++;
1102 if (lastp)
1104 int len = strlen(repository_path);
1105 memcpy(strip_path, fn, lastp - fn + len + 1);
1106 strip_path_len = lastp - fn + len + 1;
1107 strip_path[strip_path_len] = 0;
1108 debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
1109 goto ok;
1113 /* FIXME: a subdirectory may have a different Repository path
1114 * than it's parent. we'll fail the above test since strip_path
1115 * is global for the entire checked out tree (recursively).
1117 * For now just ignore such files
1119 debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring",
1120 fn, strip_path);
1121 return NULL;
1125 path_ok = 1;
1127 /* remove from beginning the 'strip_path' string */
1128 len -= strip_path_len;
1129 memmove(fn, fn + strip_path_len, len);
1130 fn[len] = 0;
1132 /* check if file is in the 'Attic/' and remove it */
1133 if ((p = strrchr(fn, '/')) &&
1134 p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1136 memmove(p - 5, p + 1, len - (p - fn + 1));
1137 len -= 6;
1138 fn[len] = 0;
1141 debug(DEBUG_STATUS, "stripped filename %s", fn);
1143 retval = (CvsFile*)get_hash_object(file_hash, fn);
1145 if (!retval)
1147 if ((retval = create_cvsfile()))
1149 retval->filename = xstrdup(fn);
1150 put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1152 else
1154 debug(DEBUG_SYSERROR, "malloc failed");
1155 exit(1);
1158 debug(DEBUG_STATUS, "new file: %s", retval->filename);
1160 else
1162 debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1165 return retval;
1168 PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
1170 PatchSet * retval = NULL, **find = NULL;
1171 int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
1173 if (!(retval = create_patch_set()))
1175 debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1176 return NULL;
1179 convert_date(&retval->date, dte);
1180 retval->author = get_string(author);
1181 retval->descr = xstrdup(log);
1182 retval->branch = get_string(branch);
1184 /* we are looking for a patchset suitable for holding this member.
1185 * this means two things:
1186 * 1) a patchset already containing an entry for the file is no good
1187 * 2) for two patchsets with same exact date/time, if they reference
1188 * the same file, we can properly order them. this primarily solves
1189 * the 'cvs import' problem and may not have general usefulness
1190 * because it would only work if the first member we consider is
1191 * present in the existing ps.
1193 if (psm)
1194 list_add(&psm->link, retval->members.prev);
1196 find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
1198 if (psm)
1199 list_del(&psm->link);
1201 if (*find != retval)
1203 debug(DEBUG_STATUS, "found existing patch set");
1205 if (bkcvs && strstr(retval->descr, "BKrev:"))
1207 free((*find)->descr);
1208 (*find)->descr = retval->descr;
1210 else
1212 free(retval->descr);
1215 /* keep the minimum date of any member as the 'actual' date */
1216 if (retval->date < (*find)->date)
1217 (*find)->date = retval->date;
1219 /* expand the min_date/max_date window to help finding other members .
1220 * open the window by an extra margin determined by the fuzz factor
1222 if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1224 (*find)->min_date = retval->date - timestamp_fuzz_factor;
1225 //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1227 else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1228 (*find)->max_date = retval->date + timestamp_fuzz_factor;
1230 free(retval);
1231 retval = *find;
1233 else
1235 debug(DEBUG_STATUS, "new patch set!");
1236 debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
1238 retval->min_date = retval->date - timestamp_fuzz_factor;
1239 retval->max_date = retval->date + timestamp_fuzz_factor;
1241 list_add(&retval->all_link, &all_patch_sets);
1245 return retval;
1248 static int get_branch_ext(char * buff, const char * rev, int * leaf)
1250 char * p;
1251 int len = strlen(rev);
1253 /* allow get_branch(buff, buff) without destroying contents */
1254 memmove(buff, rev, len);
1255 buff[len] = 0;
1257 p = strrchr(buff, '.');
1258 if (!p)
1259 return 0;
1260 *p++ = 0;
1262 if (leaf)
1263 *leaf = atoi(p);
1265 return 1;
1268 static int get_branch(char * buff, const char * rev)
1270 return get_branch_ext(buff, rev, NULL);
1274 * the goal if this function is to determine what revision to assign to
1275 * the psm->pre_rev field. usually, the log file is strictly
1276 * reverse chronological, so rev is direct ancestor to psm,
1278 * This all breaks down at branch points however
1281 static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1283 char pre[REV_STR_MAX], post[REV_STR_MAX];
1285 if (!psm)
1286 return;
1288 if (!rev)
1290 /* if psm was last rev. for file, it's either an
1291 * INITIAL, or first rev of a branch. to test if it's
1292 * the first rev of a branch, do get_branch twice -
1293 * this should be the bp.
1295 if (get_branch(post, psm->post_rev->rev) &&
1296 get_branch(pre, post))
1298 psm->pre_rev = file_get_revision(psm->file, pre);
1299 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1301 else
1303 set_psm_initial(psm);
1305 return;
1309 * is this canditate for 'pre' on the same branch as our 'post'?
1310 * this is the normal case
1312 if (!get_branch(pre, rev->rev))
1314 debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1315 return;
1318 if (!get_branch(post, psm->post_rev->rev))
1320 debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1321 return;
1324 if (strcmp(pre, post) == 0)
1326 psm->pre_rev = rev;
1327 rev->pre_psm = psm;
1328 return;
1331 /* branches don't match. new_psm must be head of branch,
1332 * so psm is oldest rev. on branch. or oldest
1333 * revision overall. if former, derive predecessor.
1334 * use get_branch to chop another rev. off of string.
1336 * FIXME:
1337 * There's also a weird case. it's possible to just re-number
1338 * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1339 * It's not widely used. In those cases of discontinuity,
1340 * we end up stamping the predecessor as 'INITIAL' incorrectly
1343 if (!get_branch(pre, post))
1345 set_psm_initial(psm);
1346 return;
1349 psm->pre_rev = file_get_revision(psm->file, pre);
1350 list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1353 static void check_print_patch_set(PatchSet * ps)
1355 if (ps->psid < 0)
1356 return;
1358 /* the funk_factor overrides the restrict_tag_start and end */
1359 if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1360 goto ok;
1362 if (ps->funk_factor == FNK_HIDE_ALL)
1363 return;
1365 if (ps->psid <= restrict_tag_ps_start)
1367 if (ps->psid == restrict_tag_ps_start)
1368 debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1370 return;
1373 if (ps->psid > restrict_tag_ps_end)
1374 return;
1377 if (restrict_date_start > 0 &&
1378 (ps->date < restrict_date_start ||
1379 (restrict_date_end > 0 && ps->date > restrict_date_end)))
1380 return;
1382 if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1383 return;
1385 if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1386 return;
1388 if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1389 return;
1391 if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1392 return;
1394 if (!list_empty(&show_patch_set_ranges))
1396 struct list_head * next = show_patch_set_ranges.next;
1398 while (next != &show_patch_set_ranges)
1400 PatchSetRange *range = list_entry(next, PatchSetRange, link);
1401 if (range->min_counter <= ps->psid &&
1402 ps->psid <= range->max_counter)
1404 break;
1406 next = next->next;
1409 if (next == &show_patch_set_ranges)
1410 return;
1413 if (patch_set_dir)
1415 char path[PATH_MAX];
1417 snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1419 fflush(stdout);
1420 close(1);
1421 if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1423 debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1424 exit(1);
1427 fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1431 * If the summary_first option is in effect, there will be
1432 * two passes through the tree. the first with summary_first == 1
1433 * the second with summary_first == 2. if the option is not
1434 * in effect, there will be one pass with summary_first == 0
1436 * When the -s option is in effect, the show_patch_set_ranges
1437 * list will be non-empty.
1439 if (summary_first <= 1)
1440 print_patch_set(ps);
1441 if (do_diff && summary_first != 1)
1442 do_cvs_diff(ps);
1444 fflush(stdout);
1447 static void print_patch_set(PatchSet * ps)
1449 struct tm *tm;
1450 struct list_head * next;
1451 const char * funk = "";
1453 tm = localtime(&ps->date);
1454 next = ps->members.next;
1456 funk = fnk_descr[ps->funk_factor];
1458 /* this '---...' is different from the 28 hyphens that separate cvs log output */
1459 printf("---------------------\n");
1460 printf("PatchSet %d %s\n", ps->psid, funk);
1461 printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
1462 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
1463 tm->tm_hour, tm->tm_min, tm->tm_sec);
1464 printf("Author: %s\n", ps->author);
1465 printf("Branch: %s\n", ps->branch);
1466 if (ps->ancestor_branch)
1467 printf("Ancestor branch: %s\n", ps->ancestor_branch);
1468 printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
1469 printf("Log:\n%s\n", ps->descr);
1470 printf("Members: \n");
1472 while (next != &ps->members)
1474 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1475 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1476 funk = "(BEFORE START TAG)";
1477 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1478 funk = "(AFTER END TAG)";
1479 else
1480 funk = "";
1482 printf("\t%s:%s->%s%s %s\n",
1483 psm->file->filename,
1484 psm->pre_rev ? psm->pre_rev->rev : "INITIAL",
1485 psm->post_rev->rev,
1486 psm->post_rev->dead ? "(DEAD)": "",
1487 funk);
1489 next = next->next;
1492 printf("\n");
1495 /* walk all the patchsets to assign monotonic psid,
1496 * and to establish branch ancestry
1498 static void assign_patchset_id(PatchSet * ps)
1501 * Ignore the 'BRANCH ADD' patchsets
1503 if (!ps->branch_add)
1505 ps_counter++;
1506 ps->psid = ps_counter;
1508 if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
1510 PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
1511 if (!head_ps)
1513 head_ps = ps;
1514 put_hash_object(branch_heads, ps->branch, head_ps);
1517 determine_branch_ancestor(ps, head_ps);
1520 else
1522 ps->psid = -1;
1526 static int compare_rev_strings(const char * cr1, const char * cr2)
1528 char r1[REV_STR_MAX];
1529 char r2[REV_STR_MAX];
1530 char *s1 = r1, *s2 = r2;
1531 char *p1, *p2;
1532 int n1, n2;
1534 strcpy(s1, cr1);
1535 strcpy(s2, cr2);
1537 for (;;)
1539 p1 = strchr(s1, '.');
1540 p2 = strchr(s2, '.');
1542 if (p1) *p1++ = 0;
1543 if (p2) *p2++ = 0;
1545 n1 = atoi(s1);
1546 n2 = atoi(s2);
1548 if (n1 < n2)
1549 return -1;
1550 if (n1 > n2)
1551 return 1;
1553 if (!p1 && p2)
1554 return -1;
1555 if (p1 && !p2)
1556 return 1;
1557 if (!p1 && !p2)
1558 return 0;
1560 s1 = p1;
1561 s2 = p2;
1565 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1567 struct list_head * i;
1569 for (i = ps1->members.next; i != &ps1->members; i = i->next)
1571 PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1572 struct list_head * j;
1574 for (j = ps2->members.next; j != &ps2->members; j = j->next)
1576 PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1577 if (psm1->file == psm2->file)
1579 int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1580 //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1581 return ret;
1586 return 0;
1589 static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
1591 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1592 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1593 long diff;
1595 diff = ps1->date - ps2->date;
1597 return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1600 static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1602 const PatchSet * ps1 = (const PatchSet *)v_ps1;
1603 const PatchSet * ps2 = (const PatchSet *)v_ps2;
1604 long diff;
1605 int ret;
1606 time_t d, min, max;
1608 /* We order by (author, descr, branch, members, date), but because of the fuzz factor
1609 * we treat times within a certain distance as equal IFF the author
1610 * and descr match.
1613 ret = strcmp(ps1->author, ps2->author);
1614 if (ret)
1615 return ret;
1617 ret = strcmp(ps1->descr, ps2->descr);
1618 if (ret)
1619 return ret;
1621 ret = strcmp(ps1->branch, ps2->branch);
1622 if (ret)
1623 return ret;
1625 ret = compare_patch_sets_by_members(ps1, ps2);
1626 if (ret)
1627 return ret;
1630 * one of ps1 or ps2 is new. the other should have the min_date
1631 * and max_date set to a window opened by the fuzz_factor
1633 if (ps1->min_date == 0)
1635 d = ps1->date;
1636 min = ps2->min_date;
1637 max = ps2->max_date;
1639 else if (ps2->min_date == 0)
1641 d = ps2->date;
1642 min = ps1->min_date;
1643 max = ps1->max_date;
1645 else
1647 debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
1648 exit(1);
1651 if (min < d && d < max)
1652 return 0;
1654 diff = ps1->date - ps2->date;
1656 return (diff < 0) ? -1 : 1;
1659 static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
1661 const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
1662 const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
1663 return compare_patch_sets_bytime(ps1, ps2);
1666 static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
1668 long diff;
1669 int ret;
1671 /* When doing a time-ordering of patchsets, we don't need to
1672 * fuzzy-match the time. We've already done fuzzy-matching so we
1673 * know that insertions are unique at this point.
1676 diff = ps1->date - ps2->date;
1677 if (diff)
1678 return (diff < 0) ? -1 : 1;
1680 ret = compare_patch_sets_by_members(ps1, ps2);
1681 if (ret)
1682 return ret;
1684 ret = strcmp(ps1->author, ps2->author);
1685 if (ret)
1686 return ret;
1688 ret = strcmp(ps1->descr, ps2->descr);
1689 if (ret)
1690 return ret;
1692 ret = strcmp(ps1->branch, ps2->branch);
1693 return ret;
1697 static int is_revision_metadata(const char * buff)
1699 char * p1, *p2;
1700 int len;
1702 if (!(p1 = strchr(buff, ':')))
1703 return 0;
1705 p2 = strchr(buff, ' ');
1707 if (p2 && p2 < p1)
1708 return 0;
1710 len = strlen(buff);
1712 /* lines have LF at end */
1713 if (len > 1 && buff[len - 2] == ';')
1714 return 1;
1716 return 0;
1719 static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
1721 struct list_head * next = ps->members.next;
1723 while (next != &ps->members)
1725 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1727 if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
1728 return 1;
1730 next = next->next;
1733 return 0;
1736 static int patch_set_affects_branch(PatchSet * ps, const char * branch)
1738 struct list_head * next;
1740 for (next = ps->members.next; next != &ps->members; next = next->next)
1742 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1745 * slight hack. if -r is specified, and this patchset
1746 * is 'before' the tag, but is FNK_SHOW_SOME, only
1747 * check if the 'after tag' revisions affect
1748 * the branch. this is especially important when
1749 * the tag is a branch point.
1751 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1752 continue;
1754 if (revision_affects_branch(psm->post_rev, branch))
1755 return 1;
1758 return 0;
1761 static void do_cvs_diff(PatchSet * ps)
1763 struct list_head * next;
1764 const char * dtype;
1765 const char * dopts;
1766 const char * utype;
1767 char use_rep_path[PATH_MAX];
1768 char esc_use_rep_path[PATH_MAX];
1770 fflush(stdout);
1771 fflush(stderr);
1774 * if cvs_direct is not in effect, and diff options are specified,
1775 * then we have to use diff instead of rdiff and we'll get a -p0
1776 * diff (instead of -p1) [in a manner of speaking]. So to make sure
1777 * that the add/remove diffs get generated likewise, we need to use
1778 * 'update' instead of 'co'
1780 * cvs_direct will always use diff (not rdiff), but will also always
1781 * generate -p1 diffs.
1783 if (diff_opts == NULL)
1785 dopts = "-u";
1786 dtype = "rdiff";
1787 utype = "co";
1788 sprintf(use_rep_path, "%s/", repository_path);
1789 /* the rep_path may contain characters that the shell will barf on */
1790 escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
1792 else
1794 dopts = diff_opts;
1795 dtype = "diff";
1796 utype = "update";
1797 use_rep_path[0] = 0;
1798 esc_use_rep_path[0] = 0;
1801 for (next = ps->members.next; next != &ps->members; next = next->next)
1803 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1804 char cmdbuff[PATH_MAX * 2+1];
1805 char esc_file[PATH_MAX];
1806 int ret, check_ret = 0;
1808 cmdbuff[0] = 0;
1809 cmdbuff[PATH_MAX*2] = 0;
1811 /* the filename may contain characters that the shell will barf on */
1812 escape_filename(esc_file, PATH_MAX, psm->file->filename);
1815 * Check the patchset funk. we may not want to diff this particular file
1817 if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1819 printf("Index: %s\n", psm->file->filename);
1820 printf("===================================================================\n");
1821 printf("*** Member not diffed, before start tag\n");
1822 continue;
1824 else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1826 printf("Index: %s\n", psm->file->filename);
1827 printf("===================================================================\n");
1828 printf("*** Member not diffed, after end tag\n");
1829 continue;
1833 * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1834 * or 'cvs update' to get the file, because cvs won't generate these diffs.
1835 * The problem is that this must be piped to diff, and so the resulting
1836 * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1837 * sed is used to replace the '-' with the filename.
1839 * It's possible for pre_rev to be a 'dead' revision. This happens when a file
1840 * is added on a branch. post_rev will be dead dead for remove
1842 if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
1844 int cr;
1845 const char * rev;
1847 if (!psm->pre_rev || psm->pre_rev->dead)
1849 cr = 1;
1850 rev = psm->post_rev->rev;
1852 else
1854 cr = 0;
1855 rev = psm->pre_rev->rev;
1858 if (cvs_direct_ctx)
1860 /* cvs_rupdate does the pipe through diff thing internally */
1861 cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
1863 else
1865 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'",
1866 compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
1867 cr?"":"-",cr?"-":"", cr?"2":"1",
1868 use_rep_path, psm->file->filename);
1871 else
1873 /* a regular diff */
1874 if (cvs_direct_ctx)
1876 cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
1878 else
1880 /* 'cvs diff' exit status '1' is ok, just means files are different */
1881 if (strcmp(dtype, "diff") == 0)
1882 check_ret = 1;
1884 snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1885 compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev,
1886 esc_use_rep_path, esc_file);
1891 * my_system doesn't block signals the way system does.
1892 * if ctrl-c is pressed while in there, we probably exit
1893 * immediately and hope the shell has sent the signal
1894 * to all of the process group members
1896 if (cmdbuff[0] && (ret = my_system(cmdbuff)))
1898 int stat = WEXITSTATUS(ret);
1901 * cvs diff returns 1 in exit status for 'files are different'
1902 * so use a better method to check for failure
1904 if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
1906 debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
1907 exit(1);
1913 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
1915 char * p;
1917 /* The "revision" log line can include extra information
1918 * including who is locking the file --- strip that out.
1921 p = rev_str;
1922 while (isdigit(*p) || *p == '.')
1923 p++;
1924 *p = 0;
1926 return cvs_file_add_revision(file, rev_str);
1929 CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
1931 CvsFileRevision * rev;
1933 if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
1935 rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
1936 rev->rev = get_string(rev_str);
1937 rev->file = file;
1938 rev->branch = NULL;
1939 rev->present = 0;
1940 rev->pre_psm = NULL;
1941 rev->post_psm = NULL;
1942 INIT_LIST_HEAD(&rev->branch_children);
1943 INIT_LIST_HEAD(&rev->tags);
1945 put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
1947 debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
1949 else
1951 debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
1955 * note: we are guaranteed to get here at least once with 'have_branches' == 1.
1956 * we may pass through once before this, because of symbolic tags, then once
1957 * always when processing the actual revision logs
1959 * rev->branch will always be set to something, maybe "HEAD"
1961 if (!rev->branch && file->have_branches)
1963 char branch_str[REV_STR_MAX];
1965 /* in the cvs cvs repository (ccvs) there are tagged versions
1966 * that don't exist. let's mark every 'known to exist'
1967 * version
1969 rev->present = 1;
1971 /* determine the branch this revision was committed on */
1972 if (!get_branch(branch_str, rev->rev))
1974 debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
1975 exit(1);
1978 rev->branch = (char*)get_hash_object(file->branches, branch_str);
1980 /* if there's no branch and it's not on the trunk, blab */
1981 if (!rev->branch)
1983 if (get_branch(branch_str, branch_str))
1985 debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
1986 rev->branch = "#CVSPS_NO_BRANCH";
1988 else
1990 rev->branch = "HEAD";
1994 debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
1997 return rev;
2000 CvsFile * create_cvsfile()
2002 CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
2003 if (!f)
2004 return NULL;
2006 f->revisions = create_hash_table(53);
2007 f->branches = create_hash_table(13);
2008 f->branches_sym = create_hash_table(13);
2009 f->symbols = create_hash_table(253);
2010 f->have_branches = 0;
2012 if (!f->revisions || !f->branches || !f->branches_sym)
2014 if (f->branches)
2015 destroy_hash_table(f->branches, NULL);
2016 if (f->revisions)
2017 destroy_hash_table(f->revisions, NULL);
2018 free(f);
2019 return NULL;
2022 return f;
2025 static PatchSet * create_patch_set()
2027 PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
2029 if (ps)
2031 INIT_LIST_HEAD(&ps->members);
2032 ps->psid = -1;
2033 ps->date = 0;
2034 ps->min_date = 0;
2035 ps->max_date = 0;
2036 ps->descr = NULL;
2037 ps->author = NULL;
2038 ps->tag = NULL;
2039 ps->tag_flags = 0;
2040 ps->branch_add = 0;
2041 ps->funk_factor = 0;
2042 ps->ancestor_branch = NULL;
2043 CLEAR_LIST_NODE(&ps->collision_link);
2046 return ps;
2049 PatchSetMember * create_patch_set_member()
2051 PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
2052 psm->pre_rev = NULL;
2053 psm->post_rev = NULL;
2054 psm->ps = NULL;
2055 psm->file = NULL;
2056 psm->bad_funk = 0;
2057 return psm;
2060 static PatchSetRange * create_patch_set_range()
2062 PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
2063 return psr;
2066 CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
2068 CvsFileRevision * rev;
2070 if (strcmp(r, "INITIAL") == 0)
2071 return NULL;
2073 rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
2075 if (!rev)
2077 debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
2078 exit(1);
2081 return rev;
2085 * Parse lines in the format:
2087 * <white space>tag_name: <rev>;
2089 * Handles both regular tags (these go into the symbols hash)
2090 * and magic-branch-tags (second to last node of revision is 0)
2091 * which go into branches and branches_sym hashes. Magic-branch
2092 * format is hidden in CVS everwhere except the 'cvs log' output.
2095 static void parse_sym(CvsFile * file, char * sym)
2097 char * tag = sym, *eot;
2098 int leaf, final_branch = -1;
2099 char rev[REV_STR_MAX];
2100 char rev2[REV_STR_MAX];
2102 while (*tag && isspace(*tag))
2103 tag++;
2105 if (!*tag)
2106 return;
2108 eot = strchr(tag, ':');
2110 if (!eot)
2111 return;
2113 *eot = 0;
2114 eot += 2;
2116 if (!get_branch_ext(rev, eot, &leaf))
2118 debug(DEBUG_APPERROR, "malformed revision");
2119 exit(1);
2123 * get_branch_ext will leave final_branch alone
2124 * if there aren't enough '.' in string
2126 get_branch_ext(rev2, rev, &final_branch);
2128 if (final_branch == 0)
2130 snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
2131 debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
2133 cvs_file_add_branch(file, rev, tag);
2135 else
2137 strcpy(rev, eot);
2138 chop(rev);
2140 /* see cvs manual: what is this vendor tag? */
2141 if (is_vendor_branch(rev))
2142 cvs_file_add_branch(file, rev, tag);
2143 else
2144 cvs_file_add_symbol(file, rev, tag);
2148 void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
2150 CvsFileRevision * rev;
2151 GlobalSymbol * sym;
2152 Tag * tag;
2154 /* get a permanent storage string */
2155 char * tag_str = get_string(p_tag_str);
2157 debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
2158 rev = cvs_file_add_revision(file, rev_str);
2159 put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
2162 * check the global_symbols
2164 sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
2165 if (!sym)
2167 sym = (GlobalSymbol*)malloc(sizeof(*sym));
2168 sym->tag = tag_str;
2169 sym->ps = NULL;
2170 INIT_LIST_HEAD(&sym->tags);
2172 put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
2175 tag = (Tag*)malloc(sizeof(*tag));
2176 tag->tag = tag_str;
2177 tag->rev = rev;
2178 tag->sym = sym;
2179 list_add(&tag->global_link, &sym->tags);
2180 list_add(&tag->rev_link, &rev->tags);
2183 char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
2185 char * new_tag;
2186 char * new_rev;
2188 if (get_hash_object(file->branches, rev))
2190 debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s",
2191 rev, tag, file->filename);
2192 return NULL;
2195 /* get permanent storage for the strings */
2196 new_tag = get_string(tag);
2197 new_rev = get_string(rev);
2199 put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
2200 put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
2202 return new_tag;
2206 * Resolve each global symbol to a PatchSet. This is
2207 * not necessarily doable, because tagging isn't
2208 * necessarily done to the project as a whole, and
2209 * it's possible that no tag is valid for all files
2210 * at a single point in time. We check for that
2211 * case though.
2213 * Implementation: the most recent PatchSet containing
2214 * a revision (post_rev) tagged by the symbol is considered
2215 * the 'tagged' PatchSet.
2218 static void resolve_global_symbols()
2220 struct hash_entry * he_sym;
2221 reset_hash_iterator(global_symbols);
2222 while ((he_sym = next_hash_entry(global_symbols)))
2224 GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
2225 PatchSet * ps;
2226 struct list_head * next;
2228 debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
2231 * First pass, determine the most recent PatchSet with a
2232 * revision tagged with the symbolic tag. This is 'the'
2233 * patchset with the tag
2236 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2238 Tag * tag = list_entry(next, Tag, global_link);
2239 CvsFileRevision * rev = tag->rev;
2241 /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
2242 if (!rev->present || !rev->post_psm)
2244 struct list_head *tmp = next->prev;
2245 debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
2246 rev->rev, rev->file->filename);
2247 /* FIXME: memleak */
2248 list_del(next);
2249 next = tmp;
2250 continue;
2253 ps = rev->post_psm->ps;
2255 if (!sym->ps || ps->date > sym->ps->date)
2256 sym->ps = ps;
2259 /* convenience variable */
2260 ps = sym->ps;
2262 if (!ps)
2264 debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
2265 return;
2268 ps->tag = sym->tag;
2270 /* check if this ps is one of the '-r' patchsets */
2271 if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
2272 restrict_tag_ps_start = ps->psid;
2274 /* the second -r implies -b */
2275 if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
2277 restrict_tag_ps_end = ps->psid;
2279 if (restrict_branch)
2281 if (strcmp(ps->branch, restrict_branch) != 0)
2283 debug(DEBUG_APPMSG1,
2284 "WARNING: -b option and second -r have conflicting branches: %s %s",
2285 restrict_branch, ps->branch);
2288 else
2290 debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
2291 restrict_branch = ps->branch;
2296 * Second pass.
2297 * check if this is an invalid patchset,
2298 * check which members are invalid. determine
2299 * the funk factor etc.
2301 for (next = sym->tags.next; next != &sym->tags; next = next->next)
2303 Tag * tag = list_entry(next, Tag, global_link);
2304 CvsFileRevision * rev = tag->rev;
2305 CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
2307 if (!next_rev)
2308 continue;
2311 * we want the 'tagged revision' to be valid until after
2312 * the date of the 'tagged patchset' or else there's something
2313 * funky going on
2315 if (next_rev->post_psm->ps->date < ps->date)
2317 int flag = check_rev_funk(ps, next_rev);
2318 debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
2319 rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
2320 ps->tag_flags |= flag;
2326 static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
2328 /* special case the branch called 'HEAD' */
2329 if (strcmp(branch, "HEAD") == 0)
2331 /* look for only one '.' in rev */
2332 char * p = strchr(rev->rev, '.');
2333 if (p && !strchr(p + 1, '.'))
2334 return 1;
2336 else
2338 char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
2340 if (branch_rev)
2342 char post_rev[REV_STR_MAX];
2343 char branch[REV_STR_MAX];
2344 int file_leaf, branch_leaf;
2346 strcpy(branch, branch_rev);
2348 /* first get the branch the file rev is on */
2349 if (get_branch_ext(post_rev, rev->rev, &file_leaf))
2351 branch_leaf = file_leaf;
2353 /* check against branch and all branch ancestor branches */
2356 debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
2357 if (strcmp(branch, post_rev) == 0)
2358 return (file_leaf <= branch_leaf);
2360 while(get_branch_ext(branch, branch, &branch_leaf));
2365 return 0;
2368 static int count_dots(const char * p)
2370 int dots = 0;
2372 while (*p)
2373 if (*p++ == '.')
2374 dots++;
2376 return dots;
2380 * When importing vendor sources, (apparently people do this)
2381 * the code is added on a 'vendor' branch, which, for some reason
2382 * doesn't use the magic-branch-tag format. Try to detect that now
2384 static int is_vendor_branch(const char * rev)
2386 return !(count_dots(rev)&1);
2389 void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
2391 /* check if a member for the same file already exists, if so
2392 * put this PatchSet on the collisions list
2394 struct list_head * next;
2395 for (next = ps->members.next; next != &ps->members; next = next->next)
2397 PatchSetMember * m = list_entry(next, PatchSetMember, link);
2398 if (m->file == psm->file && ps->collision_link.next == NULL)
2399 list_add(&ps->collision_link, &collisions);
2402 psm->ps = ps;
2403 list_add(&psm->link, ps->members.prev);
2406 static void set_psm_initial(PatchSetMember * psm)
2408 psm->pre_rev = NULL;
2409 if (psm->post_rev->dead)
2412 * we expect a 'file xyz initially added on branch abc' here
2413 * but there can only be one such member in a given patchset
2415 if (psm->ps->branch_add)
2416 debug(DEBUG_APPMSG1, "WARNING: branch_add already set!");
2417 psm->ps->branch_add = 1;
2422 * look at all revisions starting at rev and going forward until
2423 * ps->date and see whether they are invalid or just funky.
2425 static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
2427 int retval = TAG_FUNKY;
2429 while (rev)
2431 PatchSet * next_ps = rev->post_psm->ps;
2432 struct list_head * next;
2434 if (next_ps->date > ps->date)
2435 break;
2437 debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
2438 ps->date, next_ps->date, rev->rev, rev->branch);
2441 * If the ps->tag is one of the two possible '-r' tags
2442 * then the funkyness is even more important.
2444 * In the restrict_tag_start case, this next_ps is chronologically
2445 * before ps, but tagwise after, so set the funk_factor so it will
2446 * be included.
2448 * The restrict_tag_end case is similar, but backwards.
2450 * Start assuming the HIDE/SHOW_ALL case, we will determine
2451 * below if we have a split ps case
2453 if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
2454 next_ps->funk_factor = FNK_SHOW_ALL;
2455 if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
2456 next_ps->funk_factor = FNK_HIDE_ALL;
2459 * if all of the other members of this patchset are also 'after' the tag
2460 * then this is a 'funky' patchset w.r.t. the tag. however, if some are
2461 * before then the patchset is 'invalid' w.r.t. the tag, and we mark
2462 * the members individually with 'bad_funk' ,if this tag is the
2463 * '-r' tag. Then we can actually split the diff on this patchset
2465 for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
2467 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2468 if (before_tag(psm->post_rev, ps->tag))
2470 retval = TAG_INVALID;
2471 /* only set bad_funk for one of the -r tags */
2472 if (next_ps->funk_factor)
2474 psm->bad_funk = 1;
2475 next_ps->funk_factor =
2476 (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
2478 debug(DEBUG_APPMSG1,
2479 "WARNING: Invalid PatchSet %d, Tag %s:\n"
2480 " %s:%s=after, %s:%s=before. Treated as 'before'",
2481 next_ps->psid, ps->tag,
2482 rev->file->filename, rev->rev,
2483 psm->post_rev->file->filename, psm->post_rev->rev);
2487 rev = rev_follow_branch(rev, ps->branch);
2490 return retval;
2493 /* determine if the revision is before the tag */
2494 static int before_tag(CvsFileRevision * rev, const char * tag)
2496 CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
2497 int retval = 0;
2499 if (tagged_rev &&
2500 revision_affects_branch(rev, tagged_rev->branch) &&
2501 rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
2502 retval = 1;
2504 debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d",
2505 rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
2507 return retval;
2510 /* get the next revision from this one following branch if possible */
2511 /* FIXME: not sure if this needs to follow branches leading up to branches? */
2512 static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
2514 struct list_head * next;
2516 /* check for 'main line of inheritance' */
2517 if (strcmp(rev->branch, branch) == 0)
2518 return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
2520 /* look down branches */
2521 for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
2523 CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
2524 //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2525 if (strcmp(next_rev->branch, branch) == 0)
2526 return next_rev;
2529 return NULL;
2532 static void check_norc(int argc, char * argv[])
2534 int i = 1;
2535 while (i < argc)
2537 if (strcmp(argv[i], "--norc") == 0)
2539 norc = "-f";
2540 break;
2542 i++;
2546 static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
2548 struct list_head * next;
2549 CvsFileRevision * rev;
2551 /* PatchSet 1 has no ancestor */
2552 if (ps->psid == 1)
2553 return;
2555 /* HEAD branch patchsets have no ancestry, but callers should know that */
2556 if (strcmp(ps->branch, "HEAD") == 0)
2558 debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
2559 return;
2562 for (next = ps->members.next; next != &ps->members; next = next->next)
2564 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2565 rev = psm->pre_rev;
2566 int d1, d2;
2568 /* the reason this is at all complicated has to do with a
2569 * branch off of a branch. it is possible (and indeed
2570 * likely) that some file would not have been modified
2571 * from the initial branch point to the branch-off-branch
2572 * point, and therefore the branch-off-branch point is
2573 * really branch-off-HEAD for that specific member (file).
2574 * in that case, rev->branch will say HEAD but we want
2575 * to know the symbolic name of the first branch
2576 * so we continue to look member after member until we find
2577 * the 'deepest' branching. deepest can actually be determined
2578 * by considering the revision currently indicated by
2579 * ps->ancestor_branch (by symbolic lookup) and rev->rev. the
2580 * one with more dots wins
2582 * also, the first commit in which a branch-off-branch is
2583 * mentioned may ONLY modify files never committed since
2584 * original branch-off-HEAD was created, so we have to keep
2585 * checking, ps after ps to be sure to get the deepest ancestor
2587 * note: rev is the pre-commit revision, not the post-commit
2589 if (!head_ps->ancestor_branch)
2590 d1 = 0;
2591 else if (strcmp(ps->branch, rev->branch) == 0)
2592 continue;
2593 else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
2594 d1 = 1;
2595 else {
2596 /* branch_rev may not exist if the file was added on this branch for example */
2597 const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
2598 d1 = branch_rev ? count_dots(branch_rev) : 1;
2601 /* HACK: we sometimes pretend to derive from the import branch.
2602 * just don't do that. this is the easiest way to prevent...
2604 d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
2606 if (d2 > d1)
2607 head_ps->ancestor_branch = rev->branch;
2609 //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
2613 static void handle_collisions()
2615 struct list_head *next;
2616 for (next = collisions.next; next != &collisions; next = next->next)
2618 PatchSet * ps = list_entry(next, PatchSet, collision_link);
2619 printf("PatchSet %d has collisions\n", ps->psid);
2623 void walk_all_patch_sets(void (*action)(PatchSet *))
2625 struct list_head * next;;
2626 for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
2627 PatchSet * ps = list_entry(next, PatchSet, all_link);
2628 action(ps);