PageRenderTime 61ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 1ms

/cvsps.c

https://github.com/jamesblackburn/cvsps
C | 2697 lines | 1894 code | 418 blank | 385 comment | 472 complexity | 59115bef20254ad0543839ec9ac4a915 MD5 | raw file
Possible License(s): GPL-2.0
  1. /*
  2. * Copyright 2001, 2002, 2003 David Mansfield and Cobite, Inc.
  3. * See COPYING file for license information
  4. */
  5. #include <stdio.h>
  6. #include <stdlib.h>
  7. #include <string.h>
  8. #include <limits.h>
  9. #include <unistd.h>
  10. #include <search.h>
  11. #include <time.h>
  12. #include <ctype.h>
  13. #include <sys/stat.h>
  14. #include <sys/types.h>
  15. #include <fcntl.h>
  16. #include <regex.h>
  17. #include <sys/wait.h> /* for WEXITSTATUS - see system(3) */
  18. #include <cbtcommon/hash.h>
  19. #include <cbtcommon/list.h>
  20. #include <cbtcommon/text_util.h>
  21. #include <cbtcommon/debug.h>
  22. #include <cbtcommon/rcsid.h>
  23. #include "cache.h"
  24. #include "cvsps_types.h"
  25. #include "cvsps.h"
  26. #include "util.h"
  27. #include "stats.h"
  28. #include "cap.h"
  29. #include "cvs_direct.h"
  30. #include "list_sort.h"
  31. RCSID("$Id: cvsps.c,v 4.106 2005/05/26 03:39:29 david Exp $");
  32. #define CVS_LOG_BOUNDARY "----------------------------\n"
  33. #define CVS_FILE_BOUNDARY "=============================================================================\n"
  34. enum
  35. {
  36. NEED_RCS_FILE,
  37. NEED_WORKING_FILE,
  38. NEED_SYMS,
  39. NEED_EOS,
  40. NEED_START_LOG,
  41. NEED_REVISION,
  42. NEED_DATE_AUTHOR_STATE,
  43. NEED_EOM
  44. };
  45. /* true globals */
  46. struct hash_table * file_hash;
  47. CvsServerCtx * cvs_direct_ctx;
  48. char root_path[PATH_MAX];
  49. char repository_path[PATH_MAX];
  50. const char * tag_flag_descr[] = {
  51. "",
  52. "**FUNKY**",
  53. "**INVALID**",
  54. "**INVALID**"
  55. };
  56. const char * fnk_descr[] = {
  57. "",
  58. "FNK_SHOW_SOME",
  59. "FNK_SHOW_ALL",
  60. "FNK_HIDE_ALL",
  61. "FNK_HIDE_SOME"
  62. };
  63. /* static globals */
  64. static int ps_counter;
  65. static void * ps_tree;
  66. static struct hash_table * global_symbols;
  67. static char strip_path[PATH_MAX];
  68. static int strip_path_len;
  69. static time_t cache_date;
  70. static int update_cache;
  71. static int ignore_cache;
  72. static int do_write_cache;
  73. static int statistics;
  74. static const char * test_log_file;
  75. static struct hash_table * branch_heads;
  76. static struct list_head all_patch_sets;
  77. static struct list_head collisions;
  78. /* settable via options */
  79. static int timestamp_fuzz_factor = 300;
  80. static int do_diff;
  81. static const char * restrict_author;
  82. static int have_restrict_log;
  83. static regex_t restrict_log;
  84. static int have_restrict_file;
  85. static regex_t restrict_file;
  86. static time_t restrict_date_start;
  87. static time_t restrict_date_end;
  88. static const char * restrict_branch;
  89. static struct list_head show_patch_set_ranges;
  90. static int summary_first;
  91. static const char * norc = "";
  92. static const char * patch_set_dir;
  93. static const char * restrict_tag_start;
  94. static const char * restrict_tag_end;
  95. static int restrict_tag_ps_start;
  96. static int restrict_tag_ps_end = INT_MAX;
  97. static const char * diff_opts;
  98. static int bkcvs;
  99. static int no_rlog;
  100. static int cvs_direct;
  101. static int compress;
  102. static char compress_arg[8];
  103. static int track_branch_ancestry;
  104. static void check_norc(int, char *[]);
  105. static int parse_args(int, char *[]);
  106. static int parse_rc();
  107. static void load_from_cvs();
  108. static void init_paths();
  109. static CvsFile * build_file_by_name(const char *);
  110. static CvsFile * parse_rcs_file(const char *);
  111. static CvsFile * parse_working_file(const char *);
  112. static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str);
  113. static void assign_pre_revision(PatchSetMember *, CvsFileRevision * rev);
  114. static void check_print_patch_set(PatchSet *);
  115. static void print_patch_set(PatchSet *);
  116. static void assign_patchset_id(PatchSet *);
  117. static int compare_rev_strings(const char *, const char *);
  118. static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2);
  119. static int compare_patch_sets_bk(const void *, const void *);
  120. static int compare_patch_sets(const void *, const void *);
  121. static int compare_patch_sets_bytime_list(struct list_head *, struct list_head *);
  122. static int compare_patch_sets_bytime(const PatchSet *, const PatchSet *);
  123. static int is_revision_metadata(const char *);
  124. static int patch_set_member_regex(PatchSet * ps, regex_t * reg);
  125. static int patch_set_affects_branch(PatchSet *, const char *);
  126. static void do_cvs_diff(PatchSet *);
  127. static PatchSet * create_patch_set();
  128. static PatchSetRange * create_patch_set_range();
  129. static void parse_sym(CvsFile *, char *);
  130. static void resolve_global_symbols();
  131. static int revision_affects_branch(CvsFileRevision *, const char *) __attribute__ ((noinline));;
  132. static int is_vendor_branch(const char *);
  133. static void set_psm_initial(PatchSetMember * psm);
  134. static int check_rev_funk(PatchSet *, CvsFileRevision *);
  135. static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);
  136. static int before_tag(CvsFileRevision * rev, const char * tag) __attribute__ ((noinline));;
  137. static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);
  138. static void handle_collisions();
  139. int main(int argc, char *argv[])
  140. {
  141. debuglvl = DEBUG_APPERROR|DEBUG_SYSERROR|DEBUG_APPMSG1;
  142. INIT_LIST_HEAD(&show_patch_set_ranges);
  143. /*
  144. * we want to parse the rc first, so command line can override it
  145. * but also, --norc should stop the rc from being processed, so
  146. * we look for --norc explicitly first. Note: --norc in the rc
  147. * file itself will prevent the cvs rc file from being used.
  148. */
  149. check_norc(argc, argv);
  150. if (strlen(norc) == 0 && parse_rc() < 0)
  151. exit(1);
  152. if (parse_args(argc, argv) < 0)
  153. exit(1);
  154. if (diff_opts && !cvs_direct && do_diff)
  155. {
  156. debug(DEBUG_APPMSG1, "\nWARNING: diff options are not supported by 'cvs rdiff'");
  157. debug(DEBUG_APPMSG1, " which is usually used to create diffs. 'cvs diff'");
  158. debug(DEBUG_APPMSG1, " will be used instead, but the resulting patches ");
  159. debug(DEBUG_APPMSG1, " will need to be applied using the '-p0' option");
  160. debug(DEBUG_APPMSG1, " to patch(1) (in the working directory), ");
  161. debug(DEBUG_APPMSG1, " instead of '-p1'\n");
  162. }
  163. file_hash = create_hash_table(1023);
  164. global_symbols = create_hash_table(111);
  165. branch_heads = create_hash_table(1023);
  166. INIT_LIST_HEAD(&all_patch_sets);
  167. INIT_LIST_HEAD(&collisions);
  168. /* this parses some of the CVS/ files, and initializes
  169. * the repository_path and other variables
  170. */
  171. init_paths();
  172. if (!ignore_cache)
  173. {
  174. int save_fuzz_factor = timestamp_fuzz_factor;
  175. /* the timestamp fuzz should only be in effect when loading from
  176. * CVS, not re-fuzzed when loading from cache. This is a hack
  177. * working around bad use of global variables
  178. */
  179. timestamp_fuzz_factor = 0;
  180. if ((cache_date = read_cache()) < 0)
  181. update_cache = 1;
  182. timestamp_fuzz_factor = save_fuzz_factor;
  183. }
  184. if (cvs_direct && (do_diff || (update_cache && !test_log_file)))
  185. cvs_direct_ctx = open_cvs_server(root_path, compress);
  186. if (update_cache)
  187. {
  188. load_from_cvs();
  189. do_write_cache = 1;
  190. }
  191. //XXX
  192. //handle_collisions();
  193. list_sort(&all_patch_sets, compare_patch_sets_bytime_list);
  194. ps_counter = 0;
  195. walk_all_patch_sets(assign_patchset_id);
  196. handle_collisions();
  197. resolve_global_symbols();
  198. if (do_write_cache)
  199. write_cache(cache_date);
  200. if (statistics)
  201. print_statistics(ps_tree);
  202. /* check that the '-r' symbols (if specified) were resolved */
  203. if (restrict_tag_start && restrict_tag_ps_start == 0 &&
  204. strcmp(restrict_tag_start, "#CVSPS_EPOCH") != 0)
  205. {
  206. debug(DEBUG_APPERROR, "symbol given with -r: %s: not found", restrict_tag_start);
  207. exit(1);
  208. }
  209. if (restrict_tag_end && restrict_tag_ps_end == INT_MAX)
  210. {
  211. debug(DEBUG_APPERROR, "symbol given with second -r: %s: not found", restrict_tag_end);
  212. exit(1);
  213. }
  214. walk_all_patch_sets(check_print_patch_set);
  215. if (summary_first++)
  216. walk_all_patch_sets(check_print_patch_set);
  217. if (cvs_direct_ctx)
  218. close_cvs_server(cvs_direct_ctx);
  219. exit(0);
  220. }
  221. static void load_from_cvs()
  222. {
  223. FILE * cvsfp;
  224. char buff[BUFSIZ];
  225. int state = NEED_RCS_FILE;
  226. CvsFile * file = NULL;
  227. PatchSetMember * psm = NULL;
  228. char datebuff[20];
  229. char authbuff[AUTH_STR_MAX];
  230. int logbufflen = LOG_STR_MAX + 1;
  231. char * logbuff = malloc(logbufflen);
  232. int loglen = 0;
  233. int have_log = 0;
  234. char cmd[BUFSIZ];
  235. char date_str[64];
  236. char use_rep_buff[PATH_MAX];
  237. char * ltype;
  238. if (logbuff == NULL)
  239. {
  240. debug(DEBUG_SYSERROR, "could not malloc %d bytes for logbuff in load_from_cvs", logbufflen);
  241. exit(1);
  242. }
  243. if (!no_rlog && !test_log_file && cvs_check_cap(CAP_HAVE_RLOG))
  244. {
  245. ltype = "rlog";
  246. snprintf(use_rep_buff, PATH_MAX, "%s", repository_path);
  247. }
  248. else
  249. {
  250. ltype = "log";
  251. use_rep_buff[0] = 0;
  252. }
  253. if (cache_date > 0)
  254. {
  255. struct tm * tm = gmtime(&cache_date);
  256. strftime(date_str, 64, "%d %b %Y %H:%M:%S %z", tm);
  257. /* this command asks for logs using two different date
  258. * arguments, separated by ';' (see man rlog). The first
  259. * gets all revisions more recent than date, the second
  260. * gets a single revision no later than date, which combined
  261. * get us all revisions that have occurred since last update
  262. * and overlaps what we had before by exactly one revision,
  263. * which is necessary to fill in the pre_rev stuff for a
  264. * PatchSetMember
  265. */
  266. snprintf(cmd, BUFSIZ, "cvs %s %s -q %s -d '%s<;%s' %s", compress_arg, norc, ltype, date_str, date_str, use_rep_buff);
  267. }
  268. else
  269. {
  270. date_str[0] = 0;
  271. snprintf(cmd, BUFSIZ, "cvs %s %s -q %s %s", compress_arg, norc, ltype, use_rep_buff);
  272. }
  273. debug(DEBUG_STATUS, "******* USING CMD %s", cmd);
  274. cache_date = time(NULL);
  275. /* FIXME: this is ugly, need to virtualize the accesses away from here */
  276. if (test_log_file)
  277. cvsfp = fopen(test_log_file, "r");
  278. else if (cvs_direct_ctx)
  279. cvsfp = cvs_rlog_open(cvs_direct_ctx, repository_path, date_str);
  280. else
  281. cvsfp = popen(cmd, "r");
  282. if (!cvsfp)
  283. {
  284. debug(DEBUG_SYSERROR, "can't open cvs pipe using command %s", cmd);
  285. exit(1);
  286. }
  287. for (;;)
  288. {
  289. char * tst;
  290. if (cvs_direct_ctx)
  291. tst = cvs_rlog_fgets(buff, BUFSIZ, cvs_direct_ctx);
  292. else
  293. tst = fgets(buff, BUFSIZ, cvsfp);
  294. if (!tst)
  295. break;
  296. debug(DEBUG_STATUS, "state: %d read line:%s", state, buff);
  297. switch(state)
  298. {
  299. case NEED_RCS_FILE:
  300. if (strncmp(buff, "RCS file", 8) == 0) {
  301. if ((file = parse_rcs_file(buff)) != NULL)
  302. state = NEED_SYMS;
  303. else
  304. state = NEED_WORKING_FILE;
  305. }
  306. break;
  307. case NEED_WORKING_FILE:
  308. if (strncmp(buff, "Working file", 12) == 0) {
  309. if ((file = parse_working_file(buff)))
  310. state = NEED_SYMS;
  311. else
  312. state = NEED_RCS_FILE;
  313. break;
  314. } else {
  315. // Working file come just after RCS file. So reset state if it was not found
  316. state = NEED_RCS_FILE;
  317. }
  318. break;
  319. case NEED_SYMS:
  320. if (strncmp(buff, "symbolic names:", 15) == 0)
  321. state = NEED_EOS;
  322. break;
  323. case NEED_EOS:
  324. if (!isspace(buff[0]))
  325. {
  326. /* see cvsps_types.h for commentary on have_branches */
  327. file->have_branches = 1;
  328. state = NEED_START_LOG;
  329. }
  330. else
  331. parse_sym(file, buff);
  332. break;
  333. case NEED_START_LOG:
  334. if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
  335. state = NEED_REVISION;
  336. break;
  337. case NEED_REVISION:
  338. if (strncmp(buff, "revision", 8) == 0)
  339. {
  340. char new_rev[REV_STR_MAX];
  341. CvsFileRevision * rev;
  342. strcpy(new_rev, buff + 9);
  343. chop(new_rev);
  344. /*
  345. * rev may already exist (think cvsps -u), in which
  346. * case parse_revision is a hash lookup
  347. */
  348. rev = parse_revision(file, new_rev);
  349. /*
  350. * in the simple case, we are copying rev to psm->pre_rev
  351. * (psm refers to last patch set processed at this point)
  352. * since generally speaking the log is reverse chronological.
  353. * This breaks down slightly when branches are introduced
  354. */
  355. assign_pre_revision(psm, rev);
  356. /*
  357. * if this is a new revision, it will have no post_psm associated.
  358. * otherwise we are (probably?) hitting the overlap in cvsps -u
  359. */
  360. if (!rev->post_psm)
  361. {
  362. psm = rev->post_psm = create_patch_set_member();
  363. psm->post_rev = rev;
  364. psm->file = file;
  365. state = NEED_DATE_AUTHOR_STATE;
  366. }
  367. else
  368. {
  369. /* we hit this in cvsps -u mode, we are now up-to-date
  370. * w.r.t this particular file. skip all of the rest
  371. * of the info (revs and logs) until we hit the next file
  372. */
  373. psm = NULL;
  374. state = NEED_EOM;
  375. }
  376. }
  377. break;
  378. case NEED_DATE_AUTHOR_STATE:
  379. if (strncmp(buff, "date:", 5) == 0)
  380. {
  381. char * p;
  382. strncpy(datebuff, buff + 6, 19);
  383. datebuff[19] = 0;
  384. strcpy(authbuff, "unknown");
  385. p = strstr(buff, "author: ");
  386. if (p)
  387. {
  388. char * op;
  389. p += 8;
  390. op = strchr(p, ';');
  391. if (op)
  392. {
  393. strzncpy(authbuff, p, op - p + 1);
  394. }
  395. }
  396. /* read the 'state' tag to see if this is a dead revision */
  397. p = strstr(buff, "state: ");
  398. if (p)
  399. {
  400. char * op;
  401. p += 7;
  402. op = strchr(p, ';');
  403. if (op)
  404. if (strncmp(p, "dead", MIN(4, op - p)) == 0)
  405. psm->post_rev->dead = 1;
  406. }
  407. state = NEED_EOM;
  408. }
  409. break;
  410. case NEED_EOM:
  411. if (strcmp(buff, CVS_LOG_BOUNDARY) == 0)
  412. {
  413. if (psm)
  414. {
  415. PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
  416. patch_set_add_member(ps, psm);
  417. }
  418. logbuff[0] = 0;
  419. loglen = 0;
  420. have_log = 0;
  421. state = NEED_REVISION;
  422. }
  423. else if (strcmp(buff, CVS_FILE_BOUNDARY) == 0)
  424. {
  425. if (psm)
  426. {
  427. PatchSet * ps = get_patch_set(datebuff, logbuff, authbuff, psm->post_rev->branch, psm);
  428. patch_set_add_member(ps, psm);
  429. assign_pre_revision(psm, NULL);
  430. }
  431. logbuff[0] = 0;
  432. loglen = 0;
  433. have_log = 0;
  434. psm = NULL;
  435. file = NULL;
  436. state = NEED_RCS_FILE;
  437. }
  438. else
  439. {
  440. /* other "blahblah: information;" messages can
  441. * follow the stuff we pay attention to
  442. */
  443. if (have_log || !is_revision_metadata(buff))
  444. {
  445. /* If the log buffer is full, try to reallocate more. */
  446. if (loglen < logbufflen)
  447. {
  448. int len = strlen(buff);
  449. if (len >= logbufflen - loglen)
  450. {
  451. debug(DEBUG_STATUS, "reallocating logbufflen to %d bytes for file %s", logbufflen, file->filename);
  452. logbufflen += (len >= LOG_STR_MAX ? (len+1) : LOG_STR_MAX);
  453. char * newlogbuff = realloc(logbuff, logbufflen);
  454. if (newlogbuff == NULL)
  455. {
  456. debug(DEBUG_SYSERROR, "could not realloc %d bytes for logbuff in load_from_cvs", logbufflen);
  457. exit(1);
  458. }
  459. logbuff = newlogbuff;
  460. }
  461. debug(DEBUG_STATUS, "appending %s to log", buff);
  462. memcpy(logbuff + loglen, buff, len);
  463. loglen += len;
  464. logbuff[loglen] = 0;
  465. have_log = 1;
  466. }
  467. }
  468. else
  469. {
  470. debug(DEBUG_STATUS, "ignoring unhandled info %s", buff);
  471. }
  472. }
  473. break;
  474. }
  475. }
  476. if (state == NEED_SYMS)
  477. {
  478. debug(DEBUG_APPERROR, "Error: 'symbolic names' not found in log output.");
  479. debug(DEBUG_APPERROR, " Perhaps you should try running with --norc");
  480. exit(1);
  481. }
  482. if (state != NEED_RCS_FILE)
  483. {
  484. debug(DEBUG_APPERROR, "Error: Log file parsing error. (%d) Use -v to debug", state);
  485. exit(1);
  486. }
  487. if (test_log_file)
  488. {
  489. fclose(cvsfp);
  490. }
  491. else if (cvs_direct_ctx)
  492. {
  493. cvs_rlog_close(cvs_direct_ctx);
  494. }
  495. else
  496. {
  497. if (pclose(cvsfp) < 0)
  498. {
  499. debug(DEBUG_APPERROR, "cvs rlog command exited with error. aborting");
  500. exit(1);
  501. }
  502. }
  503. }
  504. static int usage(const char * str1, const char * str2)
  505. {
  506. if (str1)
  507. debug(DEBUG_APPERROR, "\nbad usage: %s %s\n", str1, str2);
  508. debug(DEBUG_APPERROR, "Usage: cvsps [-h] [-x] [-u] [-z <fuzz>] [-g] [-s <range>[,<range>]] ");
  509. debug(DEBUG_APPERROR, " [-a <author>] [-f <file>] [-d <date1> [-d <date2>]] ");
  510. debug(DEBUG_APPERROR, " [-b <branch>] [-l <regex>] [-r <tag> [-r <tag>]] ");
  511. debug(DEBUG_APPERROR, " [-p <directory>] [-v] [-t] [--norc] [--summary-first]");
  512. debug(DEBUG_APPERROR, " [--test-log <captured cvs log file>] [--bkcvs]");
  513. debug(DEBUG_APPERROR, " [--no-rlog] [--diff-opts <option string>] [--cvs-direct]");
  514. debug(DEBUG_APPERROR, " [--debuglvl <bitmask>] [-Z <compression>] [--root <cvsroot>]");
  515. debug(DEBUG_APPERROR, " [-q] [-A] [<repository>]");
  516. debug(DEBUG_APPERROR, "");
  517. debug(DEBUG_APPERROR, "Where:");
  518. debug(DEBUG_APPERROR, " -h display this informative message");
  519. debug(DEBUG_APPERROR, " -x ignore (and rebuild) cvsps.cache file");
  520. debug(DEBUG_APPERROR, " -u update cvsps.cache file");
  521. debug(DEBUG_APPERROR, " -z <fuzz> set the timestamp fuzz factor for identifying patch sets");
  522. debug(DEBUG_APPERROR, " -g generate diffs of the selected patch sets");
  523. debug(DEBUG_APPERROR, " -s <patch set>[-[<patch set>]][,<patch set>...] restrict patch sets by id");
  524. debug(DEBUG_APPERROR, " -a <author> restrict output to patch sets created by author");
  525. debug(DEBUG_APPERROR, " -f <file> restrict output to patch sets involving file");
  526. debug(DEBUG_APPERROR, " -d <date1> -d <date2> if just one date specified, show");
  527. debug(DEBUG_APPERROR, " revisions newer than date1. If two dates specified,");
  528. debug(DEBUG_APPERROR, " show revisions between two dates.");
  529. debug(DEBUG_APPERROR, " -b <branch> restrict output to patch sets affecting history of branch");
  530. debug(DEBUG_APPERROR, " -l <regex> restrict output to patch sets matching <regex> in log message");
  531. debug(DEBUG_APPERROR, " -r <tag1> -r <tag2> if just one tag specified, show");
  532. debug(DEBUG_APPERROR, " revisions since tag1. If two tags specified, show");
  533. debug(DEBUG_APPERROR, " revisions between the two tags.");
  534. debug(DEBUG_APPERROR, " -p <directory> output patch sets to individual files in <directory>");
  535. debug(DEBUG_APPERROR, " -v show very verbose parsing messages");
  536. debug(DEBUG_APPERROR, " -t show some brief memory usage statistics");
  537. debug(DEBUG_APPERROR, " --norc when invoking cvs, ignore the .cvsrc file");
  538. debug(DEBUG_APPERROR, " --summary-first when multiple patch sets are shown, put all summaries first");
  539. debug(DEBUG_APPERROR, " --test-log <captured cvs log> supply a captured cvs log for testing");
  540. debug(DEBUG_APPERROR, " --diff-opts <option string> supply special set of options to diff");
  541. debug(DEBUG_APPERROR, " --bkcvs special hack for parsing the BK -> CVS log format");
  542. debug(DEBUG_APPERROR, " --no-rlog disable rlog (it's faulty in some setups)");
  543. debug(DEBUG_APPERROR, " --cvs-direct (--no-cvs-direct) enable (disable) built-in cvs client code");
  544. debug(DEBUG_APPERROR, " --debuglvl <bitmask> enable various debug channels.");
  545. debug(DEBUG_APPERROR, " -Z <compression> A value 1-9 which specifies amount of compression");
  546. debug(DEBUG_APPERROR, " --root <cvsroot> specify cvsroot. overrides env. and working directory (cvs-direct only)");
  547. debug(DEBUG_APPERROR, " -q be quiet about warnings");
  548. debug(DEBUG_APPERROR, " -A track and report branch ancestry");
  549. debug(DEBUG_APPERROR, " <repository> apply cvsps to repository. overrides working directory");
  550. debug(DEBUG_APPERROR, "\ncvsps version %s\n", VERSION);
  551. return -1;
  552. }
  553. static int parse_args(int argc, char *argv[])
  554. {
  555. int i = 1;
  556. while (i < argc)
  557. {
  558. if (strcmp(argv[i], "-z") == 0)
  559. {
  560. if (++i >= argc)
  561. return usage("argument to -z missing", "");
  562. timestamp_fuzz_factor = atoi(argv[i++]);
  563. continue;
  564. }
  565. if (strcmp(argv[i], "-g") == 0)
  566. {
  567. do_diff = 1;
  568. i++;
  569. continue;
  570. }
  571. if (strcmp(argv[i], "-s") == 0)
  572. {
  573. PatchSetRange * range;
  574. char * min_str, * max_str;
  575. if (++i >= argc)
  576. return usage("argument to -s missing", "");
  577. min_str = strtok(argv[i++], ",");
  578. do
  579. {
  580. range = create_patch_set_range();
  581. max_str = strrchr(min_str, '-');
  582. if (max_str)
  583. *max_str++ = '\0';
  584. else
  585. max_str = min_str;
  586. range->min_counter = atoi(min_str);
  587. if (*max_str)
  588. range->max_counter = atoi(max_str);
  589. else
  590. range->max_counter = INT_MAX;
  591. list_add(&range->link, show_patch_set_ranges.prev);
  592. }
  593. while ((min_str = strtok(NULL, ",")));
  594. continue;
  595. }
  596. if (strcmp(argv[i], "-a") == 0)
  597. {
  598. if (++i >= argc)
  599. return usage("argument to -a missing", "");
  600. restrict_author = argv[i++];
  601. continue;
  602. }
  603. if (strcmp(argv[i], "-l") == 0)
  604. {
  605. int err;
  606. if (++i >= argc)
  607. return usage("argument to -l missing", "");
  608. if ((err = regcomp(&restrict_log, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
  609. {
  610. char errbuf[256];
  611. regerror(err, &restrict_log, errbuf, 256);
  612. return usage("bad regex to -l", errbuf);
  613. }
  614. have_restrict_log = 1;
  615. continue;
  616. }
  617. if (strcmp(argv[i], "-f") == 0)
  618. {
  619. int err;
  620. if (++i >= argc)
  621. return usage("argument to -f missing", "");
  622. if ((err = regcomp(&restrict_file, argv[i++], REG_EXTENDED|REG_NOSUB)) != 0)
  623. {
  624. char errbuf[256];
  625. regerror(err, &restrict_file, errbuf, 256);
  626. return usage("bad regex to -f", errbuf);
  627. }
  628. have_restrict_file = 1;
  629. continue;
  630. }
  631. if (strcmp(argv[i], "-d") == 0)
  632. {
  633. time_t *pt;
  634. if (++i >= argc)
  635. return usage("argument to -d missing", "");
  636. pt = (restrict_date_start == 0) ? &restrict_date_start : &restrict_date_end;
  637. convert_date(pt, argv[i++]);
  638. continue;
  639. }
  640. if (strcmp(argv[i], "-r") == 0)
  641. {
  642. if (++i >= argc)
  643. return usage("argument to -r missing", "");
  644. if (restrict_tag_start)
  645. restrict_tag_end = argv[i];
  646. else
  647. restrict_tag_start = argv[i];
  648. i++;
  649. continue;
  650. }
  651. if (strcmp(argv[i], "-u") == 0)
  652. {
  653. update_cache = 1;
  654. i++;
  655. continue;
  656. }
  657. if (strcmp(argv[i], "-x") == 0)
  658. {
  659. ignore_cache = 1;
  660. update_cache = 1;
  661. i++;
  662. continue;
  663. }
  664. if (strcmp(argv[i], "-b") == 0)
  665. {
  666. if (++i >= argc)
  667. return usage("argument to -b missing", "");
  668. restrict_branch = argv[i++];
  669. /* Warn if the user tries to use TRUNK. Should eventually
  670. * go away as TRUNK may be a valid branch within CVS
  671. */
  672. if (strcmp(restrict_branch, "TRUNK") == 0)
  673. debug(DEBUG_APPMSG1, "WARNING: The HEAD branch of CVS is called HEAD, not TRUNK");
  674. continue;
  675. }
  676. if (strcmp(argv[i], "-p") == 0)
  677. {
  678. if (++i >= argc)
  679. return usage("argument to -p missing", "");
  680. patch_set_dir = argv[i++];
  681. continue;
  682. }
  683. if (strcmp(argv[i], "-v") == 0)
  684. {
  685. debuglvl = ~0;
  686. i++;
  687. continue;
  688. }
  689. if (strcmp(argv[i], "-t") == 0)
  690. {
  691. statistics = 1;
  692. i++;
  693. continue;
  694. }
  695. if (strcmp(argv[i], "--summary-first") == 0)
  696. {
  697. summary_first = 1;
  698. i++;
  699. continue;
  700. }
  701. if (strcmp(argv[i], "-h") == 0)
  702. return usage(NULL, NULL);
  703. /* see special handling of --norc in main */
  704. if (strcmp(argv[i], "--norc") == 0)
  705. {
  706. norc = "-f";
  707. i++;
  708. continue;
  709. }
  710. if (strcmp(argv[i], "--test-log") == 0)
  711. {
  712. if (++i >= argc)
  713. return usage("argument to --test-log missing", "");
  714. test_log_file = argv[i++];
  715. continue;
  716. }
  717. if (strcmp(argv[i], "--diff-opts") == 0)
  718. {
  719. if (++i >= argc)
  720. return usage("argument to --diff-opts missing", "");
  721. /* allow diff_opts to be turned off by making empty string
  722. * into NULL
  723. */
  724. if (!strlen(argv[i]))
  725. diff_opts = NULL;
  726. else
  727. diff_opts = argv[i];
  728. i++;
  729. continue;
  730. }
  731. if (strcmp(argv[i], "--bkcvs") == 0)
  732. {
  733. bkcvs = 1;
  734. i++;
  735. continue;
  736. }
  737. if (strcmp(argv[i], "--no-rlog") == 0)
  738. {
  739. no_rlog = 1;
  740. i++;
  741. continue;
  742. }
  743. if (strcmp(argv[i], "--cvs-direct") == 0)
  744. {
  745. cvs_direct = 1;
  746. i++;
  747. continue;
  748. }
  749. if (strcmp(argv[i], "--no-cvs-direct") == 0)
  750. {
  751. cvs_direct = 0;
  752. i++;
  753. continue;
  754. }
  755. if (strcmp(argv[i], "--debuglvl") == 0)
  756. {
  757. if (++i >= argc)
  758. return usage("argument to --debuglvl missing", "");
  759. debuglvl = atoi(argv[i++]);
  760. continue;
  761. }
  762. if (strcmp(argv[i], "-Z") == 0)
  763. {
  764. if (++i >= argc)
  765. return usage("argument to -Z", "");
  766. compress = atoi(argv[i++]);
  767. if (compress < 0 || compress > 9)
  768. return usage("-Z level must be between 1 and 9 inclusive (0 disables compression)", argv[i-1]);
  769. if (compress == 0)
  770. compress_arg[0] = 0;
  771. else
  772. snprintf(compress_arg, 8, "-z%d", compress);
  773. continue;
  774. }
  775. if (strcmp(argv[i], "--root") == 0)
  776. {
  777. if (++i >= argc)
  778. return usage("argument to --root missing", "");
  779. strcpy(root_path, argv[i++]);
  780. continue;
  781. }
  782. if (strcmp(argv[i], "-q") == 0)
  783. {
  784. debuglvl &= ~DEBUG_APPMSG1;
  785. i++;
  786. continue;
  787. }
  788. if (strcmp(argv[i], "-A") == 0)
  789. {
  790. track_branch_ancestry = 1;
  791. i++;
  792. continue;
  793. }
  794. if (argv[i][0] == '-')
  795. return usage("invalid argument", argv[i]);
  796. strcpy(repository_path, argv[i++]);
  797. }
  798. return 0;
  799. }
  800. static int parse_rc()
  801. {
  802. char rcfile[PATH_MAX];
  803. FILE * fp;
  804. snprintf(rcfile, PATH_MAX, "%s/cvspsrc", get_cvsps_dir());
  805. if ((fp = fopen(rcfile, "r")))
  806. {
  807. char buff[BUFSIZ];
  808. while (fgets(buff, BUFSIZ, fp))
  809. {
  810. char * argv[3], *p;
  811. int argc = 2;
  812. chop(buff);
  813. argv[0] = "garbage";
  814. p = strchr(buff, ' ');
  815. if (p)
  816. {
  817. *p++ = '\0';
  818. argv[2] = xstrdup(p);
  819. argc = 3;
  820. }
  821. argv[1] = xstrdup(buff);
  822. if (parse_args(argc, argv) < 0)
  823. return -1;
  824. }
  825. fclose(fp);
  826. }
  827. return 0;
  828. }
  829. static void init_paths()
  830. {
  831. FILE * fp;
  832. char * p;
  833. int len;
  834. /* determine the CVSROOT. precedence:
  835. * 1) command line
  836. * 2) working directory (if present)
  837. * 3) environment variable CVSROOT
  838. */
  839. if (!root_path[0])
  840. {
  841. if (!(fp = fopen("CVS/Root", "r")))
  842. {
  843. const char * e;
  844. debug(DEBUG_STATUS, "Can't open CVS/Root");
  845. e = getenv("CVSROOT");
  846. if (!e)
  847. {
  848. debug(DEBUG_APPERROR, "cannot determine CVSROOT");
  849. exit(1);
  850. }
  851. strcpy(root_path, e);
  852. }
  853. else
  854. {
  855. if (!fgets(root_path, PATH_MAX, fp))
  856. {
  857. debug(DEBUG_APPERROR, "Error reading CVSROOT");
  858. exit(1);
  859. }
  860. fclose(fp);
  861. /* chop the lf and optional trailing '/' */
  862. len = strlen(root_path) - 1;
  863. root_path[len] = 0;
  864. if (root_path[len - 1] == '/')
  865. root_path[--len] = 0;
  866. }
  867. }
  868. /* Determine the repository path, precedence:
  869. * 1) command line
  870. * 2) working directory
  871. */
  872. if (!repository_path[0])
  873. {
  874. if (!(fp = fopen("CVS/Repository", "r")))
  875. {
  876. debug(DEBUG_SYSERROR, "Can't open CVS/Repository");
  877. exit(1);
  878. }
  879. if (!fgets(repository_path, PATH_MAX, fp))
  880. {
  881. debug(DEBUG_APPERROR, "Error reading repository path");
  882. exit(1);
  883. }
  884. chop(repository_path);
  885. fclose(fp);
  886. }
  887. /* get the path portion of the root */
  888. p = strrchr(root_path, ':');
  889. if (!p)
  890. p = root_path;
  891. else
  892. p++;
  893. /* some CVS have the CVSROOT string as part of the repository
  894. * string (initial substring). remove it.
  895. */
  896. len = strlen(p);
  897. if (strncmp(p, repository_path, len) == 0)
  898. {
  899. int rlen = strlen(repository_path + len + 1);
  900. memmove(repository_path, repository_path + len + 1, rlen + 1);
  901. }
  902. /* the 'strip_path' will be used whenever the CVS server gives us a
  903. * path to an 'rcs file'. the strip_path portion of these paths is
  904. * stripped off, leaving us with the working file.
  905. *
  906. * NOTE: because of some bizarre 'feature' in cvs, when 'rlog' is used
  907. * (instead of log) it gives the 'real' RCS file path, which can be different
  908. * from the 'nominal' repository path because of symlinks in the server and
  909. * the like. See also the 'parse_rcs_file' routine
  910. */
  911. strip_path_len = snprintf(strip_path, PATH_MAX, "%s/%s/", p, repository_path);
  912. if (strip_path_len < 0 || strip_path_len >= PATH_MAX)
  913. {
  914. debug(DEBUG_APPERROR, "strip_path overflow");
  915. exit(1);
  916. }
  917. debug(DEBUG_STATUS, "strip_path: %s", strip_path);
  918. }
  919. static CvsFile * parse_rcs_file(const char * buff)
  920. {
  921. char fn[PATH_MAX];
  922. int len = strlen(buff + 10);
  923. char * p;
  924. /* once a single file has been parsed ok we set this */
  925. static int path_ok;
  926. /* chop the ",v" string and the "LF" */
  927. len -= 3;
  928. memcpy(fn, buff + 10, len);
  929. fn[len] = 0;
  930. if (strncmp(fn, strip_path, strip_path_len) != 0)
  931. {
  932. /* if the very first file fails the strip path,
  933. * then maybe we need to try for an alternate.
  934. * this will happen if symlinks are being used
  935. * on the server. our best guess is to look
  936. * for the final occurance of the repository
  937. * path in the filename and use that. it should work
  938. * except in the case where:
  939. * 1) the project has no files in the top-level directory
  940. * 2) the project has a directory with the same name as the project
  941. * 3) that directory sorts alphabetically before any other directory
  942. * in which case, you are scr**ed
  943. */
  944. if (!path_ok)
  945. {
  946. char * p = fn, *lastp = NULL;
  947. while ((p = strstr(p, repository_path)))
  948. lastp = p++;
  949. if (lastp)
  950. {
  951. int len = strlen(repository_path);
  952. memcpy(strip_path, fn, lastp - fn + len + 1);
  953. strip_path_len = lastp - fn + len + 1;
  954. strip_path[strip_path_len] = 0;
  955. debug(DEBUG_APPMSG1, "NOTICE: used alternate strip path %s", strip_path);
  956. goto ok;
  957. }
  958. }
  959. /* FIXME: a subdirectory may have a different Repository path
  960. * than it's parent. we'll fail the above test since strip_path
  961. * is global for the entire checked out tree (recursively).
  962. *
  963. * For now just ignore such files
  964. */
  965. debug(DEBUG_APPMSG1, "WARNING: file %s doesn't match strip_path %s. ignoring",
  966. fn, strip_path);
  967. return NULL;
  968. }
  969. ok:
  970. path_ok = 1;
  971. /* remove from beginning the 'strip_path' string */
  972. len -= strip_path_len;
  973. memmove(fn, fn + strip_path_len, len);
  974. fn[len] = 0;
  975. /* check if file is in the 'Attic/' and remove it */
  976. if ((p = strrchr(fn, '/')) &&
  977. p - fn >= 5 && strncmp(p - 5, "Attic", 5) == 0)
  978. {
  979. memmove(p - 5, p + 1, len - (p - fn + 1));
  980. len -= 6;
  981. fn[len] = 0;
  982. }
  983. debug(DEBUG_STATUS, "stripped filename %s", fn);
  984. return build_file_by_name(fn);
  985. }
  986. static CvsFile * parse_working_file(const char * buff)
  987. {
  988. char fn[PATH_MAX];
  989. int len = strlen(buff + 14);
  990. /* chop the "LF" */
  991. len -= 1;
  992. memcpy(fn, buff + 14, len);
  993. fn[len] = 0;
  994. debug(DEBUG_STATUS, "working filename %s", fn);
  995. return build_file_by_name(fn);
  996. }
  997. static CvsFile * build_file_by_name(const char * fn)
  998. {
  999. CvsFile * retval;
  1000. retval = (CvsFile*)get_hash_object(file_hash, fn);
  1001. if (!retval)
  1002. {
  1003. if ((retval = create_cvsfile()))
  1004. {
  1005. retval->filename = xstrdup(fn);
  1006. put_hash_object_ex(file_hash, retval->filename, retval, HT_NO_KEYCOPY, NULL, NULL);
  1007. }
  1008. else
  1009. {
  1010. debug(DEBUG_SYSERROR, "malloc failed");
  1011. exit(1);
  1012. }
  1013. debug(DEBUG_STATUS, "new file: %s", retval->filename);
  1014. }
  1015. else
  1016. {
  1017. debug(DEBUG_STATUS, "existing file: %s", retval->filename);
  1018. }
  1019. return retval;
  1020. }
  1021. PatchSet * get_patch_set(const char * dte, const char * log, const char * author, const char * branch, PatchSetMember * psm)
  1022. {
  1023. PatchSet * retval = NULL, **find = NULL;
  1024. int (*cmp1)(const void *,const void*) = (bkcvs) ? compare_patch_sets_bk : compare_patch_sets;
  1025. if (!(retval = create_patch_set()))
  1026. {
  1027. debug(DEBUG_SYSERROR, "malloc failed for PatchSet");
  1028. return NULL;
  1029. }
  1030. convert_date(&retval->date, dte);
  1031. retval->author = get_string(author);
  1032. retval->descr = xstrdup(log);
  1033. retval->branch = get_string(branch);
  1034. /* we are looking for a patchset suitable for holding this member.
  1035. * this means two things:
  1036. * 1) a patchset already containing an entry for the file is no good
  1037. * 2) for two patchsets with same exact date/time, if they reference
  1038. * the same file, we can properly order them. this primarily solves
  1039. * the 'cvs import' problem and may not have general usefulness
  1040. * because it would only work if the first member we consider is
  1041. * present in the existing ps.
  1042. */
  1043. if (psm)
  1044. list_add(&psm->link, retval->members.prev);
  1045. find = (PatchSet**)tsearch(retval, &ps_tree, cmp1);
  1046. if (psm)
  1047. list_del(&psm->link);
  1048. if (*find != retval)
  1049. {
  1050. debug(DEBUG_STATUS, "found existing patch set");
  1051. if (bkcvs && strstr(retval->descr, "BKrev:"))
  1052. {
  1053. free((*find)->descr);
  1054. (*find)->descr = retval->descr;
  1055. }
  1056. else
  1057. {
  1058. free(retval->descr);
  1059. }
  1060. /* keep the minimum date of any member as the 'actual' date */
  1061. if (retval->date < (*find)->date)
  1062. (*find)->date = retval->date;
  1063. /* expand the min_date/max_date window to help finding other members .
  1064. * open the window by an extra margin determined by the fuzz factor
  1065. */
  1066. if (retval->date - timestamp_fuzz_factor < (*find)->min_date)
  1067. {
  1068. (*find)->min_date = retval->date - timestamp_fuzz_factor;
  1069. //debug(DEBUG_APPMSG1, "WARNING: non-increasing dates in encountered patchset members");
  1070. }
  1071. else if (retval->date + timestamp_fuzz_factor > (*find)->max_date)
  1072. (*find)->max_date = retval->date + timestamp_fuzz_factor;
  1073. free(retval);
  1074. retval = *find;
  1075. }
  1076. else
  1077. {
  1078. debug(DEBUG_STATUS, "new patch set!");
  1079. debug(DEBUG_STATUS, "%s %s %s", retval->author, retval->descr, dte);
  1080. retval->min_date = retval->date - timestamp_fuzz_factor;
  1081. retval->max_date = retval->date + timestamp_fuzz_factor;
  1082. list_add(&retval->all_link, &all_patch_sets);
  1083. }
  1084. return retval;
  1085. }
  1086. static int get_branch_ext(char * buff, const char * rev, int * leaf)
  1087. {
  1088. char * p;
  1089. int len = strlen(rev);
  1090. /* allow get_branch(buff, buff) without destroying contents */
  1091. memmove(buff, rev, len);
  1092. buff[len] = 0;
  1093. p = strrchr(buff, '.');
  1094. if (!p)
  1095. return 0;
  1096. *p++ = 0;
  1097. if (leaf)
  1098. *leaf = atoi(p);
  1099. return 1;
  1100. }
  1101. static int get_branch(char * buff, const char * rev)
  1102. {
  1103. return get_branch_ext(buff, rev, NULL);
  1104. }
  1105. /*
  1106. * the goal if this function is to determine what revision to assign to
  1107. * the psm->pre_rev field. usually, the log file is strictly
  1108. * reverse chronological, so rev is direct ancestor to psm,
  1109. *
  1110. * This all breaks down at branch points however
  1111. */
  1112. static void assign_pre_revision(PatchSetMember * psm, CvsFileRevision * rev)
  1113. {
  1114. char pre[REV_STR_MAX], post[REV_STR_MAX];
  1115. if (!psm)
  1116. return;
  1117. if (!rev)
  1118. {
  1119. /* if psm was last rev. for file, it's either an
  1120. * INITIAL, or first rev of a branch. to test if it's
  1121. * the first rev of a branch, do get_branch twice -
  1122. * this should be the bp.
  1123. */
  1124. if (get_branch(post, psm->post_rev->rev) &&
  1125. get_branch(pre, post))
  1126. {
  1127. psm->pre_rev = file_get_revision(psm->file, pre);
  1128. list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
  1129. }
  1130. else
  1131. {
  1132. set_psm_initial(psm);
  1133. }
  1134. return;
  1135. }
  1136. /*
  1137. * is this canditate for 'pre' on the same branch as our 'post'?
  1138. * this is the normal case
  1139. */
  1140. if (!get_branch(pre, rev->rev))
  1141. {
  1142. debug(DEBUG_APPERROR, "get_branch malformed input (1)");
  1143. return;
  1144. }
  1145. if (!get_branch(post, psm->post_rev->rev))
  1146. {
  1147. debug(DEBUG_APPERROR, "get_branch malformed input (2)");
  1148. return;
  1149. }
  1150. if (strcmp(pre, post) == 0)
  1151. {
  1152. psm->pre_rev = rev;
  1153. rev->pre_psm = psm;
  1154. return;
  1155. }
  1156. /* branches don't match. new_psm must be head of branch,
  1157. * so psm is oldest rev. on branch. or oldest
  1158. * revision overall. if former, derive predecessor.
  1159. * use get_branch to chop another rev. off of string.
  1160. *
  1161. * FIXME:
  1162. * There's also a weird case. it's possible to just re-number
  1163. * a revision to any future revision. i.e. rev 1.9 becomes 2.0
  1164. * It's not widely used. In those cases of discontinuity,
  1165. * we end up stamping the predecessor as 'INITIAL' incorrectly
  1166. *
  1167. */
  1168. if (!get_branch(pre, post))
  1169. {
  1170. set_psm_initial(psm);
  1171. return;
  1172. }
  1173. psm->pre_rev = file_get_revision(psm->file, pre);
  1174. list_add(&psm->post_rev->link, &psm->pre_rev->branch_children);
  1175. }
  1176. static void check_print_patch_set(PatchSet * ps)
  1177. {
  1178. if (ps->psid < 0)
  1179. return;
  1180. /* the funk_factor overrides the restrict_tag_start and end */
  1181. if (ps->funk_factor == FNK_SHOW_SOME || ps->funk_factor == FNK_SHOW_ALL)
  1182. goto ok;
  1183. if (ps->funk_factor == FNK_HIDE_ALL)
  1184. return;
  1185. if (ps->psid <= restrict_tag_ps_start)
  1186. {
  1187. if (ps->psid == restrict_tag_ps_start)
  1188. debug(DEBUG_STATUS, "PatchSet %d matches tag %s.", ps->psid, restrict_tag_start);
  1189. return;
  1190. }
  1191. if (ps->psid > restrict_tag_ps_end)
  1192. return;
  1193. ok:
  1194. if (restrict_date_start > 0 &&
  1195. (ps->date < restrict_date_start ||
  1196. (restrict_date_end > 0 && ps->date > restrict_date_end)))
  1197. return;
  1198. if (restrict_author && strcmp(restrict_author, ps->author) != 0)
  1199. return;
  1200. if (have_restrict_log && regexec(&restrict_log, ps->descr, 0, NULL, 0) != 0)
  1201. return;
  1202. if (have_restrict_file && !patch_set_member_regex(ps, &restrict_file))
  1203. return;
  1204. if (restrict_branch && !patch_set_affects_branch(ps, restrict_branch))
  1205. return;
  1206. if (!list_empty(&show_patch_set_ranges))
  1207. {
  1208. struct list_head * next = show_patch_set_ranges.next;
  1209. while (next != &show_patch_set_ranges)
  1210. {
  1211. PatchSetRange *range = list_entry(next, PatchSetRange, link);
  1212. if (range->min_counter <= ps->psid &&
  1213. ps->psid <= range->max_counter)
  1214. {
  1215. break;
  1216. }
  1217. next = next->next;
  1218. }
  1219. if (next == &show_patch_set_ranges)
  1220. return;
  1221. }
  1222. if (patch_set_dir)
  1223. {
  1224. char path[PATH_MAX];
  1225. snprintf(path, PATH_MAX, "%s/%d.patch", patch_set_dir, ps->psid);
  1226. fflush(stdout);
  1227. close(1);
  1228. if (open(path, O_WRONLY|O_TRUNC|O_CREAT, 0666) < 0)
  1229. {
  1230. debug(DEBUG_SYSERROR, "can't open patch file %s", path);
  1231. exit(1);
  1232. }
  1233. fprintf(stderr, "Directing PatchSet %d to file %s\n", ps->psid, path);
  1234. }
  1235. /*
  1236. * If the summary_first option is in effect, there will be
  1237. * two passes through the tree. the first with summary_first == 1
  1238. * the second with summary_first == 2. if the option is not
  1239. * in effect, there will be one pass with summary_first == 0
  1240. *
  1241. * When the -s option is in effect, the show_patch_set_ranges
  1242. * list will be non-empty.
  1243. */
  1244. if (summary_first <= 1)
  1245. print_patch_set(ps);
  1246. if (do_diff && summary_first != 1)
  1247. do_cvs_diff(ps);
  1248. fflush(stdout);
  1249. }
  1250. static void print_patch_set(PatchSet * ps)
  1251. {
  1252. struct tm *tm;
  1253. struct list_head * next;
  1254. const char * funk = "";
  1255. tm = localtime(&ps->date);
  1256. next = ps->members.next;
  1257. funk = fnk_descr[ps->funk_factor];
  1258. /* this '---...' is different from the 28 hyphens that separate cvs log output */
  1259. printf("---------------------\n");
  1260. printf("PatchSet %d %s\n", ps->psid, funk);
  1261. printf("Date: %d/%02d/%02d %02d:%02d:%02d\n",
  1262. 1900 + tm->tm_year, tm->tm_mon + 1, tm->tm_mday,
  1263. tm->tm_hour, tm->tm_min, tm->tm_sec);
  1264. printf("Author: %s\n", ps->author);
  1265. printf("Branch: %s\n", ps->branch);
  1266. if (ps->ancestor_branch)
  1267. printf("Ancestor branch: %s\n", ps->ancestor_branch);
  1268. printf("Tag: %s %s\n", ps->tag ? ps->tag : "(none)", tag_flag_descr[ps->tag_flags]);
  1269. printf("Log:\n%s\n", ps->descr);
  1270. printf("Members: \n");
  1271. while (next != &ps->members)
  1272. {
  1273. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  1274. if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
  1275. funk = "(BEFORE START TAG)";
  1276. else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
  1277. funk = "(AFTER END TAG)";
  1278. else
  1279. funk = "";
  1280. printf("\t%s:%s->%s%s %s\n",
  1281. psm->file->filename,
  1282. psm->pre_rev ? psm->pre_rev->rev : "INITIAL",
  1283. psm->post_rev->rev,
  1284. psm->post_rev->dead ? "(DEAD)": "",
  1285. funk);
  1286. next = next->next;
  1287. }
  1288. printf("\n");
  1289. }
  1290. /* walk all the patchsets to assign monotonic psid,
  1291. * and to establish branch ancestry
  1292. */
  1293. static void assign_patchset_id(PatchSet * ps)
  1294. {
  1295. /*
  1296. * Ignore the 'BRANCH ADD' patchsets
  1297. */
  1298. if (!ps->branch_add)
  1299. {
  1300. ps_counter++;
  1301. ps->psid = ps_counter;
  1302. if (track_branch_ancestry && strcmp(ps->branch, "HEAD") != 0)
  1303. {
  1304. PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);
  1305. if (!head_ps)
  1306. {
  1307. head_ps = ps;
  1308. put_hash_object(branch_heads, ps->branch, head_ps);
  1309. }
  1310. determine_branch_ancestor(ps, head_ps);
  1311. }
  1312. }
  1313. else
  1314. {
  1315. ps->psid = -1;
  1316. }
  1317. }
  1318. static int compare_rev_strings(const char * cr1, const char * cr2)
  1319. {
  1320. char r1[REV_STR_MAX];
  1321. char r2[REV_STR_MAX];
  1322. char *s1 = r1, *s2 = r2;
  1323. char *p1, *p2;
  1324. int n1, n2;
  1325. strcpy(s1, cr1);
  1326. strcpy(s2, cr2);
  1327. for (;;)
  1328. {
  1329. p1 = strchr(s1, '.');
  1330. p2 = strchr(s2, '.');
  1331. if (p1) *p1++ = 0;
  1332. if (p2) *p2++ = 0;
  1333. n1 = atoi(s1);
  1334. n2 = atoi(s2);
  1335. if (n1 < n2)
  1336. return -1;
  1337. if (n1 > n2)
  1338. return 1;
  1339. if (!p1 && p2)
  1340. return -1;
  1341. if (p1 && !p2)
  1342. return 1;
  1343. if (!p1 && !p2)
  1344. return 0;
  1345. s1 = p1;
  1346. s2 = p2;
  1347. }
  1348. }
  1349. static int compare_patch_sets_by_members(const PatchSet * ps1, const PatchSet * ps2)
  1350. {
  1351. struct list_head * i;
  1352. for (i = ps1->members.next; i != &ps1->members; i = i->next)
  1353. {
  1354. PatchSetMember * psm1 = list_entry(i, PatchSetMember, link);
  1355. struct list_head * j;
  1356. for (j = ps2->members.next; j != &ps2->members; j = j->next)
  1357. {
  1358. PatchSetMember * psm2 = list_entry(j, PatchSetMember, link);
  1359. if (psm1->file == psm2->file)
  1360. {
  1361. int ret = compare_rev_strings(psm1->post_rev->rev, psm2->post_rev->rev);
  1362. //debug(DEBUG_APPMSG1, "file: %s comparing %s %s = %d", psm1->file->filename, psm1->post_rev->rev, psm2->post_rev->rev, ret);
  1363. return ret;
  1364. }
  1365. }
  1366. }
  1367. return 0;
  1368. }
  1369. static int compare_patch_sets_bk(const void * v_ps1, const void * v_ps2)
  1370. {
  1371. const PatchSet * ps1 = (const PatchSet *)v_ps1;
  1372. const PatchSet * ps2 = (const PatchSet *)v_ps2;
  1373. long diff;
  1374. diff = ps1->date - ps2->date;
  1375. return (diff < 0) ? -1 : ((diff > 0) ? 1 : 0);
  1376. }
  1377. static int compare_patch_sets(const void * v_ps1, const void * v_ps2)
  1378. {
  1379. const PatchSet * ps1 = (const PatchSet *)v_ps1;
  1380. const PatchSet * ps2 = (const PatchSet *)v_ps2;
  1381. long diff;
  1382. int ret;
  1383. time_t d, min, max;
  1384. /* We order by (author, descr, branch, members, date), but because of the fuzz factor
  1385. * we treat times within a certain distance as equal IFF the author
  1386. * and descr match.
  1387. */
  1388. ret = strcmp(ps1->author, ps2->author);
  1389. if (ret)
  1390. return ret;
  1391. ret = strcmp(ps1->descr, ps2->descr);
  1392. if (ret)
  1393. return ret;
  1394. ret = strcmp(ps1->branch, ps2->branch);
  1395. if (ret)
  1396. return ret;
  1397. ret = compare_patch_sets_by_members(ps1, ps2);
  1398. if (ret)
  1399. return ret;
  1400. /*
  1401. * one of ps1 or ps2 is new. the other should have the min_date
  1402. * and max_date set to a window opened by the fuzz_factor
  1403. */
  1404. if (ps1->min_date == 0)
  1405. {
  1406. d = ps1->date;
  1407. min = ps2->min_date;
  1408. max = ps2->max_date;
  1409. }
  1410. else if (ps2->min_date == 0)
  1411. {
  1412. d = ps2->date;
  1413. min = ps1->min_date;
  1414. max = ps1->max_date;
  1415. }
  1416. else
  1417. {
  1418. debug(DEBUG_APPERROR, "how can we have both patchsets pre-existing?");
  1419. exit(1);
  1420. }
  1421. if (min < d && d < max)
  1422. return 0;
  1423. diff = ps1->date - ps2->date;
  1424. return (diff < 0) ? -1 : 1;
  1425. }
  1426. static int compare_patch_sets_bytime_list(struct list_head * l1, struct list_head * l2)
  1427. {
  1428. const PatchSet *ps1 = list_entry(l1, PatchSet, all_link);
  1429. const PatchSet *ps2 = list_entry(l2, PatchSet, all_link);
  1430. return compare_patch_sets_bytime(ps1, ps2);
  1431. }
  1432. static int compare_patch_sets_bytime(const PatchSet * ps1, const PatchSet * ps2)
  1433. {
  1434. long diff;
  1435. int ret;
  1436. /* When doing a time-ordering of patchsets, we don't need to
  1437. * fuzzy-match the time. We've already done fuzzy-matching so we
  1438. * know that insertions are unique at this point.
  1439. */
  1440. diff = ps1->date - ps2->date;
  1441. if (diff)
  1442. return (diff < 0) ? -1 : 1;
  1443. ret = compare_patch_sets_by_members(ps1, ps2);
  1444. if (ret)
  1445. return ret;
  1446. ret = strcmp(ps1->author, ps2->author);
  1447. if (ret)
  1448. return ret;
  1449. ret = strcmp(ps1->descr, ps2->descr);
  1450. if (ret)
  1451. return ret;
  1452. ret = strcmp(ps1->branch, ps2->branch);
  1453. return ret;
  1454. }
  1455. static int is_revision_metadata(const char * buff)
  1456. {
  1457. char * p1, *p2;
  1458. int len;
  1459. if (!(p1 = strchr(buff, ':')))
  1460. return 0;
  1461. p2 = strchr(buff, ' ');
  1462. if (p2 && p2 < p1)
  1463. return 0;
  1464. len = strlen(buff);
  1465. /* lines have LF at end */
  1466. if (len > 1 && buff[len - 2] == ';')
  1467. return 1;
  1468. return 0;
  1469. }
  1470. static int patch_set_member_regex(PatchSet * ps, regex_t * reg)
  1471. {
  1472. struct list_head * next = ps->members.next;
  1473. while (next != &ps->members)
  1474. {
  1475. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  1476. if (regexec(&restrict_file, psm->file->filename, 0, NULL, 0) == 0)
  1477. return 1;
  1478. next = next->next;
  1479. }
  1480. return 0;
  1481. }
  1482. static int patch_set_affects_branch(PatchSet * ps, const char * branch)
  1483. {
  1484. struct list_head * next;
  1485. for (next = ps->members.next; next != &ps->members; next = next->next)
  1486. {
  1487. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  1488. /*
  1489. * slight hack. if -r is specified, and this patchset
  1490. * is 'before' the tag, but is FNK_SHOW_SOME, only
  1491. * check if the 'after tag' revisions affect
  1492. * the branch. this is especially important when
  1493. * the tag is a branch point.
  1494. */
  1495. if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
  1496. continue;
  1497. if (revision_affects_branch(psm->post_rev, branch))
  1498. return 1;
  1499. }
  1500. return 0;
  1501. }
  1502. static void do_cvs_diff(PatchSet * ps)
  1503. {
  1504. struct list_head * next;
  1505. const char * dtype;
  1506. const char * dopts;
  1507. const char * utype;
  1508. char use_rep_path[PATH_MAX];
  1509. char esc_use_rep_path[PATH_MAX];
  1510. fflush(stdout);
  1511. fflush(stderr);
  1512. /*
  1513. * if cvs_direct is not in effect, and diff options are specified,
  1514. * then we have to use diff instead of rdiff and we'll get a -p0
  1515. * diff (instead of -p1) [in a manner of speaking]. So to make sure
  1516. * that the add/remove diffs get generated likewise, we need to use
  1517. * 'update' instead of 'co'
  1518. *
  1519. * cvs_direct will always use diff (not rdiff), but will also always
  1520. * generate -p1 diffs.
  1521. */
  1522. if (diff_opts == NULL)
  1523. {
  1524. dopts = "-u";
  1525. dtype = "rdiff";
  1526. utype = "co";
  1527. sprintf(use_rep_path, "%s/", repository_path);
  1528. /* the rep_path may contain characters that the shell will barf on */
  1529. escape_filename(esc_use_rep_path, PATH_MAX, use_rep_path);
  1530. }
  1531. else
  1532. {
  1533. dopts = diff_opts;
  1534. dtype = "diff";
  1535. utype = "update";
  1536. use_rep_path[0] = 0;
  1537. esc_use_rep_path[0] = 0;
  1538. }
  1539. for (next = ps->members.next; next != &ps->members; next = next->next)
  1540. {
  1541. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  1542. char cmdbuff[PATH_MAX * 2+1];
  1543. char esc_file[PATH_MAX];
  1544. int ret, check_ret = 0;
  1545. cmdbuff[0] = 0;
  1546. cmdbuff[PATH_MAX*2] = 0;
  1547. /* the filename may contain characters that the shell will barf on */
  1548. escape_filename(esc_file, PATH_MAX, psm->file->filename);
  1549. /*
  1550. * Check the patchset funk. we may not want to diff this particular file
  1551. */
  1552. if (ps->funk_factor == FNK_SHOW_SOME && psm->bad_funk)
  1553. {
  1554. printf("Index: %s\n", psm->file->filename);
  1555. printf("===================================================================\n");
  1556. printf("*** Member not diffed, before start tag\n");
  1557. continue;
  1558. }
  1559. else if (ps->funk_factor == FNK_HIDE_SOME && !psm->bad_funk)
  1560. {
  1561. printf("Index: %s\n", psm->file->filename);
  1562. printf("===================================================================\n");
  1563. printf("*** Member not diffed, after end tag\n");
  1564. continue;
  1565. }
  1566. /*
  1567. * When creating diffs for INITIAL or DEAD revisions, we have to use 'cvs co'
  1568. * or 'cvs update' to get the file, because cvs won't generate these diffs.
  1569. * The problem is that this must be piped to diff, and so the resulting
  1570. * diff doesn't contain the filename anywhere! (diff between - and /dev/null).
  1571. * sed is used to replace the '-' with the filename.
  1572. *
  1573. * It's possible for pre_rev to be a 'dead' revision. This happens when a file
  1574. * is added on a branch. post_rev will be dead dead for remove
  1575. */
  1576. if (!psm->pre_rev || psm->pre_rev->dead || psm->post_rev->dead)
  1577. {
  1578. int cr;
  1579. const char * rev;
  1580. if (!psm->pre_rev || psm->pre_rev->dead)
  1581. {
  1582. cr = 1;
  1583. rev = psm->post_rev->rev;
  1584. }
  1585. else
  1586. {
  1587. cr = 0;
  1588. rev = psm->pre_rev->rev;
  1589. }
  1590. if (cvs_direct_ctx)
  1591. {
  1592. /* cvs_rupdate does the pipe through diff thing internally */
  1593. cvs_rupdate(cvs_direct_ctx, repository_path, psm->file->filename, rev, cr, dopts);
  1594. }
  1595. else
  1596. {
  1597. 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'",
  1598. compress_arg, norc, utype, rev, esc_use_rep_path, esc_file, dopts,
  1599. cr?"":"-",cr?"-":"", cr?"2":"1",
  1600. use_rep_path, psm->file->filename);
  1601. }
  1602. }
  1603. else
  1604. {
  1605. /* a regular diff */
  1606. if (cvs_direct_ctx)
  1607. {
  1608. cvs_diff(cvs_direct_ctx, repository_path, psm->file->filename, psm->pre_rev->rev, psm->post_rev->rev, dopts);
  1609. }
  1610. else
  1611. {
  1612. /* 'cvs diff' exit status '1' is ok, just means files are different */
  1613. if (strcmp(dtype, "diff") == 0)
  1614. check_ret = 1;
  1615. snprintf(cmdbuff, PATH_MAX * 2, "cvs %s %s %s %s -r %s -r %s %s%s",
  1616. compress_arg, norc, dtype, dopts, psm->pre_rev->rev, psm->post_rev->rev,
  1617. esc_use_rep_path, esc_file);
  1618. }
  1619. }
  1620. /*
  1621. * my_system doesn't block signals the way system does.
  1622. * if ctrl-c is pressed while in there, we probably exit
  1623. * immediately and hope the shell has sent the signal
  1624. * to all of the process group members
  1625. */
  1626. if (cmdbuff[0] && (ret = my_system(cmdbuff)))
  1627. {
  1628. int stat = WEXITSTATUS(ret);
  1629. /*
  1630. * cvs diff returns 1 in exit status for 'files are different'
  1631. * so use a better method to check for failure
  1632. */
  1633. if (stat < 0 || stat > check_ret || WIFSIGNALED(ret))
  1634. {
  1635. debug(DEBUG_APPERROR, "system command returned non-zero exit status: %d: aborting", stat);
  1636. exit(1);
  1637. }
  1638. }
  1639. }
  1640. }
  1641. static CvsFileRevision * parse_revision(CvsFile * file, char * rev_str)
  1642. {
  1643. char * p;
  1644. /* The "revision" log line can include extra information
  1645. * including who is locking the file --- strip that out.
  1646. */
  1647. p = rev_str;
  1648. while (isdigit(*p) || *p == '.')
  1649. p++;
  1650. *p = 0;
  1651. return cvs_file_add_revision(file, rev_str);
  1652. }
  1653. CvsFileRevision * cvs_file_add_revision(CvsFile * file, const char * rev_str)
  1654. {
  1655. CvsFileRevision * rev;
  1656. if (!(rev = (CvsFileRevision*)get_hash_object(file->revisions, rev_str)))
  1657. {
  1658. rev = (CvsFileRevision*)calloc(1, sizeof(*rev));
  1659. rev->rev = get_string(rev_str);
  1660. rev->file = file;
  1661. rev->branch = NULL;
  1662. rev->present = 0;
  1663. rev->pre_psm = NULL;
  1664. rev->post_psm = NULL;
  1665. INIT_LIST_HEAD(&rev->branch_children);
  1666. INIT_LIST_HEAD(&rev->tags);
  1667. put_hash_object_ex(file->revisions, rev->rev, rev, HT_NO_KEYCOPY, NULL, NULL);
  1668. debug(DEBUG_STATUS, "added revision %s to file %s", rev_str, file->filename);
  1669. }
  1670. else
  1671. {
  1672. debug(DEBUG_STATUS, "found revision %s to file %s", rev_str, file->filename);
  1673. }
  1674. /*
  1675. * note: we are guaranteed to get here at least once with 'have_branches' == 1.
  1676. * we may pass through once before this, because of symbolic tags, then once
  1677. * always when processing the actual revision logs
  1678. *
  1679. * rev->branch will always be set to something, maybe "HEAD"
  1680. */
  1681. if (!rev->branch && file->have_branches)
  1682. {
  1683. char branch_str[REV_STR_MAX];
  1684. /* in the cvs cvs repository (ccvs) there are tagged versions
  1685. * that don't exist. let's mark every 'known to exist'
  1686. * version
  1687. */
  1688. rev->present = 1;
  1689. /* determine the branch this revision was committed on */
  1690. if (!get_branch(branch_str, rev->rev))
  1691. {
  1692. debug(DEBUG_APPERROR, "invalid rev format %s", rev->rev);
  1693. exit(1);
  1694. }
  1695. rev->branch = (char*)get_hash_object(file->branches, branch_str);
  1696. /* if there's no branch and it's not on the trunk, blab */
  1697. if (!rev->branch)
  1698. {
  1699. if (get_branch(branch_str, branch_str))
  1700. {
  1701. debug(DEBUG_APPMSG1, "WARNING: revision %s of file %s on unnamed branch", rev->rev, rev->file->filename);
  1702. rev->branch = "#CVSPS_NO_BRANCH";
  1703. }
  1704. else
  1705. {
  1706. rev->branch = "HEAD";
  1707. }
  1708. }
  1709. debug(DEBUG_STATUS, "revision %s of file %s on branch %s", rev->rev, rev->file->filename, rev->branch);
  1710. }
  1711. return rev;
  1712. }
  1713. CvsFile * create_cvsfile()
  1714. {
  1715. CvsFile * f = (CvsFile*)calloc(1, sizeof(*f));
  1716. if (!f)
  1717. return NULL;
  1718. f->revisions = create_hash_table(53);
  1719. f->branches = create_hash_table(13);
  1720. f->branches_sym = create_hash_table(13);
  1721. f->symbols = create_hash_table(253);
  1722. f->have_branches = 0;
  1723. if (!f->revisions || !f->branches || !f->branches_sym)
  1724. {
  1725. if (f->branches)
  1726. destroy_hash_table(f->branches, NULL);
  1727. if (f->revisions)
  1728. destroy_hash_table(f->revisions, NULL);
  1729. free(f);
  1730. return NULL;
  1731. }
  1732. return f;
  1733. }
  1734. static PatchSet * create_patch_set()
  1735. {
  1736. PatchSet * ps = (PatchSet*)calloc(1, sizeof(*ps));;
  1737. if (ps)
  1738. {
  1739. INIT_LIST_HEAD(&ps->members);
  1740. ps->psid = -1;
  1741. ps->date = 0;
  1742. ps->min_date = 0;
  1743. ps->max_date = 0;
  1744. ps->descr = NULL;
  1745. ps->author = NULL;
  1746. ps->tag = NULL;
  1747. ps->tag_flags = 0;
  1748. ps->branch_add = 0;
  1749. ps->funk_factor = 0;
  1750. ps->ancestor_branch = NULL;
  1751. CLEAR_LIST_NODE(&ps->collision_link);
  1752. }
  1753. return ps;
  1754. }
  1755. PatchSetMember * create_patch_set_member()
  1756. {
  1757. PatchSetMember * psm = (PatchSetMember*)calloc(1, sizeof(*psm));
  1758. psm->pre_rev = NULL;
  1759. psm->post_rev = NULL;
  1760. psm->ps = NULL;
  1761. psm->file = NULL;
  1762. psm->bad_funk = 0;
  1763. return psm;
  1764. }
  1765. static PatchSetRange * create_patch_set_range()
  1766. {
  1767. PatchSetRange * psr = (PatchSetRange*)calloc(1, sizeof(*psr));
  1768. return psr;
  1769. }
  1770. CvsFileRevision * file_get_revision(CvsFile * file, const char * r)
  1771. {
  1772. CvsFileRevision * rev;
  1773. if (strcmp(r, "INITIAL") == 0)
  1774. return NULL;
  1775. rev = (CvsFileRevision*)get_hash_object(file->revisions, r);
  1776. if (!rev)
  1777. {
  1778. debug(DEBUG_APPERROR, "request for non-existent rev %s in file %s", r, file->filename);
  1779. exit(1);
  1780. }
  1781. return rev;
  1782. }
  1783. /*
  1784. * Parse lines in the format:
  1785. *
  1786. * <white space>tag_name: <rev>;
  1787. *
  1788. * Handles both regular tags (these go into the symbols hash)
  1789. * and magic-branch-tags (second to last node of revision is 0)
  1790. * which go into branches and branches_sym hashes. Magic-branch
  1791. * format is hidden in CVS everwhere except the 'cvs log' output.
  1792. */
  1793. static void parse_sym(CvsFile * file, char * sym)
  1794. {
  1795. char * tag = sym, *eot;
  1796. int leaf, final_branch = -1;
  1797. char rev[REV_STR_MAX];
  1798. char rev2[REV_STR_MAX];
  1799. while (*tag && isspace(*tag))
  1800. tag++;
  1801. if (!*tag)
  1802. return;
  1803. eot = strchr(tag, ':');
  1804. if (!eot)
  1805. return;
  1806. *eot = 0;
  1807. eot += 2;
  1808. if (!get_branch_ext(rev, eot, &leaf))
  1809. {
  1810. if (strcmp(tag, "TRUNK") == 0)
  1811. {
  1812. debug(DEBUG_STATUS, "ignoring the TRUNK branch/tag");
  1813. return;
  1814. }
  1815. debug(DEBUG_APPERROR, "malformed revision");
  1816. exit(1);
  1817. }
  1818. /*
  1819. * get_branch_ext will leave final_branch alone
  1820. * if there aren't enough '.' in string
  1821. */
  1822. get_branch_ext(rev2, rev, &final_branch);
  1823. if (final_branch == 0)
  1824. {
  1825. snprintf(rev, REV_STR_MAX, "%s.%d", rev2, leaf);
  1826. debug(DEBUG_STATUS, "got sym: %s for %s", tag, rev);
  1827. cvs_file_add_branch(file, rev, tag);
  1828. }
  1829. else
  1830. {
  1831. strcpy(rev, eot);
  1832. chop(rev);
  1833. /* see cvs manual: what is this vendor tag? */
  1834. if (is_vendor_branch(rev))
  1835. cvs_file_add_branch(file, rev, tag);
  1836. else
  1837. cvs_file_add_symbol(file, rev, tag);
  1838. }
  1839. }
  1840. void cvs_file_add_symbol(CvsFile * file, const char * rev_str, const char * p_tag_str)
  1841. {
  1842. CvsFileRevision * rev;
  1843. GlobalSymbol * sym;
  1844. Tag * tag;
  1845. /* get a permanent storage string */
  1846. char * tag_str = get_string(p_tag_str);
  1847. debug(DEBUG_STATUS, "adding symbol to file: %s %s->%s", file->filename, tag_str, rev_str);
  1848. rev = cvs_file_add_revision(file, rev_str);
  1849. put_hash_object_ex(file->symbols, tag_str, rev, HT_NO_KEYCOPY, NULL, NULL);
  1850. /*
  1851. * check the global_symbols
  1852. */
  1853. sym = (GlobalSymbol*)get_hash_object(global_symbols, tag_str);
  1854. if (!sym)
  1855. {
  1856. sym = (GlobalSymbol*)malloc(sizeof(*sym));
  1857. sym->tag = tag_str;
  1858. sym->ps = NULL;
  1859. INIT_LIST_HEAD(&sym->tags);
  1860. put_hash_object_ex(global_symbols, sym->tag, sym, HT_NO_KEYCOPY, NULL, NULL);
  1861. }
  1862. tag = (Tag*)malloc(sizeof(*tag));
  1863. tag->tag = tag_str;
  1864. tag->rev = rev;
  1865. tag->sym = sym;
  1866. list_add(&tag->global_link, &sym->tags);
  1867. list_add(&tag->rev_link, &rev->tags);
  1868. }
  1869. char * cvs_file_add_branch(CvsFile * file, const char * rev, const char * tag)
  1870. {
  1871. char * new_tag;
  1872. char * new_rev;
  1873. if (get_hash_object(file->branches, rev))
  1874. {
  1875. debug(DEBUG_STATUS, "attempt to add existing branch %s:%s to %s",
  1876. rev, tag, file->filename);
  1877. return NULL;
  1878. }
  1879. /* get permanent storage for the strings */
  1880. new_tag = get_string(tag);
  1881. new_rev = get_string(rev);
  1882. put_hash_object_ex(file->branches, new_rev, new_tag, HT_NO_KEYCOPY, NULL, NULL);
  1883. put_hash_object_ex(file->branches_sym, new_tag, new_rev, HT_NO_KEYCOPY, NULL, NULL);
  1884. return new_tag;
  1885. }
  1886. /*
  1887. * Resolve each global symbol to a PatchSet. This is
  1888. * not necessarily doable, because tagging isn't
  1889. * necessarily done to the project as a whole, and
  1890. * it's possible that no tag is valid for all files
  1891. * at a single point in time. We check for that
  1892. * case though.
  1893. *
  1894. * Implementation: the most recent PatchSet containing
  1895. * a revision (post_rev) tagged by the symbol is considered
  1896. * the 'tagged' PatchSet.
  1897. */
  1898. static void resolve_global_symbols()
  1899. {
  1900. struct hash_entry * he_sym;
  1901. reset_hash_iterator(global_symbols);
  1902. while ((he_sym = next_hash_entry(global_symbols)))
  1903. {
  1904. GlobalSymbol * sym = (GlobalSymbol*)he_sym->he_obj;
  1905. PatchSet * ps;
  1906. struct list_head * next;
  1907. debug(DEBUG_STATUS, "resolving global symbol %s", sym->tag);
  1908. /*
  1909. * First pass, determine the most recent PatchSet with a
  1910. * revision tagged with the symbolic tag. This is 'the'
  1911. * patchset with the tag
  1912. */
  1913. for (next = sym->tags.next; next != &sym->tags; next = next->next)
  1914. {
  1915. Tag * tag = list_entry(next, Tag, global_link);
  1916. CvsFileRevision * rev = tag->rev;
  1917. /* FIXME:test for rev->post_psm from DEBIAN. not sure how this could happen */
  1918. if (!rev->present || !rev->post_psm)
  1919. {
  1920. struct list_head *tmp = next->prev;
  1921. debug(DEBUG_APPERROR, "revision %s of file %s is tagged but not present",
  1922. rev->rev, rev->file->filename);
  1923. /* FIXME: memleak */
  1924. list_del(next);
  1925. next = tmp;
  1926. continue;
  1927. }
  1928. ps = rev->post_psm->ps;
  1929. if (!sym->ps || ps->date > sym->ps->date)
  1930. sym->ps = ps;
  1931. }
  1932. /* convenience variable */
  1933. ps = sym->ps;
  1934. if (!ps)
  1935. {
  1936. debug(DEBUG_APPERROR, "no patchset for tag %s", sym->tag);
  1937. return;
  1938. }
  1939. ps->tag = sym->tag;
  1940. /* check if this ps is one of the '-r' patchsets */
  1941. if (restrict_tag_start && strcmp(restrict_tag_start, ps->tag) == 0)
  1942. restrict_tag_ps_start = ps->psid;
  1943. /* the second -r implies -b */
  1944. if (restrict_tag_end && strcmp(restrict_tag_end, ps->tag) == 0)
  1945. {
  1946. restrict_tag_ps_end = ps->psid;
  1947. if (restrict_branch)
  1948. {
  1949. if (strcmp(ps->branch, restrict_branch) != 0)
  1950. {
  1951. debug(DEBUG_APPMSG1,
  1952. "WARNING: -b option and second -r have conflicting branches: %s %s",
  1953. restrict_branch, ps->branch);
  1954. }
  1955. }
  1956. else
  1957. {
  1958. debug(DEBUG_APPMSG1, "NOTICE: implicit branch restriction set to %s", ps->branch);
  1959. restrict_branch = ps->branch;
  1960. }
  1961. }
  1962. /*
  1963. * Second pass.
  1964. * check if this is an invalid patchset,
  1965. * check which members are invalid. determine
  1966. * the funk factor etc.
  1967. */
  1968. for (next = sym->tags.next; next != &sym->tags; next = next->next)
  1969. {
  1970. Tag * tag = list_entry(next, Tag, global_link);
  1971. CvsFileRevision * rev = tag->rev;
  1972. CvsFileRevision * next_rev = rev_follow_branch(rev, ps->branch);
  1973. if (!next_rev)
  1974. continue;
  1975. /*
  1976. * we want the 'tagged revision' to be valid until after
  1977. * the date of the 'tagged patchset' or else there's something
  1978. * funky going on
  1979. */
  1980. if (next_rev->post_psm->ps->date < ps->date)
  1981. {
  1982. int flag = check_rev_funk(ps, next_rev);
  1983. debug(DEBUG_STATUS, "file %s revision %s tag %s: TAG VIOLATION %s",
  1984. rev->file->filename, rev->rev, sym->tag, tag_flag_descr[flag]);
  1985. ps->tag_flags |= flag;
  1986. }
  1987. }
  1988. }
  1989. }
  1990. static int revision_affects_branch(CvsFileRevision * rev, const char * branch)
  1991. {
  1992. /* special case the branch called 'HEAD' */
  1993. if (strcmp(branch, "HEAD") == 0)
  1994. {
  1995. /* look for only one '.' in rev */
  1996. char * p = strchr(rev->rev, '.');
  1997. if (p && !strchr(p + 1, '.'))
  1998. return 1;
  1999. }
  2000. else
  2001. {
  2002. char * branch_rev = (char*)get_hash_object(rev->file->branches_sym, branch);
  2003. if (branch_rev)
  2004. {
  2005. char post_rev[REV_STR_MAX];
  2006. char branch[REV_STR_MAX];
  2007. int file_leaf, branch_leaf;
  2008. strcpy(branch, branch_rev);
  2009. /* first get the branch the file rev is on */
  2010. if (get_branch_ext(post_rev, rev->rev, &file_leaf))
  2011. {
  2012. branch_leaf = file_leaf;
  2013. /* check against branch and all branch ancestor branches */
  2014. do
  2015. {
  2016. debug(DEBUG_STATUS, "check %s against %s for %s", branch, post_rev, rev->file->filename);
  2017. if (strcmp(branch, post_rev) == 0)
  2018. return (file_leaf <= branch_leaf);
  2019. }
  2020. while(get_branch_ext(branch, branch, &branch_leaf));
  2021. }
  2022. }
  2023. }
  2024. return 0;
  2025. }
  2026. static int count_dots(const char * p)
  2027. {
  2028. int dots = 0;
  2029. while (*p)
  2030. if (*p++ == '.')
  2031. dots++;
  2032. return dots;
  2033. }
  2034. /*
  2035. * When importing vendor sources, (apparently people do this)
  2036. * the code is added on a 'vendor' branch, which, for some reason
  2037. * doesn't use the magic-branch-tag format. Try to detect that now
  2038. */
  2039. static int is_vendor_branch(const char * rev)
  2040. {
  2041. return !(count_dots(rev)&1);
  2042. }
  2043. void patch_set_add_member(PatchSet * ps, PatchSetMember * psm)
  2044. {
  2045. /* check if a member for the same file already exists, if so
  2046. * put this PatchSet on the collisions list
  2047. */
  2048. struct list_head * next;
  2049. for (next = ps->members.next; next != &ps->members; next = next->next)
  2050. {
  2051. PatchSetMember * m = list_entry(next, PatchSetMember, link);
  2052. if (m->file == psm->file) {
  2053. int order = compare_rev_strings(psm->post_rev->rev, m->post_rev->rev);
  2054. /*
  2055. * Same revision too? Add it to the collision list
  2056. * if it isn't already.
  2057. */
  2058. if (!order) {
  2059. if (ps->collision_link.next == NULL)
  2060. list_add(&ps->collision_link, &collisions);
  2061. return;
  2062. }
  2063. /*
  2064. * If this is an older revision than the one we already have
  2065. * in this patchset, just ignore it
  2066. */
  2067. if (order < 0)
  2068. return;
  2069. /*
  2070. * This is a newer one, remove the old one
  2071. */
  2072. list_del(&m->link);
  2073. }
  2074. }
  2075. psm->ps = ps;
  2076. list_add(&psm->link, ps->members.prev);
  2077. }
  2078. static void set_psm_initial(PatchSetMember * psm)
  2079. {
  2080. psm->pre_rev = NULL;
  2081. if (psm->post_rev->dead)
  2082. {
  2083. /*
  2084. * we expect a 'file xyz initially added on branch abc' here
  2085. * but there can only be one such member in a given patchset
  2086. */
  2087. if (psm->ps->branch_add)
  2088. debug(DEBUG_APPMSG1, "WARNING: branch_add already set!");
  2089. psm->ps->branch_add = 1;
  2090. }
  2091. }
  2092. /*
  2093. * look at all revisions starting at rev and going forward until
  2094. * ps->date and see whether they are invalid or just funky.
  2095. */
  2096. static int check_rev_funk(PatchSet * ps, CvsFileRevision * rev)
  2097. {
  2098. int retval = TAG_FUNKY;
  2099. while (rev)
  2100. {
  2101. PatchSet * next_ps = rev->post_psm->ps;
  2102. struct list_head * next;
  2103. if (next_ps->date > ps->date)
  2104. break;
  2105. debug(DEBUG_STATUS, "ps->date %d next_ps->date %d rev->rev %s rev->branch %s",
  2106. ps->date, next_ps->date, rev->rev, rev->branch);
  2107. /*
  2108. * If the ps->tag is one of the two possible '-r' tags
  2109. * then the funkyness is even more important.
  2110. *
  2111. * In the restrict_tag_start case, this next_ps is chronologically
  2112. * before ps, but tagwise after, so set the funk_factor so it will
  2113. * be included.
  2114. *
  2115. * The restrict_tag_end case is similar, but backwards.
  2116. *
  2117. * Start assuming the HIDE/SHOW_ALL case, we will determine
  2118. * below if we have a split ps case
  2119. */
  2120. if (restrict_tag_start && strcmp(ps->tag, restrict_tag_start) == 0)
  2121. next_ps->funk_factor = FNK_SHOW_ALL;
  2122. if (restrict_tag_end && strcmp(ps->tag, restrict_tag_end) == 0)
  2123. next_ps->funk_factor = FNK_HIDE_ALL;
  2124. /*
  2125. * if all of the other members of this patchset are also 'after' the tag
  2126. * then this is a 'funky' patchset w.r.t. the tag. however, if some are
  2127. * before then the patchset is 'invalid' w.r.t. the tag, and we mark
  2128. * the members individually with 'bad_funk' ,if this tag is the
  2129. * '-r' tag. Then we can actually split the diff on this patchset
  2130. */
  2131. for (next = next_ps->members.next; next != &next_ps->members; next = next->next)
  2132. {
  2133. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  2134. if (before_tag(psm->post_rev, ps->tag))
  2135. {
  2136. retval = TAG_INVALID;
  2137. /* only set bad_funk for one of the -r tags */
  2138. if (next_ps->funk_factor)
  2139. {
  2140. psm->bad_funk = 1;
  2141. next_ps->funk_factor =
  2142. (next_ps->funk_factor == FNK_SHOW_ALL) ? FNK_SHOW_SOME : FNK_HIDE_SOME;
  2143. }
  2144. debug(DEBUG_APPMSG1,
  2145. "WARNING: Invalid PatchSet %d, Tag %s:\n"
  2146. " %s:%s=after, %s:%s=before. Treated as 'before'",
  2147. next_ps->psid, ps->tag,
  2148. rev->file->filename, rev->rev,
  2149. psm->post_rev->file->filename, psm->post_rev->rev);
  2150. }
  2151. }
  2152. rev = rev_follow_branch(rev, ps->branch);
  2153. }
  2154. return retval;
  2155. }
  2156. /* determine if the revision is before the tag */
  2157. static int before_tag(CvsFileRevision * rev, const char * tag)
  2158. {
  2159. CvsFileRevision * tagged_rev = (CvsFileRevision*)get_hash_object(rev->file->symbols, tag);
  2160. int retval = 0;
  2161. if (tagged_rev && (tagged_rev->branch == NULL || tagged_rev->post_psm == NULL))
  2162. debug(DEBUG_APPMSG1, "WARNING: Branch / post_psm == NULL for: %s %s %s %s %d",
  2163. rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
  2164. if (tagged_rev && tagged_rev->branch &&
  2165. revision_affects_branch(rev, tagged_rev->branch) &&
  2166. tagged_rev->post_psm &&
  2167. rev->post_psm->ps->date <= tagged_rev->post_psm->ps->date)
  2168. retval = 1;
  2169. debug(DEBUG_STATUS, "before_tag: %s %s %s %s %d",
  2170. rev->file->filename, tag, rev->rev, tagged_rev ? tagged_rev->rev : "N/A", retval);
  2171. return retval;
  2172. }
  2173. /* get the next revision from this one following branch if possible */
  2174. /* FIXME: not sure if this needs to follow branches leading up to branches? */
  2175. static CvsFileRevision * rev_follow_branch(CvsFileRevision * rev, const char * branch)
  2176. {
  2177. struct list_head * next;
  2178. /* check for 'main line of inheritance' */
  2179. if (strcmp(rev->branch, branch) == 0)
  2180. return rev->pre_psm ? rev->pre_psm->post_rev : NULL;
  2181. /* look down branches */
  2182. for (next = rev->branch_children.next; next != &rev->branch_children; next = next->next)
  2183. {
  2184. CvsFileRevision * next_rev = list_entry(next, CvsFileRevision, link);
  2185. //debug(DEBUG_STATUS, "SCANNING BRANCH CHILDREN: %s %s", next_rev->branch, branch);
  2186. if (strcmp(next_rev->branch, branch) == 0)
  2187. return next_rev;
  2188. }
  2189. return NULL;
  2190. }
  2191. static void check_norc(int argc, char * argv[])
  2192. {
  2193. int i = 1;
  2194. while (i < argc)
  2195. {
  2196. if (strcmp(argv[i], "--norc") == 0)
  2197. {
  2198. norc = "-f";
  2199. break;
  2200. }
  2201. i++;
  2202. }
  2203. }
  2204. static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)
  2205. {
  2206. struct list_head * next;
  2207. CvsFileRevision * rev;
  2208. /* PatchSet 1 has no ancestor */
  2209. if (ps->psid == 1)
  2210. return;
  2211. /* HEAD branch patchsets have no ancestry, but callers should know that */
  2212. if (strcmp(ps->branch, "HEAD") == 0)
  2213. {
  2214. debug(DEBUG_APPMSG1, "WARNING: no branch ancestry for HEAD");
  2215. return;
  2216. }
  2217. for (next = ps->members.next; next != &ps->members; next = next->next)
  2218. {
  2219. PatchSetMember * psm = list_entry(next, PatchSetMember, link);
  2220. rev = psm->pre_rev;
  2221. int d1, d2;
  2222. /* the reason this is at all complicated has to do with a
  2223. * branch off of a branch. it is possible (and indeed
  2224. * likely) that some file would not have been modified
  2225. * from the initial branch point to the branch-off-branch
  2226. * point, and therefore the branch-off-branch point is
  2227. * really branch-off-HEAD for that specific member (file).
  2228. * in that case, rev->branch will say HEAD but we want
  2229. * to know the symbolic name of the first branch
  2230. * so we continue to look member after member until we find
  2231. * the 'deepest' branching. deepest can actually be determined
  2232. * by considering the revision currently indicated by
  2233. * ps->ancestor_branch (by symbolic lookup) and rev->rev. the
  2234. * one with more dots wins
  2235. *
  2236. * also, the first commit in which a branch-off-branch is
  2237. * mentioned may ONLY modify files never committed since
  2238. * original branch-off-HEAD was created, so we have to keep
  2239. * checking, ps after ps to be sure to get the deepest ancestor
  2240. *
  2241. * note: rev is the pre-commit revision, not the post-commit
  2242. */
  2243. if (!head_ps->ancestor_branch)
  2244. d1 = -1;
  2245. else if (strcmp(ps->branch, rev->branch) == 0)
  2246. continue;
  2247. else if (strcmp(head_ps->ancestor_branch, "HEAD") == 0)
  2248. d1 = 1;
  2249. else {
  2250. /* branch_rev may not exist if the file was added on this branch for example */
  2251. const char * branch_rev = (char *)get_hash_object(rev->file->branches_sym, head_ps->ancestor_branch);
  2252. d1 = branch_rev ? count_dots(branch_rev) : 1;
  2253. }
  2254. /* HACK: we sometimes pretend to derive from the import branch.
  2255. * just don't do that. this is the easiest way to prevent...
  2256. */
  2257. d2 = (strcmp(rev->rev, "1.1.1.1") == 0) ? 0 : count_dots(rev->rev);
  2258. if (d2 > d1)
  2259. head_ps->ancestor_branch = rev->branch;
  2260. //printf("-----> %d ancestry %s %s %s\n", ps->psid, ps->branch, head_ps->ancestor_branch, rev->file->filename);
  2261. }
  2262. }
  2263. static void handle_collisions()
  2264. {
  2265. struct list_head *next;
  2266. for (next = collisions.next; next != &collisions; next = next->next)
  2267. {
  2268. PatchSet * ps = list_entry(next, PatchSet, collision_link);
  2269. printf("PatchSet %d has collisions\n", ps->psid);
  2270. }
  2271. }
  2272. void walk_all_patch_sets(void (*action)(PatchSet *))
  2273. {
  2274. struct list_head * next;;
  2275. for (next = all_patch_sets.next; next != &all_patch_sets; next = next->next) {
  2276. PatchSet * ps = list_entry(next, PatchSet, all_link);
  2277. action(ps);
  2278. }
  2279. }