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"
48 NEED_DATE_AUTHOR_STATE
,
53 struct hash_table
* file_hash
;
54 CvsServerCtx
* cvs_direct_ctx
;
55 char root_path
[PATH_MAX
];
56 char repository_path
[PATH_MAX
];
58 const char * tag_flag_descr
[] = {
65 const char * fnk_descr
[] = {
74 static int ps_counter
;
75 static void * ps_tree
;
76 static struct hash_table
* global_symbols
;
77 static char strip_path
[PATH_MAX
];
78 static int strip_path_len
;
79 static time_t cache_date
;
80 static int update_cache
;
81 static int ignore_cache
;
82 static int do_write_cache
;
83 static int statistics
;
84 static const char * test_log_file
;
85 static struct hash_table
* branch_heads
;
86 static struct list_head all_patch_sets
;
87 static struct list_head collisions
;
89 /* settable via options */
90 static int timestamp_fuzz_factor
= 300;
92 static const char * restrict_author
;
93 static int have_restrict_log
;
94 static regex_t restrict_log
;
95 static int have_restrict_file
;
96 static regex_t restrict_file
;
97 static time_t restrict_date_start
;
98 static time_t restrict_date_end
;
99 static const char * restrict_branch
;
100 static struct list_head show_patch_set_ranges
;
101 static int summary_first
;
102 static const char * norc
= "";
103 static const char * patch_set_dir
;
104 static const char * restrict_tag_start
;
105 static const char * restrict_tag_end
;
106 static int restrict_tag_ps_start
;
107 static int restrict_tag_ps_end
= INT_MAX
;
108 static const char * diff_opts
;
111 static int cvs_direct
;
113 static char compress_arg
[8];
114 static int track_branch_ancestry
;
116 static void check_norc(int, char *[]);
117 static int parse_args(int, char *[]);
118 static int parse_rc();
119 static void load_from_cvs();
120 static void init_paths();
121 static CvsFile
* build_file_by_name(const char *);
122 static CvsFile
* parse_rcs_file(const char *);
123 static CvsFile
* parse_working_file(const char *);
124 static CvsFileRevision
* parse_revision(CvsFile
* file
, char * rev_str
);
125 static void assign_pre_revision(PatchSetMember
*, CvsFileRevision
* rev
);
126 static void check_print_patch_set(PatchSet
*);
127 static void print_patch_set(PatchSet
*);
128 static void assign_patchset_id(PatchSet
*);
129 static int compare_rev_strings(const char *, const char *);
130 static int compare_patch_sets_by_members(const PatchSet
* ps1
, const PatchSet
* ps2
);
131 static int compare_patch_sets_bk(const void *, const void *);
132 static int compare_patch_sets(const void *, const void *);
133 static int compare_patch_sets_bytime_list(struct list_head
*, struct list_head
*);
134 static int compare_patch_sets_bytime(const PatchSet
*, const PatchSet
*);
135 static int is_revision_metadata(const char *);
136 static int patch_set_member_regex(PatchSet
* ps
, regex_t
* reg
);
137 static int patch_set_affects_branch(PatchSet
*, const char *);
138 static void do_cvs_diff(PatchSet
*);
139 static PatchSet
* create_patch_set();
140 static PatchSetRange
* create_patch_set_range();
141 static void parse_sym(CvsFile
*, char *);
142 static void resolve_global_symbols();
143 static int revision_affects_branch(CvsFileRevision
*, const char *);
144 static int is_vendor_branch(const char *);
145 static void set_psm_initial(PatchSetMember
* psm
);
146 static int check_rev_funk(PatchSet
*, CvsFileRevision
*);
147 static CvsFileRevision
* rev_follow_branch(CvsFileRevision
*, const char *);
148 static int before_tag(CvsFileRevision
* rev
, const char * tag
);
149 static void determine_branch_ancestor(PatchSet
* ps
, PatchSet
* head_ps
);
150 static void handle_collisions();
152 int main(int argc
, char *argv
[])
154 debuglvl
= DEBUG_APPERROR
|DEBUG_SYSERROR
|DEBUG_APPMSG1
;
156 INIT_LIST_HEAD(&show_patch_set_ranges
);
159 * we want to parse the rc first, so command line can override it
160 * but also, --norc should stop the rc from being processed, so
161 * we look for --norc explicitly first. Note: --norc in the rc
162 * file itself will prevent the cvs rc file from being used.
164 check_norc(argc
, argv
);
166 if (strlen(norc
) == 0 && parse_rc() < 0)
169 if (parse_args(argc
, argv
) < 0)
172 if (diff_opts
&& !cvs_direct
&& do_diff
)
174 debug(DEBUG_APPMSG1
, "\nWARNING: diff options are not supported by 'cvs rdiff'");
175 debug(DEBUG_APPMSG1
, " which is usually used to create diffs. 'cvs diff'");
176 debug(DEBUG_APPMSG1
, " will be used instead, but the resulting patches ");
177 debug(DEBUG_APPMSG1
, " will need to be applied using the '-p0' option");
178 debug(DEBUG_APPMSG1
, " to patch(1) (in the working directory), ");
179 debug(DEBUG_APPMSG1
, " instead of '-p1'\n");
182 file_hash
= create_hash_table(1023);
183 global_symbols
= create_hash_table(111);
184 branch_heads
= create_hash_table(1023);
185 INIT_LIST_HEAD(&all_patch_sets
);
186 INIT_LIST_HEAD(&collisions
);
188 /* this parses some of the CVS/ files, and initializes
189 * the repository_path and other variables
195 int save_fuzz_factor
= timestamp_fuzz_factor
;
197 /* the timestamp fuzz should only be in effect when loading from
198 * CVS, not re-fuzzed when loading from cache. This is a hack
199 * working around bad use of global variables
202 timestamp_fuzz_factor
= 0;
204 if ((cache_date
= read_cache()) < 0)
207 timestamp_fuzz_factor
= save_fuzz_factor
;
210 if (cvs_direct
&& (do_diff
|| (update_cache
&& !test_log_file
)))
211 cvs_direct_ctx
= open_cvs_server(root_path
, compress
);
220 //handle_collisions();
222 list_sort(&all_patch_sets
, compare_patch_sets_bytime_list
);
225 walk_all_patch_sets(assign_patchset_id
);
229 resolve_global_symbols();
232 write_cache(cache_date
);
235 print_statistics(ps_tree
);
237 /* check that the '-r' symbols (if specified) were resolved */
238 if (restrict_tag_start
&& restrict_tag_ps_start
== 0 &&
239 strcmp(restrict_tag_start
, "#CVSPS_EPOCH") != 0)
241 debug(DEBUG_APPERROR
, "symbol given with -r: %s: not found", restrict_tag_start
);
245 if (restrict_tag_end
&& restrict_tag_ps_end
== INT_MAX
)
247 debug(DEBUG_APPERROR
, "symbol given with second -r: %s: not found", restrict_tag_end
);
251 walk_all_patch_sets(check_print_patch_set
);
254 walk_all_patch_sets(check_print_patch_set
);
257 close_cvs_server(cvs_direct_ctx
);
262 static void load_from_cvs()
266 int state
= NEED_RCS_FILE
;
267 CvsFile
* file
= NULL
;
268 PatchSetMember
* psm
= NULL
;
270 char authbuff
[AUTH_STR_MAX
];
271 char cidbuff
[CID_STR_MAX
];
272 int logbufflen
= LOG_STR_MAX
+ 1;
273 char * logbuff
= malloc(logbufflen
);
278 char use_rep_buff
[PATH_MAX
];
283 debug(DEBUG_SYSERROR
, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen
);
287 if (!no_rlog
&& !test_log_file
&& cvs_check_cap(CAP_HAVE_RLOG
))
290 snprintf(use_rep_buff
, PATH_MAX
, "%s", repository_path
);
300 struct tm
* tm
= gmtime(&cache_date
);
301 strftime(date_str
, 64, "%d %b %Y %H:%M:%S %z", tm
);
303 /* this command asks for logs using two different date
304 * arguments, separated by ';' (see man rlog). The first
305 * gets all revisions more recent than date, the second
306 * gets a single revision no later than date, which combined
307 * get us all revisions that have occurred since last update
308 * and overlaps what we had before by exactly one revision,
309 * which is necessary to fill in the pre_rev stuff for a
312 snprintf(cmd
, BUFSIZ
, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg
, norc
, ltype
, date_str
, date_str
, use_rep_buff
);
317 snprintf(cmd
, BUFSIZ
, "cvs %s %s -q %s %s", compress_arg
, norc
, ltype
, use_rep_buff
);
320 debug(DEBUG_STATUS
, "******* USING CMD %s", cmd
);
322 cache_date
= time(NULL
);
324 /* FIXME: this is ugly, need to virtualize the accesses away from here */
326 cvsfp
= fopen(test_log_file
, "r");
327 else if (cvs_direct_ctx
)
328 cvsfp
= cvs_rlog_open(cvs_direct_ctx
, repository_path
, date_str
);
330 cvsfp
= popen(cmd
, "r");
334 debug(DEBUG_SYSERROR
, "can't open cvs pipe using command %s", cmd
);
342 tst
= cvs_rlog_fgets(buff
, BUFSIZ
, cvs_direct_ctx
);
344 tst
= fgets(buff
, BUFSIZ
, cvsfp
);
349 debug(DEBUG_STATUS
, "state: %d read line:%s", state
, buff
);
354 if (strncmp(buff
, "RCS file", 8) == 0) {
355 if ((file
= parse_rcs_file(buff
)) != NULL
)
358 state
= NEED_WORKING_FILE
;
361 case NEED_WORKING_FILE
:
362 if (strncmp(buff
, "Working file", 12) == 0) {
363 if ((file
= parse_working_file(buff
)))
366 state
= NEED_RCS_FILE
;
369 // Working file come just after RCS file. So reset state if it was not found
370 state
= NEED_RCS_FILE
;
374 if (strncmp(buff
, "symbolic names:", 15) == 0)
378 if (!isspace(buff
[0]))
380 /* see cvsps_types.h for commentary on have_branches */
381 file
->have_branches
= 1;
382 state
= NEED_START_LOG
;
385 parse_sym(file
, buff
);
388 if (strcmp(buff
, CVS_LOG_BOUNDARY
) == 0)
389 state
= NEED_REVISION
;
392 if (strncmp(buff
, "revision", 8) == 0)
394 char new_rev
[REV_STR_MAX
];
395 CvsFileRevision
* rev
;
397 strcpy(new_rev
, buff
+ 9);
401 * rev may already exist (think cvsps -u), in which
402 * case parse_revision is a hash lookup
404 rev
= parse_revision(file
, new_rev
);
407 * in the simple case, we are copying rev to psm->pre_rev
408 * (psm refers to last patch set processed at this point)
409 * since generally speaking the log is reverse chronological.
410 * This breaks down slightly when branches are introduced
413 assign_pre_revision(psm
, rev
);
416 * if this is a new revision, it will have no post_psm associated.
417 * otherwise we are (probably?) hitting the overlap in cvsps -u
421 psm
= rev
->post_psm
= create_patch_set_member();
424 state
= NEED_DATE_AUTHOR_STATE
;
428 /* we hit this in cvsps -u mode, we are now up-to-date
429 * w.r.t this particular file. skip all of the rest
430 * of the info (revs and logs) until we hit the next file
437 case NEED_DATE_AUTHOR_STATE
:
438 if (strncmp(buff
, "date:", 5) == 0)
442 strncpy(datebuff
, buff
+ 6, 19);
445 strcpy(authbuff
, "unknown");
446 p
= strstr(buff
, "author: ");
454 strzncpy(authbuff
, p
, op
- p
+ 1);
458 /* read the 'state' tag to see if this is a dead revision */
459 p
= strstr(buff
, "state: ");
466 if (strncmp(p
, "dead", MIN(4, op
- p
)) == 0)
467 psm
->post_rev
->dead
= 1;
471 p
= strstr(buff
, "commitid: ");
479 strzncpy(cidbuff
, p
, op
- p
+ 1);
487 if (strcmp(buff
, CVS_LOG_BOUNDARY
) == 0)
491 PatchSet
* ps
= get_patch_set(datebuff
, logbuff
, authbuff
, psm
->post_rev
->branch
, cidbuff
, psm
);
492 patch_set_add_member(ps
, psm
);
498 state
= NEED_REVISION
;
500 else if (strcmp(buff
, CVS_FILE_BOUNDARY
) == 0)
504 PatchSet
* ps
= get_patch_set(datebuff
, logbuff
, authbuff
, psm
->post_rev
->branch
, cidbuff
, psm
);
505 patch_set_add_member(ps
, psm
);
506 assign_pre_revision(psm
, NULL
);
514 state
= NEED_RCS_FILE
;
518 /* other "blahblah: information;" messages can
519 * follow the stuff we pay attention to
521 if (have_log
|| !is_revision_metadata(buff
))
523 /* If the log buffer is full, try to reallocate more. */
524 if (loglen
< logbufflen
)
526 int len
= strlen(buff
);
528 if (len
>= logbufflen
- loglen
)
530 debug(DEBUG_STATUS
, "reallocating logbufflen to %d bytes for file %s", logbufflen
, file
->filename
);
531 logbufflen
+= (len
>= LOG_STR_MAX
? (len
+1) : LOG_STR_MAX
);
532 char * newlogbuff
= realloc(logbuff
, logbufflen
);
533 if (newlogbuff
== NULL
)
535 debug(DEBUG_SYSERROR
, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen
);
538 logbuff
= newlogbuff
;
541 debug(DEBUG_STATUS
, "appending %s to log", buff
);
542 memcpy(logbuff
+ loglen
, buff
, len
);
550 debug(DEBUG_STATUS
, "ignoring unhandled info %s", buff
);
558 if (state
== NEED_SYMS
)
560 debug(DEBUG_APPERROR
, "Error: 'symbolic names' not found in log output.");
561 debug(DEBUG_APPERROR
, " Perhaps you should try running with --norc");
565 if (state
!= NEED_RCS_FILE
)
567 debug(DEBUG_APPERROR
, "Error: Log file parsing error. (%d) Use -v to debug", state
);
575 else if (cvs_direct_ctx
)
577 cvs_rlog_close(cvs_direct_ctx
);
581 if (pclose(cvsfp
) < 0)
583 debug(DEBUG_APPERROR
, "cvs rlog command exited with error. aborting");
589 static int usage(const char * str1
, const char * str2
)
592 debug(DEBUG_APPERROR
, "\nbad usage: %s %s\n", str1
, str2
);
594 debug(DEBUG_APPERROR
, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
595 debug(DEBUG_APPERROR
, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
596 debug(DEBUG_APPERROR
, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
597 debug(DEBUG_APPERROR
, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
598 debug(DEBUG_APPERROR
, " [--test-log <captured cvs log file>] [--bkcvs]");
599 debug(DEBUG_APPERROR
, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
600 debug(DEBUG_APPERROR
, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
601 debug(DEBUG_APPERROR
, " [-q] [-A] [<repository>]");
602 debug(DEBUG_APPERROR
, "");
603 debug(DEBUG_APPERROR
, "Where:");
604 debug(DEBUG_APPERROR
, " -h display this informative message");
605 debug(DEBUG_APPERROR
, " -x ignore (and rebuild) cvsps.cache file");
606 debug(DEBUG_APPERROR
, " -u update cvsps.cache file");
607 debug(DEBUG_APPERROR
, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
608 debug(DEBUG_APPERROR
, " -g generate diffs of the selected patch sets");
609 debug(DEBUG_APPERROR
, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
610 debug(DEBUG_APPERROR
, " -a <author> restrict output to patch sets created by author");
611 debug(DEBUG_APPERROR
, " -f <file> restrict output to patch sets involving file");
612 debug(DEBUG_APPERROR
, " -d <date1> -d <date2> if just one date specified, show");
613 debug(DEBUG_APPERROR
, " revisions newer than date1. If two dates specified,");
614 debug(DEBUG_APPERROR
, " show revisions between two dates.");
615 debug(DEBUG_APPERROR
, " -b <branch> restrict output to patch sets affecting history of branch");
616 debug(DEBUG_APPERROR
, " -l <regex> restrict output to patch sets matching <regex> in log message");
617 debug(DEBUG_APPERROR
, " -r <tag1> -r <tag2> if just one tag specified, show");
618 debug(DEBUG_APPERROR
, " revisions since tag1. If two tags specified, show");
619 debug(DEBUG_APPERROR
, " revisions between the two tags.");
620 debug(DEBUG_APPERROR
, " -p <directory> output patch sets to individual files in <directory>");
621 debug(DEBUG_APPERROR
, " -v show very verbose parsing messages");
622 debug(DEBUG_APPERROR
, " -t show some brief memory usage statistics");
623 debug(DEBUG_APPERROR
, " --norc when invoking cvs, ignore the .cvsrc file");
624 debug(DEBUG_APPERROR
, " --summary-first when multiple patch sets are shown, put all summaries first");
625 debug(DEBUG_APPERROR
, " --test-log <captured cvs log> supply a captured cvs log for testing");
626 debug(DEBUG_APPERROR
, " --diff-opts <option string> supply special set of options to diff");
627 debug(DEBUG_APPERROR
, " --bkcvs special hack for parsing the BK -> CVS log format");
628 debug(DEBUG_APPERROR
, " --no-rlog disable rlog (it's faulty in some setups)");
629 debug(DEBUG_APPERROR
, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
630 debug(DEBUG_APPERROR
, " --debuglvl <bitmask> enable various debug channels.");
631 debug(DEBUG_APPERROR
, " -Z <compression> A value 1-9 which specifies amount of compression");
632 debug(DEBUG_APPERROR
, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
633 debug(DEBUG_APPERROR
, " -q be quiet about warnings");
634 debug(DEBUG_APPERROR
, " -A track and report branch ancestry");
635 debug(DEBUG_APPERROR
, " <repository> apply cvsps to repository. overrides working directory");
636 debug(DEBUG_APPERROR
, "\ncvsps version %s\n", VERSION
);
641 static int parse_args(int argc
, char *argv
[])
646 if (strcmp(argv
[i
], "-z") == 0)
649 return usage("argument to -z missing", "");
651 timestamp_fuzz_factor
= atoi(argv
[i
++]);
655 if (strcmp(argv
[i
], "-g") == 0)
662 if (strcmp(argv
[i
], "-s") == 0)
664 PatchSetRange
* range
;
665 char * min_str
, * max_str
;
668 return usage("argument to -s missing", "");
670 min_str
= strtok(argv
[i
++], ",");
673 range
= create_patch_set_range();
675 max_str
= strrchr(min_str
, '-');
681 range
->min_counter
= atoi(min_str
);
684 range
->max_counter
= atoi(max_str
);
686 range
->max_counter
= INT_MAX
;
688 list_add(&range
->link
, show_patch_set_ranges
.prev
);
690 while ((min_str
= strtok(NULL
, ",")));
695 if (strcmp(argv
[i
], "-a") == 0)
698 return usage("argument to -a missing", "");
700 restrict_author
= argv
[i
++];
704 if (strcmp(argv
[i
], "-l") == 0)
709 return usage("argument to -l missing", "");
711 if ((err
= regcomp(&restrict_log
, argv
[i
++], REG_EXTENDED
|REG_NOSUB
)) != 0)
714 regerror(err
, &restrict_log
, errbuf
, 256);
715 return usage("bad regex to -l", errbuf
);
718 have_restrict_log
= 1;
723 if (strcmp(argv
[i
], "-f") == 0)
728 return usage("argument to -f missing", "");
730 if ((err
= regcomp(&restrict_file
, argv
[i
++], REG_EXTENDED
|REG_NOSUB
)) != 0)
733 regerror(err
, &restrict_file
, errbuf
, 256);
734 return usage("bad regex to -f", errbuf
);
737 have_restrict_file
= 1;
742 if (strcmp(argv
[i
], "-d") == 0)
747 return usage("argument to -d missing", "");
749 pt
= (restrict_date_start
== 0) ? &restrict_date_start
: &restrict_date_end
;
750 convert_date(pt
, argv
[i
++]);
754 if (strcmp(argv
[i
], "-r") == 0)
757 return usage("argument to -r missing", "");
759 if (restrict_tag_start
)
760 restrict_tag_end
= argv
[i
];
762 restrict_tag_start
= argv
[i
];
768 if (strcmp(argv
[i
], "-u") == 0)
775 if (strcmp(argv
[i
], "-x") == 0)
783 if (strcmp(argv
[i
], "-b") == 0)
786 return usage("argument to -b missing", "");
788 restrict_branch
= argv
[i
++];
789 /* Warn if the user tries to use TRUNK. Should eventually
790 * go away as TRUNK may be a valid branch within CVS
792 if (strcmp(restrict_branch
, "TRUNK") == 0)
793 debug(DEBUG_APPMSG1
, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
797 if (strcmp(argv
[i
], "-p") == 0)
800 return usage("argument to -p missing", "");
802 patch_set_dir
= argv
[i
++];
806 if (strcmp(argv
[i
], "-v") == 0)
813 if (strcmp(argv
[i
], "-t") == 0)
820 if (strcmp(argv
[i
], "--summary-first") == 0)
827 if (strcmp(argv
[i
], "-h") == 0)
828 return usage(NULL
, NULL
);
830 /* see special handling of --norc in main */
831 if (strcmp(argv
[i
], "--norc") == 0)
838 if (strcmp(argv
[i
], "--test-log") == 0)
841 return usage("argument to --test-log missing", "");
843 test_log_file
= argv
[i
++];
847 if (strcmp(argv
[i
], "--diff-opts") == 0)
850 return usage("argument to --diff-opts missing", "");
852 /* allow diff_opts to be turned off by making empty string
855 if (!strlen(argv
[i
]))
863 if (strcmp(argv
[i
], "--bkcvs") == 0)
870 if (strcmp(argv
[i
], "--no-rlog") == 0)
877 if (strcmp(argv
[i
], "--cvs-direct") == 0)
884 if (strcmp(argv
[i
], "--no-cvs-direct") == 0)
891 if (strcmp(argv
[i
], "--debuglvl") == 0)
894 return usage("argument to --debuglvl missing", "");
896 debuglvl
= atoi(argv
[i
++]);
900 if (strcmp(argv
[i
], "-Z") == 0)
903 return usage("argument to -Z", "");
905 compress
= atoi(argv
[i
++]);
907 if (compress
< 0 || compress
> 9)
908 return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv
[i
-1]);
913 snprintf(compress_arg
, 8, "-z%d", compress
);
917 if (strcmp(argv
[i
], "--root") == 0)
920 return usage("argument to --root missing", "");
922 strcpy(root_path
, argv
[i
++]);
926 if (strcmp(argv
[i
], "-q") == 0)
928 debuglvl
&= ~DEBUG_APPMSG1
;
933 if (strcmp(argv
[i
], "-A") == 0)
935 track_branch_ancestry
= 1;
940 if (argv
[i
][0] == '-')
941 return usage("invalid argument", argv
[i
]);
943 strcpy(repository_path
, argv
[i
++]);
949 static int parse_rc()
951 char rcfile
[PATH_MAX
];
953 snprintf(rcfile
, PATH_MAX
, "%s/cvspsrc", get_cvsps_dir());
954 if ((fp
= fopen(rcfile
, "r")))
957 while (fgets(buff
, BUFSIZ
, fp
))
966 p
= strchr(buff
, ' ');
970 argv
[2] = xstrdup(p
);
974 argv
[1] = xstrdup(buff
);
976 if (parse_args(argc
, argv
) < 0)
985 static void init_paths()
991 /* determine the CVSROOT. precedence:
993 * 2) working directory (if present)
994 * 3) environment variable CVSROOT
998 if (!(fp
= fopen("CVS/Root", "r")))
1002 debug(DEBUG_STATUS
, "Can't open CVS/Root");
1003 e
= getenv("CVSROOT");
1007 debug(DEBUG_APPERROR
, "cannot determine CVSROOT");
1011 strcpy(root_path
, e
);
1015 if (!fgets(root_path
, PATH_MAX
, fp
))
1017 debug(DEBUG_APPERROR
, "Error reading CVSROOT");
1023 /* chop the lf and optional trailing '/' */
1024 len
= strlen(root_path
) - 1;
1026 if (root_path
[len
- 1] == '/')
1027 root_path
[--len
] = 0;
1031 /* Determine the repository path, precedence:
1033 * 2) working directory
1036 if (!repository_path
[0])
1038 if (!(fp
= fopen("CVS/Repository", "r")))
1040 debug(DEBUG_SYSERROR
, "Can't open CVS/Repository");
1044 if (!fgets(repository_path
, PATH_MAX
, fp
))
1046 debug(DEBUG_APPERROR
, "Error reading repository path");
1050 chop(repository_path
);
1054 /* get the path portion of the root */
1055 p
= strrchr(root_path
, ':');
1062 /* some CVS have the CVSROOT string as part of the repository
1063 * string (initial substring). remove it.
1067 if (strncmp(p
, repository_path
, len
) == 0)
1069 int rlen
= strlen(repository_path
+ len
+ 1);
1070 memmove(repository_path
, repository_path
+ len
+ 1, rlen
+ 1);
1073 /* the 'strip_path' will be used whenever the CVS server gives us a
1074 * path to an 'rcs file'. the strip_path portion of these paths is
1075 * stripped off, leaving us with the working file.
1077 * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
1078 * (instead of log) it gives the 'real' RCS file path, which can be different
1079 * from the 'nominal' repository path because of symlinks in the server and
1080 * the like. See also the 'parse_rcs_file' routine
1082 strip_path_len
= snprintf(strip_path
, PATH_MAX
, "%s/%s/", p
, repository_path
);
1084 if (strip_path_len
< 0 || strip_path_len
>= PATH_MAX
)
1086 debug(DEBUG_APPERROR
, "strip_path overflow");
1090 debug(DEBUG_STATUS
, "strip_path: %s", strip_path
);
1093 static CvsFile
* parse_rcs_file(const char * buff
)
1096 int len
= strlen(buff
+ 10);
1099 /* once a single file has been parsed ok we set this */
1102 /* chop the ",v" string and the "LF" */
1104 memcpy(fn
, buff
+ 10, len
);
1107 if (strncmp(fn
, strip_path
, strip_path_len
) != 0)
1109 /* if the very first file fails the strip path,
1110 * then maybe we need to try for an alternate.
1111 * this will happen if symlinks are being used
1112 * on the server. our best guess is to look
1113 * for the final occurance of the repository
1114 * path in the filename and use that. it should work
1115 * except in the case where:
1116 * 1) the project has no files in the top-level directory
1117 * 2) the project has a directory with the same name as the project
1118 * 3) that directory sorts alphabetically before any other directory
1119 * in which case, you are scr**ed
1123 char * p
= fn
, *lastp
= NULL
;
1125 while ((p
= strstr(p
, repository_path
)))
1130 int len
= strlen(repository_path
);
1131 memcpy(strip_path
, fn
, lastp
- fn
+ len
+ 1);
1132 strip_path_len
= lastp
- fn
+ len
+ 1;
1133 strip_path
[strip_path_len
] = 0;
1134 debug(DEBUG_APPMSG1
, "NOTICE: used alternate strip path %s", strip_path
);
1139 /* FIXME: a subdirectory may have a different Repository path
1140 * than it's parent. we'll fail the above test since strip_path
1141 * is global for the entire checked out tree (recursively).
1143 * For now just ignore such files
1145 debug(DEBUG_APPMSG1
, "WARNING: file %s doesn't match strip_path %s. ignoring",
1153 /* remove from beginning the 'strip_path' string */
1154 len
-= strip_path_len
;
1155 memmove(fn
, fn
+ strip_path_len
, len
);
1158 /* check if file is in the 'Attic/' and remove it */
1159 if ((p
= strrchr(fn
, '/')) &&
1160 p
- fn
>= 5 && strncmp(p
- 5, "Attic", 5) == 0)
1162 memmove(p
- 5, p
+ 1, len
- (p
- fn
+ 1));
1167 debug(DEBUG_STATUS
, "stripped filename %s", fn
);
1169 return build_file_by_name(fn
);
1172 static CvsFile
* parse_working_file(const char * buff
)
1175 int len
= strlen(buff
+ 14);
1179 memcpy(fn
, buff
+ 14, len
);
1182 debug(DEBUG_STATUS
, "working filename %s", fn
);
1184 return build_file_by_name(fn
);
1187 static CvsFile
* build_file_by_name(const char * fn
)
1191 retval
= (CvsFile
*)get_hash_object(file_hash
, fn
);
1195 if ((retval
= create_cvsfile()))
1197 retval
->filename
= xstrdup(fn
);
1198 put_hash_object_ex(file_hash
, retval
->filename
, retval
, HT_NO_KEYCOPY
, NULL
, NULL
);
1202 debug(DEBUG_SYSERROR
, "malloc failed");
1206 debug(DEBUG_STATUS
, "new file: %s", retval
->filename
);
1210 debug(DEBUG_STATUS
, "existing file: %s", retval
->filename
);
1216 PatchSet
* get_patch_set(const char * dte
, const char * log
, const char * author
, const char * branch
, const char *commitid
, PatchSetMember
* psm
)
1218 PatchSet
* retval
= NULL
, **find
= NULL
;
1219 int (*cmp1
)(const void *,const void*) = (bkcvs
) ? compare_patch_sets_bk
: compare_patch_sets
;
1221 if (!(retval
= create_patch_set()))
1223 debug(DEBUG_SYSERROR
, "malloc failed for PatchSet");
1227 convert_date(&retval
->date
, dte
);
1228 retval
->author
= get_string(author
);
1229 retval
->commitid
= get_string(commitid
);
1230 retval
->descr
= xstrdup(log
);
1231 retval
->branch
= get_string(branch
);
1233 /* we are looking for a patchset suitable for holding this member.
1234 * this means two things:
1235 * 1) a patchset already containing an entry for the file is no good
1236 * 2) for two patchsets with same exact date/time, if they reference
1237 * the same file, we can properly order them. this primarily solves
1238 * the 'cvs import' problem and may not have general usefulness
1239 * because it would only work if the first member we consider is
1240 * present in the existing ps.
1243 list_add(&psm
->link
, retval
->members
.prev
);
1245 find
= (PatchSet
**)tsearch(retval
, &ps_tree
, cmp1
);
1248 list_del(&psm
->link
);
1250 if (*find
!= retval
)
1252 debug(DEBUG_STATUS
, "found existing patch set");
1254 if (bkcvs
&& strstr(retval
->descr
, "BKrev:"))
1256 free((*find
)->descr
);
1257 (*find
)->descr
= retval
->descr
;
1261 free(retval
->descr
);
1264 /* keep the minimum date of any member as the 'actual' date */
1265 if (retval
->date
< (*find
)->date
)
1266 (*find
)->date
= retval
->date
;
1268 /* expand the min_date/max_date window to help finding other members .
1269 * open the window by an extra margin determined by the fuzz factor
1271 if (retval
->date
- timestamp_fuzz_factor
< (*find
)->min_date
)
1273 (*find
)->min_date
= retval
->date
- timestamp_fuzz_factor
;
1274 //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
1276 else if (retval
->date
+ timestamp_fuzz_factor
> (*find
)->max_date
)
1277 (*find
)->max_date
= retval
->date
+ timestamp_fuzz_factor
;
1284 debug(DEBUG_STATUS
, "new patch set!");
1285 debug(DEBUG_STATUS
, "%s %s %s %s", retval
->author
, retval
->descr
, retval
->commitid
, dte
);
1287 retval
->min_date
= retval
->date
- timestamp_fuzz_factor
;
1288 retval
->max_date
= retval
->date
+ timestamp_fuzz_factor
;
1290 list_add(&retval
->all_link
, &all_patch_sets
);
1297 static int get_branch_ext(char * buff
, const char * rev
, int * leaf
)
1300 int len
= strlen(rev
);
1302 /* allow get_branch(buff, buff) without destroying contents */
1303 memmove(buff
, rev
, len
);
1306 p
= strrchr(buff
, '.');
1317 static int get_branch(char * buff
, const char * rev
)
1319 return get_branch_ext(buff
, rev
, NULL
);
1323 * the goal if this function is to determine what revision to assign to
1324 * the psm->pre_rev field. usually, the log file is strictly
1325 * reverse chronological, so rev is direct ancestor to psm,
1327 * This all breaks down at branch points however
1330 static void assign_pre_revision(PatchSetMember
* psm
, CvsFileRevision
* rev
)
1332 char pre
[REV_STR_MAX
], post
[REV_STR_MAX
];
1339 /* if psm was last rev. for file, it's either an
1340 * INITIAL, or first rev of a branch. to test if it's
1341 * the first rev of a branch, do get_branch twice -
1342 * this should be the bp.
1344 if (get_branch(post
, psm
->post_rev
->rev
) &&
1345 get_branch(pre
, post
))
1347 psm
->pre_rev
= file_get_revision(psm
->file
, pre
);
1348 list_add(&psm
->post_rev
->link
, &psm
->pre_rev
->branch_children
);
1352 set_psm_initial(psm
);
1358 * is this canditate for 'pre' on the same branch as our 'post'?
1359 * this is the normal case
1361 if (!get_branch(pre
, rev
->rev
))
1363 debug(DEBUG_APPERROR
, "get_branch malformed input (1)");
1367 if (!get_branch(post
, psm
->post_rev
->rev
))
1369 debug(DEBUG_APPERROR
, "get_branch malformed input (2)");
1373 if (strcmp(pre
, post
) == 0)
1380 /* branches don't match. new_psm must be head of branch,
1381 * so psm is oldest rev. on branch. or oldest
1382 * revision overall. if former, derive predecessor.
1383 * use get_branch to chop another rev. off of string.
1386 * There's also a weird case. it's possible to just re-number
1387 * a revision to any future revision. i.e. rev 1.9 becomes 2.0
1388 * It's not widely used. In those cases of discontinuity,
1389 * we end up stamping the predecessor as 'INITIAL' incorrectly
1392 if (!get_branch(pre
, post
))
1394 set_psm_initial(psm
);
1398 psm
->pre_rev
= file_get_revision(psm
->file
, pre
);
1399 list_add(&psm
->post_rev
->link
, &psm
->pre_rev
->branch_children
);
1402 static void check_print_patch_set(PatchSet
* ps
)
1407 /* the funk_factor overrides the restrict_tag_start and end */
1408 if (ps
->funk_factor
== FNK_SHOW_SOME
|| ps
->funk_factor
== FNK_SHOW_ALL
)
1411 if (ps
->funk_factor
== FNK_HIDE_ALL
)
1414 if (ps
->psid
<= restrict_tag_ps_start
)
1416 if (ps
->psid
== restrict_tag_ps_start
)
1417 debug(DEBUG_STATUS
, "PatchSet %d matches tag %s.", ps
->psid
, restrict_tag_start
);
1422 if (ps
->psid
> restrict_tag_ps_end
)
1426 if (restrict_date_start
> 0 &&
1427 (ps
->date
< restrict_date_start
||
1428 (restrict_date_end
> 0 && ps
->date
> restrict_date_end
)))
1431 if (restrict_author
&& strcmp(restrict_author
, ps
->author
) != 0)
1434 if (have_restrict_log
&& regexec(&restrict_log
, ps
->descr
, 0, NULL
, 0) != 0)
1437 if (have_restrict_file
&& !patch_set_member_regex(ps
, &restrict_file
))
1440 if (restrict_branch
&& !patch_set_affects_branch(ps
, restrict_branch
))
1443 if (!list_empty(&show_patch_set_ranges
))
1445 struct list_head
* next
= show_patch_set_ranges
.next
;
1447 while (next
!= &show_patch_set_ranges
)
1449 PatchSetRange
*range
= list_entry(next
, PatchSetRange
, link
);
1450 if (range
->min_counter
<= ps
->psid
&&
1451 ps
->psid
<= range
->max_counter
)
1458 if (next
== &show_patch_set_ranges
)
1464 char path
[PATH_MAX
];
1466 snprintf(path
, PATH_MAX
, "%s/%d.patch", patch_set_dir
, ps
->psid
);
1470 if (open(path
, O_WRONLY
|O_TRUNC
|O_CREAT
, 0666) < 0)
1472 debug(DEBUG_SYSERROR
, "can't open patch file %s", path
);
1476 fprintf(stderr
, "Directing PatchSet %d to file %s\n", ps
->psid
, path
);
1480 * If the summary_first option is in effect, there will be
1481 * two passes through the tree. the first with summary_first == 1
1482 * the second with summary_first == 2. if the option is not
1483 * in effect, there will be one pass with summary_first == 0
1485 * When the -s option is in effect, the show_patch_set_ranges
1486 * list will be non-empty.
1488 if (summary_first
<= 1)
1489 print_patch_set(ps
);
1490 if (do_diff
&& summary_first
!= 1)
1496 static void print_patch_set(PatchSet
* ps
)
1499 struct list_head
* next
;
1500 const char * funk
= "";
1502 tm
= localtime(&ps
->date
);
1503 next
= ps
->members
.next
;
1505 funk
= fnk_descr
[ps
->funk_factor
];
1507 /* this '---...' is different from the 28 hyphens that separate cvs log output */
1508 printf("---------------------\n");
1509 printf("PatchSet %d %s\n", ps
->psid
, funk
);
1510 printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
1511 1900 + tm
->tm_year
, tm
->tm_mon
+ 1, tm
->tm_mday
,
1512 tm
->tm_hour
, tm
->tm_min
, tm
->tm_sec
);
1513 printf("Author: %s\n", ps
->author
);
1514 printf("Branch: %s\n", ps
->branch
);
1515 if (ps
->ancestor_branch
)
1516 printf("Ancestor branch: %s\n", ps
->ancestor_branch
);
1517 printf("Tag: %s %s\n", ps
->tag
? ps
->tag
: "(none)", tag_flag_descr
[ps
->tag_flags
]);
1518 printf("Log:\n%s\n", ps
->descr
);
1519 printf("Members: \n");
1521 while (next
!= &ps
->members
)
1523 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
1524 if (ps
->funk_factor
== FNK_SHOW_SOME
&& psm
->bad_funk
)
1525 funk
= "(BEFORE START TAG)";
1526 else if (ps
->funk_factor
== FNK_HIDE_SOME
&& !psm
->bad_funk
)
1527 funk
= "(AFTER END TAG)";
1531 printf("\t%s:%s->%s%s %s\n",
1532 psm
->file
->filename
,
1533 psm
->pre_rev
? psm
->pre_rev
->rev
: "INITIAL",
1535 psm
->post_rev
->dead
? "(DEAD)": "",
1544 /* walk all the patchsets to assign monotonic psid,
1545 * and to establish branch ancestry
1547 static void assign_patchset_id(PatchSet
* ps
)
1550 * Ignore the 'BRANCH ADD' patchsets
1552 if (!ps
->branch_add
)
1555 ps
->psid
= ps_counter
;
1557 if (track_branch_ancestry
&& strcmp(ps
->branch
, "HEAD") != 0)
1559 PatchSet
* head_ps
= (PatchSet
*)get_hash_object(branch_heads
, ps
->branch
);
1563 put_hash_object(branch_heads
, ps
->branch
, head_ps
);
1566 determine_branch_ancestor(ps
, head_ps
);
1575 static int compare_rev_strings(const char * cr1
, const char * cr2
)
1577 char r1
[REV_STR_MAX
];
1578 char r2
[REV_STR_MAX
];
1579 char *s1
= r1
, *s2
= r2
;
1588 p1
= strchr(s1
, '.');
1589 p2
= strchr(s2
, '.');
1614 static int compare_patch_sets_by_members(const PatchSet
* ps1
, const PatchSet
* ps2
)
1616 struct list_head
* i
;
1618 for (i
= ps1
->members
.next
; i
!= &ps1
->members
; i
= i
->next
)
1620 PatchSetMember
* psm1
= list_entry(i
, PatchSetMember
, link
);
1621 struct list_head
* j
;
1623 for (j
= ps2
->members
.next
; j
!= &ps2
->members
; j
= j
->next
)
1625 PatchSetMember
* psm2
= list_entry(j
, PatchSetMember
, link
);
1626 if (psm1
->file
== psm2
->file
)
1628 int ret
= compare_rev_strings(psm1
->post_rev
->rev
, psm2
->post_rev
->rev
);
1629 //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
1638 static int compare_patch_sets_bk(const void * v_ps1
, const void * v_ps2
)
1640 const PatchSet
* ps1
= (const PatchSet
*)v_ps1
;
1641 const PatchSet
* ps2
= (const PatchSet
*)v_ps2
;
1644 diff
= ps1
->date
- ps2
->date
;
1646 return (diff
< 0) ? -1 : ((diff
> 0) ? 1 : 0);
1649 static int compare_patch_sets(const void * v_ps1
, const void * v_ps2
)
1651 const PatchSet
* ps1
= (const PatchSet
*)v_ps1
;
1652 const PatchSet
* ps2
= (const PatchSet
*)v_ps2
;
1657 /* We order by (author, descr, branch, commitid, members, date), but because of the fuzz factor
1658 * we treat times within a certain distance as equal IFF the author
1662 ret
= strcmp(ps1
->author
, ps2
->author
);
1666 ret
= strcmp(ps1
->descr
, ps2
->descr
);
1670 ret
= strcmp(ps1
->branch
, ps2
->branch
);
1674 ret
= strcmp(ps1
->commitid
, ps2
->commitid
);
1678 ret
= compare_patch_sets_by_members(ps1
, ps2
);
1683 * one of ps1 or ps2 is new. the other should have the min_date
1684 * and max_date set to a window opened by the fuzz_factor
1686 if (ps1
->min_date
== 0)
1689 min
= ps2
->min_date
;
1690 max
= ps2
->max_date
;
1692 else if (ps2
->min_date
== 0)
1695 min
= ps1
->min_date
;
1696 max
= ps1
->max_date
;
1700 debug(DEBUG_APPERROR
, "how can we have both patchsets pre-existing?");
1704 if (min
< d
&& d
< max
)
1707 diff
= ps1
->date
- ps2
->date
;
1709 return (diff
< 0) ? -1 : 1;
1712 static int compare_patch_sets_bytime_list(struct list_head
* l1
, struct list_head
* l2
)
1714 const PatchSet
*ps1
= list_entry(l1
, PatchSet
, all_link
);
1715 const PatchSet
*ps2
= list_entry(l2
, PatchSet
, all_link
);
1716 return compare_patch_sets_bytime(ps1
, ps2
);
1719 static int compare_patch_sets_bytime(const PatchSet
* ps1
, const PatchSet
* ps2
)
1724 /* When doing a time-ordering of patchsets, we don't need to
1725 * fuzzy-match the time. We've already done fuzzy-matching so we
1726 * know that insertions are unique at this point.
1729 diff
= ps1
->date
- ps2
->date
;
1731 return (diff
< 0) ? -1 : 1;
1733 ret
= compare_patch_sets_by_members(ps1
, ps2
);
1737 ret
= strcmp(ps1
->author
, ps2
->author
);
1741 ret
= strcmp(ps1
->descr
, ps2
->descr
);
1745 ret
= strcmp(ps1
->branch
, ps2
->branch
);
1749 ret
= strcmp(ps1
->commitid
, ps2
->commitid
);
1754 static int is_revision_metadata(const char * buff
)
1759 if (!(p1
= strchr(buff
, ':')))
1762 p2
= strchr(buff
, ' ');
1769 /* lines have LF at end */
1770 if (len
> 1 && buff
[len
- 2] == ';')
1776 static int patch_set_member_regex(PatchSet
* ps
, regex_t
* reg
)
1778 struct list_head
* next
= ps
->members
.next
;
1780 while (next
!= &ps
->members
)
1782 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
1784 if (regexec(&restrict_file
, psm
->file
->filename
, 0, NULL
, 0) == 0)
1793 static int patch_set_affects_branch(PatchSet
* ps
, const char * branch
)
1795 struct list_head
* next
;
1797 for (next
= ps
->members
.next
; next
!= &ps
->members
; next
= next
->next
)
1799 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
1802 * slight hack. if -r is specified, and this patchset
1803 * is 'before' the tag, but is FNK_SHOW_SOME, only
1804 * check if the 'after tag' revisions affect
1805 * the branch. this is especially important when
1806 * the tag is a branch point.
1808 if (ps
->funk_factor
== FNK_SHOW_SOME
&& psm
->bad_funk
)
1811 if (revision_affects_branch(psm
->post_rev
, branch
))
1818 static void do_cvs_diff(PatchSet
* ps
)
1820 struct list_head
* next
;
1824 char use_rep_path
[PATH_MAX
];
1825 char esc_use_rep_path
[PATH_MAX
];
1831 * if cvs_direct is not in effect, and diff options are specified,
1832 * then we have to use diff instead of rdiff and we'll get a -p0
1833 * diff (instead of -p1) [in a manner of speaking]. So to make sure
1834 * that the add/remove diffs get generated likewise, we need to use
1835 * 'update' instead of 'co'
1837 * cvs_direct will always use diff (not rdiff), but will also always
1838 * generate -p1 diffs.
1840 if (diff_opts
== NULL
)
1845 sprintf(use_rep_path
, "%s/", repository_path
);
1846 /* the rep_path may contain characters that the shell will barf on */
1847 escape_filename(esc_use_rep_path
, PATH_MAX
, use_rep_path
);
1854 use_rep_path
[0] = 0;
1855 esc_use_rep_path
[0] = 0;
1858 for (next
= ps
->members
.next
; next
!= &ps
->members
; next
= next
->next
)
1860 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
1861 char cmdbuff
[PATH_MAX
* 2+1];
1862 char esc_file
[PATH_MAX
];
1863 int ret
, check_ret
= 0;
1866 cmdbuff
[PATH_MAX
*2] = 0;
1868 /* the filename may contain characters that the shell will barf on */
1869 escape_filename(esc_file
, PATH_MAX
, psm
->file
->filename
);
1872 * Check the patchset funk. we may not want to diff this particular file
1874 if (ps
->funk_factor
== FNK_SHOW_SOME
&& psm
->bad_funk
)
1876 printf("Index: %s\n", psm
->file
->filename
);
1877 printf("===================================================================\n");
1878 printf("*** Member not diffed, before start tag\n");
1881 else if (ps
->funk_factor
== FNK_HIDE_SOME
&& !psm
->bad_funk
)
1883 printf("Index: %s\n", psm
->file
->filename
);
1884 printf("===================================================================\n");
1885 printf("*** Member not diffed, after end tag\n");
1890 * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
1891 * or 'cvs update' to get the file, because cvs won't generate these diffs.
1892 * The problem is that this must be piped to diff, and so the resulting
1893 * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
1894 * sed is used to replace the '-' with the filename.
1896 * It's possible for pre_rev to be a 'dead' revision. This happens when a file
1897 * is added on a branch. post_rev will be dead dead for remove
1899 if (!psm
->pre_rev
|| psm
->pre_rev
->dead
|| psm
->post_rev
->dead
)
1904 if (!psm
->pre_rev
|| psm
->pre_rev
->dead
)
1907 rev
= psm
->post_rev
->rev
;
1912 rev
= psm
->pre_rev
->rev
;
1917 /* cvs_rupdate does the pipe through diff thing internally */
1918 cvs_rupdate(cvs_direct_ctx
, repository_path
, psm
->file
->filename
, rev
, cr
, dopts
);
1922 snprintf(cmdbuff
, PATH_MAX
* 2, "cvs %s %s %s -p -r %s %s%s | diff %s %s /dev/null %s | sed -e '%s s|^\\([+-][+-][+-]\\) -|\\1 %s%s|g'",
1923 compress_arg
, norc
, utype
, rev
, esc_use_rep_path
, esc_file
, dopts
,
1924 cr
?"":"-",cr
?"-":"", cr
?"2":"1",
1925 use_rep_path
, psm
->file
->filename
);
1930 /* a regular diff */
1933 cvs_diff(cvs_direct_ctx
, repository_path
, psm
->file
->filename
, psm
->pre_rev
->rev
, psm
->post_rev
->rev
, dopts
);
1937 /* 'cvs diff' exit status '1' is ok, just means files are different */
1938 if (strcmp(dtype
, "diff") == 0)
1941 snprintf(cmdbuff
, PATH_MAX
* 2, "cvs %s %s %s %s -r %s -r %s %s%s",
1942 compress_arg
, norc
, dtype
, dopts
, psm
->pre_rev
->rev
, psm
->post_rev
->rev
,
1943 esc_use_rep_path
, esc_file
);
1948 * my_system doesn't block signals the way system does.
1949 * if ctrl-c is pressed while in there, we probably exit
1950 * immediately and hope the shell has sent the signal
1951 * to all of the process group members
1953 if (cmdbuff
[0] && (ret
= my_system(cmdbuff
)))
1955 int stat
= WEXITSTATUS(ret
);
1958 * cvs diff returns 1 in exit status for 'files are different'
1959 * so use a better method to check for failure
1961 if (stat
< 0 || stat
> check_ret
|| WIFSIGNALED(ret
))
1963 debug(DEBUG_APPERROR
, "system command returned non-zero exit status: %d: aborting", stat
);
1970 static CvsFileRevision
* parse_revision(CvsFile
* file
, char * rev_str
)
1974 /* The "revision" log line can include extra information
1975 * including who is locking the file --- strip that out.
1979 while (isdigit(*p
) || *p
== '.')
1983 return cvs_file_add_revision(file
, rev_str
);
1986 CvsFileRevision
* cvs_file_add_revision(CvsFile
* file
, const char * rev_str
)
1988 CvsFileRevision
* rev
;
1990 if (!(rev
= (CvsFileRevision
*)get_hash_object(file
->revisions
, rev_str
)))
1992 rev
= (CvsFileRevision
*)calloc(1, sizeof(*rev
));
1993 rev
->rev
= get_string(rev_str
);
1997 rev
->pre_psm
= NULL
;
1998 rev
->post_psm
= NULL
;
1999 INIT_LIST_HEAD(&rev
->branch_children
);
2000 INIT_LIST_HEAD(&rev
->tags
);
2002 put_hash_object_ex(file
->revisions
, rev
->rev
, rev
, HT_NO_KEYCOPY
, NULL
, NULL
);
2004 debug(DEBUG_STATUS
, "added revision %s to file %s", rev_str
, file
->filename
);
2008 debug(DEBUG_STATUS
, "found revision %s to file %s", rev_str
, file
->filename
);
2012 * note: we are guaranteed to get here at least once with 'have_branches' == 1.
2013 * we may pass through once before this, because of symbolic tags, then once
2014 * always when processing the actual revision logs
2016 * rev->branch will always be set to something, maybe "HEAD"
2018 if (!rev
->branch
&& file
->have_branches
)
2020 char branch_str
[REV_STR_MAX
];
2022 /* in the cvs cvs repository (ccvs) there are tagged versions
2023 * that don't exist. let's mark every 'known to exist'
2028 /* determine the branch this revision was committed on */
2029 if (!get_branch(branch_str
, rev
->rev
))
2031 debug(DEBUG_APPERROR
, "invalid rev format %s", rev
->rev
);
2035 rev
->branch
= (char*)get_hash_object(file
->branches
, branch_str
);
2037 /* if there's no branch and it's not on the trunk, blab */
2040 if (get_branch(branch_str
, branch_str
))
2042 debug(DEBUG_APPMSG1
, "WARNING: revision %s of file %s on unnamed branch", rev
->rev
, rev
->file
->filename
);
2043 rev
->branch
= "#CVSPS_NO_BRANCH";
2047 rev
->branch
= "HEAD";
2051 debug(DEBUG_STATUS
, "revision %s of file %s on branch %s", rev
->rev
, rev
->file
->filename
, rev
->branch
);
2057 CvsFile
* create_cvsfile()
2059 CvsFile
* f
= (CvsFile
*)calloc(1, sizeof(*f
));
2063 f
->revisions
= create_hash_table(53);
2064 f
->branches
= create_hash_table(13);
2065 f
->branches_sym
= create_hash_table(13);
2066 f
->symbols
= create_hash_table(253);
2067 f
->have_branches
= 0;
2069 if (!f
->revisions
|| !f
->branches
|| !f
->branches_sym
)
2072 destroy_hash_table(f
->branches
, NULL
);
2074 destroy_hash_table(f
->revisions
, NULL
);
2082 static PatchSet
* create_patch_set()
2084 PatchSet
* ps
= (PatchSet
*)calloc(1, sizeof(*ps
));;
2088 INIT_LIST_HEAD(&ps
->members
);
2099 ps
->funk_factor
= 0;
2100 ps
->ancestor_branch
= NULL
;
2101 CLEAR_LIST_NODE(&ps
->collision_link
);
2107 PatchSetMember
* create_patch_set_member()
2109 PatchSetMember
* psm
= (PatchSetMember
*)calloc(1, sizeof(*psm
));
2110 psm
->pre_rev
= NULL
;
2111 psm
->post_rev
= NULL
;
2118 static PatchSetRange
* create_patch_set_range()
2120 PatchSetRange
* psr
= (PatchSetRange
*)calloc(1, sizeof(*psr
));
2124 CvsFileRevision
* file_get_revision(CvsFile
* file
, const char * r
)
2126 CvsFileRevision
* rev
;
2128 if (strcmp(r
, "INITIAL") == 0)
2131 rev
= (CvsFileRevision
*)get_hash_object(file
->revisions
, r
);
2135 debug(DEBUG_APPERROR
, "request for non-existent rev %s in file %s", r
, file
->filename
);
2143 * Parse lines in the format:
2145 * <white space>tag_name: <rev>;
2147 * Handles both regular tags (these go into the symbols hash)
2148 * and magic-branch-tags (second to last node of revision is 0)
2149 * which go into branches and branches_sym hashes. Magic-branch
2150 * format is hidden in CVS everwhere except the 'cvs log' output.
2153 static void parse_sym(CvsFile
* file
, char * sym
)
2155 char * tag
= sym
, *eot
;
2156 int leaf
, final_branch
= -1;
2157 char rev
[REV_STR_MAX
];
2158 char rev2
[REV_STR_MAX
];
2160 while (*tag
&& isspace(*tag
))
2166 eot
= strchr(tag
, ':');
2174 if (!get_branch_ext(rev
, eot
, &leaf
))
2176 if (strcmp(tag
, "TRUNK") == 0)
2178 debug(DEBUG_STATUS
, "ignoring the TRUNK branch/tag");
2181 debug(DEBUG_APPERROR
, "malformed revision");
2186 * get_branch_ext will leave final_branch alone
2187 * if there aren't enough '.' in string
2189 get_branch_ext(rev2
, rev
, &final_branch
);
2191 if (final_branch
== 0)
2193 snprintf(rev
, REV_STR_MAX
, "%s.%d", rev2
, leaf
);
2194 debug(DEBUG_STATUS
, "got sym: %s for %s", tag
, rev
);
2196 cvs_file_add_branch(file
, rev
, tag
);
2203 /* see cvs manual: what is this vendor tag? */
2204 if (is_vendor_branch(rev
))
2205 cvs_file_add_branch(file
, rev
, tag
);
2207 cvs_file_add_symbol(file
, rev
, tag
);
2211 void cvs_file_add_symbol(CvsFile
* file
, const char * rev_str
, const char * p_tag_str
)
2213 CvsFileRevision
* rev
;
2217 /* get a permanent storage string */
2218 char * tag_str
= get_string(p_tag_str
);
2220 debug(DEBUG_STATUS
, "adding symbol to file: %s %s->%s", file
->filename
, tag_str
, rev_str
);
2221 rev
= cvs_file_add_revision(file
, rev_str
);
2222 put_hash_object_ex(file
->symbols
, tag_str
, rev
, HT_NO_KEYCOPY
, NULL
, NULL
);
2225 * check the global_symbols
2227 sym
= (GlobalSymbol
*)get_hash_object(global_symbols
, tag_str
);
2230 sym
= (GlobalSymbol
*)malloc(sizeof(*sym
));
2233 INIT_LIST_HEAD(&sym
->tags
);
2235 put_hash_object_ex(global_symbols
, sym
->tag
, sym
, HT_NO_KEYCOPY
, NULL
, NULL
);
2238 tag
= (Tag
*)malloc(sizeof(*tag
));
2242 list_add(&tag
->global_link
, &sym
->tags
);
2243 list_add(&tag
->rev_link
, &rev
->tags
);
2246 char * cvs_file_add_branch(CvsFile
* file
, const char * rev
, const char * tag
)
2251 if (get_hash_object(file
->branches
, rev
))
2253 debug(DEBUG_STATUS
, "attempt to add existing branch %s:%s to %s",
2254 rev
, tag
, file
->filename
);
2258 /* get permanent storage for the strings */
2259 new_tag
= get_string(tag
);
2260 new_rev
= get_string(rev
);
2262 put_hash_object_ex(file
->branches
, new_rev
, new_tag
, HT_NO_KEYCOPY
, NULL
, NULL
);
2263 put_hash_object_ex(file
->branches_sym
, new_tag
, new_rev
, HT_NO_KEYCOPY
, NULL
, NULL
);
2269 * Resolve each global symbol to a PatchSet. This is
2270 * not necessarily doable, because tagging isn't
2271 * necessarily done to the project as a whole, and
2272 * it's possible that no tag is valid for all files
2273 * at a single point in time. We check for that
2276 * Implementation: the most recent PatchSet containing
2277 * a revision (post_rev) tagged by the symbol is considered
2278 * the 'tagged' PatchSet.
2281 static void resolve_global_symbols()
2283 struct hash_entry
* he_sym
;
2284 reset_hash_iterator(global_symbols
);
2285 while ((he_sym
= next_hash_entry(global_symbols
)))
2287 GlobalSymbol
* sym
= (GlobalSymbol
*)he_sym
->he_obj
;
2289 struct list_head
* next
;
2291 debug(DEBUG_STATUS
, "resolving global symbol %s", sym
->tag
);
2294 * First pass, determine the most recent PatchSet with a
2295 * revision tagged with the symbolic tag. This is 'the'
2296 * patchset with the tag
2299 for (next
= sym
->tags
.next
; next
!= &sym
->tags
; next
= next
->next
)
2301 Tag
* tag
= list_entry(next
, Tag
, global_link
);
2302 CvsFileRevision
* rev
= tag
->rev
;
2304 /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
2305 if (!rev
->present
|| !rev
->post_psm
)
2307 struct list_head
*tmp
= next
->prev
;
2308 debug(DEBUG_APPERROR
, "revision %s of file %s is tagged but not present",
2309 rev
->rev
, rev
->file
->filename
);
2310 /* FIXME: memleak */
2316 ps
= rev
->post_psm
->ps
;
2318 if (!sym
->ps
|| ps
->date
> sym
->ps
->date
)
2322 /* convenience variable */
2327 debug(DEBUG_APPERROR
, "no patchset for tag %s", sym
->tag
);
2333 /* check if this ps is one of the '-r' patchsets */
2334 if (restrict_tag_start
&& strcmp(restrict_tag_start
, ps
->tag
) == 0)
2335 restrict_tag_ps_start
= ps
->psid
;
2337 /* the second -r implies -b */
2338 if (restrict_tag_end
&& strcmp(restrict_tag_end
, ps
->tag
) == 0)
2340 restrict_tag_ps_end
= ps
->psid
;
2342 if (restrict_branch
)
2344 if (strcmp(ps
->branch
, restrict_branch
) != 0)
2346 debug(DEBUG_APPMSG1
,
2347 "WARNING: -b option and second -r have conflicting branches: %s %s",
2348 restrict_branch
, ps
->branch
);
2353 debug(DEBUG_APPMSG1
, "NOTICE: implicit branch restriction set to %s", ps
->branch
);
2354 restrict_branch
= ps
->branch
;
2360 * check if this is an invalid patchset,
2361 * check which members are invalid. determine
2362 * the funk factor etc.
2364 for (next
= sym
->tags
.next
; next
!= &sym
->tags
; next
= next
->next
)
2366 Tag
* tag
= list_entry(next
, Tag
, global_link
);
2367 CvsFileRevision
* rev
= tag
->rev
;
2368 CvsFileRevision
* next_rev
= rev_follow_branch(rev
, ps
->branch
);
2374 * we want the 'tagged revision' to be valid until after
2375 * the date of the 'tagged patchset' or else there's something
2378 if (next_rev
->post_psm
->ps
->date
< ps
->date
)
2380 int flag
= check_rev_funk(ps
, next_rev
);
2381 debug(DEBUG_STATUS
, "file %s revision %s tag %s: TAG VIOLATION %s",
2382 rev
->file
->filename
, rev
->rev
, sym
->tag
, tag_flag_descr
[flag
]);
2383 ps
->tag_flags
|= flag
;
2389 static int revision_affects_branch(CvsFileRevision
* rev
, const char * branch
)
2391 /* special case the branch called 'HEAD' */
2392 if (strcmp(branch
, "HEAD") == 0)
2394 /* look for only one '.' in rev */
2395 char * p
= strchr(rev
->rev
, '.');
2396 if (p
&& !strchr(p
+ 1, '.'))
2401 char * branch_rev
= (char*)get_hash_object(rev
->file
->branches_sym
, branch
);
2405 char post_rev
[REV_STR_MAX
];
2406 char branch
[REV_STR_MAX
];
2407 int file_leaf
, branch_leaf
;
2409 strcpy(branch
, branch_rev
);
2411 /* first get the branch the file rev is on */
2412 if (get_branch_ext(post_rev
, rev
->rev
, &file_leaf
))
2414 branch_leaf
= file_leaf
;
2416 /* check against branch and all branch ancestor branches */
2419 debug(DEBUG_STATUS
, "check %s against %s for %s", branch
, post_rev
, rev
->file
->filename
);
2420 if (strcmp(branch
, post_rev
) == 0)
2421 return (file_leaf
<= branch_leaf
);
2423 while(get_branch_ext(branch
, branch
, &branch_leaf
));
2431 static int count_dots(const char * p
)
2443 * When importing vendor sources, (apparently people do this)
2444 * the code is added on a 'vendor' branch, which, for some reason
2445 * doesn't use the magic-branch-tag format. Try to detect that now
2447 static int is_vendor_branch(const char * rev
)
2449 return !(count_dots(rev
)&1);
2452 void patch_set_add_member(PatchSet
* ps
, PatchSetMember
* psm
)
2454 /* check if a member for the same file already exists, if so
2455 * put this PatchSet on the collisions list
2457 struct list_head
* next
;
2458 for (next
= ps
->members
.next
; next
!= &ps
->members
; next
= next
->next
)
2460 PatchSetMember
* m
= list_entry(next
, PatchSetMember
, link
);
2461 if (m
->file
== psm
->file
) {
2462 int order
= compare_rev_strings(psm
->post_rev
->rev
, m
->post_rev
->rev
);
2465 * Same revision too? Add it to the collision list
2466 * if it isn't already.
2469 if (ps
->collision_link
.next
== NULL
)
2470 list_add(&ps
->collision_link
, &collisions
);
2475 * If this is an older revision than the one we already have
2476 * in this patchset, just ignore it
2482 * This is a newer one, remove the old one
2489 list_add(&psm
->link
, ps
->members
.prev
);
2492 static void set_psm_initial(PatchSetMember
* psm
)
2494 psm
->pre_rev
= NULL
;
2495 if (psm
->post_rev
->dead
)
2498 * We expect a 'file xyz initially added on branch abc' here.
2499 * There can only be several such member in a given patchset,
2500 * since cvs only includes the file basename in the log message.
2502 psm
->ps
->branch_add
= 1;
2507 * look at all revisions starting at rev and going forward until
2508 * ps->date and see whether they are invalid or just funky.
2510 static int check_rev_funk(PatchSet
* ps
, CvsFileRevision
* rev
)
2512 int retval
= TAG_FUNKY
;
2516 PatchSet
* next_ps
= rev
->post_psm
->ps
;
2517 struct list_head
* next
;
2519 if (next_ps
->date
> ps
->date
)
2522 debug(DEBUG_STATUS
, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
2523 ps
->date
, next_ps
->date
, rev
->rev
, rev
->branch
);
2526 * If the ps->tag is one of the two possible '-r' tags
2527 * then the funkyness is even more important.
2529 * In the restrict_tag_start case, this next_ps is chronologically
2530 * before ps, but tagwise after, so set the funk_factor so it will
2533 * The restrict_tag_end case is similar, but backwards.
2535 * Start assuming the HIDE/SHOW_ALL case, we will determine
2536 * below if we have a split ps case
2538 if (restrict_tag_start
&& strcmp(ps
->tag
, restrict_tag_start
) == 0)
2539 next_ps
->funk_factor
= FNK_SHOW_ALL
;
2540 if (restrict_tag_end
&& strcmp(ps
->tag
, restrict_tag_end
) == 0)
2541 next_ps
->funk_factor
= FNK_HIDE_ALL
;
2544 * if all of the other members of this patchset are also 'after' the tag
2545 * then this is a 'funky' patchset w.r.t. the tag. however, if some are
2546 * before then the patchset is 'invalid' w.r.t. the tag, and we mark
2547 * the members individually with 'bad_funk' ,if this tag is the
2548 * '-r' tag. Then we can actually split the diff on this patchset
2550 for (next
= next_ps
->members
.next
; next
!= &next_ps
->members
; next
= next
->next
)
2552 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
2553 if (before_tag(psm
->post_rev
, ps
->tag
))
2555 retval
= TAG_INVALID
;
2556 /* only set bad_funk for one of the -r tags */
2557 if (next_ps
->funk_factor
)
2560 next_ps
->funk_factor
=
2561 (next_ps
->funk_factor
== FNK_SHOW_ALL
) ? FNK_SHOW_SOME
: FNK_HIDE_SOME
;
2563 debug(DEBUG_APPMSG1
,
2564 "WARNING: Invalid PatchSet %d, Tag %s:\n"
2565 " %s:%s=after, %s:%s=before. Treated as 'before'",
2566 next_ps
->psid
, ps
->tag
,
2567 rev
->file
->filename
, rev
->rev
,
2568 psm
->post_rev
->file
->filename
, psm
->post_rev
->rev
);
2572 rev
= rev_follow_branch(rev
, ps
->branch
);
2578 /* determine if the revision is before the tag */
2579 static int before_tag(CvsFileRevision
* rev
, const char * tag
)
2581 CvsFileRevision
* tagged_rev
= (CvsFileRevision
*)get_hash_object(rev
->file
->symbols
, tag
);
2585 revision_affects_branch(rev
, tagged_rev
->branch
) &&
2586 rev
->post_psm
->ps
->date
<= tagged_rev
->post_psm
->ps
->date
)
2589 debug(DEBUG_STATUS
, "before_tag: %s %s %s %s %d",
2590 rev
->file
->filename
, tag
, rev
->rev
, tagged_rev
? tagged_rev
->rev
: "N/A", retval
);
2595 /* get the next revision from this one following branch if possible */
2596 /* FIXME: not sure if this needs to follow branches leading up to branches? */
2597 static CvsFileRevision
* rev_follow_branch(CvsFileRevision
* rev
, const char * branch
)
2599 struct list_head
* next
;
2601 /* check for 'main line of inheritance' */
2602 if (strcmp(rev
->branch
, branch
) == 0)
2603 return rev
->pre_psm
? rev
->pre_psm
->post_rev
: NULL
;
2605 /* look down branches */
2606 for (next
= rev
->branch_children
.next
; next
!= &rev
->branch_children
; next
= next
->next
)
2608 CvsFileRevision
* next_rev
= list_entry(next
, CvsFileRevision
, link
);
2609 //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
2610 if (strcmp(next_rev
->branch
, branch
) == 0)
2617 static void check_norc(int argc
, char * argv
[])
2622 if (strcmp(argv
[i
], "--norc") == 0)
2631 static void determine_branch_ancestor(PatchSet
* ps
, PatchSet
* head_ps
)
2633 struct list_head
* next
;
2634 CvsFileRevision
* rev
;
2636 /* PatchSet 1 has no ancestor */
2640 /* HEAD branch patchsets have no ancestry, but callers should know that */
2641 if (strcmp(ps
->branch
, "HEAD") == 0)
2643 debug(DEBUG_APPMSG1
, "WARNING: no branch ancestry for HEAD");
2647 for (next
= ps
->members
.next
; next
!= &ps
->members
; next
= next
->next
)
2649 PatchSetMember
* psm
= list_entry(next
, PatchSetMember
, link
);
2653 /* the reason this is at all complicated has to do with a
2654 * branch off of a branch. it is possible (and indeed
2655 * likely) that some file would not have been modified
2656 * from the initial branch point to the branch-off-branch
2657 * point, and therefore the branch-off-branch point is
2658 * really branch-off-HEAD for that specific member (file).
2659 * in that case, rev->branch will say HEAD but we want
2660 * to know the symbolic name of the first branch
2661 * so we continue to look member after member until we find
2662 * the 'deepest' branching. deepest can actually be determined
2663 * by considering the revision currently indicated by
2664 * ps->ancestor_branch (by symbolic lookup) and rev->rev. the
2665 * one with more dots wins
2667 * also, the first commit in which a branch-off-branch is
2668 * mentioned may ONLY modify files never committed since
2669 * original branch-off-HEAD was created, so we have to keep
2670 * checking, ps after ps to be sure to get the deepest ancestor
2672 * note: rev is the pre-commit revision, not the post-commit
2674 if (!head_ps
->ancestor_branch
)
2676 else if (strcmp(ps
->branch
, rev
->branch
) == 0)
2678 else if (strcmp(head_ps
->ancestor_branch
, "HEAD") == 0)
2681 /* branch_rev may not exist if the file was added on this branch for example */
2682 const char * branch_rev
= (char *)get_hash_object(rev
->file
->branches_sym
, head_ps
->ancestor_branch
);
2683 d1
= branch_rev
? count_dots(branch_rev
) : 1;
2686 /* HACK: we sometimes pretend to derive from the import branch.
2687 * just don't do that. this is the easiest way to prevent...
2689 d2
= (strcmp(rev
->rev
, "1.1.1.1") == 0) ? 0 : count_dots(rev
->rev
);
2692 head_ps
->ancestor_branch
= rev
->branch
;
2694 //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
2698 static void handle_collisions()
2700 struct list_head
*next
;
2701 for (next
= collisions
.next
; next
!= &collisions
; next
= next
->next
)
2703 PatchSet
* ps
= list_entry(next
, PatchSet
, collision_link
);
2704 printf("PatchSet %d has collisions\n", ps
->psid
);
2708 void walk_all_patch_sets(void (*action
)(PatchSet
*))
2710 struct list_head
* next
;;
2711 for (next
= all_patch_sets
.next
; next
!= &all_patch_sets
; next
= next
->next
) {
2712 PatchSet
* ps
= list_entry(next
, PatchSet
, all_link
);