git p4 test: cleanup_git should make a new $git
[gitweb.git] / rerere.c
index 323e493dafee46c0d3f95e3c4cd9c4c9b463bbef..dcb525a4d03faeba53d108d2e175a6d04f99d160 100644 (file)
--- a/rerere.c
+++ b/rerere.c
@@ -1,8 +1,16 @@
 #include "cache.h"
 #include "string-list.h"
 #include "rerere.h"
-#include "xdiff/xdiff.h"
 #include "xdiff-interface.h"
+#include "dir.h"
+#include "resolve-undo.h"
+#include "ll-merge.h"
+#include "attr.h"
+
+#define RESOLVED 0
+#define PUNTED 1
+#define THREE_STAGED 2
+void *RERERE_RESOLVED = &RERERE_RESOLVED;
 
 /* if rerere_enabled == -1, fall back to detection of .git/rr-cache */
 static int rerere_enabled = -1;
@@ -12,15 +20,15 @@ static int rerere_autoupdate;
 
 static char *merge_rr_path;
 
-static const char *rr_path(const char *name, const char *file)
+const char *rerere_path(const char *hex, const char *file)
 {
-       return git_path("rr-cache/%s/%s", name, file);
+       return git_path("rr-cache/%s/%s", hex, file);
 }
 
-static int has_resolution(const char *name)
+int has_rerere_resolution(const char *hex)
 {
        struct stat st;
-       return !stat(rr_path(name, "postimage"), &st);
+       return !stat(rerere_path(hex, "postimage"), &st);
 }
 
 static void read_rr(struct string_list *rr)
@@ -39,11 +47,17 @@ static void read_rr(struct string_list *rr)
                name = xstrdup(buf);
                if (fgetc(in) != '\t')
                        die("corrupt MERGE_RR");
-               for (i = 0; i < sizeof(buf) && (buf[i] = fgetc(in)); i++)
-                       ; /* do nothing */
+               for (i = 0; i < sizeof(buf); i++) {
+                       int c = fgetc(in);
+                       if (c < 0)
+                               die("corrupt MERGE_RR");
+                       buf[i] = c;
+                       if (c == 0)
+                                break;
+               }
                if (i == sizeof(buf))
                        die("filename too long");
-               string_list_insert(buf, rr)->util = name;
+               string_list_insert(rr, buf)->util = name;
        }
        fclose(in);
 }
@@ -61,7 +75,7 @@ static int write_rr(struct string_list *rr, int out_fd)
                path = rr->items[i].string;
                length = strlen(path) + 1;
                if (write_in_full(out_fd, rr->items[i].util, 40) != 40 ||
-                   write_in_full(out_fd, "\t", 1) != 1 ||
+                   write_str_in_full(out_fd, "\t") != 1 ||
                    write_in_full(out_fd, path, length) != length)
                        die("unable to write rerere record");
        }
@@ -70,69 +84,135 @@ static int write_rr(struct string_list *rr, int out_fd)
        return 0;
 }
 
-static int handle_file(const char *path,
-        unsigned char *sha1, const char *output)
+static void ferr_write(const void *p, size_t count, FILE *fp, int *err)
 {
-       SHA_CTX ctx;
-       char buf[1024];
-       int hunk = 0, hunk_no = 0;
-       struct strbuf one, two;
-       FILE *f = fopen(path, "r");
-       FILE *out = NULL;
+       if (!count || *err)
+               return;
+       if (fwrite(p, count, 1, fp) != 1)
+               *err = errno;
+}
 
-       if (!f)
-               return error("Could not open %s", path);
+static inline void ferr_puts(const char *s, FILE *fp, int *err)
+{
+       ferr_write(s, strlen(s), fp, err);
+}
 
-       if (output) {
-               out = fopen(output, "w");
-               if (!out) {
-                       fclose(f);
-                       return error("Could not write %s", output);
+struct rerere_io {
+       int (*getline)(struct strbuf *, struct rerere_io *);
+       FILE *output;
+       int wrerror;
+       /* some more stuff */
+};
+
+static void rerere_io_putstr(const char *str, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_puts(str, io->output, &io->wrerror);
+}
+
+static void rerere_io_putconflict(int ch, int size, struct rerere_io *io)
+{
+       char buf[64];
+
+       while (size) {
+               if (size < sizeof(buf) - 2) {
+                       memset(buf, ch, size);
+                       buf[size] = '\n';
+                       buf[size + 1] = '\0';
+                       size = 0;
+               } else {
+                       int sz = sizeof(buf) - 1;
+                       if (size <= sz)
+                               sz -= (sz - size) + 1;
+                       memset(buf, ch, sz);
+                       buf[sz] = '\0';
+                       size -= sz;
                }
+               rerere_io_putstr(buf, io);
        }
+}
+
+static void rerere_io_putmem(const char *mem, size_t sz, struct rerere_io *io)
+{
+       if (io->output)
+               ferr_write(mem, sz, io->output, &io->wrerror);
+}
+
+struct rerere_io_file {
+       struct rerere_io io;
+       FILE *input;
+};
+
+static int rerere_file_getline(struct strbuf *sb, struct rerere_io *io_)
+{
+       struct rerere_io_file *io = (struct rerere_io_file *)io_;
+       return strbuf_getwholeline(sb, io->input, '\n');
+}
+
+static int is_cmarker(char *buf, int marker_char, int marker_size, int want_sp)
+{
+       while (marker_size--)
+               if (*buf++ != marker_char)
+                       return 0;
+       if (want_sp && *buf != ' ')
+               return 0;
+       return isspace(*buf);
+}
+
+static int handle_path(unsigned char *sha1, struct rerere_io *io, int marker_size)
+{
+       git_SHA_CTX ctx;
+       int hunk_no = 0;
+       enum {
+               RR_CONTEXT = 0, RR_SIDE_1, RR_SIDE_2, RR_ORIGINAL
+       } hunk = RR_CONTEXT;
+       struct strbuf one = STRBUF_INIT, two = STRBUF_INIT;
+       struct strbuf buf = STRBUF_INIT;
 
        if (sha1)
-               SHA1_Init(&ctx);
+               git_SHA1_Init(&ctx);
 
-       strbuf_init(&one, 0);
-       strbuf_init(&two,  0);
-       while (fgets(buf, sizeof(buf), f)) {
-               if (!prefixcmp(buf, "<<<<<<< ")) {
-                       if (hunk)
+       while (!io->getline(&buf, io)) {
+               if (is_cmarker(buf.buf, '<', marker_size, 1)) {
+                       if (hunk != RR_CONTEXT)
                                goto bad;
-                       hunk = 1;
-               } else if (!prefixcmp(buf, "=======") && isspace(buf[7])) {
-                       if (hunk != 1)
+                       hunk = RR_SIDE_1;
+               } else if (is_cmarker(buf.buf, '|', marker_size, 0)) {
+                       if (hunk != RR_SIDE_1)
                                goto bad;
-                       hunk = 2;
-               } else if (!prefixcmp(buf, ">>>>>>> ")) {
-                       if (hunk != 2)
+                       hunk = RR_ORIGINAL;
+               } else if (is_cmarker(buf.buf, '=', marker_size, 0)) {
+                       if (hunk != RR_SIDE_1 && hunk != RR_ORIGINAL)
+                               goto bad;
+                       hunk = RR_SIDE_2;
+               } else if (is_cmarker(buf.buf, '>', marker_size, 1)) {
+                       if (hunk != RR_SIDE_2)
                                goto bad;
                        if (strbuf_cmp(&one, &two) > 0)
                                strbuf_swap(&one, &two);
                        hunk_no++;
-                       hunk = 0;
-                       if (out) {
-                               fputs("<<<<<<<\n", out);
-                               fwrite(one.buf, one.len, 1, out);
-                               fputs("=======\n", out);
-                               fwrite(two.buf, two.len, 1, out);
-                               fputs(">>>>>>>\n", out);
-                       }
+                       hunk = RR_CONTEXT;
+                       rerere_io_putconflict('<', marker_size, io);
+                       rerere_io_putmem(one.buf, one.len, io);
+                       rerere_io_putconflict('=', marker_size, io);
+                       rerere_io_putmem(two.buf, two.len, io);
+                       rerere_io_putconflict('>', marker_size, io);
                        if (sha1) {
-                               SHA1_Update(&ctx, one.buf ? one.buf : "",
+                               git_SHA1_Update(&ctx, one.buf ? one.buf : "",
                                            one.len + 1);
-                               SHA1_Update(&ctx, two.buf ? two.buf : "",
+                               git_SHA1_Update(&ctx, two.buf ? two.buf : "",
                                            two.len + 1);
                        }
                        strbuf_reset(&one);
                        strbuf_reset(&two);
-               } else if (hunk == 1)
-                       strbuf_addstr(&one, buf);
-               else if (hunk == 2)
-                       strbuf_addstr(&two, buf);
-               else if (out)
-                       fputs(buf, out);
+               } else if (hunk == RR_SIDE_1)
+                       strbuf_addstr(&one, buf.buf);
+               else if (hunk == RR_ORIGINAL)
+                       ; /* discard */
+               else if (hunk == RR_SIDE_2)
+                       strbuf_addstr(&two, buf.buf);
+               else
+                       rerere_io_putstr(buf.buf, io);
                continue;
        bad:
                hunk = 99; /* force error exit */
@@ -140,35 +220,210 @@ static int handle_file(const char *path,
        }
        strbuf_release(&one);
        strbuf_release(&two);
+       strbuf_release(&buf);
 
-       fclose(f);
-       if (out)
-               fclose(out);
        if (sha1)
-               SHA1_Final(sha1, &ctx);
-       if (hunk) {
+               git_SHA1_Final(sha1, &ctx);
+       if (hunk != RR_CONTEXT)
+               return -1;
+       return hunk_no;
+}
+
+static int handle_file(const char *path, unsigned char *sha1, const char *output)
+{
+       int hunk_no = 0;
+       struct rerere_io_file io;
+       int marker_size = ll_merge_marker_size(path);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_file_getline;
+       io.input = fopen(path, "r");
+       io.io.wrerror = 0;
+       if (!io.input)
+               return error("Could not open %s", path);
+
+       if (output) {
+               io.io.output = fopen(output, "w");
+               if (!io.io.output) {
+                       fclose(io.input);
+                       return error("Could not write %s", output);
+               }
+       }
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+
+       fclose(io.input);
+       if (io.io.wrerror)
+               error("There were errors while writing %s (%s)",
+                     path, strerror(io.io.wrerror));
+       if (io.io.output && fclose(io.io.output))
+               io.io.wrerror = error("Failed to flush %s: %s",
+                                     path, strerror(errno));
+
+       if (hunk_no < 0) {
                if (output)
-                       unlink(output);
+                       unlink_or_warn(output);
                return error("Could not parse conflict hunks in %s", path);
        }
+       if (io.io.wrerror)
+               return -1;
        return hunk_no;
 }
 
-static int find_conflict(struct string_list *conflict)
+struct rerere_io_mem {
+       struct rerere_io io;
+       struct strbuf input;
+};
+
+static int rerere_mem_getline(struct strbuf *sb, struct rerere_io *io_)
 {
-       int i;
-       if (read_cache() < 0)
-               return error("Could not read index");
-       for (i = 0; i+1 < active_nr; i++) {
+       struct rerere_io_mem *io = (struct rerere_io_mem *)io_;
+       char *ep;
+       size_t len;
+
+       strbuf_release(sb);
+       if (!io->input.len)
+               return -1;
+       ep = strchrnul(io->input.buf, '\n');
+       if (*ep == '\n')
+               ep++;
+       len = ep - io->input.buf;
+       strbuf_add(sb, io->input.buf, len);
+       strbuf_remove(&io->input, 0, len);
+       return 0;
+}
+
+static int handle_cache(const char *path, unsigned char *sha1, const char *output)
+{
+       mmfile_t mmfile[3];
+       mmbuffer_t result = {NULL, 0};
+       struct cache_entry *ce;
+       int pos, len, i, hunk_no;
+       struct rerere_io_mem io;
+       int marker_size = ll_merge_marker_size(path);
+
+       /*
+        * Reproduce the conflicted merge in-core
+        */
+       len = strlen(path);
+       pos = cache_name_pos(path, len);
+       if (0 <= pos)
+               return -1;
+       pos = -pos - 1;
+
+       for (i = 0; i < 3; i++) {
+               enum object_type type;
+               unsigned long size;
+
+               mmfile[i].size = 0;
+               mmfile[i].ptr = NULL;
+               if (active_nr <= pos)
+                       break;
+               ce = active_cache[pos++];
+               if (ce_namelen(ce) != len || memcmp(ce->name, path, len)
+                   || ce_stage(ce) != i + 1)
+                       break;
+               mmfile[i].ptr = read_sha1_file(ce->sha1, &type, &size);
+               mmfile[i].size = size;
+       }
+       for (i = 0; i < 3; i++) {
+               if (!mmfile[i].ptr && !mmfile[i].size)
+                       mmfile[i].ptr = xstrdup("");
+       }
+       /*
+        * NEEDSWORK: handle conflicts from merges with
+        * merge.renormalize set, too
+        */
+       ll_merge(&result, path, &mmfile[0], NULL,
+                &mmfile[1], "ours",
+                &mmfile[2], "theirs", NULL);
+       for (i = 0; i < 3; i++)
+               free(mmfile[i].ptr);
+
+       memset(&io, 0, sizeof(io));
+       io.io.getline = rerere_mem_getline;
+       if (output)
+               io.io.output = fopen(output, "w");
+       else
+               io.io.output = NULL;
+       strbuf_init(&io.input, 0);
+       strbuf_attach(&io.input, result.ptr, result.size, result.size);
+
+       hunk_no = handle_path(sha1, (struct rerere_io *)&io, marker_size);
+       strbuf_release(&io.input);
+       if (io.io.output)
+               fclose(io.io.output);
+       return hunk_no;
+}
+
+static int check_one_conflict(int i, int *type)
+{
+       struct cache_entry *e = active_cache[i];
+
+       if (!ce_stage(e)) {
+               *type = RESOLVED;
+               return i + 1;
+       }
+
+       *type = PUNTED;
+       if (ce_stage(e) == 1) {
+               if (active_nr <= ++i)
+                       return i + 1;
+       }
+
+       /* Only handle regular files with both stages #2 and #3 */
+       if (i + 1 < active_nr) {
                struct cache_entry *e2 = active_cache[i];
-               struct cache_entry *e3 = active_cache[i+1];
+               struct cache_entry *e3 = active_cache[i + 1];
                if (ce_stage(e2) == 2 &&
                    ce_stage(e3) == 3 &&
-                   ce_same_name(e2, e3) &&
+                   ce_same_name(e, e3) &&
                    S_ISREG(e2->ce_mode) &&
-                   S_ISREG(e3->ce_mode)) {
-                       string_list_insert((const char *)e2->name, conflict);
-                       i++; /* skip over both #2 and #3 */
+                   S_ISREG(e3->ce_mode))
+                       *type = THREE_STAGED;
+       }
+
+       /* Skip the entries with the same name */
+       while (i < active_nr && ce_same_name(e, active_cache[i]))
+               i++;
+       return i;
+}
+
+static int find_conflict(struct string_list *conflict)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       for (i = 0; i < active_nr;) {
+               int conflict_type;
+               struct cache_entry *e = active_cache[i];
+               i = check_one_conflict(i, &conflict_type);
+               if (conflict_type == THREE_STAGED)
+                       string_list_insert(conflict, (const char *)e->name);
+       }
+       return 0;
+}
+
+int rerere_remaining(struct string_list *merge_rr)
+{
+       int i;
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       for (i = 0; i < active_nr;) {
+               int conflict_type;
+               struct cache_entry *e = active_cache[i];
+               i = check_one_conflict(i, &conflict_type);
+               if (conflict_type == PUNTED)
+                       string_list_insert(merge_rr, (const char *)e->name);
+               else if (conflict_type == RESOLVED) {
+                       struct string_list_item *it;
+                       it = string_list_lookup(merge_rr, (const char *)e->name);
+                       if (it != NULL) {
+                               free(it->util);
+                               it->util = RERERE_RESOLVED;
+                       }
                }
        }
        return 0;
@@ -177,27 +432,38 @@ static int find_conflict(struct string_list *conflict)
 static int merge(const char *name, const char *path)
 {
        int ret;
-       mmfile_t cur, base, other;
+       mmfile_t cur = {NULL, 0}, base = {NULL, 0}, other = {NULL, 0};
        mmbuffer_t result = {NULL, 0};
-       xpparam_t xpp = {XDF_NEED_MINIMAL};
 
-       if (handle_file(path, NULL, rr_path(name, "thisimage")) < 0)
+       if (handle_file(path, NULL, rerere_path(name, "thisimage")) < 0)
                return 1;
 
-       if (read_mmfile(&cur, rr_path(name, "thisimage")) ||
-                       read_mmfile(&base, rr_path(name, "preimage")) ||
-                       read_mmfile(&other, rr_path(name, "postimage")))
-               return 1;
-       ret = xdl_merge(&base, &cur, "", &other, "",
-                       &xpp, XDL_MERGE_ZEALOUS, &result);
+       if (read_mmfile(&cur, rerere_path(name, "thisimage")) ||
+                       read_mmfile(&base, rerere_path(name, "preimage")) ||
+                       read_mmfile(&other, rerere_path(name, "postimage"))) {
+               ret = 1;
+               goto out;
+       }
+       ret = ll_merge(&result, path, &base, NULL, &cur, "", &other, "", NULL);
        if (!ret) {
-               FILE *f = fopen(path, "w");
+               FILE *f;
+
+               if (utime(rerere_path(name, "postimage"), NULL) < 0)
+                       warning("failed utime() on %s: %s",
+                                       rerere_path(name, "postimage"),
+                                       strerror(errno));
+               f = fopen(path, "w");
                if (!f)
-                       return error("Could not write to %s", path);
-               fwrite(result.ptr, result.size, 1, f);
-               fclose(f);
+                       return error("Could not open %s: %s", path,
+                                    strerror(errno));
+               if (fwrite(result.ptr, result.size, 1, f) != 1)
+                       error("Could not write %s: %s", path, strerror(errno));
+               if (fclose(f))
+                       return error("Writing %s failed: %s", path,
+                                    strerror(errno));
        }
 
+out:
        free(cur.ptr);
        free(base.ptr);
        free(other.ptr);
@@ -234,8 +500,8 @@ static int update_paths(struct string_list *update)
 
 static int do_plain_rerere(struct string_list *rr, int fd)
 {
-       struct string_list conflict = { NULL, 0, 0, 1 };
-       struct string_list update = { NULL, 0, 0, 1 };
+       struct string_list conflict = STRING_LIST_INIT_DUP;
+       struct string_list update = STRING_LIST_INIT_DUP;
        int i;
 
        find_conflict(&conflict);
@@ -257,10 +523,10 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        if (ret < 1)
                                continue;
                        hex = xstrdup(sha1_to_hex(sha1));
-                       string_list_insert(path, rr)->util = hex;
+                       string_list_insert(rr, path)->util = hex;
                        if (mkdir(git_path("rr-cache/%s", hex), 0755))
-                               continue;;
-                       handle_file(path, NULL, rr_path(hex, "preimage"));
+                               continue;
+                       handle_file(path, NULL, rerere_path(hex, "preimage"));
                        fprintf(stderr, "Recorded preimage for '%s'\n", path);
                }
        }
@@ -276,10 +542,10 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                const char *path = rr->items[i].string;
                const char *name = (const char *)rr->items[i].util;
 
-               if (has_resolution(name)) {
+               if (has_rerere_resolution(name)) {
                        if (!merge(name, path)) {
                                if (rerere_autoupdate)
-                                       string_list_insert(path, &update);
+                                       string_list_insert(&update, path);
                                fprintf(stderr,
                                        "%s '%s' using previous resolution.\n",
                                        rerere_autoupdate
@@ -295,7 +561,7 @@ static int do_plain_rerere(struct string_list *rr, int fd)
                        continue;
 
                fprintf(stderr, "Recorded resolution for '%s'.\n", path);
-               copy_file(rr_path(name, "postimage"), path, 0666);
+               copy_file(rerere_path(name, "postimage"), path, 0666);
        mark_resolved:
                rr->items[i].util = NULL;
        }
@@ -319,7 +585,6 @@ static int git_rerere_config(const char *var, const char *value, void *cb)
 
 static int is_rerere_enabled(void)
 {
-       struct stat st;
        const char *rr_cache;
        int rr_cache_exists;
 
@@ -327,17 +592,16 @@ static int is_rerere_enabled(void)
                return 0;
 
        rr_cache = git_path("rr-cache");
-       rr_cache_exists = !stat(rr_cache, &st) && S_ISDIR(st.st_mode);
+       rr_cache_exists = is_directory(rr_cache);
        if (rerere_enabled < 0)
                return rr_cache_exists;
 
-       if (!rr_cache_exists &&
-           (mkdir(rr_cache, 0777) || adjust_shared_perm(rr_cache)))
+       if (!rr_cache_exists && mkdir_in_gitdir(rr_cache))
                die("Could not create directory %s", rr_cache);
        return 1;
 }
 
-int setup_rerere(struct string_list *merge_rr)
+int setup_rerere(struct string_list *merge_rr, int flags)
 {
        int fd;
 
@@ -345,19 +609,156 @@ int setup_rerere(struct string_list *merge_rr)
        if (!is_rerere_enabled())
                return -1;
 
-       merge_rr_path = xstrdup(git_path("MERGE_RR"));
-       fd = hold_lock_file_for_update(&write_lock, merge_rr_path, 1);
+       if (flags & (RERERE_AUTOUPDATE|RERERE_NOAUTOUPDATE))
+               rerere_autoupdate = !!(flags & RERERE_AUTOUPDATE);
+       merge_rr_path = git_pathdup("MERGE_RR");
+       fd = hold_lock_file_for_update(&write_lock, merge_rr_path,
+                                      LOCK_DIE_ON_ERROR);
        read_rr(merge_rr);
        return fd;
 }
 
-int rerere(void)
+int rerere(int flags)
 {
-       struct string_list merge_rr = { NULL, 0, 0, 1 };
+       struct string_list merge_rr = STRING_LIST_INIT_DUP;
        int fd;
 
-       fd = setup_rerere(&merge_rr);
+       fd = setup_rerere(&merge_rr, flags);
        if (fd < 0)
                return 0;
        return do_plain_rerere(&merge_rr, fd);
 }
+
+static int rerere_forget_one_path(const char *path, struct string_list *rr)
+{
+       const char *filename;
+       char *hex;
+       unsigned char sha1[20];
+       int ret;
+
+       ret = handle_cache(path, sha1, NULL);
+       if (ret < 1)
+               return error("Could not parse conflict hunks in '%s'", path);
+       hex = xstrdup(sha1_to_hex(sha1));
+       filename = rerere_path(hex, "postimage");
+       if (unlink(filename))
+               return (errno == ENOENT
+                       ? error("no remembered resolution for %s", path)
+                       : error("cannot unlink %s: %s", filename, strerror(errno)));
+
+       handle_cache(path, sha1, rerere_path(hex, "preimage"));
+       fprintf(stderr, "Updated preimage for '%s'\n", path);
+
+
+       string_list_insert(rr, path)->util = hex;
+       fprintf(stderr, "Forgot resolution for %s\n", path);
+       return 0;
+}
+
+int rerere_forget(const char **pathspec)
+{
+       int i, fd;
+       struct string_list conflict = STRING_LIST_INIT_DUP;
+       struct string_list merge_rr = STRING_LIST_INIT_DUP;
+
+       if (read_cache() < 0)
+               return error("Could not read index");
+
+       fd = setup_rerere(&merge_rr, RERERE_NOAUTOUPDATE);
+
+       unmerge_cache(pathspec);
+       find_conflict(&conflict);
+       for (i = 0; i < conflict.nr; i++) {
+               struct string_list_item *it = &conflict.items[i];
+               if (!match_pathspec(pathspec, it->string, strlen(it->string),
+                                   0, NULL))
+                       continue;
+               rerere_forget_one_path(it->string, &merge_rr);
+       }
+       return write_rr(&merge_rr, fd);
+}
+
+static time_t rerere_created_at(const char *name)
+{
+       struct stat st;
+       return stat(rerere_path(name, "preimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static time_t rerere_last_used_at(const char *name)
+{
+       struct stat st;
+       return stat(rerere_path(name, "postimage"), &st) ? (time_t) 0 : st.st_mtime;
+}
+
+static void unlink_rr_item(const char *name)
+{
+       unlink(rerere_path(name, "thisimage"));
+       unlink(rerere_path(name, "preimage"));
+       unlink(rerere_path(name, "postimage"));
+       rmdir(git_path("rr-cache/%s", name));
+}
+
+struct rerere_gc_config_cb {
+       int cutoff_noresolve;
+       int cutoff_resolve;
+};
+
+static int git_rerere_gc_config(const char *var, const char *value, void *cb)
+{
+       struct rerere_gc_config_cb *cf = cb;
+
+       if (!strcmp(var, "gc.rerereresolved"))
+               cf->cutoff_resolve = git_config_int(var, value);
+       else if (!strcmp(var, "gc.rerereunresolved"))
+               cf->cutoff_noresolve = git_config_int(var, value);
+       else
+               return git_default_config(var, value, cb);
+       return 0;
+}
+
+void rerere_gc(struct string_list *rr)
+{
+       struct string_list to_remove = STRING_LIST_INIT_DUP;
+       DIR *dir;
+       struct dirent *e;
+       int i, cutoff;
+       time_t now = time(NULL), then;
+       struct rerere_gc_config_cb cf = { 15, 60 };
+
+       git_config(git_rerere_gc_config, &cf);
+       dir = opendir(git_path("rr-cache"));
+       if (!dir)
+               die_errno("unable to open rr-cache directory");
+       while ((e = readdir(dir))) {
+               if (is_dot_or_dotdot(e->d_name))
+                       continue;
+
+               then = rerere_last_used_at(e->d_name);
+               if (then) {
+                       cutoff = cf.cutoff_resolve;
+               } else {
+                       then = rerere_created_at(e->d_name);
+                       if (!then)
+                               continue;
+                       cutoff = cf.cutoff_noresolve;
+               }
+               if (then < now - cutoff * 86400)
+                       string_list_append(&to_remove, e->d_name);
+       }
+       closedir(dir);
+       for (i = 0; i < to_remove.nr; i++)
+               unlink_rr_item(to_remove.items[i].string);
+       string_list_clear(&to_remove, 0);
+}
+
+void rerere_clear(struct string_list *merge_rr)
+{
+       int i;
+
+       for (i = 0; i < merge_rr->nr; i++) {
+               const char *name = (const char *)merge_rr->items[i].util;
+               if (!has_rerere_resolution(name))
+                       unlink_rr_item(name);
+       }
+       unlink_or_warn(git_path("MERGE_RR"));
+}