2
* Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
3
* See COPYING file for license information
15
#include <sys/types.h>
18
#include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
20
#include <cbtcommon/hash.h>
21
#include <cbtcommon/list.h>
22
#include <cbtcommon/text_util.h>
23
#include <cbtcommon/debug.h>
24
#include <cbtcommon/rcsid.h>
27
#include "cvsps_types.h"
32
#include "cvs_direct.h"
33
#include "list_sort.h"
35
RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
37
#define CVS_LOG_BOUNDARY "----------------------------\n"
38
#define CVS_FILE_BOUNDARY "=============================================================================\n"
47
NEED_DATE_AUTHOR_STATE,
52
struct hash_table * file_hash;
53
CvsServerCtx * cvs_direct_ctx;
54
char root_path[PATH_MAX];
55
char repository_path[PATH_MAX];
57
const char * tag_flag_descr[] = {
64
const char * fnk_descr[] = {
73
static int ps_counter;
74
static void * ps_tree;
75
static struct hash_table * global_symbols;
76
static char strip_path[PATH_MAX];
77
static int strip_path_len;
78
static time_t cache_date;
79
static int update_cache;
80
static int ignore_cache;
81
static int do_write_cache;
82
static int statistics;
83
static const char * test_log_file;
84
static struct hash_table * branch_heads;
85
static struct list_head all_patch_sets;
86
static struct list_head collisions;
88
/* settable via options */
89
static int timestamp_fuzz_factor = 300;
91
static const char * restrict_author;
92
static int have_restrict_log;
93
static regex_t restrict_log;
94
static int have_restrict_file;
95
static regex_t restrict_file;
96
static time_t restrict_date_start;
97
static time_t restrict_date_end;
98
static const char * restrict_branch;
99
static struct list_head show_patch_set_ranges;
100
static int summary_first;
101
static const char * norc = "";
102
static const char * patch_set_dir;
103
static const char * restrict_tag_start;
104
static const char * restrict_tag_end;
105
static int restrict_tag_ps_start;
106
static int restrict_tag_ps_end = INT_MAX;
107
static const char * diff_opts;
110
static int cvs_direct;
112
static char compress_arg[8];
113
static int track_branch_ancestry;
115
static void check_norc(int, char *[]);
116
static int parse_args(int, char *[]);
117
static int parse_rc();
118
static void load_from_cvs();
119
static void init_paths();
120
static CvsFile * parse_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 assign_patchset_id(PatchSet *);
126
static int compare_rev_strings(const char *, const char *);
127
static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
128
static int compare_patch_sets_bk(const void *, const void *);
129
static int compare_patch_sets(const void *, const void *);
130
static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
131
static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
132
static int is_revision_metadata(const char *);
133
static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
134
static int patch_set_affects_branch(PatchSet *, const char *);
135
static void do_cvs_diff(PatchSet *);
136
static PatchSet * create_patch_set();
137
static PatchSetRange * create_patch_set_range();
138
static void parse_sym(CvsFile *, char *);
139
static void resolve_global_symbols();
140
static int revision_affects_branch(CvsFileRevision *, const char *);
141
static int is_vendor_branch(const char *);
142
static void set_psm_initial(PatchSetMember * psm);
143
static int check_rev_funk(PatchSet *, CvsFileRevision *);
144
static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
145
static int before_tag(CvsFileRevision * rev, const char * tag);
146
static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
147
static void handle_collisions();
149
int main(int argc, char *argv[])
151
debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
153
INIT_LIST_HEAD(&show_patch_set_ranges);
156
* we want to parse the rc first, so command line can override it
157
* but also, --norc should stop the rc from being processed, so
158
* we look for --norc explicitly first. Note: --norc in the rc
159
* file itself will prevent the cvs rc file from being used.
161
check_norc(argc, argv);
163
if (strlen(norc) == 0 && parse_rc() < 0)
166
if (parse_args(argc, argv) < 0)
169
if (diff_opts && !cvs_direct && do_diff)
171
debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
172
debug(DEBUG_APPMSG1, " which is usually used to create diffs. 'cvs diff'");
173
debug(DEBUG_APPMSG1, " will be used instead, but the resulting patches ");
174
debug(DEBUG_APPMSG1, " will need to be applied using the '-p0' option");
175
debug(DEBUG_APPMSG1, " to patch(1) (in the working directory), ");
176
debug(DEBUG_APPMSG1, " instead of '-p1'\n");
179
file_hash = create_hash_table(1023);
180
global_symbols = create_hash_table(111);
181
branch_heads = create_hash_table(1023);
182
INIT_LIST_HEAD(&all_patch_sets);
183
INIT_LIST_HEAD(&collisions);
185
/* this parses some of the CVS/ files, and initializes
186
* the repository_path and other variables
192
int save_fuzz_factor = timestamp_fuzz_factor;
194
/* the timestamp fuzz should only be in effect when loading from
195
* CVS, not re-fuzzed when loading from cache. This is a hack
196
* working around bad use of global variables
199
timestamp_fuzz_factor = 0;
201
if ((cache_date = read_cache()) < 0)
204
timestamp_fuzz_factor = save_fuzz_factor;
207
if (cvs_direct && (do_diff || (update_cache && !test_log_file)))
208
cvs_direct_ctx = open_cvs_server(root_path, compress);
217
//handle_collisions();
219
list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
222
walk_all_patch_sets(assign_patchset_id);
226
resolve_global_symbols();
229
write_cache(cache_date);
232
print_statistics(ps_tree);
234
/* check that the '-r' symbols (if specified) were resolved */
235
if (restrict_tag_start && restrict_tag_ps_start == 0 &&
236
strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
238
debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
242
if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
244
debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
248
walk_all_patch_sets(check_print_patch_set);
251
walk_all_patch_sets(check_print_patch_set);
254
close_cvs_server(cvs_direct_ctx);
259
static void load_from_cvs()
263
int state = NEED_FILE;
264
CvsFile * file = NULL;
265
PatchSetMember * psm = NULL;
267
char authbuff[AUTH_STR_MAX];
268
char logbuff[LOG_STR_MAX + 1];
273
char use_rep_buff[PATH_MAX];
276
if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
279
snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
289
struct tm * tm = gmtime(&cache_date);
290
strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
292
/* this command asks for logs using two different date
293
* arguments, separated by ';' (see man rlog). The first
294
* gets all revisions more recent than date, the second
295
* gets a single revision no later than date, which combined
296
* get us all revisions that have occurred since last update
297
* and overlaps what we had before by exactly one revision,
298
* which is necessary to fill in the pre_rev stuff for a
301
snprintf(cmd, BUFSIZ, "cvs %s %s %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
306
snprintf(cmd, BUFSIZ, "cvs %s %s %s %s", compress_arg, norc, ltype, use_rep_buff);
309
debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
311
cache_date = time(NULL);
313
/* FIXME: this is ugly, need to virtualize the accesses away from here */
315
cvsfp = fopen(test_log_file, "r");
316
else if (cvs_direct_ctx)
317
cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
319
cvsfp = popen(cmd, "r");
323
debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
331
tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
333
tst = fgets(buff, BUFSIZ, cvsfp);
338
debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
343
if (strncmp(buff, "RCS file", 8) == 0 && (file = parse_file(buff)))
347
if (strncmp(buff, "symbolic names:", 15) == 0)
351
if (!isspace(buff[0]))
353
/* see cvsps_types.h for commentary on have_branches */
354
file->have_branches = 1;
355
state = NEED_START_LOG;
358
parse_sym(file, buff);
361
if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
362
state = NEED_REVISION;
365
if (strncmp(buff, "revision", 8) == 0)
367
char new_rev[REV_STR_MAX];
368
CvsFileRevision * rev;
370
strcpy(new_rev, buff + 9);
374
* rev may already exist (think cvsps -u), in which
375
* case parse_revision is a hash lookup
377
rev = parse_revision(file, new_rev);
380
* in the simple case, we are copying rev to psm->pre_rev
381
* (psm refers to last patch set processed at this point)
382
* since generally speaking the log is reverse chronological.
383
* This breaks down slightly when branches are introduced
386
assign_pre_revision(psm, rev);
389
* if this is a new revision, it will have no post_psm associated.
390
* otherwise we are (probably?) hitting the overlap in cvsps -u
394
psm = rev->post_psm = create_patch_set_member();
397
state = NEED_DATE_AUTHOR_STATE;
401
/* we hit this in cvsps -u mode, we are now up-to-date
402
* w.r.t this particular file. skip all of the rest
403
* of the info (revs and logs) until we hit the next file
410
case NEED_DATE_AUTHOR_STATE:
411
if (strncmp(buff, "date:", 5) == 0)
415
strncpy(datebuff, buff + 6, 19);
418
strcpy(authbuff, "unknown");
419
p = strstr(buff, "author: ");
427
strzncpy(authbuff, p, op - p + 1);
431
/* read the 'state' tag to see if this is a dead revision */
432
p = strstr(buff, "state: ");
439
if (strncmp(p, "dead", MIN(4, op - p)) == 0)
440
psm->post_rev->dead = 1;
447
if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
451
PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
452
patch_set_add_member(ps, psm);
458
state = NEED_REVISION;
460
else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
464
PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
465
patch_set_add_member(ps, psm);
466
assign_pre_revision(psm, NULL);
478
/* other "blahblah: information;" messages can
479
* follow the stuff we pay attention to
481
if (have_log || !is_revision_metadata(buff))
483
/* if the log buffer is full, that's it.
485
* Also, read lines (fgets) always have \n in them
486
* which we count on. So if truncation happens,
487
* be careful to put a \n on.
489
* Buffer has LOG_STR_MAX + 1 for room for \0 if
492
if (loglen < LOG_STR_MAX)
494
int len = strlen(buff);
496
if (len >= LOG_STR_MAX - loglen)
498
debug(DEBUG_APPMSG1, "WARNING: maximum log length exceeded, truncating log");
499
len = LOG_STR_MAX - loglen;
500
buff[len - 1] = '\n';
503
debug(DEBUG_STATUS, "appending %s to log", buff);
504
memcpy(logbuff + loglen, buff, len);
512
debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
520
if (state == NEED_SYMS)
522
debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
523
debug(DEBUG_APPERROR, " Perhaps you should try running with --norc");
527
if (state != NEED_FILE)
529
debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state);
537
else if (cvs_direct_ctx)
539
cvs_rlog_close(cvs_direct_ctx);
543
if (pclose(cvsfp) < 0)
545
debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
551
static int usage(const char * str1, const char * str2)
554
debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
556
debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
557
debug(DEBUG_APPERROR, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
558
debug(DEBUG_APPERROR, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
559
debug(DEBUG_APPERROR, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
560
debug(DEBUG_APPERROR, " [--test-log <captured cvs log file>] [--bkcvs]");
561
debug(DEBUG_APPERROR, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
562
debug(DEBUG_APPERROR, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
563
debug(DEBUG_APPERROR, " [-q] [-A] [<repository>]");
564
debug(DEBUG_APPERROR, "");
565
debug(DEBUG_APPERROR, "Where:");
566
debug(DEBUG_APPERROR, " -h display this informative message");
567
debug(DEBUG_APPERROR, " -x ignore (and rebuild) cvsps.cache file");
568
debug(DEBUG_APPERROR, " -u update cvsps.cache file");
569
debug(DEBUG_APPERROR, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
570
debug(DEBUG_APPERROR, " -g generate diffs of the selected patch sets");
571
debug(DEBUG_APPERROR, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
572
debug(DEBUG_APPERROR, " -a <author> restrict output to patch sets created by author");
573
debug(DEBUG_APPERROR, " -f <file> restrict output to patch sets involving file");
574
debug(DEBUG_APPERROR, " -d <date1> -d <date2> if just one date specified, show");
575
debug(DEBUG_APPERROR, " revisions newer than date1. If two dates specified,");
576
debug(DEBUG_APPERROR, " show revisions between two dates.");
577
debug(DEBUG_APPERROR, " -b <branch> restrict output to patch sets affecting history of branch");
578
debug(DEBUG_APPERROR, " -l <regex> restrict output to patch sets matching <regex> in log message");
579
debug(DEBUG_APPERROR, " -r <tag1> -r <tag2> if just one tag specified, show");
580
debug(DEBUG_APPERROR, " revisions since tag1. If two tags specified, show");
581
debug(DEBUG_APPERROR, " revisions between the two tags.");
582
debug(DEBUG_APPERROR, " -p <directory> output patch sets to individual files in <directory>");
583
debug(DEBUG_APPERROR, " -v show very verbose parsing messages");
584
debug(DEBUG_APPERROR, " -t show some brief memory usage statistics");
585
debug(DEBUG_APPERROR, " --norc when invoking cvs, ignore the .cvsrc file");
586
debug(DEBUG_APPERROR, " --summary-first when multiple patch sets are shown, put all summaries first");
587
debug(DEBUG_APPERROR, " --test-log <captured cvs log> supply a captured cvs log for testing");
588
debug(DEBUG_APPERROR, " --diff-opts <option string> supply special set of options to diff");
589
debug(DEBUG_APPERROR, " --bkcvs special hack for parsing the BK -> CVS log format");
590
debug(DEBUG_APPERROR, " --no-rlog disable rlog (it's faulty in some setups)");
591
debug(DEBUG_APPERROR, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
592
debug(DEBUG_APPERROR, " --debuglvl <bitmask> enable various debug channels.");
593
debug(DEBUG_APPERROR, " -Z <compression> A value 1-9 which specifies amount of compression");
594
debug(DEBUG_APPERROR, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
595
debug(DEBUG_APPERROR, " -q be quiet about warnings");
596
debug(DEBUG_APPERROR, " -A track and report branch ancestry");
597
debug(DEBUG_APPERROR, " <repository> apply cvsps to repository. overrides working directory");
598
debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
603
static int parse_args(int argc, char *argv[])
608
if (strcmp(argv[i], "-z") == 0)
611
return usage("argument to -z missing", "");
613
timestamp_fuzz_factor = atoi(argv[i++]);
617
if (strcmp(argv[i], "-g") == 0)
624
if (strcmp(argv[i], "-s") == 0)
626
PatchSetRange * range;
627
char * min_str, * max_str;
630
return usage("argument to -s missing", "");
632
min_str = strtok(argv[i++], ",");
635
range = create_patch_set_range();
637
max_str = strrchr(min_str, '-');
643
range->min_counter = atoi(min_str);
646
range->max_counter = atoi(max_str);
648
range->max_counter = INT_MAX;
650
list_add(&range->link, show_patch_set_ranges.prev);
652
while ((min_str = strtok(NULL, ",")));
657
if (strcmp(argv[i], "-a") == 0)
660
return usage("argument to -a missing", "");
662
restrict_author = argv[i++];
666
if (strcmp(argv[i], "-l") == 0)
671
return usage("argument to -l missing", "");
673
if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
676
regerror(err, &restrict_log, errbuf, 256);
677
return usage("bad regex to -l", errbuf);
680
have_restrict_log = 1;
685
if (strcmp(argv[i], "-f") == 0)
690
return usage("argument to -f missing", "");
692
if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
695
regerror(err, &restrict_file, errbuf, 256);
696
return usage("bad regex to -f", errbuf);
699
have_restrict_file = 1;
704
if (strcmp(argv[i], "-d") == 0)
709
return usage("argument to -d missing", "");
711
pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
712
convert_date(pt, argv[i++]);
716
if (strcmp(argv[i], "-r") == 0)
719
return usage("argument to -r missing", "");
721
if (restrict_tag_start)
722
restrict_tag_end = argv[i];
724
restrict_tag_start = argv[i];
730
if (strcmp(argv[i], "-u") == 0)
737
if (strcmp(argv[i], "-x") == 0)
745
if (strcmp(argv[i], "-b") == 0)
748
return usage("argument to -b missing", "");
750
restrict_branch = argv[i++];
751
/* Warn if the user tries to use TRUNK. Should eventually
752
* go away as TRUNK may be a valid branch within CVS
754
if (strcmp(restrict_branch, "TRUNK") == 0)
755
debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
759
if (strcmp(argv[i], "-p") == 0)
762
return usage("argument to -p missing", "");
764
patch_set_dir = argv[i++];
768
if (strcmp(argv[i], "-v") == 0)
775
if (strcmp(argv[i], "-t") == 0)
782
if (strcmp(argv[i], "--summary-first") == 0)
789
if (strcmp(argv[i], "-h") == 0)
790
return usage(NULL, NULL);
792
/* see special handling of --norc in main */
793
if (strcmp(argv[i], "--norc") == 0)
800
if (strcmp(argv[i], "--test-log") == 0)
803
return usage("argument to --test-log missing", "");
805
test_log_file = argv[i++];
809
if (strcmp(argv[i], "--diff-opts") == 0)
812
return usage("argument to --diff-opts missing", "");
814
/* allow diff_opts to be turned off by making empty string
817
if (!strlen(argv[i]))
825
if (strcmp(argv[i], "--bkcvs") == 0)
832
if (strcmp(argv[i], "--no-rlog") == 0)
839
if (strcmp(argv[i], "--cvs-direct") == 0)
846
if (strcmp(argv[i], "--no-cvs-direct") == 0)
853
if (strcmp(argv[i], "--debuglvl") == 0)
856
return usage("argument to --debuglvl missing", "");
858
debuglvl = atoi(argv[i++]);
862
if (strcmp(argv[i], "-Z") == 0)
865
return usage("argument to -Z", "");
867
compress = atoi(argv[i++]);
869
if (compress < 0 || compress > 9)
870
return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
875
snprintf(compress_arg, 8, "-z%d", compress);
879
if (strcmp(argv[i], "--root") == 0)
882
return usage("argument to --root missing", "");
884
strcpy(root_path, argv[i++]);
888
if (strcmp(argv[i], "-q") == 0)
890
debuglvl &= ~DEBUG_APPMSG1;
895
if (strcmp(argv[i], "-A") == 0)
897
track_branch_ancestry = 1;
902
if (argv[i][0] == '-')
903
return usage("invalid argument", argv[i]);
905
strcpy(repository_path, argv[i++]);
911
static int parse_rc()
913
char rcfile[PATH_MAX];
915
snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
916
if ((fp = fopen(rcfile, "r")))
919
while (fgets(buff, BUFSIZ, fp))
928
p = strchr(buff, ' ');
932
argv[2] = xstrdup(p);
936
argv[1] = xstrdup(buff);
938
if (parse_args(argc, argv) < 0)
947
static void init_paths()
953
/* determine the CVSROOT. precedence:
955
* 2) working directory (if present)
956
* 3) environment variable CVSROOT
960
if (!(fp = fopen("CVS/Root", "r")))
964
debug(DEBUG_STATUS, "Can't open CVS/Root");
965
e = getenv("CVSROOT");
969
debug(DEBUG_APPERROR, "cannot determine CVSROOT");
973
strcpy(root_path, e);
977
if (!fgets(root_path, PATH_MAX, fp))
979
debug(DEBUG_APPERROR, "Error reading CVSROOT");
985
/* chop the lf and optional trailing '/' */
986
len = strlen(root_path) - 1;
988
if (root_path[len - 1] == '/')
989
root_path[--len] = 0;
993
/* Determine the repository path, precedence:
995
* 2) working directory
998
if (!repository_path[0])
1000
if (!(fp = fopen("CVS/Repository", "r")))
1002
debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
1006
if (!fgets(repository_path, PATH_MAX, fp))
1008
debug(DEBUG_APPERROR, "Error reading repository path");
1012
chop(repository_path);
1016
/* get the path portion of the root */
1017
p = strrchr(root_path, ':');
1024
/* some CVS have the CVSROOT string as part of the repository
1025
* string (initial substring). remove it.
1029
if (strncmp(p, repository_path, len) == 0)
1031
int rlen = strlen(repository_path + len + 1);
1032
memmove(repository_path, repository_path + len + 1, rlen + 1);
1035
/* the 'strip_path' will be used whenever the CVS server gives us a
1036
* path to an 'rcs file'. the strip_path portion of these paths is
1037
* stripped off, leaving us with the working file.
1039
* NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1040
* (instead of log) it gives the 'real' RCS file path, which can be different
1041
* from the 'nominal' repository path because of symlinks in the server and
1042
* the like. See also the 'parse_file' routine
1044
strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
1046
if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
1048
debug(DEBUG_APPERROR, "strip_path overflow");
1052
debug(DEBUG_STATUS, "strip_path: %s", strip_path);
1055
static CvsFile * parse_file(const char * buff)
1059
int len = strlen(buff + 10);
1062
/* once a single file has been parsed ok we set this */
1065
/* chop the ",v" string and the "LF" */
1067
memcpy(fn, buff + 10, len);
1070
if (strncmp(fn, strip_path, strip_path_len) != 0)
1072
/* if the very first file fails the strip path,
1073
* then maybe we need to try for an alternate.
1074
* this will happen if symlinks are being used
1075
* on the server. our best guess is to look
1076
* for the final occurance of the repository
1077
* path in the filename and use that. it should work
1078
* except in the case where:
1079
* 1) the project has no files in the top-level directory
1080
* 2) the project has a directory with the same name as the project
1081
* 3) that directory sorts alphabetically before any other directory
1082
* in which case, you are scr**ed
1086
char * p = fn, *lastp = NULL;
1088
while ((p = strstr(p, repository_path)))
1093
int len = strlen(repository_path);
1094
memcpy(strip_path, fn, lastp - fn + len + 1);
1095
strip_path_len = lastp - fn + len + 1;
1096
strip_path[strip_path_len] = 0;
1097
debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
1102
/* FIXME: a subdirectory may have a different Repository path
1103
* than it's parent. we'll fail the above test since strip_path
1104
* is global for the entire checked out tree (recursively).
1106
* For now just ignore such files
1108
debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring",
1116
/* remove from beginning the 'strip_path' string */
1117
len -= strip_path_len;
1118
memmove(fn, fn + strip_path_len, len);
1121
/* check if file is in the 'Attic/' and remove it */
1122
if ((p = strrchr(fn, '/')) &&
1123
p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
1125
memmove(p - 5, p + 1, len - (p - fn + 1));
1130
debug(DEBUG_STATUS, "stripped filename %s", fn);
1132
retval = (CvsFile*)get_hash_object(file_hash, fn);
1136
if ((retval = create_cvsfile()))
1138
retval->filename = xstrdup(fn);
1139
put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
1143
debug(DEBUG_SYSERROR, "malloc failed");
1147
debug(DEBUG_STATUS, "new file: %s", retval->filename);
1151
debug(DEBUG_STATUS, "existing file: %s", retval->filename);
1157
PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
1159
PatchSet * retval = NULL, **find = NULL;
1160
int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
1162
if (!(retval = create_patch_set()))
1164
debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
1168
convert_date(&retval->date, dte);
1169
retval->author = get_string(author);
1170
retval->descr = xstrdup(log);
1171
retval->branch = get_string(branch);
1173
/* we are looking for a patchset suitable for holding this member.
1174
* this means two things:
1175
* 1) a patchset already containing an entry for the file is no good
1176
* 2) for two patchsets with same exact date/time, if they reference
1177
* the same file, we can properly order them. this primarily solves
1178
* the 'cvs import' problem and may not have general usefulness
1179
* because it would only work if the first member we consider is
1180
* present in the existing ps.
1183
list_add(&psm->link, retval->members.prev);
1185
find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
1188
list_del(&psm->link);
1190
if (*find != retval)
1192
debug(DEBUG_STATUS, "found existing patch set");
1194
if (bkcvs && strstr(retval->descr, "BKrev:"))
1196
free((*find)->descr);
1197
(*find)->descr = retval->descr;
1201
free(retval->descr);
1204
/* keep the minimum date of any member as the 'actual' date */
1205
if (retval->date < (*find)->date)
1206
(*find)->date = retval->date;
1208
/* expand the min_date/max_date window to help finding other members .
1209
* open the window by an extra margin determined by the fuzz factor
1211
if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
1213
(*find)->min_date = retval->date - timestamp_fuzz_factor;
1214
//debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1216
else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
1217
(*find)->max_date = retval->date + timestamp_fuzz_factor;
1224
debug(DEBUG_STATUS, "new patch set!");
1225
debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
1227
retval->min_date = retval->date - timestamp_fuzz_factor;
1228
retval->max_date = retval->date + timestamp_fuzz_factor;
1230
list_add(&retval->all_link, &all_patch_sets);
1237
static int get_branch_ext(char * buff, const char * rev, int * leaf)
1240
int len = strlen(rev);
1242
/* allow get_branch(buff, buff) without destroying contents */
1243
memmove(buff, rev, len);
1246
p = strrchr(buff, '.');
1257
static int get_branch(char * buff, const char * rev)
1259
return get_branch_ext(buff, rev, NULL);
1263
* the goal if this function is to determine what revision to assign to
1264
* the psm->pre_rev field. usually, the log file is strictly
1265
* reverse chronological, so rev is direct ancestor to psm,
1267
* This all breaks down at branch points however
1270
static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
1272
char pre[REV_STR_MAX], post[REV_STR_MAX];
1279
/* if psm was last rev. for file, it's either an
1280
* INITIAL, or first rev of a branch. to test if it's
1281
* the first rev of a branch, do get_branch twice -
1282
* this should be the bp.
1284
if (get_branch(post, psm->post_rev->rev) &&
1285
get_branch(pre, post))
1287
psm->pre_rev = file_get_revision(psm->file, pre);
1288
list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1292
set_psm_initial(psm);
1298
* is this canditate for 'pre' on the same branch as our 'post'?
1299
* this is the normal case
1301
if (!get_branch(pre, rev->rev))
1303
debug(DEBUG_APPERROR, "get_branch malformed input (1)");
1307
if (!get_branch(post, psm->post_rev->rev))
1309
debug(DEBUG_APPERROR, "get_branch malformed input (2)");
1313
if (strcmp(pre, post) == 0)
1320
/* branches don't match. new_psm must be head of branch,
1321
* so psm is oldest rev. on branch. or oldest
1322
* revision overall. if former, derive predecessor.
1323
* use get_branch to chop another rev. off of string.
1326
* There's also a weird case. it's possible to just re-number
1327
* a revision to any future revision. i.e. rev 1.9 becomes 2.0
1328
* It's not widely used. In those cases of discontinuity,
1329
* we end up stamping the predecessor as 'INITIAL' incorrectly
1332
if (!get_branch(pre, post))
1334
set_psm_initial(psm);
1338
psm->pre_rev = file_get_revision(psm->file, pre);
1339
list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
1342
static void check_print_patch_set(PatchSet * ps)
1347
/* the funk_factor overrides the restrict_tag_start and end */
1348
if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
1351
if (ps->funk_factor == FNK_HIDE_ALL)
1354
if (ps->psid <= restrict_tag_ps_start)
1356
if (ps->psid == restrict_tag_ps_start)
1357
debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
1362
if (ps->psid > restrict_tag_ps_end)
1366
if (restrict_date_start > 0 &&
1367
(ps->date < restrict_date_start ||
1368
(restrict_date_end > 0 && ps->date > restrict_date_end)))
1371
if (restrict_author && strcmp(restrict_author, ps->author) != 0)
1374
if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
1377
if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
1380
if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
1383
if (!list_empty(&show_patch_set_ranges))
1385
struct list_head * next = show_patch_set_ranges.next;
1387
while (next != &show_patch_set_ranges)
1389
PatchSetRange *range = list_entry(next, PatchSetRange, link);
1390
if (range->min_counter <= ps->psid &&
1391
ps->psid <= range->max_counter)
1398
if (next == &show_patch_set_ranges)
1404
char path[PATH_MAX];
1406
snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
1410
if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
1412
debug(DEBUG_SYSERROR, "can't open patch file %s", path);
1416
fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
1420
* If the summary_first option is in effect, there will be
1421
* two passes through the tree. the first with summary_first == 1
1422
* the second with summary_first == 2. if the option is not
1423
* in effect, there will be one pass with summary_first == 0
1425
* When the -s option is in effect, the show_patch_set_ranges
1426
* list will be non-empty.
1428
if (summary_first <= 1)
1429
print_patch_set(ps);
1430
if (do_diff && summary_first != 1)
1436
static void print_patch_set(PatchSet * ps)
1439
struct list_head * next;
1440
const char * funk = "";
1442
tm = localtime(&ps->date);
1443
next = ps->members.next;
1445
funk = fnk_descr[ps->funk_factor];
1447
/* this '---...' is different from the 28 hyphens that separate cvs log output */
1448
printf("---------------------\n");
1449
printf("PatchSet %d %s\n", ps->psid, funk);
1450
printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
1451
1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
1452
tm->tm_hour, tm->tm_min, tm->tm_sec);
1453
printf("Author: %s\n", ps->author);
1454
printf("Branch: %s\n", ps->branch);
1455
if (ps->ancestor_branch)
1456
printf("Ancestor branch: %s\n", ps->ancestor_branch);
1457
printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
1458
printf("Log:\n%s\n", ps->descr);
1459
printf("Members: \n");
1461
while (next != &ps->members)
1463
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1464
if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1465
funk = "(BEFORE START TAG)";
1466
else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1467
funk = "(AFTER END TAG)";
1471
printf("\t%s:%s->%s%s %s\n",
1472
psm->file->filename,
1473
psm->pre_rev ? psm->pre_rev->rev : "INITIAL",
1475
psm->post_rev->dead ? "(DEAD)": "",
1484
/* walk all the patchsets to assign monotonic psid,
1485
* and to establish branch ancestry
1487
static void assign_patchset_id(PatchSet * ps)
1490
* Ignore the 'BRANCH ADD' patchsets
1492
if (!ps->branch_add)
1495
ps->psid = ps_counter;
1497
if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
1499
PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
1503
put_hash_object(branch_heads, ps->branch, head_ps);
1506
determine_branch_ancestor(ps, head_ps);
1515
static int compare_rev_strings(const char * cr1, const char * cr2)
1517
char r1[REV_STR_MAX];
1518
char r2[REV_STR_MAX];
1519
char *s1 = r1, *s2 = r2;
1528
p1 = strchr(s1, '.');
1529
p2 = strchr(s2, '.');
1554
static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
1556
struct list_head * i;
1558
for (i = ps1->members.next; i != &ps1->members; i = i->next)
1560
PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
1561
struct list_head * j;
1563
for (j = ps2->members.next; j != &ps2->members; j = j->next)
1565
PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
1566
if (psm1->file == psm2->file)
1568
int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
1569
//debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1578
static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
1580
const PatchSet * ps1 = (const PatchSet *)v_ps1;
1581
const PatchSet * ps2 = (const PatchSet *)v_ps2;
1584
diff = ps1->date - ps2->date;
1586
return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
1589
static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
1591
const PatchSet * ps1 = (const PatchSet *)v_ps1;
1592
const PatchSet * ps2 = (const PatchSet *)v_ps2;
1597
/* We order by (author, descr, branch, members, date), but because of the fuzz factor
1598
* we treat times within a certain distance as equal IFF the author
1602
ret = strcmp(ps1->author, ps2->author);
1606
ret = strcmp(ps1->descr, ps2->descr);
1610
ret = strcmp(ps1->branch, ps2->branch);
1614
ret = compare_patch_sets_by_members(ps1, ps2);
1619
* one of ps1 or ps2 is new. the other should have the min_date
1620
* and max_date set to a window opened by the fuzz_factor
1622
if (ps1->min_date == 0)
1625
min = ps2->min_date;
1626
max = ps2->max_date;
1628
else if (ps2->min_date == 0)
1631
min = ps1->min_date;
1632
max = ps1->max_date;
1636
debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
1640
if (min < d && d < max)
1643
diff = ps1->date - ps2->date;
1645
return (diff < 0) ? -1 : 1;
1648
static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
1650
const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
1651
const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
1652
return compare_patch_sets_bytime(ps1, ps2);
1655
static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
1660
/* When doing a time-ordering of patchsets, we don't need to
1661
* fuzzy-match the time. We've already done fuzzy-matching so we
1662
* know that insertions are unique at this point.
1665
diff = ps1->date - ps2->date;
1667
return (diff < 0) ? -1 : 1;
1669
ret = compare_patch_sets_by_members(ps1, ps2);
1673
ret = strcmp(ps1->author, ps2->author);
1677
ret = strcmp(ps1->descr, ps2->descr);
1681
ret = strcmp(ps1->branch, ps2->branch);
1686
static int is_revision_metadata(const char * buff)
1691
if (!(p1 = strchr(buff, ':')))
1694
p2 = strchr(buff, ' ');
1701
/* lines have LF at end */
1702
if (len > 1 && buff[len - 2] == ';')
1708
static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
1710
struct list_head * next = ps->members.next;
1712
while (next != &ps->members)
1714
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1716
if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
1725
static int patch_set_affects_branch(PatchSet * ps, const char * branch)
1727
struct list_head * next;
1729
for (next = ps->members.next; next != &ps->members; next = next->next)
1731
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1734
* slight hack. if -r is specified, and this patchset
1735
* is 'before' the tag, but is FNK_SHOW_SOME, only
1736
* check if the 'after tag' revisions affect
1737
* the branch. this is especially important when
1738
* the tag is a branch point.
1740
if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1743
if (revision_affects_branch(psm->post_rev, branch))
1750
static void do_cvs_diff(PatchSet * ps)
1752
struct list_head * next;
1756
char use_rep_path[PATH_MAX];
1757
char esc_use_rep_path[PATH_MAX];
1763
* if cvs_direct is not in effect, and diff options are specified,
1764
* then we have to use diff instead of rdiff and we'll get a -p0
1765
* diff (instead of -p1) [in a manner of speaking]. So to make sure
1766
* that the add/remove diffs get generated likewise, we need to use
1767
* 'update' instead of 'co'
1769
* cvs_direct will always use diff (not rdiff), but will also always
1770
* generate -p1 diffs.
1772
if (diff_opts == NULL)
1777
sprintf(use_rep_path, "%s/", repository_path);
1778
/* the rep_path may contain characters that the shell will barf on */
1779
escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
1786
use_rep_path[0] = 0;
1787
esc_use_rep_path[0] = 0;
1790
for (next = ps->members.next; next != &ps->members; next = next->next)
1792
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
1793
char cmdbuff[PATH_MAX * 2+1];
1794
char esc_file[PATH_MAX];
1795
int ret, check_ret = 0;
1798
cmdbuff[PATH_MAX*2] = 0;
1800
/* the filename may contain characters that the shell will barf on */
1801
escape_filename(esc_file, PATH_MAX, psm->file->filename);
1804
* Check the patchset funk. we may not want to diff this particular file
1806
if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
1808
printf("Index: %s\n", psm->file->filename);
1809
printf("===================================================================\n");
1810
printf("*** Member not diffed, before start tag\n");
1813
else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
1815
printf("Index: %s\n", psm->file->filename);
1816
printf("===================================================================\n");
1817
printf("*** Member not diffed, after end tag\n");
1822
* When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1823
* or 'cvs update' to get the file, because cvs won't generate these diffs.
1824
* The problem is that this must be piped to diff, and so the resulting
1825
* diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1826
* sed is used to replace the '-' with the filename.
1828
* It's possible for pre_rev to be a 'dead' revision. This happens when a file
1829
* is added on a branch. post_rev will be dead dead for remove
1831
if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
1836
if (!psm->pre_rev || psm->pre_rev->dead)
1839
rev = psm->post_rev->rev;
1844
rev = psm->pre_rev->rev;
1849
/* cvs_rupdate does the pipe through diff thing internally */
1850
cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
1854
snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s -p -r %s %s%s | diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s%s|g'",
1855
compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
1856
cr?"":"-",cr?"-":"", cr?"2":"1",
1857
use_rep_path, psm->file->filename);
1862
/* a regular diff */
1865
cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
1869
/* 'cvs diff' exit status '1' is ok, just means files are different */
1870
if (strcmp(dtype, "diff") == 0)
1873
snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1874
compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev,
1875
esc_use_rep_path, esc_file);
1880
* my_system doesn't block signals the way system does.
1881
* if ctrl-c is pressed while in there, we probably exit
1882
* immediately and hope the shell has sent the signal
1883
* to all of the process group members
1885
if (cmdbuff[0] && (ret = my_system(cmdbuff)))
1887
int stat = WEXITSTATUS(ret);
1890
* cvs diff returns 1 in exit status for 'files are different'
1891
* so use a better method to check for failure
1893
if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
1895
debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
1902
static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
1906
/* The "revision" log line can include extra information
1907
* including who is locking the file --- strip that out.
1911
while (isdigit(*p) || *p == '.')
1915
return cvs_file_add_revision(file, rev_str);
1918
CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
1920
CvsFileRevision * rev;
1922
if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
1924
rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
1925
rev->rev = get_string(rev_str);
1929
rev->pre_psm = NULL;
1930
rev->post_psm = NULL;
1931
INIT_LIST_HEAD(&rev->branch_children);
1932
INIT_LIST_HEAD(&rev->tags);
1934
put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
1936
debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
1940
debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
1944
* note: we are guaranteed to get here at least once with 'have_branches' == 1.
1945
* we may pass through once before this, because of symbolic tags, then once
1946
* always when processing the actual revision logs
1948
* rev->branch will always be set to something, maybe "HEAD"
1950
if (!rev->branch && file->have_branches)
1952
char branch_str[REV_STR_MAX];
1954
/* in the cvs cvs repository (ccvs) there are tagged versions
1955
* that don't exist. let's mark every 'known to exist'
1960
/* determine the branch this revision was committed on */
1961
if (!get_branch(branch_str, rev->rev))
1963
debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
1967
rev->branch = (char*)get_hash_object(file->branches, branch_str);
1969
/* if there's no branch and it's not on the trunk, blab */
1972
if (get_branch(branch_str, branch_str))
1974
debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
1975
rev->branch = "#CVSPS_NO_BRANCH";
1979
rev->branch = "HEAD";
1983
debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
1989
CvsFile * create_cvsfile()
1991
CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
1995
f->revisions = create_hash_table(53);
1996
f->branches = create_hash_table(13);
1997
f->branches_sym = create_hash_table(13);
1998
f->symbols = create_hash_table(253);
1999
f->have_branches = 0;
2001
if (!f->revisions || !f->branches || !f->branches_sym)
2004
destroy_hash_table(f->branches, NULL);
2006
destroy_hash_table(f->revisions, NULL);
2014
static PatchSet * create_patch_set()
2016
PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
2020
INIT_LIST_HEAD(&ps->members);
2030
ps->funk_factor = 0;
2031
ps->ancestor_branch = NULL;
2032
CLEAR_LIST_NODE(&ps->collision_link);
2038
PatchSetMember * create_patch_set_member()
2040
PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
2041
psm->pre_rev = NULL;
2042
psm->post_rev = NULL;
2049
static PatchSetRange * create_patch_set_range()
2051
PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
2055
CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
2057
CvsFileRevision * rev;
2059
if (strcmp(r, "INITIAL") == 0)
2062
rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
2066
debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
2074
* Parse lines in the format:
2076
* <white space>tag_name: <rev>;
2078
* Handles both regular tags (these go into the symbols hash)
2079
* and magic-branch-tags (second to last node of revision is 0)
2080
* which go into branches and branches_sym hashes. Magic-branch
2081
* format is hidden in CVS everwhere except the 'cvs log' output.
2084
static void parse_sym(CvsFile * file, char * sym)
2086
char * tag = sym, *eot;
2087
int leaf, final_branch = -1;
2088
char rev[REV_STR_MAX];
2089
char rev2[REV_STR_MAX];
2091
while (*tag && isspace(*tag))
2097
eot = strchr(tag, ':');
2105
if (!get_branch_ext(rev, eot, &leaf))
2107
debug(DEBUG_APPERROR, "malformed revision");
2112
* get_branch_ext will leave final_branch alone
2113
* if there aren't enough '.' in string
2115
get_branch_ext(rev2, rev, &final_branch);
2117
if (final_branch == 0)
2119
snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
2120
debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
2122
cvs_file_add_branch(file, rev, tag);
2129
/* see cvs manual: what is this vendor tag? */
2130
if (is_vendor_branch(rev))
2131
cvs_file_add_branch(file, rev, tag);
2133
cvs_file_add_symbol(file, rev, tag);
2137
void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
2139
CvsFileRevision * rev;
2143
/* get a permanent storage string */
2144
char * tag_str = get_string(p_tag_str);
2146
debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
2147
rev = cvs_file_add_revision(file, rev_str);
2148
put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
2151
* check the global_symbols
2153
sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
2156
sym = (GlobalSymbol*)malloc(sizeof(*sym));
2159
INIT_LIST_HEAD(&sym->tags);
2161
put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
2164
tag = (Tag*)malloc(sizeof(*tag));
2168
list_add(&tag->global_link, &sym->tags);
2169
list_add(&tag->rev_link, &rev->tags);
2172
char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
2177
if (get_hash_object(file->branches, rev))
2179
debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s",
2180
rev, tag, file->filename);
2184
/* get permanent storage for the strings */
2185
new_tag = get_string(tag);
2186
new_rev = get_string(rev);
2188
put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
2189
put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
2195
* Resolve each global symbol to a PatchSet. This is
2196
* not necessarily doable, because tagging isn't
2197
* necessarily done to the project as a whole, and
2198
* it's possible that no tag is valid for all files
2199
* at a single point in time. We check for that
2202
* Implementation: the most recent PatchSet containing
2203
* a revision (post_rev) tagged by the symbol is considered
2204
* the 'tagged' PatchSet.
2207
static void resolve_global_symbols()
2209
struct hash_entry * he_sym;
2210
reset_hash_iterator(global_symbols);
2211
while ((he_sym = next_hash_entry(global_symbols)))
2213
GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
2215
struct list_head * next;
2217
debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
2220
* First pass, determine the most recent PatchSet with a
2221
* revision tagged with the symbolic tag. This is 'the'
2222
* patchset with the tag
2225
for (next = sym->tags.next; next != &sym->tags; next = next->next)
2227
Tag * tag = list_entry(next, Tag, global_link);
2228
CvsFileRevision * rev = tag->rev;
2230
/* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
2231
if (!rev->present || !rev->post_psm)
2233
struct list_head *tmp = next->prev;
2234
debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
2235
rev->rev, rev->file->filename);
2236
/* FIXME: memleak */
2242
ps = rev->post_psm->ps;
2244
if (!sym->ps || ps->date > sym->ps->date)
2248
/* convenience variable */
2253
debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
2259
/* check if this ps is one of the '-r' patchsets */
2260
if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
2261
restrict_tag_ps_start = ps->psid;
2263
/* the second -r implies -b */
2264
if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
2266
restrict_tag_ps_end = ps->psid;
2268
if (restrict_branch)
2270
if (strcmp(ps->branch, restrict_branch) != 0)
2272
debug(DEBUG_APPMSG1,
2273
"WARNING: -b option and second -r have conflicting branches: %s %s",
2274
restrict_branch, ps->branch);
2279
debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
2280
restrict_branch = ps->branch;
2286
* check if this is an invalid patchset,
2287
* check which members are invalid. determine
2288
* the funk factor etc.
2290
for (next = sym->tags.next; next != &sym->tags; next = next->next)
2292
Tag * tag = list_entry(next, Tag, global_link);
2293
CvsFileRevision * rev = tag->rev;
2294
CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
2300
* we want the 'tagged revision' to be valid until after
2301
* the date of the 'tagged patchset' or else there's something
2304
if (next_rev->post_psm->ps->date < ps->date)
2306
int flag = check_rev_funk(ps, next_rev);
2307
debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
2308
rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
2309
ps->tag_flags |= flag;
2315
static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
2317
/* special case the branch called 'HEAD' */
2318
if (strcmp(branch, "HEAD") == 0)
2320
/* look for only one '.' in rev */
2321
char * p = strchr(rev->rev, '.');
2322
if (p && !strchr(p + 1, '.'))
2327
char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
2331
char post_rev[REV_STR_MAX];
2332
char branch[REV_STR_MAX];
2333
int file_leaf, branch_leaf;
2335
strcpy(branch, branch_rev);
2337
/* first get the branch the file rev is on */
2338
if (get_branch_ext(post_rev, rev->rev, &file_leaf))
2340
branch_leaf = file_leaf;
2342
/* check against branch and all branch ancestor branches */
2345
debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
2346
if (strcmp(branch, post_rev) == 0)
2347
return (file_leaf <= branch_leaf);
2349
while(get_branch_ext(branch, branch, &branch_leaf));
2357
static int count_dots(const char * p)
2369
* When importing vendor sources, (apparently people do this)
2370
* the code is added on a 'vendor' branch, which, for some reason
2371
* doesn't use the magic-branch-tag format. Try to detect that now
2373
static int is_vendor_branch(const char * rev)
2375
return !(count_dots(rev)&1);
2378
void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
2380
/* check if a member for the same file already exists, if so
2381
* put this PatchSet on the collisions list
2383
struct list_head * next;
2384
for (next = ps->members.next; next != &ps->members; next = next->next)
2386
PatchSetMember * m = list_entry(next, PatchSetMember, link);
2387
if (m->file == psm->file && ps->collision_link.next == NULL)
2388
list_add(&ps->collision_link, &collisions);
2392
list_add(&psm->link, ps->members.prev);
2395
static void set_psm_initial(PatchSetMember * psm)
2397
psm->pre_rev = NULL;
2398
if (psm->post_rev->dead)
2401
* we expect a 'file xyz initially added on branch abc' here
2402
* but there can only be one such member in a given patchset
2404
if (psm->ps->branch_add)
2405
debug(DEBUG_APPMSG1, "WARNING: branch_add already set!");
2406
psm->ps->branch_add = 1;
2411
* look at all revisions starting at rev and going forward until
2412
* ps->date and see whether they are invalid or just funky.
2414
static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
2416
int retval = TAG_FUNKY;
2420
PatchSet * next_ps = rev->post_psm->ps;
2421
struct list_head * next;
2423
if (next_ps->date > ps->date)
2426
debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
2427
ps->date, next_ps->date, rev->rev, rev->branch);
2430
* If the ps->tag is one of the two possible '-r' tags
2431
* then the funkyness is even more important.
2433
* In the restrict_tag_start case, this next_ps is chronologically
2434
* before ps, but tagwise after, so set the funk_factor so it will
2437
* The restrict_tag_end case is similar, but backwards.
2439
* Start assuming the HIDE/SHOW_ALL case, we will determine
2440
* below if we have a split ps case
2442
if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
2443
next_ps->funk_factor = FNK_SHOW_ALL;
2444
if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
2445
next_ps->funk_factor = FNK_HIDE_ALL;
2448
* if all of the other members of this patchset are also 'after' the tag
2449
* then this is a 'funky' patchset w.r.t. the tag. however, if some are
2450
* before then the patchset is 'invalid' w.r.t. the tag, and we mark
2451
* the members individually with 'bad_funk' ,if this tag is the
2452
* '-r' tag. Then we can actually split the diff on this patchset
2454
for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
2456
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2457
if (before_tag(psm->post_rev, ps->tag))
2459
retval = TAG_INVALID;
2460
/* only set bad_funk for one of the -r tags */
2461
if (next_ps->funk_factor)
2464
next_ps->funk_factor =
2465
(next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
2467
debug(DEBUG_APPMSG1,
2468
"WARNING: Invalid PatchSet %d, Tag %s:\n"
2469
" %s:%s=after, %s:%s=before. Treated as 'before'",
2470
next_ps->psid, ps->tag,
2471
rev->file->filename, rev->rev,
2472
psm->post_rev->file->filename, psm->post_rev->rev);
2476
rev = rev_follow_branch(rev, ps->branch);
2482
/* determine if the revision is before the tag */
2483
static int before_tag(CvsFileRevision * rev, const char * tag)
2485
CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
2489
revision_affects_branch(rev, tagged_rev->branch) &&
2490
rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
2493
debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d",
2494
rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
2499
/* get the next revision from this one following branch if possible */
2500
/* FIXME: not sure if this needs to follow branches leading up to branches? */
2501
static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
2503
struct list_head * next;
2505
/* check for 'main line of inheritance' */
2506
if (strcmp(rev->branch, branch) == 0)
2507
return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
2509
/* look down branches */
2510
for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
2512
CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
2513
//debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2514
if (strcmp(next_rev->branch, branch) == 0)
2521
static void check_norc(int argc, char * argv[])
2526
if (strcmp(argv[i], "--norc") == 0)
2535
static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
2537
struct list_head * next;
2538
CvsFileRevision * rev;
2540
/* PatchSet 1 has no ancestor */
2544
/* HEAD branch patchsets have no ancestry, but callers should know that */
2545
if (strcmp(ps->branch, "HEAD") == 0)
2547
debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
2551
for (next = ps->members.next; next != &ps->members; next = next->next)
2553
PatchSetMember * psm = list_entry(next, PatchSetMember, link);
2557
/* the reason this is at all complicated has to do with a
2558
* branch off of a branch. it is possible (and indeed
2559
* likely) that some file would not have been modified
2560
* from the initial branch point to the branch-off-branch
2561
* point, and therefore the branch-off-branch point is
2562
* really branch-off-HEAD for that specific member (file).
2563
* in that case, rev->branch will say HEAD but we want
2564
* to know the symbolic name of the first branch
2565
* so we continue to look member after member until we find
2566
* the 'deepest' branching. deepest can actually be determined
2567
* by considering the revision currently indicated by
2568
* ps->ancestor_branch (by symbolic lookup) and rev->rev. the
2569
* one with more dots wins
2571
* also, the first commit in which a branch-off-branch is
2572
* mentioned may ONLY modify files never committed since
2573
* original branch-off-HEAD was created, so we have to keep
2574
* checking, ps after ps to be sure to get the deepest ancestor
2576
* note: rev is the pre-commit revision, not the post-commit
2578
if (!head_ps->ancestor_branch)
2580
else if (strcmp(ps->branch, rev->branch) == 0)
2582
else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
2585
/* branch_rev may not exist if the file was added on this branch for example */
2586
const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
2587
d1 = branch_rev ? count_dots(branch_rev) : 1;
2590
/* HACK: we sometimes pretend to derive from the import branch.
2591
* just don't do that. this is the easiest way to prevent...
2593
d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
2596
head_ps->ancestor_branch = rev->branch;
2598
//printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
2602
static void handle_collisions()
2604
struct list_head *next;
2605
for (next = collisions.next; next != &collisions; next = next->next)
2607
PatchSet * ps = list_entry(next, PatchSet, collision_link);
2608
printf("PatchSet %d has collisions\n", ps->psid);
2612
void walk_all_patch_sets(void (*action)(PatchSet *))
2614
struct list_head * next;;
2615
for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
2616
PatchSet * ps = list_entry(next, PatchSet, all_link);