rerere: gc and clear

Adjust "git rerere gc" and "git rerere clear" to the new world order with rerere database with multiple variants for the same shape of conflicts. Signed-off-by: Junio C Hamano <gitster@pobox.com>

Junio C Hamano committed Mar 8, 2016 at 12:11 UTC 1be1e85115244a3a11f89b75c798823a7dce795f
2 files changed +123 -45
rerere.c
+48 -39
@@ -1081,29 +1081,16 @@ int rerere_forget(struct pathspec *pathspec)
1081 * Garbage collection support
1082 */
1083
1084 -/*
1085 - * Note that this is not reentrant but is used only one-at-a-time
1086 - * so it does not matter right now.
1087 - */
1088 -static struct rerere_id *dirname_to_id(const char *name)
1089 -{
1090 - static struct rerere_id id;
1091 - id.collection = find_rerere_dir(name);
1092 - return &id;
1093 -}
1094 -
1095 -static time_t rerere_created_at(const char *dir_name)
1084 +static time_t rerere_created_at(struct rerere_id *id)
1085 {
1086 struct stat st;
1098 - struct rerere_id *id = dirname_to_id(dir_name);
1087
1088 return stat(rerere_path(id, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
1089 }
1090
1103 -static time_t rerere_last_used_at(const char *dir_name)
1091 +static time_t rerere_last_used_at(struct rerere_id *id)
1092 {
1093 struct stat st;
1106 - struct rerere_id *id = dirname_to_id(dir_name);
1094
1095 return stat(rerere_path(id, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
1096 }
@@ -1113,15 +1100,28 @@ static time_t rerere_last_used_at(const char *dir_name)
1100 */
1101 static void unlink_rr_item(struct rerere_id *id)
1102 {
1116 - unlink(rerere_path(id, "thisimage"));
1117 - unlink(rerere_path(id, "preimage"));
1118 - unlink(rerere_path(id, "postimage"));
1119 - /*
1120 - * NEEDSWORK: what if this rmdir() fails? Wouldn't we then
1121 - * assume that we already have preimage recorded in
1122 - * do_plain_rerere()?
1123 - */
1124 - rmdir(rerere_path(id, NULL));
1103 + unlink_or_warn(rerere_path(id, "thisimage"));
1104 + remove_variant(id);
1105 + id->collection->status[id->variant] = 0;
1106 +}
1107 +
1108 +static void prune_one(struct rerere_id *id, time_t now,
1109 + int cutoff_resolve, int cutoff_noresolve)
1110 +{
1111 + time_t then;
1112 + int cutoff;
1113 +
1114 + then = rerere_last_used_at(id);
1115 + if (then)
1116 + cutoff = cutoff_resolve;
1117 + else {
1118 + then = rerere_created_at(id);
1119 + if (!then)
1120 + return;
1121 + cutoff = cutoff_noresolve;
1122 + }
1123 + if (then < now - cutoff * 86400)
1124 + unlink_rr_item(id);
1125 }
1126
1127 void rerere_gc(struct string_list *rr)
@@ -1129,8 +1129,8 @@ void rerere_gc(struct string_list *rr)
1129 struct string_list to_remove = STRING_LIST_INIT_DUP;
1130 DIR *dir;
1131 struct dirent *e;
1132 - int i, cutoff;
1133 - time_t now = time(NULL), then;
1132 + int i;
1133 + time_t now = time(NULL);
1134 int cutoff_noresolve = 15;
1135 int cutoff_resolve = 60;
1136
@@ -1142,25 +1142,32 @@ void rerere_gc(struct string_list *rr)
1142 die_errno("unable to open rr-cache directory");
1143 /* Collect stale conflict IDs ... */
1144 while ((e = readdir(dir))) {
1145 + struct rerere_dir *rr_dir;
1146 + struct rerere_id id;
1147 + int now_empty;
1148 +
1149 if (is_dot_or_dotdot(e->d_name))
1150 continue;
1147 -
1148 - then = rerere_last_used_at(e->d_name);
1149 - if (then) {
1150 - cutoff = cutoff_resolve;
1151 - } else {
1152 - then = rerere_created_at(e->d_name);
1153 - if (!then)
1154 - continue;
1155 - cutoff = cutoff_noresolve;
1151 + rr_dir = find_rerere_dir(e->d_name);
1152 + if (!rr_dir)
1153 + continue; /* or should we remove e->d_name? */
1154 +
1155 + now_empty = 1;
1156 + for (id.variant = 0, id.collection = rr_dir;
1157 + id.variant < id.collection->status_nr;
1158 + id.variant++) {
1159 + prune_one(&id, now, cutoff_resolve, cutoff_noresolve);
1160 + if (id.collection->status[id.variant])
1161 + now_empty = 0;
1162 }
1157 - if (then < now - cutoff * 86400)
1163 + if (now_empty)
1164 string_list_append(&to_remove, e->d_name);
1165 }
1166 closedir(dir);
1161 - /* ... and then remove them one-by-one */
1167 +
1168 + /* ... and then remove the empty directories */
1169 for (i = 0; i < to_remove.nr; i++)
1163 - unlink_rr_item(dirname_to_id(to_remove.items[i].string));
1170 + rmdir(git_path("rr-cache/%s", to_remove.items[i].string));
1171 string_list_clear(&to_remove, 0);
1172 }
1173
@@ -1177,8 +1184,10 @@ void rerere_clear(struct string_list *merge_rr)
1184
1185 for (i = 0; i < merge_rr->nr; i++) {
1186 struct rerere_id *id = merge_rr->items[i].util;
1180 - if (!has_rerere_resolution(id))
1187 + if (!has_rerere_resolution(id)) {
1188 unlink_rr_item(id);
1189 + rmdir(rerere_path(id, NULL));
1190 + }
1191 }
1192 unlink_or_warn(git_path("MERGE_RR"));
1193 }
t/t4200-rerere.sh
+75 -6
@@ -412,6 +412,39 @@ concat_insert () {
412 cat early && printf "%s\n" "$@" && cat late "$last"
413 }
414
415 +count_pre_post () {
416 + find .git/rr-cache/ -type f -name "preimage*" >actual &&
417 + test_line_count = "$1" actual &&
418 + find .git/rr-cache/ -type f -name "postimage*" >actual &&
419 + test_line_count = "$2" actual
420 +}
421 +
422 +test_expect_success 'rerere gc' '
423 + find .git/rr-cache -type f >original &&
424 + xargs test-chmtime -172800 <original &&
425 +
426 + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
427 + find .git/rr-cache -type f >actual &&
428 + test_cmp original actual &&
429 +
430 + git -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&
431 + find .git/rr-cache -type f >actual &&
432 + test_cmp original actual &&
433 +
434 + git -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&
435 + find .git/rr-cache -type f >actual &&
436 + >expect &&
437 + test_cmp expect actual
438 +'
439 +
440 +merge_conflict_resolve () {
441 + git reset --hard &&
442 + test_must_fail git merge six.1 &&
443 + # Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)
444 + concat_insert short 6.1 6.2 >file1 &&
445 + concat_insert long 6.1 6.2 >file2
446 +}
447 +
448 test_expect_success 'multiple identical conflicts' '
449 git reset --hard &&
450
@@ -441,7 +474,7 @@ test_expect_success 'multiple identical conflicts' '
474 # - six.1 replaces these 7s with 6.1
475 # - six.2 replaces these 7s with 6.2
476
444 - test_must_fail git merge six.1 &&
477 + merge_conflict_resolve &&
478
479 # Check that rerere knows that file1 and file2 have conflicts
480
@@ -452,19 +485,43 @@ test_expect_success 'multiple identical conflicts' '
485 git rerere status | sort >actual &&
486 test_cmp expect actual &&
487
455 - # Resolution is to replace 7 with 6.1 and 6.2 (i.e. take both)
456 - concat_insert short 6.1 6.2 >file1 &&
457 - concat_insert long 6.1 6.2 >file2 &&
458 -
488 git rerere remaining >actual &&
489 test_cmp expect actual &&
490
491 + count_pre_post 2 0 &&
492 +
493 + # Pretend that the conflicts were made quite some time ago
494 + find .git/rr-cache/ -type f | xargs test-chmtime -172800 &&
495 +
496 + # Unresolved entries have not expired yet
497 + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
498 + count_pre_post 2 0 &&
499 +
500 + # Unresolved entries have expired
501 + git -c gc.rerereresolved=5 -c gc.rerereunresolved=1 rerere gc &&
502 + count_pre_post 0 0 &&
503 +
504 + # Recreate the conflicted state
505 + merge_conflict_resolve &&
506 + count_pre_post 2 0 &&
507 +
508 + # Clear it
509 + git rerere clear &&
510 + count_pre_post 0 0 &&
511 +
512 + # Recreate the conflicted state
513 + merge_conflict_resolve &&
514 + count_pre_post 2 0 &&
515 +
516 # We resolved file1 and file2
517 git rerere &&
518 >expect &&
519 git rerere remaining >actual &&
520 test_cmp expect actual &&
521
522 + # We must have recorded both of them
523 + count_pre_post 2 2 &&
524 +
525 # Now we should be able to resolve them both
526 git reset --hard &&
527 test_must_fail git merge six.1 &&
@@ -477,7 +534,19 @@ test_expect_success 'multiple identical conflicts' '
534 concat_insert short 6.1 6.2 >file1.expect &&
535 concat_insert long 6.1 6.2 >file2.expect &&
536 test_cmp file1.expect file1 &&
480 - test_cmp file2.expect file2
537 + test_cmp file2.expect file2 &&
538 +
539 + # Pretend that the resolutions are old again
540 + find .git/rr-cache/ -type f | xargs test-chmtime -172800 &&
541 +
542 + # Resolved entries have not expired yet
543 + git -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&
544 +
545 + count_pre_post 2 2 &&
546 +
547 + # Resolved entries have expired
548 + git -c gc.rerereresolved=1 -c gc.rerereunresolved=5 rerere gc &&
549 + count_pre_post 0 0
550 '
551
552 test_done