range-diff.c 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569
  1. #include "cache.h"
  2. #include "range-diff.h"
  3. #include "string-list.h"
  4. #include "run-command.h"
  5. #include "strvec.h"
  6. #include "hashmap.h"
  7. #include "xdiff-interface.h"
  8. #include "linear-assignment.h"
  9. #include "diffcore.h"
  10. #include "commit.h"
  11. #include "pretty.h"
  12. #include "userdiff.h"
  13. #include "apply.h"
  14. struct patch_util {
  15. /* For the search for an exact match */
  16. struct hashmap_entry e;
  17. const char *diff, *patch;
  18. int i, shown;
  19. int diffsize;
  20. size_t diff_offset;
  21. /* the index of the matching item in the other branch, or -1 */
  22. int matching;
  23. struct object_id oid;
  24. };
  25. static size_t find_end_of_line(char *buffer, unsigned long size)
  26. {
  27. char *eol = memchr(buffer, '\n', size);
  28. if (!eol)
  29. return size;
  30. *eol = '\0';
  31. return eol + 1 - buffer;
  32. }
  33. /*
  34. * Reads the patches into a string list, with the `util` field being populated
  35. * as struct object_id (will need to be free()d).
  36. */
  37. static int read_patches(const char *range, struct string_list *list,
  38. const struct strvec *other_arg)
  39. {
  40. struct child_process cp = CHILD_PROCESS_INIT;
  41. struct strbuf buf = STRBUF_INIT, contents = STRBUF_INIT;
  42. struct patch_util *util = NULL;
  43. int in_header = 1;
  44. char *line, *current_filename = NULL;
  45. int offset, len;
  46. size_t size;
  47. strvec_pushl(&cp.args, "log", "--no-color", "-p", "--no-merges",
  48. "--reverse", "--date-order", "--decorate=no",
  49. "--no-prefix",
  50. /*
  51. * Choose indicators that are not used anywhere
  52. * else in diffs, but still look reasonable
  53. * (e.g. will not be confusing when debugging)
  54. */
  55. "--output-indicator-new=>",
  56. "--output-indicator-old=<",
  57. "--output-indicator-context=#",
  58. "--no-abbrev-commit",
  59. "--pretty=medium",
  60. "--notes",
  61. NULL);
  62. if (other_arg)
  63. strvec_pushv(&cp.args, other_arg->v);
  64. strvec_push(&cp.args, range);
  65. cp.out = -1;
  66. cp.no_stdin = 1;
  67. cp.git_cmd = 1;
  68. if (start_command(&cp))
  69. return error_errno(_("could not start `log`"));
  70. if (strbuf_read(&contents, cp.out, 0) < 0) {
  71. error_errno(_("could not read `log` output"));
  72. finish_command(&cp);
  73. return -1;
  74. }
  75. line = contents.buf;
  76. size = contents.len;
  77. for (offset = 0; size > 0; offset += len, size -= len, line += len) {
  78. const char *p;
  79. len = find_end_of_line(line, size);
  80. line[len - 1] = '\0';
  81. if (skip_prefix(line, "commit ", &p)) {
  82. if (util) {
  83. string_list_append(list, buf.buf)->util = util;
  84. strbuf_reset(&buf);
  85. }
  86. util = xcalloc(sizeof(*util), 1);
  87. if (get_oid(p, &util->oid)) {
  88. error(_("could not parse commit '%s'"), p);
  89. free(util);
  90. string_list_clear(list, 1);
  91. strbuf_release(&buf);
  92. strbuf_release(&contents);
  93. finish_command(&cp);
  94. return -1;
  95. }
  96. util->matching = -1;
  97. in_header = 1;
  98. continue;
  99. }
  100. if (!util) {
  101. error(_("could not parse first line of `log` output: "
  102. "did not start with 'commit ': '%s'"),
  103. line);
  104. string_list_clear(list, 1);
  105. strbuf_release(&buf);
  106. strbuf_release(&contents);
  107. finish_command(&cp);
  108. return -1;
  109. }
  110. if (starts_with(line, "diff --git")) {
  111. struct patch patch = { 0 };
  112. struct strbuf root = STRBUF_INIT;
  113. int linenr = 0;
  114. int orig_len;
  115. in_header = 0;
  116. strbuf_addch(&buf, '\n');
  117. if (!util->diff_offset)
  118. util->diff_offset = buf.len;
  119. line[len - 1] = '\n';
  120. orig_len = len;
  121. len = parse_git_diff_header(&root, &linenr, 0, line,
  122. len, size, &patch);
  123. if (len < 0)
  124. die(_("could not parse git header '%.*s'"),
  125. orig_len, line);
  126. strbuf_addstr(&buf, " ## ");
  127. if (patch.is_new > 0)
  128. strbuf_addf(&buf, "%s (new)", patch.new_name);
  129. else if (patch.is_delete > 0)
  130. strbuf_addf(&buf, "%s (deleted)", patch.old_name);
  131. else if (patch.is_rename)
  132. strbuf_addf(&buf, "%s => %s", patch.old_name, patch.new_name);
  133. else
  134. strbuf_addstr(&buf, patch.new_name);
  135. free(current_filename);
  136. if (patch.is_delete > 0)
  137. current_filename = xstrdup(patch.old_name);
  138. else
  139. current_filename = xstrdup(patch.new_name);
  140. if (patch.new_mode && patch.old_mode &&
  141. patch.old_mode != patch.new_mode)
  142. strbuf_addf(&buf, " (mode change %06o => %06o)",
  143. patch.old_mode, patch.new_mode);
  144. strbuf_addstr(&buf, " ##");
  145. } else if (in_header) {
  146. if (starts_with(line, "Author: ")) {
  147. strbuf_addstr(&buf, " ## Metadata ##\n");
  148. strbuf_addstr(&buf, line);
  149. strbuf_addstr(&buf, "\n\n");
  150. strbuf_addstr(&buf, " ## Commit message ##\n");
  151. } else if (starts_with(line, "Notes") &&
  152. line[strlen(line) - 1] == ':') {
  153. strbuf_addstr(&buf, "\n\n");
  154. /* strip the trailing colon */
  155. strbuf_addf(&buf, " ## %.*s ##\n",
  156. (int)(strlen(line) - 1), line);
  157. } else if (starts_with(line, " ")) {
  158. p = line + len - 2;
  159. while (isspace(*p) && p >= line)
  160. p--;
  161. strbuf_add(&buf, line, p - line + 1);
  162. strbuf_addch(&buf, '\n');
  163. }
  164. continue;
  165. } else if (skip_prefix(line, "@@ ", &p)) {
  166. p = strstr(p, "@@");
  167. strbuf_addstr(&buf, "@@");
  168. if (current_filename && p[2])
  169. strbuf_addf(&buf, " %s:", current_filename);
  170. if (p)
  171. strbuf_addstr(&buf, p + 2);
  172. } else if (!line[0])
  173. /*
  174. * A completely blank (not ' \n', which is context)
  175. * line is not valid in a diff. We skip it
  176. * silently, because this neatly handles the blank
  177. * separator line between commits in git-log
  178. * output.
  179. */
  180. continue;
  181. else if (line[0] == '>') {
  182. strbuf_addch(&buf, '+');
  183. strbuf_addstr(&buf, line + 1);
  184. } else if (line[0] == '<') {
  185. strbuf_addch(&buf, '-');
  186. strbuf_addstr(&buf, line + 1);
  187. } else if (line[0] == '#') {
  188. strbuf_addch(&buf, ' ');
  189. strbuf_addstr(&buf, line + 1);
  190. } else {
  191. strbuf_addch(&buf, ' ');
  192. strbuf_addstr(&buf, line);
  193. }
  194. strbuf_addch(&buf, '\n');
  195. util->diffsize++;
  196. }
  197. strbuf_release(&contents);
  198. if (util)
  199. string_list_append(list, buf.buf)->util = util;
  200. strbuf_release(&buf);
  201. free(current_filename);
  202. if (finish_command(&cp))
  203. return -1;
  204. return 0;
  205. }
  206. static int patch_util_cmp(const void *dummy, const struct patch_util *a,
  207. const struct patch_util *b, const char *keydata)
  208. {
  209. return strcmp(a->diff, keydata ? keydata : b->diff);
  210. }
  211. static void find_exact_matches(struct string_list *a, struct string_list *b)
  212. {
  213. struct hashmap map;
  214. int i;
  215. hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0);
  216. /* First, add the patches of a to a hash map */
  217. for (i = 0; i < a->nr; i++) {
  218. struct patch_util *util = a->items[i].util;
  219. util->i = i;
  220. util->patch = a->items[i].string;
  221. util->diff = util->patch + util->diff_offset;
  222. hashmap_entry_init(&util->e, strhash(util->diff));
  223. hashmap_add(&map, &util->e);
  224. }
  225. /* Now try to find exact matches in b */
  226. for (i = 0; i < b->nr; i++) {
  227. struct patch_util *util = b->items[i].util, *other;
  228. util->i = i;
  229. util->patch = b->items[i].string;
  230. util->diff = util->patch + util->diff_offset;
  231. hashmap_entry_init(&util->e, strhash(util->diff));
  232. other = hashmap_remove_entry(&map, util, e, NULL);
  233. if (other) {
  234. if (other->matching >= 0)
  235. BUG("already assigned!");
  236. other->matching = i;
  237. util->matching = other->i;
  238. }
  239. }
  240. hashmap_free(&map);
  241. }
  242. static void diffsize_consume(void *data, char *line, unsigned long len)
  243. {
  244. (*(int *)data)++;
  245. }
  246. static void diffsize_hunk(void *data, long ob, long on, long nb, long nn,
  247. const char *funcline, long funclen)
  248. {
  249. diffsize_consume(data, NULL, 0);
  250. }
  251. static int diffsize(const char *a, const char *b)
  252. {
  253. xpparam_t pp = { 0 };
  254. xdemitconf_t cfg = { 0 };
  255. mmfile_t mf1, mf2;
  256. int count = 0;
  257. mf1.ptr = (char *)a;
  258. mf1.size = strlen(a);
  259. mf2.ptr = (char *)b;
  260. mf2.size = strlen(b);
  261. cfg.ctxlen = 3;
  262. if (!xdi_diff_outf(&mf1, &mf2,
  263. diffsize_hunk, diffsize_consume, &count,
  264. &pp, &cfg))
  265. return count;
  266. error(_("failed to generate diff"));
  267. return COST_MAX;
  268. }
  269. static void get_correspondences(struct string_list *a, struct string_list *b,
  270. int creation_factor)
  271. {
  272. int n = a->nr + b->nr;
  273. int *cost, c, *a2b, *b2a;
  274. int i, j;
  275. ALLOC_ARRAY(cost, st_mult(n, n));
  276. ALLOC_ARRAY(a2b, n);
  277. ALLOC_ARRAY(b2a, n);
  278. for (i = 0; i < a->nr; i++) {
  279. struct patch_util *a_util = a->items[i].util;
  280. for (j = 0; j < b->nr; j++) {
  281. struct patch_util *b_util = b->items[j].util;
  282. if (a_util->matching == j)
  283. c = 0;
  284. else if (a_util->matching < 0 && b_util->matching < 0)
  285. c = diffsize(a_util->diff, b_util->diff);
  286. else
  287. c = COST_MAX;
  288. cost[i + n * j] = c;
  289. }
  290. c = a_util->matching < 0 ?
  291. a_util->diffsize * creation_factor / 100 : COST_MAX;
  292. for (j = b->nr; j < n; j++)
  293. cost[i + n * j] = c;
  294. }
  295. for (j = 0; j < b->nr; j++) {
  296. struct patch_util *util = b->items[j].util;
  297. c = util->matching < 0 ?
  298. util->diffsize * creation_factor / 100 : COST_MAX;
  299. for (i = a->nr; i < n; i++)
  300. cost[i + n * j] = c;
  301. }
  302. for (i = a->nr; i < n; i++)
  303. for (j = b->nr; j < n; j++)
  304. cost[i + n * j] = 0;
  305. compute_assignment(n, n, cost, a2b, b2a);
  306. for (i = 0; i < a->nr; i++)
  307. if (a2b[i] >= 0 && a2b[i] < b->nr) {
  308. struct patch_util *a_util = a->items[i].util;
  309. struct patch_util *b_util = b->items[a2b[i]].util;
  310. a_util->matching = a2b[i];
  311. b_util->matching = i;
  312. }
  313. free(cost);
  314. free(a2b);
  315. free(b2a);
  316. }
  317. static void output_pair_header(struct diff_options *diffopt,
  318. int patch_no_width,
  319. struct strbuf *buf,
  320. struct strbuf *dashes,
  321. struct patch_util *a_util,
  322. struct patch_util *b_util)
  323. {
  324. struct object_id *oid = a_util ? &a_util->oid : &b_util->oid;
  325. struct commit *commit;
  326. char status;
  327. const char *color_reset = diff_get_color_opt(diffopt, DIFF_RESET);
  328. const char *color_old = diff_get_color_opt(diffopt, DIFF_FILE_OLD);
  329. const char *color_new = diff_get_color_opt(diffopt, DIFF_FILE_NEW);
  330. const char *color_commit = diff_get_color_opt(diffopt, DIFF_COMMIT);
  331. const char *color;
  332. if (!dashes->len)
  333. strbuf_addchars(dashes, '-',
  334. strlen(find_unique_abbrev(oid,
  335. DEFAULT_ABBREV)));
  336. if (!b_util) {
  337. color = color_old;
  338. status = '<';
  339. } else if (!a_util) {
  340. color = color_new;
  341. status = '>';
  342. } else if (strcmp(a_util->patch, b_util->patch)) {
  343. color = color_commit;
  344. status = '!';
  345. } else {
  346. color = color_commit;
  347. status = '=';
  348. }
  349. strbuf_reset(buf);
  350. strbuf_addstr(buf, status == '!' ? color_old : color);
  351. if (!a_util)
  352. strbuf_addf(buf, "%*s: %s ", patch_no_width, "-", dashes->buf);
  353. else
  354. strbuf_addf(buf, "%*d: %s ", patch_no_width, a_util->i + 1,
  355. find_unique_abbrev(&a_util->oid, DEFAULT_ABBREV));
  356. if (status == '!')
  357. strbuf_addf(buf, "%s%s", color_reset, color);
  358. strbuf_addch(buf, status);
  359. if (status == '!')
  360. strbuf_addf(buf, "%s%s", color_reset, color_new);
  361. if (!b_util)
  362. strbuf_addf(buf, " %*s: %s", patch_no_width, "-", dashes->buf);
  363. else
  364. strbuf_addf(buf, " %*d: %s", patch_no_width, b_util->i + 1,
  365. find_unique_abbrev(&b_util->oid, DEFAULT_ABBREV));
  366. commit = lookup_commit_reference(the_repository, oid);
  367. if (commit) {
  368. if (status == '!')
  369. strbuf_addf(buf, "%s%s", color_reset, color);
  370. strbuf_addch(buf, ' ');
  371. pp_commit_easy(CMIT_FMT_ONELINE, commit, buf);
  372. }
  373. strbuf_addf(buf, "%s\n", color_reset);
  374. fwrite(buf->buf, buf->len, 1, diffopt->file);
  375. }
  376. static struct userdiff_driver section_headers = {
  377. .funcname = { "^ ## (.*) ##$\n"
  378. "^.?@@ (.*)$", REG_EXTENDED }
  379. };
  380. static struct diff_filespec *get_filespec(const char *name, const char *p)
  381. {
  382. struct diff_filespec *spec = alloc_filespec(name);
  383. fill_filespec(spec, &null_oid, 0, 0100644);
  384. spec->data = (char *)p;
  385. spec->size = strlen(p);
  386. spec->should_munmap = 0;
  387. spec->is_stdin = 1;
  388. spec->driver = &section_headers;
  389. return spec;
  390. }
  391. static void patch_diff(const char *a, const char *b,
  392. struct diff_options *diffopt)
  393. {
  394. diff_queue(&diff_queued_diff,
  395. get_filespec("a", a), get_filespec("b", b));
  396. diffcore_std(diffopt);
  397. diff_flush(diffopt);
  398. }
  399. static void output(struct string_list *a, struct string_list *b,
  400. struct diff_options *diffopt)
  401. {
  402. struct strbuf buf = STRBUF_INIT, dashes = STRBUF_INIT;
  403. int patch_no_width = decimal_width(1 + (a->nr > b->nr ? a->nr : b->nr));
  404. int i = 0, j = 0;
  405. /*
  406. * We assume the user is really more interested in the second argument
  407. * ("newer" version). To that end, we print the output in the order of
  408. * the RHS (the `b` parameter). To put the LHS (the `a` parameter)
  409. * commits that are no longer in the RHS into a good place, we place
  410. * them once we have shown all of their predecessors in the LHS.
  411. */
  412. while (i < a->nr || j < b->nr) {
  413. struct patch_util *a_util, *b_util;
  414. a_util = i < a->nr ? a->items[i].util : NULL;
  415. b_util = j < b->nr ? b->items[j].util : NULL;
  416. /* Skip all the already-shown commits from the LHS. */
  417. while (i < a->nr && a_util->shown)
  418. a_util = ++i < a->nr ? a->items[i].util : NULL;
  419. /* Show unmatched LHS commit whose predecessors were shown. */
  420. if (i < a->nr && a_util->matching < 0) {
  421. output_pair_header(diffopt, patch_no_width,
  422. &buf, &dashes, a_util, NULL);
  423. i++;
  424. continue;
  425. }
  426. /* Show unmatched RHS commits. */
  427. while (j < b->nr && b_util->matching < 0) {
  428. output_pair_header(diffopt, patch_no_width,
  429. &buf, &dashes, NULL, b_util);
  430. b_util = ++j < b->nr ? b->items[j].util : NULL;
  431. }
  432. /* Show matching LHS/RHS pair. */
  433. if (j < b->nr) {
  434. a_util = a->items[b_util->matching].util;
  435. output_pair_header(diffopt, patch_no_width,
  436. &buf, &dashes, a_util, b_util);
  437. if (!(diffopt->output_format & DIFF_FORMAT_NO_OUTPUT))
  438. patch_diff(a->items[b_util->matching].string,
  439. b->items[j].string, diffopt);
  440. a_util->shown = 1;
  441. j++;
  442. }
  443. }
  444. strbuf_release(&buf);
  445. strbuf_release(&dashes);
  446. }
  447. static struct strbuf *output_prefix_cb(struct diff_options *opt, void *data)
  448. {
  449. return data;
  450. }
  451. int show_range_diff(const char *range1, const char *range2,
  452. int creation_factor, int dual_color,
  453. const struct diff_options *diffopt,
  454. const struct strvec *other_arg)
  455. {
  456. int res = 0;
  457. struct string_list branch1 = STRING_LIST_INIT_DUP;
  458. struct string_list branch2 = STRING_LIST_INIT_DUP;
  459. if (read_patches(range1, &branch1, other_arg))
  460. res = error(_("could not parse log for '%s'"), range1);
  461. if (!res && read_patches(range2, &branch2, other_arg))
  462. res = error(_("could not parse log for '%s'"), range2);
  463. if (!res) {
  464. struct diff_options opts;
  465. struct strbuf indent = STRBUF_INIT;
  466. if (diffopt)
  467. memcpy(&opts, diffopt, sizeof(opts));
  468. else
  469. diff_setup(&opts);
  470. if (!opts.output_format)
  471. opts.output_format = DIFF_FORMAT_PATCH;
  472. opts.flags.suppress_diff_headers = 1;
  473. opts.flags.dual_color_diffed_diffs = dual_color;
  474. opts.flags.suppress_hunk_header_line_count = 1;
  475. opts.output_prefix = output_prefix_cb;
  476. strbuf_addstr(&indent, " ");
  477. opts.output_prefix_data = &indent;
  478. diff_setup_done(&opts);
  479. find_exact_matches(&branch1, &branch2);
  480. get_correspondences(&branch1, &branch2, creation_factor);
  481. output(&branch1, &branch2, &opts);
  482. strbuf_release(&indent);
  483. }
  484. string_list_clear(&branch1, 1);
  485. string_list_clear(&branch2, 1);
  486. return res;
  487. }