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