PageRenderTime 52ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 0ms

/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

Large files files are truncated, but you can click here to view the full file

  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++;

Large files files are truncated, but you can click here to view the full file