PageRenderTime 63ms CodeModel.GetById 16ms RepoModel.GetById 1ms app.codeStats 0ms

/cvsps-2.2b1/cvsps.c

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