cvs_direct -> cvsclient. All regression tests pass.
[cvsps:cvsps.git] / cvsps.c
1 /*
2  * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3  * See COPYING file for license information 
4  */
5
6 #include <stdio.h>
7 #include <stdlib.h>
8 #include <string.h>
9 #include <limits.h>
10 #include <unistd.h>
11 #include <search.h>
12 #include <time.h>
13 #include <ctype.h>
14 #include <sys/stat.h>
15 #include <sys/types.h>
16 #include <stdbool.h>
17 #include <fcntl.h>
18 #include <regex.h>
19 #include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
20
21 #include <cbtcommon/hash.h>
22 #include <cbtcommon/list.h>
23 #include <cbtcommon/text_util.h>
24 #include <cbtcommon/debug.h>
25
26 #include "cvsps_types.h"
27 #include "cvsps.h"
28 #include "util.h"
29 #include "stats.h"
30 #include "cvsclient.h"
31 #include "list_sort.h"
32
33 #define CVS_LOG_BOUNDARY "----------------------------\n"
34 #define CVS_FILE_BOUNDARY "=============================================================================\n"
35
36 enum
37 {
38     NEED_RCS_FILE,
39     NEED_WORKING_FILE,
40     NEED_SYMS,
41     NEED_EOS,
42     NEED_START_LOG,
43     NEED_REVISION,
44     NEED_DATE_AUTHOR_STATE,
45     NEED_EOM
46 };
47
48 /* true globals */
49 struct hash_table * file_hash;
50 CvsServerCtx * cvsclient_ctx;
51 char root_path[PATH_MAX];
52 char repository_path[PATH_MAX];
53
54 const char * tag_flag_descr[] = {
55     "",
56     "**FUNKY**",
57     "**INVALID**",
58     "**INVALID**"
59 };
60
61 const char * fnk_descr[] = {
62     "",
63     "FNK_SHOW_SOME",
64     "FNK_SHOW_ALL",
65     "FNK_HIDE_ALL",
66     "FNK_HIDE_SOME"
67 };
68
69 /* static globals */
70 static int ps_counter;
71 static void * ps_tree;
72 static struct hash_table * global_symbols;
73 static char strip_path[PATH_MAX];
74 static int strip_path_len;
75 static bool statistics;
76 static const char * test_log_file;
77 static struct hash_table * branch_heads;
78 static struct list_head all_patch_sets;
79 static struct list_head collisions;
80 static struct hash_table * branches;
81 static int dubious_branches = 0;
82
83 /* settable via options */
84 static int timestamp_fuzz_factor = 300;
85 static bool do_diff;
86 static const char * restrict_author;
87 static bool have_restrict_log;
88 static regex_t restrict_log;
89 static bool have_restrict_file;
90 static regex_t restrict_file;
91 static time_t restrict_date_start;
92 static time_t restrict_date_end;
93 static const char * restrict_branch;
94 static struct list_head show_patch_set_ranges;
95 static struct list_head authormap;
96 static int summary_first;
97 static bool fast_export;
98 static const char * patch_set_dir;
99 static const char * restrict_tag_start;
100 static const char * restrict_tag_end;
101 static int restrict_tag_ps_start;
102 static int restrict_tag_ps_end = INT_MAX;
103 static const char * diff_opts;
104 static int compress;
105 static char compress_arg[8];
106 static time_t regression_time;
107 static bool selection_sense = true;
108 static FILE *revfp;
109
110 static int parse_args(int, char *[]);
111 static int parse_rc();
112 static void load_from_cvs();
113 static void init_paths();
114 static CvsFile * build_file_by_name(const char *);
115 static CvsFile * parse_rcs_file(const char *);
116 static CvsFile * parse_working_file(const char *);
117 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
118 static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
119 static void check_print_patch_set(PatchSet *);
120 static void print_patch_set(PatchSet *);
121 static void print_fast_export(PatchSet *);
122 static void assign_patchset_id(PatchSet *);
123 static int compare_rev_strings(const char *, const char *);
124 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
125 static int compare_patch_sets(const void *, const void *);
126 static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
127 static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
128 static bool is_revision_metadata(const char *);
129 static bool patch_set_member_regex(PatchSet * ps, regex_t * reg);
130 static bool patch_set_affects_branch(PatchSet *, const char *);
131 static void do_cvs_diff(PatchSet *);
132 static PatchSet * create_patch_set();
133 static PatchSetRange * create_patch_set_range();
134 static void parse_sym(CvsFile *, char *);
135 static void resolve_global_symbols();
136 static bool revision_affects_branch(CvsFileRevision *, const char *);
137 static bool is_vendor_branch(const char *);
138 static void set_psm_initial(PatchSetMember * psm);
139 static int check_rev_funk(PatchSet *, CvsFileRevision *);
140 static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
141 static bool before_tag(CvsFileRevision * rev, const char * tag);
142 static void handle_collisions();
143 static Branch * create_branch(const char * name) ;
144 static void find_branch_points(PatchSet * ps);
145
146 int main(int argc, char *argv[])
147 {
148     struct list_head * next;
149
150     debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPWARN;
151
152     INIT_LIST_HEAD(&show_patch_set_ranges);
153     INIT_LIST_HEAD(&authormap);
154
155     if (parse_rc() < 0)
156         exit(1);
157
158     if (parse_args(argc, argv) < 0)
159         exit(1);
160
161     file_hash = create_hash_table(1023);
162     global_symbols = create_hash_table(111);
163     branch_heads = create_hash_table(1023);
164     branches = create_hash_table(1023);
165     INIT_LIST_HEAD(&all_patch_sets);
166     INIT_LIST_HEAD(&collisions);
167
168     /* this parses some of the CVS/ files, and initializes
169      * the repository_path and other variables 
170      */
171     init_paths();
172
173     if (!test_log_file)
174         cvsclient_ctx = open_cvs_server(root_path, compress);
175
176     load_from_cvs();
177
178     //XXX
179     //handle_collisions();
180
181     list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
182
183     ps_counter = 0;
184     walk_all_patch_sets(assign_patchset_id);
185
186     handle_collisions();
187
188     resolve_global_symbols();
189
190     if (statistics)
191         print_statistics(ps_tree);
192
193     /* check that the '-r' symbols (if specified) were resolved */
194     if (restrict_tag_start && restrict_tag_ps_start == 0 && 
195         strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
196     {
197         debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
198         exit(1);
199     }
200
201     if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
202     {
203         debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
204         exit(1);
205     }
206
207     for (next=all_patch_sets.next; next!=&all_patch_sets; next=next->next) {
208         PatchSet * ps = list_entry(next, PatchSet, all_link);
209         PatchSet * nextps = next->next ? list_entry(next->next, PatchSet, all_link) : NULL;
210         if (ps->commitid == NULL
211             && (next->next == NULL || nextps == NULL || nextps->commitid == NULL))
212         {
213             if (fast_export)
214                 debug(DEBUG_APPERROR,
215                       "commitid reliable only after commit :%d%s",
216                       ps->mark);
217             else
218                 debug(DEBUG_APPERROR,
219                       "commitid reliable only after patch set %d%s",
220                       ps->psid);
221         }
222     }
223
224     walk_all_patch_sets(check_print_patch_set);
225
226     if (summary_first++)
227         walk_all_patch_sets(check_print_patch_set);
228
229     if (cvsclient_ctx)
230         close_cvs_server(cvsclient_ctx);
231
232     if (fast_export) {
233         fputs("done\n", stdout);
234         if (dubious_branches > 1)
235             debug(DEBUG_APPWARN, "multiple vendor or anonymous branches; head content may be incorrect.");
236         if (revfp)
237             fclose(revfp);
238     }
239
240     exit(0);
241 }
242
243 #ifdef HEIKO
244 void detect_and_repair_time_skew(const char *last_date, char *date, int n,
245                                  PatchSetMember *psm)
246 {
247
248     time_t smaller;
249     time_t bigger;
250     char *rev_end;
251
252     /* if last_date does not exist do nothing */
253     if (last_date[0] == '\0')
254         return;
255
256     /* TODO: repairing of branch times, skipping them for the moment */
257     /* check whether rev is of the form /1.[0-9]+/ */
258     if (psm->post_rev->rev[0] != '1' || psm->post_rev->rev[1] != '.')
259         return;
260     strtol(psm->post_rev->rev+2, &rev_end, 10);
261     if (*rev_end != '\0')
262         return;
263
264     /* important: because rlog is showing revisions backwards last_date should
265      * always be bigger than date */
266     convert_date(&bigger, last_date);
267     convert_date(&smaller, date);
268
269     if (difftime(bigger, smaller) <= 0) {
270         struct tm *ts;
271         debug(DEBUG_APPWARN,"broken revision date: %s -> %s file: %s, repairing.\n",
272               date, last_date, psm->file->filename);
273         if (!(bigger > 0)) {
274             debug(DEBUG_APPERROR, "timestamp underflow, exiting ... ");
275             exit(1);
276         }
277         smaller = bigger - 1;
278         ts = gmtime(&smaller);
279         strftime(date, n, "%Y-%m-%d %H:%M:%S", ts);
280     }
281 }
282 #endif
283
284 static void load_from_cvs()
285 {
286     FILE * cvsfp = NULL;
287     char buff[BUFSIZ];
288     int state = NEED_RCS_FILE;
289     CvsFile * file = NULL;
290     PatchSetMember * psm = NULL;
291     char datebuff[26];
292 #ifdef HEIKO
293     char last_datebuff[20];
294 #endif
295     char authbuff[AUTH_STR_MAX];
296     char cidbuff[CID_STR_MAX];
297     int logbufflen = LOG_STR_MAX + 1;
298     char * logbuff = malloc(logbufflen);
299     int loglen = 0;
300     bool have_log = false;
301     char date_str[64];
302
303     if (test_log_file)
304         cvsfp = fopen(test_log_file, "r");
305     else if (cvsclient_ctx)
306         cvsfp = cvs_rlog_open(cvsclient_ctx, repository_path, date_str);
307
308     if (!cvsfp)
309     {
310         debug(DEBUG_SYSERROR, "can't get CVS log data");
311         exit(1);
312     }
313
314 #ifdef HEIKO
315     /* initialize the last_datebuff with value indicating invalid date */
316     last_datebuff[0]='\0';
317 #endif
318     for (;;)
319     {
320         char * tst;
321         if (cvsclient_ctx)
322             tst = cvs_rlog_fgets(buff, BUFSIZ, cvsclient_ctx);
323         else
324             tst = fgets(buff, BUFSIZ, cvsfp);
325
326         if (!tst)
327             break;
328
329         debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
330
331         switch(state)
332         {
333         case NEED_RCS_FILE:
334             if (strncmp(buff, "RCS file", 8) == 0) {
335               if ((file = parse_rcs_file(buff)) != NULL)
336                 state = NEED_SYMS;
337               else
338                 state = NEED_WORKING_FILE;
339             }
340             break;
341         case NEED_WORKING_FILE:
342             if (strncmp(buff, "Working file", 12) == 0) {
343               if ((file = parse_working_file(buff)))
344                 state = NEED_SYMS;
345               else
346                 state = NEED_RCS_FILE;
347                 break;
348             } else {
349               // Working file come just after RCS file. So reset state if it was not found
350               state = NEED_RCS_FILE;
351             }
352             break;
353         case NEED_SYMS:
354             if (strncmp(buff, "symbolic names:", 15) == 0)
355                 state = NEED_EOS;
356             break;
357         case NEED_EOS:
358             if (!isspace(buff[0]))
359             {
360                 /* see cvsps_types.h for commentary on have_branches */
361                 file->have_branches = true;
362                 state = NEED_START_LOG;
363             }
364             else
365                 parse_sym(file, buff);
366             break;
367         case NEED_START_LOG:
368             if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
369                 state = NEED_REVISION;
370             break;
371         case NEED_REVISION:
372             if (strncmp(buff, "revision", 8) == 0)
373             {
374                 char new_rev[REV_STR_MAX];
375                 CvsFileRevision * rev;
376
377                 strcpy(new_rev, buff + 9);
378                 chop(new_rev);
379
380                 /* 
381                  * rev may already exist (think cvsps -u), in which
382                  * case parse_revision is a hash lookup
383                  */
384                 rev = parse_revision(file, new_rev);
385
386                 /* 
387                  * in the simple case, we are copying rev to psm->pre_rev
388                  * (psm refers to last patch set processed at this point)
389                  * since generally speaking the log is reverse chronological.
390                  * This breaks down slightly when branches are introduced 
391                  */
392
393                 assign_pre_revision(psm, rev);
394
395                 /*
396                  * if this is a new revision, it will have no post_psm associated.
397                  * otherwise we are (probably?) hitting the overlap in cvsps -u 
398                  */
399                 if (!rev->post_psm)
400                 {
401                     psm = rev->post_psm = create_patch_set_member();
402                     psm->post_rev = rev;
403                     psm->file = file;
404                     state = NEED_DATE_AUTHOR_STATE;
405                 }
406                 else
407                 {
408                     /* we hit this in cvsps -u mode, we are now up-to-date
409                      * w.r.t this particular file. skip all of the rest 
410                      * of the info (revs and logs) until we hit the next file
411                      */
412                     psm = NULL;
413                     state = NEED_EOM;
414                 }
415             }
416             break;
417         case NEED_DATE_AUTHOR_STATE:
418             if (strncmp(buff, "date:", 5) == 0)
419             {
420                 char * p;
421
422                 strncpy(datebuff, buff + 6, sizeof(datebuff));
423                 datebuff[sizeof(datebuff)-1] = 0;
424
425                 strcpy(authbuff, "unknown");
426                 p = strstr(buff, "author: ");
427                 if (p)
428                 {
429                     char * op;
430                     p += 8;
431                     op = strchr(p, ';');
432                     if (op)
433                     {
434                         strzncpy(authbuff, p, op - p + 1);
435                     }
436                 }
437                 
438                 /* read the 'state' tag to see if this is a dead revision */
439                 p = strstr(buff, "state: ");
440                 if (p)
441                 {
442                     char * op;
443                     p += 7;
444                     op = strchr(p, ';');
445                     if (op)
446                         if (strncmp(p, "dead", MIN(4, op - p)) == 0)
447                             psm->post_rev->dead = true;
448                 }
449
450                 cidbuff[0] = 0;
451                 p = strstr(buff, "commitid: ");
452                 if (p)
453                 {
454                     char * op;
455                     p += 10;
456                     op = strchr(p, ';');
457                     if (op)
458                     {
459                         strzncpy(cidbuff, p, op - p + 1);
460                     }
461                 }
462
463                 state = NEED_EOM;
464             }
465             break;
466         case NEED_EOM:
467             if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
468             {
469                 if (psm)
470                 {
471                     PatchSet *ps;
472 #ifdef HEIKO
473                     detect_and_repair_time_skew(last_datebuff, 
474                                                 datebuff, sizeof(datebuff), 
475                                                 psm);
476 #endif
477                     ps = get_patch_set(datebuff,
478                                        logbuff,
479                                        authbuff, 
480                                        psm->post_rev->branch,
481                                        cidbuff,
482                                        psm);
483                     patch_set_add_member(ps, psm);
484 #ifdef HEIKO
485                     /* remember last revision */
486                     strncpy(last_datebuff, datebuff, 20);
487                     /* just to be sure */
488                     last_datebuff[19] = '\0';
489 #endif
490                 }
491
492                 logbuff[0] = 0;
493                 loglen = 0;
494                 have_log = false;
495                 state = NEED_REVISION;
496             }
497             else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
498             {
499                 if (psm)
500                 {
501                     PatchSet *ps;
502 #ifdef HEIKO
503                     detect_and_repair_time_skew(last_datebuff, 
504                                                 datebuff, sizeof(datebuff),
505                                                 psm);
506 #endif
507                     ps = get_patch_set(datebuff, 
508                                        logbuff, 
509                                        authbuff, 
510                                        psm->post_rev->branch,
511                                        cidbuff,
512                                        psm);
513                     patch_set_add_member(ps, psm);
514
515 #ifdef HEIKO
516                     /* just finished the last revision of this file,
517                      * set last_datebuff to invalid */
518                     last_datebuff[0]='\0';
519 #endif
520
521                     assign_pre_revision(psm, NULL);
522                 }
523
524                 logbuff[0] = 0;
525                 loglen = 0;
526                 have_log = false;
527                 psm = NULL;
528                 file = NULL;
529                 state = NEED_RCS_FILE;
530             }
531             else
532             {
533                 /* other "blahblah: information;" messages can 
534                  * follow the stuff we pay attention to
535                  */
536                 if (have_log || !is_revision_metadata(buff))
537                 {
538                     /* If the log buffer is full, try to reallocate more. */
539                     if (loglen < logbufflen)
540                     {
541                         int len = strlen(buff);
542                         
543                         if (len >= logbufflen - loglen)
544                         {
545                             debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename);
546                             logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX);
547                             char * newlogbuff = realloc(logbuff, logbufflen);
548                             if (newlogbuff == NULL)
549                             {
550                                 debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen);
551                                 exit(1);
552                             }
553                             logbuff = newlogbuff;
554                         }
555
556                         debug(DEBUG_STATUS, "appending %s to log", buff);
557                         memcpy(logbuff + loglen, buff, len);
558                         loglen += len;
559                         logbuff[loglen] = 0;
560                         have_log = true;
561                     }
562                 }
563                 else 
564                 {
565                     debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
566                 }
567             }
568
569             break;
570         }
571     }
572
573     if (state == NEED_SYMS)
574     {
575         debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
576         exit(1);
577     }
578
579     if (state != NEED_RCS_FILE)
580     {
581         debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d)  Use -v to debug", state);
582         exit(1);
583     }
584     
585     if (test_log_file)
586     {
587         fclose(cvsfp);
588     }
589     else if (cvsclient_ctx)
590     {
591         cvs_rlog_close(cvsclient_ctx);
592     }
593 }
594
595 static int usage(const char * str1, const char * str2)
596 {
597     if (str1)
598         debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
599
600     debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]]  ");
601     debug(DEBUG_APPERROR, "             [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
602     debug(DEBUG_APPERROR, "             [-b <branch>]  [-l <regex>] [-n] [-r <tag> [-r <tag>]] ");
603     debug(DEBUG_APPERROR, "             [-p <directory>] [-A 'authormap'] [-v] [-t] [--summary-first]");
604     debug(DEBUG_APPERROR, "             [--test-log <captured cvs log file>]");
605     debug(DEBUG_APPERROR, "             [--diff-opts <option string>]");
606     debug(DEBUG_APPERROR, "             [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
607     debug(DEBUG_APPERROR, "             [-T] [-V] [<repository>]");
608     debug(DEBUG_APPERROR, "");
609     debug(DEBUG_APPERROR, "Where:");
610     debug(DEBUG_APPERROR, "  -h display this informative message");
611     debug(DEBUG_APPERROR, "  -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
612     debug(DEBUG_APPERROR, "  -g generate diffs of the selected patch sets");
613     debug(DEBUG_APPERROR, "  -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
614     debug(DEBUG_APPERROR, "  -a <author> restrict output to patch sets created by author");
615     debug(DEBUG_APPERROR, "  -f <file> restrict output to patch sets involving file");
616     debug(DEBUG_APPERROR, "  -d <date1> -d <date2> if just one date specified, show");
617     debug(DEBUG_APPERROR, "     revisions newer than date1.  If two dates specified,");
618     debug(DEBUG_APPERROR, "     show revisions between two dates.");
619     debug(DEBUG_APPERROR, "  -b <branch> restrict output to patch sets affecting history of branch");
620     debug(DEBUG_APPERROR, "  -l <regex> restrict output to patch sets matching <regex> in log message");
621     debug(DEBUG_APPERROR, "  -n negate filter sense, print all patchsetss *not* matching.");
622     debug(DEBUG_APPERROR, "  -r <tag1> -r <tag2> if just one tag specified, show");
623     debug(DEBUG_APPERROR, "     revisions since tag1. If two tags specified, show");
624     debug(DEBUG_APPERROR, "     revisions between the two tags.");
625     debug(DEBUG_APPERROR, "  -p <directory> output patch sets to individual files in <directory>");
626     debug(DEBUG_APPERROR, "  -v show very verbose parsing messages");
627     debug(DEBUG_APPERROR, "  -t show some brief memory usage statistics");
628     debug(DEBUG_APPERROR, "  --summary-first when multiple patch sets are shown, put all summaries first");
629     debug(DEBUG_APPERROR, "  --test-log <captured cvs log> supply a captured cvs log for testing");
630     debug(DEBUG_APPERROR, "  --diff-opts <option string> supply special set of options to diff");
631     debug(DEBUG_APPERROR, "  --debuglvl <bitmask> enable various debug channels.");
632     debug(DEBUG_APPERROR, "  -Z <compression> A value 1-9 which specifies amount of compression");
633     debug(DEBUG_APPERROR, "  --root <cvsroot> specify cvsroot.  overrides env. and working directory");
634     debug(DEBUG_APPERROR, "  -T <date> set base date for regression testing");
635     debug(DEBUG_APPERROR, "  --fast-export emit a git-style fast-import stream");
636     debug(DEBUG_APPERROR, "  -V emit version and exit");
637     debug(DEBUG_APPERROR, "  <repository> apply cvsps to repository. Overrides working directory");
638     debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
639
640     return -1;
641 }
642
643 static int parse_args(int argc, char *argv[])
644 {
645     int i = 1;
646     while (i < argc)
647     {
648         if (strcmp(argv[i], "-z") == 0)
649         {
650             if (++i >= argc)
651                 return usage("argument to -z missing", "");
652
653             timestamp_fuzz_factor = atoi(argv[i++]);
654             continue;
655         }
656         
657         if (strcmp(argv[i], "-g") == 0)
658         {
659             do_diff = true;
660             i++;
661             continue;
662         }
663         
664         /* leave this in place so git-cvsimport will cause graceful death */
665         if (strcmp(argv[i], "-u") == 0)
666         {
667             fprintf(stderr, "cvsps: -u is no longer supported.\n");
668             fprintf(stderr, "cvsps: your calling program needs to be upgraded to work with cvsps 3.x.\n");
669             exit(1);
670         }
671
672         if (strcmp(argv[i], "-s") == 0)
673         {
674             PatchSetRange * range;
675             char * min_str, * max_str;
676
677             if (++i >= argc)
678                 return usage("argument to -s missing", "");
679
680             min_str = strtok(argv[i++], ",");
681             do
682             {
683                 range = create_patch_set_range();
684
685                 max_str = strrchr(min_str, '-');
686                 if (max_str)
687                     *max_str++ = '\0';
688                 else
689                     max_str = min_str;
690
691                 range->min_counter = atoi(min_str);
692
693                 if (*max_str)
694                     range->max_counter = atoi(max_str);
695                 else
696                     range->max_counter = INT_MAX;
697
698                 list_add(&range->link, show_patch_set_ranges.prev);
699             }
700             while ((min_str = strtok(NULL, ",")));
701
702             continue;
703         }
704         
705         if (strcmp(argv[i], "-a") == 0)
706         {
707             if (++i >= argc)
708                 return usage("argument to -a missing", "");
709
710             restrict_author = argv[i++];
711             continue;
712         }
713
714         if (strcmp(argv[i], "-A") == 0)
715         {
716             FILE *fp;
717             char authorline[BUFSIZ];
718             if (++i >= argc)
719                 return usage("argument to -A missing", "");
720
721             fp = fopen(argv[i++], "r");
722             if (fp == NULL) {
723                 fprintf(stderr, "cvsps: couldn't open specified author map.\n");
724                 exit(1);
725             }
726             while (fgets(authorline, sizeof(authorline), fp) != NULL)
727             {
728                 char *shortname, *longname, *timezone, *eq, *cp;
729                 MapEntry *mapentry;
730
731                 if ((eq = strchr(authorline, '=')) == NULL)
732                     continue;
733                 shortname = authorline;
734                 while (isspace(*shortname))
735                     ++shortname;
736                 if (*shortname == '#')
737                     continue;
738                 for (cp = eq; cp >= shortname; --cp)
739                     if (*cp == '=')
740                         continue;
741                     else if (isspace(*cp))
742                         *cp = '\0';
743                 for (longname = eq + 1; isspace(*longname); ++longname)
744                     continue;
745                 timezone = strchr(longname, '>');
746                 if (timezone == NULL)
747                     continue;
748                 for (++timezone; isspace(*timezone); timezone++)
749                     *timezone = '\0';
750                 for (cp = timezone + strlen(timezone) - 1; isspace(*cp); --cp)
751                     *cp = '\0';
752
753                 mapentry = (MapEntry*)malloc(sizeof(*mapentry));
754                 mapentry->shortname = strdup(shortname);
755                 mapentry->longname = strdup(longname);
756                 mapentry->timezone = strdup(timezone);
757                 list_add(&mapentry->link, &authormap);
758             }
759
760             fclose(fp);
761             
762             continue;
763         }
764
765         if (strcmp(argv[i], "-R") == 0)
766         {
767             if (++i >= argc)
768                 return usage("argument to -R missing", "");
769
770             revfp = fopen(argv[i++], "w");
771             continue;
772         }
773
774         if (strcmp(argv[i], "-l") == 0)
775         {
776             int err;
777
778             if (++i >= argc)
779                 return usage("argument to -l missing", "");
780
781             if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
782             {
783                 char errbuf[256];
784                 regerror(err, &restrict_log, errbuf, 256);
785                 return usage("bad regex to -l", errbuf);
786             }
787
788             have_restrict_log = true;
789
790             continue;
791         }
792
793         if (strcmp(argv[i], "-f") == 0)
794         {
795             int err;
796
797             if (++i >= argc)
798                 return usage("argument to -f missing", "");
799
800             if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
801             {
802                 char errbuf[256];
803                 regerror(err, &restrict_file, errbuf, 256);
804                 return usage("bad regex to -f", errbuf);
805             }
806
807             have_restrict_file = true;
808
809             continue;
810         }
811         
812         if (strcmp(argv[i], "-n") == 0)
813         {
814             selection_sense = false;
815             i++;
816             continue;
817         }
818         
819         if (strcmp(argv[i], "-d") == 0)
820         {
821             time_t *pt;
822
823             if (++i >= argc)
824                 return usage("argument to -d missing", "");
825
826             pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
827             convert_date(pt, argv[i++]);
828             continue;
829         }
830
831         if (strcmp(argv[i], "-T") == 0)
832         {
833             if (++i >= argc)
834                 return usage("argument to -T missing", "");
835
836             convert_date(&regression_time, argv[i++]);
837             continue;
838         }
839
840         if (strcmp(argv[i], "-r") == 0)
841         {
842             if (++i >= argc)
843                 return usage("argument to -r missing", "");
844
845             if (restrict_tag_start)
846                 restrict_tag_end = argv[i];
847             else
848                 restrict_tag_start = argv[i];
849
850             i++;
851             continue;
852         }
853
854         if (strcmp(argv[i], "-b") == 0)
855         {
856             if (++i >= argc)
857                 return usage("argument to -b missing", "");
858
859             restrict_branch = argv[i++];
860             /* Warn if the user tries to use TRUNK. Should eventually
861              * go away as TRUNK may be a valid branch within CVS
862              */
863             if (strcmp(restrict_branch, "TRUNK") == 0)
864                 debug(DEBUG_APPWARN, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
865             continue;
866         }
867
868         if (strcmp(argv[i], "-p") == 0)
869         {
870             if (++i >= argc)
871                 return usage("argument to -p missing", "");
872             
873             patch_set_dir = argv[i++];
874             continue;
875         }
876
877         if (strcmp(argv[i], "-v") == 0)
878         {
879             debuglvl = ~0;
880             i++;
881             continue;
882         }
883         
884         if (strcmp(argv[i], "-t") == 0)
885         {
886             statistics = true;
887             i++;
888             continue;
889         }
890
891         if (strcmp(argv[i], "--summary-first") == 0)
892         {
893             summary_first = 1;
894             i++;
895             continue;
896         }
897
898         if (strcmp(argv[i], "-h") == 0)
899             return usage(NULL, NULL);
900
901         if (strcmp(argv[i], "--test-log") == 0)
902         {
903             if (++i >= argc)
904                 return usage("argument to --test-log missing", "");
905
906             test_log_file = argv[i++];
907             continue;
908         }
909
910         if (strcmp(argv[i], "--diff-opts") == 0)
911         {
912             if (++i >= argc)
913                 return usage("argument to --diff-opts missing", "");
914
915             /* allow diff_opts to be turned off by making empty string
916              * into NULL
917              */
918             if (!strlen(argv[i]))
919                 diff_opts = NULL;
920             else
921                 diff_opts = argv[i];
922             i++;
923             continue;
924         }
925
926         if (strcmp(argv[i], "--debuglvl") == 0)
927         {
928             if (++i >= argc)
929                 return usage("argument to --debuglvl missing", "");
930
931             debuglvl = atoi(argv[i++]);
932             continue;
933         }
934
935         if (strcmp(argv[i], "-Z") == 0)
936         {
937             if (++i >= argc)
938                 return usage("argument to -Z", "");
939
940             compress = atoi(argv[i++]);
941
942             if (compress < 0 || compress > 9)
943                 return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
944
945             if (compress == 0)
946                 compress_arg[0] = 0;
947             else
948                 snprintf(compress_arg, 8, "-z%d", compress);
949             continue;
950         }
951         
952         if (strcmp(argv[i], "--root") == 0)
953         {
954             if (++i >= argc)
955                 return usage("argument to --root missing", "");
956
957             strcpy(root_path, argv[i++]);
958             continue;
959         }
960
961         if (strcmp(argv[i], "--fast-export") == 0)
962         {
963             fast_export = true;
964             i++;
965             continue;
966         }
967
968         if (strcmp(argv[i], "-V") == 0)
969         {
970             printf("cvsps: version " VERSION "\n");
971             exit(0);
972         }
973
974         if (argv[i][0] == '-')
975             return usage("invalid argument", argv[i]);
976         
977         strcpy(repository_path, argv[i++]);
978     }
979
980     if (fast_export && test_log_file)
981     {
982         fprintf(stderr, "cvsps: --fast-export and --test-log are not compatible.\n");
983         exit(1);
984     }
985
986     if (do_diff && test_log_file)
987     {
988         fprintf(stderr, "cvsps: -g and --test-log are not compatible.\n");
989         exit(1);
990     }
991
992     return 0;
993 }
994
995 static int parse_rc()
996 {
997     char rcfile[PATH_MAX];
998     FILE * fp;
999     /* coverity[tainted_data] */
1000     snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
1001     if ((fp = fopen(rcfile, "r")))
1002     {
1003         char buff[BUFSIZ];
1004         while (fgets(buff, BUFSIZ, fp))
1005         {
1006             char * argv[3], *p;
1007             int argc = 2;
1008
1009             chop(buff);
1010
1011             argv[0] = "garbage";
1012
1013             p = strchr(buff, ' ');
1014             if (p)
1015             {
1016                 *p++ = '\0';
1017                 argv[2] = xstrdup(p);
1018                 argc = 3;
1019             }
1020
1021             argv[1] = xstrdup(buff);
1022
1023             if (parse_args(argc, argv) < 0)
1024                 return -1;
1025         }
1026         fclose(fp);
1027     }
1028
1029     return 0;
1030 }
1031
1032 static void init_paths()
1033 {
1034     FILE * fp;
1035     char * p;
1036     int len;
1037
1038     /* Determine the CVSROOT. Precedence:
1039      * 1) command line
1040      * 2) checkout directory
1041      * 3) top level of repository
1042      * 4) module directory just beneath a repository root.
1043      * 5) environment variable CVSROOT
1044      */
1045     if (!root_path[0])
1046     {
1047         /* Are we in a working directory? */
1048         if ((fp = fopen("CVS/Root", "r")) != NULL)
1049         {
1050             if (fgets(root_path, PATH_MAX, fp) == NULL)
1051             {
1052                 debug(DEBUG_APPERROR, "Error reading CVSROOT");
1053                 exit(1);
1054             }
1055             
1056             fclose(fp);
1057             
1058             /* chop the lf and optional trailing '/' */
1059             len = strlen(root_path) - 1;
1060             root_path[len] = 0;
1061             if (root_path[len - 1] == '/')
1062                 root_path[--len] = 0;
1063         }
1064         else
1065         {
1066             struct stat st;
1067
1068             debug(DEBUG_STATUS, "Can't open CVS/Root");
1069
1070             /* 
1071              * We're not in a working directory; are we in a repository root?
1072              * If so, monkey up a local path to access it.
1073              */
1074             if (stat("CVSROOT", &st) == 0 && S_ISDIR(st.st_mode)) {
1075                 strcpy(root_path, ":local:");
1076                 if (getcwd(root_path + strlen(root_path),
1077                            sizeof(root_path) - strlen(root_path) - 1) == NULL)
1078                 {
1079                     debug(DEBUG_APPERROR, "cannot get working directory");
1080                     exit(1);
1081                 }
1082             }
1083             /*
1084              * We might be in a module directory just below a repository root.
1085              * The right thing to do in this case is also clear.
1086              */
1087             else if (stat("../CVSROOT", &st) == 0 && S_ISDIR(st.st_mode)) {
1088                 char *sl;
1089                 strcpy(root_path, ":local:");
1090                 if (getcwd(root_path + strlen(root_path),
1091                            sizeof(root_path) - strlen(root_path) - 1) == NULL)
1092                 {
1093                     debug(DEBUG_APPERROR, "cannot get working directory");
1094                     exit(1);
1095                 }
1096                 sl = strrchr(root_path, '/');
1097                 *sl++ = '\0';
1098                 if (repository_path[0])
1099                 {
1100                     memmove(repository_path + strlen(sl) + 1, 
1101                             repository_path,
1102                             strlen(repository_path) + 1); 
1103                     repository_path[strlen(sl)] = '/';
1104                 }
1105                 strcpy(repository_path, sl);
1106             }
1107             else 
1108             {
1109                 const char * e = getenv("CVSROOT");
1110
1111                 if (e)
1112                     strcpy(root_path, e);
1113                 else
1114                 {
1115                     debug(DEBUG_APPERROR, "cannot determine CVSROOT");
1116                     exit(1);
1117                 }
1118             }
1119         }
1120     }
1121
1122     /* Determine the repository path, precedence:
1123      * 1) command line
1124      * 2) working directory
1125      * Note that one of the root-directory cases above prepends to this path.
1126      */
1127       
1128     if (!repository_path[0])
1129     {
1130         if ((fp = fopen("CVS/Repository", "r")) == NULL)
1131         {
1132             debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1133             exit(1);
1134         }
1135         
1136         if (fgets(repository_path, PATH_MAX, fp) == NULL)
1137         {
1138             debug(DEBUG_APPERROR, "Error reading repository path");
1139             exit(1);
1140         }
1141         
1142         chop(repository_path);
1143         fclose(fp);
1144     }
1145
1146     /* get the path portion of the root */
1147     p = strrchr(root_path, ':');
1148
1149     if (!p)
1150         p = root_path;
1151     else 
1152         p++;
1153
1154     /* some CVS have the CVSROOT string as part of the repository
1155      * string (initial substring).  remove it.
1156      */
1157     len = strlen(p);
1158
1159     if (strncmp(p, repository_path, len) == 0)
1160     {
1161         int rlen = strlen(repository_path + len + 1);
1162         memmove(repository_path, repository_path + len + 1, rlen + 1);
1163     }
1164
1165     /* the 'strip_path' will be used whenever the CVS server gives us a
1166      * path to an 'rcs file'.  the strip_path portion of these paths is
1167      * stripped off, leaving us with the working file.
1168      *
1169      * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is
1170      * used (instead of log) it gives the 'real' RCS file path, which
1171      * can be different from the 'nominal' repository path because of
1172      * symlinks in the server and the like.  See also the
1173      * 'parse_rcs_file' routine
1174      *
1175      * When you've checked out the root, rather than a specific
1176      * module, repository_path is . but we should use only p without
1177      * anything added for path stripping.
1178      */
1179     if (!strcmp(repository_path,".")) {
1180         strip_path_len = snprintf(strip_path, PATH_MAX, "%s/", p);
1181     } else {
1182         strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1183     }
1184
1185
1186     if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1187     {
1188         debug(DEBUG_APPERROR, "strip_path overflow");
1189         exit(1);
1190     }
1191
1192     if (strip_path_len > 3 && !strcmp(strip_path + strip_path_len - 3, "/./"))
1193     {
1194         debug(DEBUG_STATUS, "pruning /./ off end of strip_path");
1195         strip_path_len -= 2;
1196         strip_path[strip_path_len] = '\0';
1197     }
1198
1199     debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1200 }
1201
1202 static CvsFile * parse_rcs_file(const char * buff)
1203 {
1204     char fn[PATH_MAX];
1205     size_t len = strlen(buff + 10);
1206     char * p;
1207
1208     /* once a single file has been parsed ok we set this */
1209     static bool path_ok;
1210
1211     /* chop the ",v" string and the "LF" */
1212     len -= 3;
1213     memcpy(fn, buff + 10, len);
1214     fn[len] = 0;
1215     if (strncmp(fn, strip_path, strip_path_len) != 0)
1216     {
1217         /* if the very first file fails the strip path,
1218          * then maybe we need to try for an alternate.
1219          * this will happen if symlinks are being used
1220          * on the server.  our best guess is to look
1221          * for the final occurance of the repository
1222          * path in the filename and use that.  it should work
1223          * except in the case where:
1224          * 1) the project has no files in the top-level directory
1225          * 2) the project has a directory with the same name as the project
1226          * 3) that directory sorts alphabetically before any other directory
1227          * in which case, you are scr**ed
1228          */
1229         if (!path_ok)
1230         {
1231             char * p = fn, *lastp = NULL;
1232
1233             while ((p = strstr(p, repository_path)))
1234                 lastp = p++;
1235
1236             if (lastp)
1237             {
1238                 size_t len = strlen(repository_path);
1239                 memcpy(strip_path, fn, lastp - fn + len + 1);
1240                 strip_path_len = lastp - fn + len + 1;
1241                 strip_path[strip_path_len] = 0;
1242                 debug(DEBUG_APPWARN, "NOTICE: used alternate strip path %s", strip_path);
1243                 goto ok;
1244             }
1245         }
1246
1247
1248         /* Windows CVS server may use two path separators: / for files
1249          * and \ for subdirectories. */
1250         if (strncmp(fn, strip_path, strip_path_len-1) == 0 &&
1251             (fn[strip_path_len-1] == '\\' ||
1252              fn[strip_path_len-1] == '/')) {
1253                 goto ok;
1254         }
1255
1256         /* FIXME: a subdirectory may have a different Repository path
1257          * than its parent.  we'll fail the above test since strip_path
1258          * is global for the entire checked out tree (recursively).
1259          *
1260          * For now just ignore such files
1261          */
1262         debug(DEBUG_APPWARN, "WARNING: file %s doesn't match strip_path %s. ignoring",
1263               fn, strip_path);
1264         return NULL;
1265     }
1266
1267  ok:
1268     if(len <= strip_path_len)
1269     {
1270         debug(DEBUG_APPWARN, "WARNING: file %s doesn't match strip_path %s. ignoring",
1271               fn, strip_path);
1272         return NULL;
1273     }
1274     /* remove from beginning the 'strip_path' string */
1275     len -= strip_path_len;
1276     path_ok = true;
1277
1278     memmove(fn, fn + strip_path_len, len);
1279     fn[len] = 0;
1280
1281     /* check if file is in the 'Attic/' and remove it */
1282     if ((p = strrchr(fn, '/')) &&
1283         p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1284     {
1285         memmove(p - 5, p + 1, len - (p - fn + 1));
1286         len -= 6;
1287         fn[len] = 0;
1288     }
1289
1290     debug(DEBUG_STATUS, "stripped filename %s", fn);
1291
1292     return build_file_by_name(fn);
1293 }
1294
1295 static CvsFile * parse_working_file(const char * buff)
1296 {
1297     char fn[PATH_MAX];
1298     int len = strlen(buff + 14);
1299
1300     /* chop the "LF" */
1301     len -= 1;
1302     memcpy(fn, buff + 14, len);
1303     fn[len] = 0;
1304
1305     debug(DEBUG_STATUS, "working filename %s", fn);
1306
1307     return build_file_by_name(fn);
1308 }
1309
1310 static CvsFile * build_file_by_name(const char * fn)
1311 {
1312     CvsFile * retval;
1313
1314     retval = (CvsFile*)get_hash_object(file_hash, fn);
1315
1316     if (!retval)
1317     {
1318         if ((retval = create_cvsfile()))
1319         {
1320             retval->filename = xstrdup(fn);
1321             put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1322         }
1323         else
1324         {
1325             debug(DEBUG_SYSERROR, "malloc failed");
1326             exit(1);
1327         }
1328
1329         debug(DEBUG_STATUS, "new file: %s", retval->filename);
1330     }
1331     else
1332     {
1333         debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1334     }
1335
1336     return retval;
1337 }
1338
1339 PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, const char *commitid, PatchSetMember * psm)
1340 {
1341     PatchSet * retval = NULL, **find = NULL;
1342
1343     if (!(retval = create_patch_set()))
1344     {
1345         debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1346         return NULL;
1347     }
1348
1349     convert_date(&retval->date, dte);
1350     retval->author = get_string(author);
1351     retval->commitid = get_string(commitid);
1352     retval->descr = xstrdup(log);
1353     retval->branch = get_string(branch);
1354     
1355     /* we are looking for a patchset suitable for holding this member.
1356      * this means two things:
1357      * 1) a patchset already containing an entry for the file is no good
1358      * 2) for two patchsets with same exact date/time, if they reference 
1359      *    the same file, we can properly order them.  this primarily solves
1360      *    the 'cvs import' problem and may not have general usefulness
1361      *    because it would only work if the first member we consider is
1362      *    present in the existing ps.
1363      */
1364     if (psm)
1365         list_add(&psm->link, retval->members.prev);
1366
1367     find = (PatchSet**)tsearch(retval, &ps_tree, compare_patch_sets);
1368
1369     if (psm)
1370         list_del(&psm->link);
1371
1372     if (*find != retval)
1373     {
1374         debug(DEBUG_STATUS, "found existing patch set");
1375
1376         free(retval->descr);
1377
1378         /* keep the minimum date of any member as the 'actual' date */
1379         if (retval->date < (*find)->date)
1380             (*find)->date = retval->date;
1381
1382         /* expand the min_date/max_date window to help finding other members .
1383          * open the window by an extra margin determined by the fuzz factor 
1384          */
1385         if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1386         {
1387             (*find)->min_date = retval->date - timestamp_fuzz_factor;
1388             //debug(DEBUG_APPWARN, "WARNING: non-increasing dates in encountered patchset members");
1389         }
1390         else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1391             (*find)->max_date = retval->date + timestamp_fuzz_factor;
1392
1393         free(retval);
1394         retval = *find;
1395     }
1396     else
1397     {
1398         debug(DEBUG_STATUS, "new patch set!");
1399         debug(DEBUG_STATUS, "%s %s %s %s", retval->author, retval->descr, retval->commitid, dte);
1400
1401         retval->min_date = retval->date - timestamp_fuzz_factor;
1402         retval->max_date = retval->date + timestamp_fuzz_factor;
1403
1404         list_add(&retval->all_link, &all_patch_sets);
1405     }
1406
1407
1408     return retval;
1409 }
1410
1411 static bool get_branch_ext(char * buff, const char * rev, int * leaf)
1412 {
1413     char * p;
1414     int len = strlen(rev);
1415
1416     /* allow get_branch(buff, buff) without destroying contents */
1417     memmove(buff, rev, len);
1418     buff[len] = 0;
1419
1420     p = strrchr(buff, '.');
1421     if (!p)
1422         return false;
1423     *p++ = 0;
1424
1425     if (leaf)
1426         *leaf = atoi(p);
1427
1428     return true;
1429 }
1430
1431 static int get_branch(char * buff, const char * rev)
1432 /* return true if rev is a non-trunk branch */
1433 {
1434     return get_branch_ext(buff, rev, NULL);
1435 }
1436
1437 /* 
1438  * the goal if this function is to determine what revision to assign to
1439  * the psm->pre_rev field.  usually, the log file is strictly 
1440  * reverse chronological, so rev is direct ancestor to psm, 
1441  * 
1442  * This all breaks down at branch points however
1443  */
1444
1445 static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1446 {
1447     char pre[REV_STR_MAX], post[REV_STR_MAX];
1448
1449     if (!psm)
1450         return;
1451     
1452     if (!rev)
1453     {
1454         /* if psm was last rev. for file, it's either an 
1455          * INITIAL, or first rev of a branch.  to test if it's 
1456          * the first rev of a branch, do get_branch twice - 
1457          * this should be the bp.
1458          */
1459         if (get_branch(post, psm->post_rev->rev) && 
1460             get_branch(pre, post))
1461         {
1462             psm->pre_rev = file_get_revision(psm->file, pre);
1463             list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1464         }
1465         else
1466         {
1467             set_psm_initial(psm);
1468         }
1469         return;
1470     }
1471
1472     /* 
1473      * is this canditate for 'pre' on the same branch as our 'post'? 
1474      * this is the normal case
1475      */
1476     if (!get_branch(pre, rev->rev))
1477     {
1478         debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1479         return;
1480     }
1481
1482     if (!get_branch(post, psm->post_rev->rev))
1483     {
1484         debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1485         return;
1486     }
1487
1488     if (strcmp(pre, post) == 0)
1489     {
1490         psm->pre_rev = rev;
1491         rev->pre_psm = psm;
1492         return;
1493     }
1494     
1495     /* branches don't match. new_psm must be head of branch,
1496      * so psm is oldest rev. on branch. or oldest
1497      * revision overall.  if former, derive predecessor.  
1498      * use get_branch to chop another rev. off of string.
1499      *
1500      * FIXME:
1501      * There's also a weird case.  it's possible to just re-number
1502      * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1503      * It's not widely used.  In those cases of discontinuity,
1504      * we end up stamping the predecessor as 'INITIAL' incorrectly
1505      *
1506      */
1507     if (!get_branch(pre, post))
1508     {
1509         set_psm_initial(psm);
1510         return;
1511     }
1512     
1513     psm->pre_rev = file_get_revision(psm->file, pre);
1514     list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1515 }
1516
1517 static bool visible(PatchSet * ps)
1518 /* should we display this patch set? */
1519 {
1520     /* the funk_factor overrides the restrict_tag_start and end */
1521     if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1522         goto ok;
1523
1524     if (ps->funk_factor == FNK_HIDE_ALL)
1525         return false;
1526
1527     if (ps->psid <= restrict_tag_ps_start)
1528     {
1529         if (ps->psid == restrict_tag_ps_start)
1530             debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1531         
1532         return false;
1533     }
1534     
1535     if (ps->psid > restrict_tag_ps_end)
1536         return false;
1537
1538  ok:
1539     if (restrict_date_start > 0 &&
1540         (ps->date < restrict_date_start ||
1541          (restrict_date_end > 0 && ps->date > restrict_date_end)))
1542         return false;
1543
1544     if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1545         return false;
1546
1547     if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1548         return false;
1549
1550     if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1551         return false;
1552
1553     if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1554         return false;
1555     
1556     if (!list_empty(&show_patch_set_ranges))
1557     {
1558         struct list_head * next = show_patch_set_ranges.next;
1559         
1560         while (next != &show_patch_set_ranges)
1561         {
1562             PatchSetRange *range = list_entry(next, PatchSetRange, link);
1563             if (range->min_counter <= ps->psid &&
1564                 ps->psid <= range->max_counter)
1565             {
1566                 break;
1567             }
1568             next = next->next;
1569         }
1570         
1571         if (next == &show_patch_set_ranges)
1572             return false;
1573     }
1574
1575     return true;
1576 }
1577
1578 static void check_print_patch_set(PatchSet * ps)
1579 {
1580     if (ps->psid < 0)
1581         return;
1582
1583     if (visible(ps) != selection_sense)
1584         return;
1585
1586     if (patch_set_dir)
1587     {
1588         char path[PATH_MAX];
1589
1590         snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1591
1592         fflush(stdout);
1593         close(1);
1594         if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1595         {
1596             debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1597             exit(1);
1598         }
1599
1600         fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1601     }
1602
1603     /*
1604      * If the summary_first option is in effect, there will be 
1605      * two passes through the tree.  the first with summary_first == 1
1606      * the second with summary_first == 2.  if the option is not
1607      * in effect, there will be one pass with summary_first == 0
1608      *
1609      * When the -s option is in effect, the show_patch_set_ranges
1610      * list will be non-empty.
1611      *
1612      * In fast-export mode, the do_diff and summary_first options 
1613      * are ignored.
1614      */
1615     if (fast_export)
1616         print_fast_export(ps);
1617     else if (summary_first <= 1)
1618         print_patch_set(ps);
1619     if (do_diff && summary_first != 1)
1620         do_cvs_diff(ps);
1621
1622     fflush(stdout);
1623 }
1624
1625 static void print_patch_set(PatchSet * ps)
1626 {
1627     struct tm *tm;
1628     struct list_head * next, * tagl;
1629     const char * funk = "";
1630
1631     tm = localtime(&ps->date);
1632     
1633     funk = fnk_descr[ps->funk_factor];
1634
1635     /* this '---...' is different from the 28 hyphens that separate cvs log output */
1636     printf("---------------------\n");
1637     printf("PatchSet %d %s\n", ps->psid, funk);
1638     printf("Date: %d/%02d/%02d %02d:%02d:%02d\n", 
1639            1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday, 
1640            tm->tm_hour, tm->tm_min, tm->tm_sec);
1641     printf("Author: %s\n", ps->author);
1642     printf("Branch: %s\n", ps->branch);
1643     if (ps->ancestor_branch)
1644         printf("Ancestor branch: %s\n", ps->ancestor_branch);
1645     printf("Tags:");
1646     for (tagl = ps->tags.next; tagl != &ps->tags; tagl = tagl->next)
1647     {
1648         TagName* tag = list_entry (tagl, TagName, link);
1649
1650         printf(" %s %s%s", tag->name, tag_flag_descr[tag->flags],
1651                (tagl->next == &ps->tags) ? "" : ",");
1652     }
1653     printf("\n");
1654     printf("Branches: ");
1655     for (next = ps->branches.next; next != &ps->branches; next = next->next) {
1656         Branch * branch = list_entry(next, Branch, link);
1657         if (next != ps->branches.next)
1658             printf(",");
1659         printf("%s", branch->name);
1660     }
1661     printf("\n");
1662     printf("Log:\n%s\n", ps->descr);
1663     printf("Members: \n");
1664
1665     next = ps->members.next;
1666     while (next != &ps->members)
1667     {
1668         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1669         if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1670             funk = "(BEFORE START TAG)";
1671         else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1672             funk = "(AFTER END TAG)";
1673         else
1674             funk = "";
1675
1676         printf("\t%s:%s->%s%s %s\n", 
1677                psm->file->filename, 
1678                psm->pre_rev ? psm->pre_rev->rev : "INITIAL", 
1679                psm->post_rev->rev, 
1680                psm->post_rev->dead ? "(DEAD)": "",
1681                funk);
1682
1683         next = next->next;
1684     }
1685
1686     printf("\n");
1687 }
1688
1689 #define SUFFIX(a, s)    (strcmp(a + strlen(a) - strlen(s), s) == 0) 
1690
1691 static char *fast_export_sanitize(char *name, char *sanitized, int sanlength)
1692 {
1693     char *sp, *tp;
1694
1695 #define BADCHARS        "~^:\\*?[]"
1696     memset(tp = sanitized, '\0', sanlength);
1697     for (sp = name; *sp; sp++) {
1698         if (!isgraph(*sp) || strchr(BADCHARS, *sp) == NULL) {
1699             *tp++ = *sp;
1700             if (SUFFIX(sanitized,"@{")||SUFFIX(sanitized,"..")) {
1701                 fprintf(stderr, 
1702                         "Tag or branch name %s is ill-formed.\n", 
1703                         name);
1704                 exit(1);
1705             }
1706         }
1707     }
1708     if (strlen(sanitized) == 0) {
1709         fprintf(stderr, 
1710                 "Tag or branch name %s was empty after sanitization.\n", 
1711                 name);
1712         exit(1);
1713     }
1714
1715     return sanitized;
1716 }
1717
1718 static void print_fast_export(PatchSet * ps)
1719 {
1720     struct tm *tm;
1721     struct list_head * next, * tagl, * mapl;
1722     static int mark = 0;
1723     char *tf = tmpnam(NULL);    /* ugly necessity */
1724     struct stat st;
1725     int basemark = mark;
1726     int c;
1727     int ancestor_mark = 0;
1728     char sanitized_branch[strlen(ps->branch)+1];
1729     char *match, *tz;
1730  
1731     struct branch_head {
1732         char *name;
1733         int mark;
1734         struct branch_head *prev;
1735     };
1736     static struct branch_head *heads = NULL;
1737     struct branch_head *tip = NULL;
1738
1739     for (tip = heads; tip; tip = tip->prev) 
1740         if (strcmp(tip->name, ps->branch) == 0) {
1741             ancestor_mark = tip->mark;
1742             break;
1743         }
1744     if (tip == NULL) {
1745         /* we're at a branch division */
1746         tip = malloc(sizeof(struct branch_head));
1747         tip->mark = 0;
1748         tip->name = ps->branch;
1749         tip->prev = heads;
1750         heads = tip;
1751
1752         /* look for the branch join */
1753         for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
1754             struct list_head * child_iter;
1755
1756             PatchSet * as = list_entry(next, PatchSet, all_link);
1757
1758             /* walk the branches looking for the join */
1759             for (child_iter = as->branches.next; child_iter != &as->branches; child_iter = child_iter->next) {
1760                 Branch * branch = list_entry(child_iter, Branch, link);
1761                 if (strcmp(ps->branch, branch->name) == 0) {
1762                     ancestor_mark = as->mark;
1763                     break;
1764                 }
1765             }
1766         }
1767     }
1768
1769     /* we need to be able to fake dates for regression testing */
1770     if (regression_time == 0)
1771         tm = localtime(&ps->date);
1772     else
1773     {
1774         time_t clock_tick = regression_time + ps->psid;
1775         tm = localtime(&clock_tick);
1776     }
1777
1778     next = ps->members.next;
1779     while (next != &ps->members)
1780     {
1781         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1782
1783         if (!psm->post_rev->dead) 
1784         {
1785             FILE *ofp = fopen(tf, "w");
1786             FILE *cfp;
1787
1788             if (ofp == NULL)
1789             {
1790                 fprintf(stderr, "CVS direct retrieval of %s failed.\n",
1791                         psm->file->filename);
1792                 exit(1);
1793             }
1794
1795             debug(DEBUG_APPMSG2, "retrieving %s for %s at :%d", 
1796                   psm->post_rev->rev,
1797                   psm->file->filename, 
1798                   mark+1);
1799             cvs_rupdate(cvsclient_ctx,
1800                         repository_path,
1801                         psm->file->filename,
1802                         psm->post_rev->rev, ofp);
1803
1804             /* coverity[toctou] */
1805             if (stat(tf, &st) != 0)
1806             {
1807                 fprintf(stderr, "stat(2) of %s:%s copy failed.\n",
1808                         psm->file->filename, psm->post_rev->rev);
1809                 exit(1);
1810             }
1811
1812             printf("blob\nmark :%d\ndata %zd\n", ++mark, st.st_size);
1813             if ((cfp = fopen(tf, "r")) == NULL)
1814             {
1815                 fprintf(stderr, "blobfile open of  %s:%s failed.\n",
1816                         psm->file->filename, psm->post_rev->rev);
1817                 exit(1);
1818             }
1819             while ((c = fgetc(cfp)) != EOF)
1820                 putchar(c);
1821             (void)fclose(cfp);
1822             putchar('\n');
1823
1824             if (revfp)
1825                 fprintf(revfp, "%s %s :%d\n",
1826                         psm->file->filename,
1827                         psm->post_rev->rev,
1828                         mark);
1829         }
1830
1831         next = next->next;
1832     }
1833
1834     match = NULL;
1835     tz = "+0000";
1836     for (mapl = authormap.next; mapl != &authormap; mapl = mapl->next)
1837     {
1838         MapEntry* mapentry = list_entry (mapl, MapEntry, link);
1839         if (strcmp(mapentry->shortname, ps->author) == 0)
1840         {
1841             match = mapentry->longname;
1842             if (mapentry->timezone[0])
1843                 tz = mapentry->timezone;
1844         }
1845     }
1846
1847     /* map HEAD branch to master, leave others unchanged */
1848     printf("commit refs/heads/%s\n", 
1849            strcmp("HEAD", ps->branch) ? fast_export_sanitize(ps->branch, sanitized_branch, sizeof(sanitized_branch)) : "master");
1850     printf("mark :%d\n", ++mark);
1851     if (match != NULL)
1852         printf("committer %s", match);
1853     else
1854         printf("committer %s <%s>", ps->author, ps->author);
1855     printf(" %zd %s\n", mktime(tm) - tm->tm_gmtoff, tz);
1856     printf("data %zd\n%s\n", strlen(ps->descr), ps->descr); 
1857     if (ancestor_mark)
1858         printf("from :%d\n", ancestor_mark);
1859     ps->mark = tip->mark = mark;
1860
1861     next = ps->members.next;
1862     while (next != &ps->members)
1863     {
1864         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1865
1866         /* 
1867          * .cvsignore files have a gloobing syntax that is upward-compatible
1868          * with git's, 
1869          */
1870         if (SUFFIX(psm->file->filename, ".cvsignore")) {
1871             char *end = psm->file->filename + strlen(psm->file->filename);
1872             end[-9] = 'g';
1873             end[-8] = 'i';
1874             end[-7] = 't';
1875         }
1876
1877         if (psm->post_rev->dead)
1878             printf("D 100644 %s\n", psm->file->filename);
1879         else if (st.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH))
1880             printf("M 100755 :%d %s\n", ++basemark, psm->file->filename);
1881         else
1882             printf("M 100644 :%d %s\n", ++basemark, psm->file->filename);
1883
1884         next = next->next;
1885     }
1886     printf("\n");
1887
1888     for (tagl = ps->tags.next; tagl != &ps->tags; tagl = tagl->next)
1889     {
1890         TagName* tag = list_entry (tagl, TagName, link);
1891         char sanitized_tag[strlen(tag->name) + 1];
1892
1893         /* might be this patchset has tags pointing to it */
1894         printf("reset refs/tags/%s\nfrom :%d\n\n", 
1895                fast_export_sanitize(tag->name, sanitized_tag, sizeof(sanitized_tag)), ps->mark);
1896     }
1897
1898     unlink(tf);
1899 }
1900
1901 /* walk all the patchsets to assign monotonic psid, 
1902  * and to establish  branch ancestry
1903  */
1904 static void assign_patchset_id(PatchSet * ps)
1905 {
1906     /*
1907      * Ignore the 'BRANCH ADD' patchsets 
1908      */
1909     if (!ps->branch_add)
1910     {
1911         ps_counter++;
1912         ps->psid = ps_counter;
1913
1914         find_branch_points(ps);
1915     }
1916     else
1917     {
1918         ps->psid = -1;
1919     }
1920 }
1921
1922 static int compare_rev_strings(const char * cr1, const char * cr2)
1923 {
1924     char r1[REV_STR_MAX];
1925     char r2[REV_STR_MAX];
1926     char *s1 = r1, *s2 = r2;
1927     char *p1, *p2;
1928     int n1, n2;
1929
1930     strcpy(s1, cr1);
1931     strcpy(s2, cr2);
1932
1933     for (;;) 
1934     {
1935         p1 = strchr(s1, '.');
1936         p2 = strchr(s2, '.');
1937
1938         if (p1) *p1++ = 0;
1939         if (p2) *p2++ = 0;
1940         
1941         n1 = atoi(s1);
1942         n2 = atoi(s2);
1943         
1944         if (n1 < n2)
1945             return -1;
1946         if (n1 > n2)
1947             return 1;
1948
1949         if (!p1 && p2)
1950             return -1;
1951         if (p1 && !p2)
1952             return 1;
1953         if (!p1 && !p2)
1954             return 0;
1955
1956         s1 = p1;
1957         s2 = p2;
1958     }
1959 }
1960
1961 static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1962 {
1963     struct list_head * i;
1964
1965     for (i = ps1->members.next; i != &ps1->members; i = i->next)
1966     {
1967         PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1968         struct list_head * j;
1969
1970         for (j = ps2->members.next; j != &ps2->members; j = j->next)
1971         {
1972             PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1973             if (psm1->file == psm2->file) 
1974             {
1975                 int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1976                 //debug(DEBUG_APPWARN, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1977                 return ret;
1978             }
1979         }
1980     }
1981     
1982     return 0;
1983 }
1984
1985 static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1986 {
1987     const PatchSet * ps1 = (const PatchSet *)v_ps1;
1988     const PatchSet * ps2 = (const PatchSet *)v_ps2;
1989     long diff;
1990     int ret;
1991     time_t d, min, max;
1992
1993     /* We order by (author, descr, branch, commitid, members, date), but because
1994      * of the fuzz factor we treat times within a certain distance as
1995      * equal IFF the author and descr match.
1996      */
1997
1998     ret = compare_patch_sets_by_members(ps1, ps2);
1999     if (ret)
2000         return ret;
2001
2002     ret = strcmp(ps1->author, ps2->author);
2003     if (ret)
2004             return ret;
2005
2006     ret = strcmp(ps1->descr, ps2->descr);
2007     if (ret)
2008             return ret;
2009
2010     ret = strcmp(ps1->branch, ps2->branch);
2011     if (ret)
2012         return ret;
2013
2014     ret = strcmp(ps1->commitid, ps2->commitid);
2015     if (ret)
2016         return ret;
2017
2018     /* 
2019      * one of ps1 or ps2 is new.  the other should have the min_date
2020      * and max_date set to a window opened by the fuzz_factor
2021      */
2022     if (ps1->min_date == 0)
2023     {
2024         d = ps1->date;
2025         min = ps2->min_date;
2026         max = ps2->max_date;
2027     } 
2028     else if (ps2->min_date == 0)
2029     {
2030         d = ps2->date;
2031         min = ps1->min_date;
2032         max = ps1->max_date;
2033     }
2034     else
2035     {
2036         debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
2037         exit(1);
2038     }
2039
2040     if (min < d && d < max)
2041         return 0;
2042
2043     diff = ps1->date - ps2->date;
2044
2045     return (diff < 0) ? -1 : 1;
2046 }
2047
2048 static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
2049 {
2050     const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
2051     const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
2052     return compare_patch_sets_bytime(ps1, ps2);
2053 }
2054
2055 static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
2056 {
2057     long diff;
2058     int ret;
2059
2060     /* When doing a time-ordering of patchsets, we don't need to
2061      * fuzzy-match the time.  We've already done fuzzy-matching so we
2062      * know that insertions are unique at this point.
2063      */
2064
2065     diff = ps1->date - ps2->date;
2066     if (diff)
2067         return (diff < 0) ? -1 : 1;
2068
2069     ret = compare_patch_sets_by_members(ps1, ps2);
2070     if (ret)
2071         return ret;
2072
2073     ret = strcmp(ps1->author, ps2->author);
2074     if (ret)
2075         return ret;
2076
2077     ret = strcmp(ps1->descr, ps2->descr);
2078     if (ret)
2079         return ret;
2080
2081     ret = strcmp(ps1->branch, ps2->branch);
2082     if (ret)
2083         return ret;
2084
2085     ret = strcmp(ps1->commitid, ps2->commitid);
2086     return ret;
2087 }
2088
2089
2090 static bool is_revision_metadata(const char * buff)
2091 {
2092     char * p1, *p2;
2093     int len;
2094
2095     if (!(p1 = strchr(buff, ':')))
2096         return 0;
2097
2098     p2 = strchr(buff, ' ');
2099     
2100     if (p2 && p2 < p1)
2101         return false;
2102
2103     len = strlen(buff);
2104
2105     /* lines have LF at end */
2106     if (len > 1 && buff[len - 2] == ';')
2107         return true;
2108
2109     return false;
2110 }
2111
2112 static bool patch_set_member_regex(PatchSet * ps, regex_t * reg)
2113 {
2114     struct list_head * next = ps->members.next;
2115
2116     while (next != &ps->members)
2117     {
2118         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2119         
2120         if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
2121             return true;
2122
2123         next = next->next;
2124     }
2125
2126     return false;
2127 }
2128
2129 static bool patch_set_affects_branch(PatchSet * ps, const char * branch)
2130 {
2131     struct list_head * next;
2132
2133     for (next = ps->members.next; next != &ps->members; next = next->next)
2134     {
2135         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2136
2137         /*
2138          * slight hack. if -r is specified, and this patchset
2139          * is 'before' the tag, but is FNK_SHOW_SOME, only
2140          * check if the 'after tag' revisions affect
2141          * the branch.  this is especially important when
2142          * the tag is a branch point.
2143          */
2144         if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
2145             continue;
2146
2147         if (revision_affects_branch(psm->post_rev, branch))
2148             return true;
2149     }
2150
2151     return false;
2152 }
2153
2154 static void do_cvs_diff(PatchSet * ps)
2155 {
2156     struct list_head * next;
2157     const char * dopts;
2158     char use_rep_path[PATH_MAX];
2159     char esc_use_rep_path[PATH_MAX];
2160
2161     fflush(stdout);
2162     fflush(stderr);
2163
2164     if (diff_opts == NULL) 
2165     {
2166         dopts = "-u";
2167         sprintf(use_rep_path, "%s/", repository_path);
2168         /* the rep_path may contain characters that the shell will barf on */
2169         escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
2170     }
2171     else
2172     {
2173         dopts = diff_opts;
2174         use_rep_path[0] = 0;
2175         esc_use_rep_path[0] = 0;
2176     }
2177
2178     for (next = ps->members.next; next != &ps->members; next = next->next)
2179     {
2180         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2181         char esc_file[PATH_MAX];
2182
2183         /* the filename may contain characters that the shell will barf on */
2184         escape_filename(esc_file, PATH_MAX, psm->file->filename);
2185
2186         /*
2187          * Check the patchset funk. We may not want to diff this
2188          * particular file
2189          */
2190         if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
2191         {
2192             printf("Index: %s\n", psm->file->filename);
2193             printf("===================================================================\n");
2194             printf("*** Member not diffed, before start tag\n");
2195             continue;
2196         }
2197         else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
2198         {
2199             printf("Index: %s\n", psm->file->filename);
2200             printf("===================================================================\n");
2201             printf("*** Member not diffed, after end tag\n");
2202             continue;
2203         }
2204
2205         /* 
2206          * When creating diffs for INITIAL or DEAD revisions, we have
2207          * to use 'cvs co' or 'cvs update' to get the file, because
2208          * cvs won't generate these diffs.  The problem is that this
2209          * must be piped to diff, and so the resulting diff doesn't
2210          * contain the filename anywhere! (diff between - and
2211          * /dev/null).  sed is used to replace the '-' with the
2212          * filename.
2213          *
2214          * It's possible for pre_rev to be a 'dead' revision. This
2215          * happens when a file is added on a branch. post_rev will be
2216          * dead dead for remove
2217          */
2218         if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
2219         {
2220             bool cr;
2221             const char * rev;
2222             char cmdbuff[BUFSIZ];
2223             FILE *fp;
2224
2225             if (!psm->pre_rev || psm->pre_rev->dead)
2226             {
2227                 cr = true;
2228                 rev = psm->post_rev->rev;
2229             }
2230             else
2231             {
2232                 cr = false;
2233                 rev = psm->pre_rev->rev;
2234             }
2235
2236             snprintf(cmdbuff, BUFSIZ, "diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s/%s|g'",
2237                      dopts, cr?"":"-", cr?"-":"", cr?"2":"1", repository_path, psm->file->filename);
2238
2239             /* debug(DEBUG_TCP, "cmdbuff: %s", cmdbuff); */
2240
2241             if (!(fp = popen(cmdbuff, "w")))
2242             {
2243                 debug(DEBUG_APPERROR, "cvsclient: popen for diff failed: %s", cmdbuff);
2244                 exit(1);
2245             }
2246
2247             cvs_rupdate(cvsclient_ctx, 
2248                         repository_path, psm->file->filename, rev, fp);
2249             pclose(fp);
2250         }
2251         else
2252             cvs_diff(cvsclient_ctx, 
2253                      repository_path, 
2254                      psm->file->filename, 
2255                      psm->pre_rev->rev, 
2256                      psm->post_rev->rev,
2257                      dopts);
2258
2259     }
2260 }
2261
2262 static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
2263 {
2264     char * p;
2265
2266     /* The "revision" log line can include extra information 
2267      * including who is locking the file --- strip that out.
2268      */
2269     
2270     p = rev_str;
2271     while (isdigit(*p) || *p == '.')
2272             p++;
2273     *p = 0;
2274
2275     return cvs_file_add_revision(file, rev_str);
2276 }
2277
2278 CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
2279 {
2280     CvsFileRevision * rev;
2281
2282     if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
2283     {
2284         rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
2285         rev->rev = get_string(rev_str);
2286         rev->file = file;
2287         rev->branch = NULL;
2288         rev->present = false;
2289         rev->pre_psm = NULL;
2290         rev->post_psm = NULL;
2291         INIT_LIST_HEAD(&rev->branch_children);
2292         INIT_LIST_HEAD(&rev->tags);
2293         
2294         put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
2295
2296         debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
2297     }
2298     else
2299     {
2300         debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
2301     }
2302
2303     /* 
2304      * note: we are guaranteed to get here at least once with
2305      * 'have_branches' == true.  we may pass through once before this,
2306      * because of symbolic tags, then once always when processing the
2307      * actual revision logs
2308      *
2309      * rev->branch will always be set to something, maybe "HEAD"
2310      */
2311     if (!rev->branch && file->have_branches)
2312     {
2313         char branch_str[REV_STR_MAX];
2314
2315         /* in the cvs cvs repository (ccvs) there are tagged versions
2316          * that don't exist.  let's mark every 'known to exist' 
2317          * version
2318          */
2319         rev->present = true;
2320
2321         /* determine the branch this revision was committed on */
2322         if (!get_branch(branch_str, rev->rev))
2323         {
2324             debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
2325             exit(1);
2326         }
2327         
2328         rev->branch = (char*)get_hash_object(file->branches, branch_str);
2329         
2330         /* if there's no branch and it's not on the trunk, blab */
2331         if (!rev->branch)
2332         {
2333             if (get_branch(branch_str, branch_str))
2334             {
2335                 debug(DEBUG_APPMSG2, 
2336                       "revision %s of file %s on unnamed branch at %s", 
2337                       rev->rev, rev->file->filename, branch_str);
2338                 rev->branch = "#CVSPS_NO_BRANCH";
2339                 /* this is just to suppress a warning on re-import */
2340                 cvs_file_add_branch(rev->file, rev->rev, rev->branch,
2341                                     is_vendor_branch(rev->rev));
2342                 /*
2343                  * This triggers a warning about a the broken case
2344                  * in the t9601 case. I haven't figured it out yet,
2345                  * but we can at least warn when it might happen.
2346                  */
2347                 dubious_branches++;
2348
2349             }
2350             else
2351             {
2352                 rev->branch = "HEAD";
2353             }
2354         }
2355
2356         debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
2357     }
2358
2359     return rev;
2360 }
2361
2362 CvsFile * create_cvsfile()
2363 {
2364     CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
2365     if (!f)
2366         return NULL;
2367
2368     f->revisions = create_hash_table(53);
2369     f->branches = create_hash_table(13);
2370     f->branches_sym = create_hash_table(13);
2371     f->symbols = create_hash_table(253);
2372     f->have_branches = false;
2373
2374     if (!f->revisions || !f->branches || !f->branches_sym)
2375     {
2376         if (f->branches)
2377             destroy_hash_table(f->branches, NULL);
2378         if (f->revisions)
2379             destroy_hash_table(f->revisions, NULL);
2380         free(f);
2381         return NULL;
2382     }
2383    
2384     return f;
2385 }
2386
2387 static PatchSet * create_patch_set()
2388 {
2389     PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
2390     
2391     if (ps)
2392     {
2393         INIT_LIST_HEAD(&ps->members);
2394         INIT_LIST_HEAD(&ps->branches);
2395         INIT_LIST_HEAD(&ps->tags);
2396         ps->psid = -1;
2397         ps->date = 0;
2398         ps->min_date = 0;
2399         ps->max_date = 0;
2400         ps->descr = NULL;
2401         ps->author = NULL;
2402         ps->branch_add = false;
2403         ps->commitid = "";
2404         ps->funk_factor = 0;
2405         ps->ancestor_branch = NULL;
2406         CLEAR_LIST_NODE(&ps->collision_link);
2407     }
2408
2409     return ps;
2410 }
2411
2412 PatchSetMember * create_patch_set_member()
2413 {
2414     PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
2415     psm->pre_rev = NULL;
2416     psm->post_rev = NULL;
2417     psm->ps = NULL;
2418     psm->file = NULL;
2419     psm->bad_funk = false;
2420     return psm;
2421 }
2422
2423 static PatchSetRange * create_patch_set_range()
2424 {
2425     PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
2426     return psr;
2427 }
2428
2429 CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
2430 {
2431     CvsFileRevision * rev;
2432
2433     if (strcmp(r, "INITIAL") == 0)
2434         return NULL;
2435
2436     rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
2437     
2438     if (!rev)
2439     {
2440         debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
2441         exit(1);
2442     }
2443
2444     return rev;
2445 }
2446
2447 /*
2448  * Parse lines in the format:
2449  * 
2450  * <white space>tag_name: <rev>;
2451  *
2452  * Handles both regular tags (these go into the symbols hash)
2453  * and magic-branch-tags (second to last node of revision is 0)
2454  * which go into branches and branches_sym hashes.  Magic-branch
2455  * format is hidden in CVS everwhere except the 'cvs log' output.
2456  */
2457
2458 static void parse_sym(CvsFile * file, char * sym)
2459 {
2460     char * tag = sym, *eot;
2461     int leaf, final_branch = -1;
2462     char rev[REV_STR_MAX];
2463     char rev2[REV_STR_MAX];
2464     
2465     while (*tag && isspace(*tag))
2466         tag++;
2467
2468     if (!*tag)
2469         return;
2470
2471     eot = strchr(tag, ':');
2472     
2473     if (!eot)
2474         return;
2475
2476     *eot = 0;
2477     eot += 2;
2478     
2479     if (!get_branch_ext(rev, eot, &leaf))
2480     {
2481         if (strcmp(tag, "TRUNK") == 0)
2482         {
2483             debug(DEBUG_STATUS, "ignoring the TRUNK branch/tag");
2484             return;
2485         }
2486         debug(DEBUG_APPERROR, "malformed revision");
2487         exit(1);
2488     }
2489
2490     /* 
2491      * get_branch_ext will leave final_branch alone
2492      * if there aren't enough '.' in string 
2493      */
2494     get_branch_ext(rev2, rev, &final_branch);
2495
2496     if (final_branch == 0)
2497     {
2498         snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
2499         debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
2500         
2501         cvs_file_add_branch(file, rev, tag, false);
2502     }
2503     else
2504     {
2505         strcpy(rev, eot);
2506         chop(rev);
2507
2508         /* see cvs manual: what is this vendor tag? */
2509         if (is_vendor_branch(rev))
2510             cvs_file_add_branch(file, rev, tag, true);
2511         else
2512             cvs_file_add_symbol(file, rev, tag);
2513     }
2514 }
2515
2516 void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
2517 {
2518     CvsFileRevision * rev;
2519     GlobalSymbol * sym;
2520     Tag * tag;
2521
2522     /* get a permanent storage string */
2523     char * tag_str = get_string(p_tag_str);
2524
2525     debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
2526     rev = cvs_file_add_revision(file, rev_str);
2527     put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
2528     
2529     /*
2530      * check the global_symbols
2531      */
2532     sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
2533     if (!sym)
2534     {
2535         sym = (GlobalSymbol*)malloc(sizeof(*sym));
2536         sym->tag = tag_str;
2537         sym->ps = NULL;
2538         INIT_LIST_HEAD(&sym->tags);
2539
2540         put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
2541     }
2542
2543     tag = (Tag*)malloc(sizeof(*tag));
2544     tag->tag = tag_str;
2545     tag->rev = rev;
2546     tag->sym = sym;
2547     list_add(&tag->global_link, &sym->tags);
2548     list_add(&tag->rev_link, &rev->tags);
2549 }
2550
2551 char * cvs_file_add_branch(CvsFile * file,
2552                            const char * rev, 
2553                            const char * tag,
2554                            bool vendor_branch)
2555 {
2556     char * new_tag;
2557     char * new_rev;
2558
2559     if (get_hash_object(file->branches, rev))
2560     {
2561         debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s", 
2562               rev, tag, file->filename);
2563         return NULL;
2564     }
2565
2566     /* get permanent storage for the strings */
2567     new_tag = get_string(tag);
2568     new_rev = get_string(rev); 
2569
2570     put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
2571     put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
2572     
2573     if (get_hash_object(branches, tag) == NULL) {
2574         debug(DEBUG_STATUS, "adding new branch to branches hash: %s", tag);
2575         Branch * branch = create_branch(tag);
2576         branch->vendor_branch = vendor_branch;
2577         if (vendor_branch)
2578             dubious_branches += 1;
2579         put_hash_object_ex(branches, new_tag, branch, HT_NO_KEYCOPY, NULL, NULL);
2580     }
2581     
2582
2583     return new_tag;
2584 }
2585
2586 /*
2587  * Resolve each global symbol to a PatchSet.  This is
2588  * not necessarily doable, because tagging isn't 
2589  * necessarily done to the project as a whole, and
2590  * it's possible that no tag is valid for all files 
2591  * at a single point in time.  We check for that
2592  * case though.
2593  *
2594  * Implementation: the most recent PatchSet containing
2595  * a revision (post_rev) tagged by the symbol is considered
2596  * the 'tagged' PatchSet.
2597  */
2598
2599 static void resolve_global_symbols()
2600 {
2601     struct hash_entry * he_sym;
2602
2603     reset_hash_iterator(global_symbols);
2604     while ((he_sym = next_hash_entry(global_symbols)))
2605     {
2606         GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
2607         PatchSet * ps;
2608         TagName * tagname;
2609         struct list_head * next;
2610
2611         debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
2612
2613         /*
2614          * First pass, determine the most recent PatchSet with a 
2615          * revision tagged with the symbolic tag.  This is 'the'
2616          * patchset with the tag
2617          */
2618
2619         for (next = sym->tags.next; next != &sym->tags; next = next->next)
2620         {
2621             Tag * tag = list_entry(next, Tag, global_link);
2622             CvsFileRevision * rev = tag->rev;
2623
2624             /* FIXME:test for rev->post_psm from DEBIAN. not sure how
2625              * this could happen */
2626             if (!rev->present || !rev->post_psm)
2627             {
2628                 struct list_head *tmp = next->prev;
2629                 debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
2630                       rev->rev, rev->file->filename);
2631                 /* FIXME: memleak */
2632                 list_del(next);
2633                 next = tmp;
2634                 continue;
2635             }
2636
2637             ps = rev->post_psm->ps;
2638
2639             if (!sym->ps || ps->date > sym->ps->date)
2640                 sym->ps = ps;
2641         }
2642         
2643         /* convenience variable */
2644         ps = sym->ps;
2645
2646         if (!ps)
2647         {
2648             debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
2649             return;
2650         }
2651
2652         tagname = (TagName*)malloc(sizeof(TagName));
2653         tagname->name = sym->tag;
2654         tagname->flags = 0;
2655         list_add(&tagname->link, &ps->tags);
2656
2657
2658         /* check if this ps is one of the '-r' patchsets */
2659         if (restrict_tag_start && strcmp(restrict_tag_start, sym->tag) == 0)
2660             restrict_tag_ps_start = ps->psid;
2661
2662         /* the second -r implies -b */
2663         if (restrict_tag_end && strcmp(restrict_tag_end, sym->tag) == 0)
2664         {
2665             restrict_tag_ps_end = ps->psid;
2666
2667             if (restrict_branch)
2668             {
2669                 if (strcmp(ps->branch, restrict_branch) != 0)
2670                 {
2671                     debug(DEBUG_APPWARN,
2672                           "WARNING: -b option and second -r have conflicting branches: %s %s", 
2673                           restrict_branch, ps->branch);
2674                 }
2675             }
2676             else
2677             {
2678                 debug(DEBUG_APPWARN, "NOTICE: implicit branch restriction set to %s", ps->branch);
2679                 restrict_branch = ps->branch;
2680             }
2681         }
2682
2683         /* 
2684          * Second pass. 
2685          * check if this is an invalid patchset, 
2686          * check which members are invalid.  determine
2687          * the funk factor etc.
2688          */
2689         for (next = sym->tags.next; next != &sym->tags; next = next->next)
2690         {
2691             Tag * tag = list_entry(next, Tag, global_link);
2692             CvsFileRevision * rev = tag->rev;
2693             CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
2694             
2695             if (!next_rev)
2696                 continue;
2697                 
2698             /*
2699              * we want the 'tagged revision' to be valid until after
2700              * the date of the 'tagged patchset' or else there's something
2701              * funky going on
2702              */
2703             if (next_rev->post_psm->ps->date < ps->date)
2704             {
2705                 int flag = check_rev_funk(ps, next_rev);
2706                 debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
2707                       rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
2708                 /* FIXME: using tags.next is somewhat kludgy */
2709                 list_entry(ps->tags.next, TagName, link)->flags |= flag;
2710             }
2711         }
2712     }
2713 }
2714
2715 static bool revision_affects_branch(CvsFileRevision * rev, const char * branch)
2716 {
2717     /* special case the branch called 'HEAD' */
2718     if (strcmp(branch, "HEAD") == 0)
2719     {
2720         /* look for only one '.' in rev */
2721         char * p = strchr(rev->rev, '.');
2722         if (p && !strchr(p + 1, '.'))
2723             return true;
2724     }
2725     else
2726     {
2727         char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
2728         
2729         if (branch_rev)
2730         {
2731             char post_rev[REV_STR_MAX];
2732             char branch[REV_STR_MAX];
2733             int file_leaf, branch_leaf;
2734             
2735             strcpy(branch, branch_rev);
2736             
2737             /* first get the branch the file rev is on */
2738             if (get_branch_ext(post_rev, rev->rev, &file_leaf))
2739             {
2740                 branch_leaf = file_leaf;
2741                 
2742                 /* check against branch and all branch ancestor branches */
2743                 do 
2744                 {
2745                     debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
2746                     if (strcmp(branch, post_rev) == 0)
2747                         return (file_leaf <= branch_leaf);
2748                 }
2749                 while(get_branch_ext(branch, branch, &branch_leaf));
2750             }
2751         }
2752     }
2753
2754     return false;
2755 }
2756
2757 static int count_dots(const char * p)
2758 {
2759     int dots = 0;
2760
2761     while (*p)
2762         if (*p++ == '.')
2763             dots++;
2764
2765     return dots;
2766 }
2767
2768 /*
2769  * When importing vendor sources, (apparently people do this)
2770  * the code is added on a 'vendor' branch, which, for some reason
2771  * doesn't use the magic-branch-tag format.  Try to detect that now
2772  */
2773 static bool is_vendor_branch(const char * rev)
2774 {
2775     return !(count_dots(rev)&1);
2776 }
2777
2778 void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
2779 {
2780     /* check if a member for the same file already exists, if so
2781      * put this PatchSet on the collisions list 
2782      */
2783     struct list_head * next;
2784     for (next = ps->members.next; next != &ps->members; next = next->next) 
2785     {
2786         PatchSetMember * m = list_entry(next, PatchSetMember, link);
2787         if (m->file == psm->file) {
2788                 int order = compare_rev_strings(psm->post_rev->rev, m->post_rev->rev);
2789
2790                 /*
2791                  * Same revision too? Add it to the collision list
2792                  * if it isn't already.
2793                  */
2794                 if (!order) {
2795                         if (ps->collision_link.next == NULL)
2796                                 list_add(&ps->collision_link, &collisions);
2797                         return;
2798                 }
2799
2800                 /*
2801                  * If this is an older revision than the one we already have
2802                  * in this patchset, just ignore it
2803                  */
2804                 if (order < 0)
2805                         return;
2806
2807                 /*
2808                  * This is a newer one, remove the old one
2809                  */
2810                 list_del(&m->link);
2811         }
2812     }
2813
2814     psm->ps = ps;
2815     list_add(&psm->link, ps->members.prev);
2816 }
2817
2818 static void set_psm_initial(PatchSetMember * psm)
2819 {
2820     psm->pre_rev = NULL;
2821     if (psm->post_rev->dead)
2822     {
2823         /* 
2824          * We expect a 'file xyz initially added on branch abc' here.
2825          * There can be several such members in a given patchset,
2826          * since cvs only includes the file basename in the log message.
2827          */
2828         psm->ps->branch_add = true;
2829     }
2830 }
2831
2832 /* 
2833  * look at all revisions starting at rev and going forward until 
2834  * ps->date and see whether they are invalid or just funky.
2835  */
2836 static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
2837 {
2838     struct list_head * tag;
2839
2840     int retval = TAG_FUNKY;
2841
2842     for (tag = ps->tags.next; tag != &ps->tags; tag = tag->next)
2843     {
2844         char* tagname = list_entry (&tag, TagName, link)->name;
2845
2846         while (rev)
2847         {
2848             PatchSet * next_ps = rev->post_psm->ps;
2849             struct list_head * next;
2850
2851             if (next_ps->date > ps->date)
2852                 break;
2853
2854             debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s", 
2855                   ps->date, next_ps->date, rev->rev, rev->branch);
2856
2857             /*
2858              * If the tagname is one of the two possible '-r' tags
2859              * then the funkyness is even more important.
2860              *
2861              * In the restrict_tag_start case, this next_ps is chronologically
2862              * before ps, but tagwise after, so set the funk_factor so it will
2863              * be included.
2864              *
2865              * The restrict_tag_end case is similar, but backwards.
2866              *
2867              * Start assuming the HIDE/SHOW_ALL case, we will determine
2868              * below if we have a split ps case 
2869              */
2870             if (restrict_tag_start && strcmp(tagname, restrict_tag_start) == 0)
2871                 next_ps->funk_factor = FNK_SHOW_ALL;
2872             if (restrict_tag_end && strcmp(tagname, restrict_tag_end) == 0)
2873                 next_ps->funk_factor = FNK_HIDE_ALL;
2874
2875             /*
2876              * if all of the other members of this patchset are also 'after' the tag
2877              * then this is a 'funky' patchset w.r.t. the tag.  however, if some are
2878              * before then the patchset is 'invalid' w.r.t. the tag, and we mark
2879              * the members individually with 'bad_funk' ,if this tag is the
2880              * '-r' tag.  Then we can actually split the diff on this patchset
2881              */
2882             for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
2883             {
2884                 PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2885                 if (before_tag(psm->post_rev, tagname))
2886                 {
2887                     retval = TAG_INVALID;
2888                     /* only set bad_funk for one of the -r tags */
2889                     if (next_ps->funk_factor)
2890                     {
2891                         psm->bad_funk = true;
2892                         next_ps->funk_factor = 
2893                             (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
2894                     }
2895                     debug(DEBUG_APPWARN,
2896                           "WARNING: Invalid PatchSet %d, Tag %s:\n"
2897                           "    %s:%s=after, %s:%s=before. Treated as 'before'", 
2898                           next_ps->psid, tagname, 
2899                           rev->file->filename, rev->rev, 
2900                           psm->post_rev->file->filename, psm->post_rev->rev);
2901                 }
2902             }
2903
2904             rev = rev_follow_branch(rev, ps->branch);
2905         }
2906     }
2907
2908     return retval;
2909 }
2910
2911 /* determine if the revision is before the tag */
2912 static bool before_tag(CvsFileRevision * rev, const char * tag)
2913 {
2914     CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
2915     bool retval = false;
2916
2917     if (tagged_rev && tagged_rev->branch == NULL)
2918         debug(DEBUG_APPWARN, "WARNING: Branch == NULL for: %s %s %s %s %d",
2919               rev->file->filename, tag, rev->rev, tagged_rev->rev, retval);
2920
2921     if (tagged_rev && tagged_rev->branch != NULL &&
2922         revision_affects_branch(rev, tagged_rev->branch) && 
2923         rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
2924         retval = true;
2925
2926     debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d", 
2927           rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
2928
2929     return retval;
2930 }
2931
2932 /* get the next revision from this one following branch if possible */
2933 /* FIXME: not sure if this needs to follow branches leading up to branches? */
2934 static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
2935 {
2936     struct list_head * next;
2937
2938     /* check for 'main line of inheritance' */
2939     if (strcmp(rev->branch, branch) == 0)
2940         return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
2941
2942     /* look down branches */
2943     for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
2944     {
2945         CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
2946         //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2947         if (strcmp(next_rev->branch, branch) == 0)
2948             return next_rev;
2949     }
2950     
2951     return NULL;
2952 }
2953
2954 static void handle_collisions()
2955 {
2956     struct list_head *next;
2957     for (next = collisions.next; next != &collisions; next = next->next) 
2958     {
2959         PatchSet * ps = list_entry(next, PatchSet, collision_link);
2960         printf("PatchSet %d has collisions\n", ps->psid);
2961     }
2962 }
2963
2964 void walk_all_patch_sets(void (*action)(PatchSet *))
2965 {
2966     struct list_head * next;;
2967     for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
2968         PatchSet * ps = list_entry(next, PatchSet, all_link);
2969         action(ps);
2970     }
2971 }
2972
2973 static Branch * create_branch(const char * name) 
2974 {
2975     Branch * branch = (Branch*)calloc(1, sizeof(*branch));
2976     branch->name = get_string(name);
2977     branch->ps = NULL;
2978     CLEAR_LIST_NODE(&branch->link);
2979     return branch;
2980 }
2981
2982 static void find_branch_points(PatchSet * ps)
2983 {
2984     struct list_head * next;
2985     
2986     /*
2987      * for each member, check if the post-rev has any branch children.
2988      * if so, the branch point for that branch cannot be earlier than this 
2989      * PatchSet, so just assign here for now.  It'll get pushed ahead
2990      * bit by bit until it falls into the right place.
2991      */
2992     for (next = ps->members.next; next != &ps->members; next = next->next) 
2993     {
2994         PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2995         CvsFileRevision * rev = psm->post_rev;
2996         struct list_head * child_iter;
2997
2998         for (child_iter = rev->branch_children.next; child_iter != &rev->branch_children; child_iter = child_iter->next) {
2999             CvsFileRevision * branch_child = list_entry(child_iter, CvsFileRevision, link);
3000             Branch * branch = get_hash_object(branches, branch_child->branch);
3001             if (branch == NULL) {
3002                 debug(DEBUG_APPERROR, "branch %s not found in global branch hash", branch_child->branch);
3003                 return;
3004             }
3005             
3006             if (branch->ps != NULL) {
3007                 list_del(&branch->link);
3008             }
3009
3010             branch->ps = ps;
3011             list_add(&branch->link, ps->branches.prev);
3012         }
3013     }
3014         
3015 }