{"thread":{"id":"46435","subject":"fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit","startedAt":"2017-07-21T13:00:00Z","lastAt":"2017-08-22T21:47:22Z","messageCount":16,"participants":["Uwe Hausbrand","Martin Ågren","Junio C Hamano","Ramsay Jones"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"324858","messageId":"CAHNyMp8xeye8EvtOdrX34QVCDsBCu3aLQVdyGRTVqyHXjZ4FFQ@mail.gmail.com","threadId":"46435","inReplyTo":null,"subject":"fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit","fromName":"Uwe Hausbrand","fromEmail":"uwe.hausbrand@gmx.de","sentAt":"2017-07-21T12:59:52Z","receivedAt":"2017-07-21T13:00:00Z","isPatch":false,"sender":{"key":"uwe.hausbrand@gmx.de","avatar":"https://gravatar.com/avatar/232f15ea514b68e4ba8ae775a2b2bec2204e9fa9312a7e12912cb6be69fa8591?d=mp&s=160"},"body":"Hi all,\n\nseems like there is a bug with \"git rerere gc\" not understanding grace\nperiods like \"60 days\" defined in the config.\n\nWhat I did:\n\ngit config gc.rerereresolved \"60 days\"\ngit gc\n\nresults in:\n\nCounting objects: 158790, done.\nDelta compression using up to 8 threads.\nCompressing objects: 100% (26849/26849), done.\nWriting objects: 100% (158790/158790), done.\nTotal 158790 (delta 116114), reused 158790 (delta 116114)\nfatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit\nerror: failed to run rerere\n\ngit --version = git version 2.13.0\n\nBest regards,\n\nUwe\n"},{"id":"324860","messageId":"CAN0heSrQv=TjjE_=9PC14Ob2dn9g4Qq0Vm+yPoh6hmGzd1W_Uw@mail.gmail.com","threadId":"46435","inReplyTo":"CAHNyMp8xeye8EvtOdrX34QVCDsBCu3aLQVdyGRTVqyHXjZ4FFQ@mail.gmail.com","subject":"Re: fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit","fromName":"Martin Ågren","fromEmail":"martin.agren@gmail.com","sentAt":"2017-07-21T14:30:18Z","receivedAt":"2017-07-21T14:30:24Z","isPatch":false,"sender":{"key":"martin.agren@gmail.com","avatar":null},"body":"On 21 July 2017 at 14:59, Uwe Hausbrand <uwe.hausbrand@gmx.de> wrote:\n> git config gc.rerereresolved \"60 days\"\n> git gc\n>\n> results in:\n[...]\n> fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit\n> error: failed to run rerere\n\nIt's not entirely clear, but \"man git-gc\" does seem to suggest -- at\nleast to me -- that one can say \"60 days\". Especially after reading\nabout gc.reflogExpire a few paragraphs earlier, where \"90 days\" and \"3\nmonths\" are given as examples. Luckily, \"man git-config\" is a bit\nclearer. It says that \"records ... are kept for this many days\". The\nlatter matches the implementation and the tests, which are all written\nto work with just an integer.\n\nSo \"git config gc.rerereresolved 60\" should work. Hope that helps.\n\nMartin\n"},{"id":"324861","messageId":"xmqqfudpj1vo.fsf@gitster.mtv.corp.google.com","threadId":"46435","inReplyTo":"CAHNyMp8xeye8EvtOdrX34QVCDsBCu3aLQVdyGRTVqyHXjZ4FFQ@mail.gmail.com","subject":"Re: fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-07-21T14:33:31Z","receivedAt":"2017-07-21T14:33:38Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Uwe Hausbrand <uwe.hausbrand@gmx.de> writes:\n\n> seems like there is a bug with \"git rerere gc\" not understanding grace\n> periods like \"60 days\" defined in the config.\n>\n> What I did:\n>\n> git config gc.rerereresolved \"60 days\"\n\nLet's see how the variable is explained in the documentation.\n\n    gc.rerereResolved::\n            Records of conflicted merge you resolved earlier are\n            kept for this many days when 'git rerere gc' is run.\n            The default is 60 days.  See linkgit:git-rerere[1].\n\nNotice that \"for this many days\" tries to (and probably\nunsuccessfully) tell you that this variable is expected to be set to\nan integer [*1*], counted in \"days\".  IOW, you'd want \"60\" instead.\n\nHaving said that, it may not be a bad idea to enumerate these\n\"expected to be an integer that counts in some unit\" variables that\nare described in a similar way (i.e. look for \"this many\" in\nDocumentation/config.txt), and then for each of them that could be\ncounted in different unit (e.g. it is not outrageously wrong to\nexpect that you could specify that rerere records that are older\nthan 3 months are expired):\n\n - decide what kind of quantity the variable specifies (e.g. \"this\n   many days\" and \"this many seconds\" variables are giving a\n   \"timeperiod\").\n\n - keep the code that reacts to an integer without any unit to\n   behave the same (e.g. \"[gc] rerereresolved = 30\" will keep\n   meaning \"30 days\");\n\n - extend the code so that when the value given is not an integer,\n   it tries to parse it as a specification for the expected quantity\n   (e.g. \"this many days\" and \"this many seconds\" variables would\n   understand if you said \"60 days\" or \"2 months\")\n\n\n[Footnote]\n\n *1* I think we actually expect a scaled integer whenever we expect\n     an integral value, so you probably could say \"6k\" to specify\n     \"6,000 days\"; \"days\" not being any of the recognised unit\n     suffix like k, M, G, etc. is where \"invalid unit\" comes from.\n"},{"id":"324878","messageId":"CAHNyMp-oXPDjQ1960mKiSdvvDZc5b85z2Cc9pRvs8QQSfprp=A@mail.gmail.com","threadId":"46435","inReplyTo":"xmqqfudpj1vo.fsf@gitster.mtv.corp.google.com","subject":"Re: fatal: bad numeric config value '60 days' for 'gc.rerereresolved': invalid unit","fromName":"Uwe Hausbrand","fromEmail":"uwe.hausbrand@gmx.de","sentAt":"2017-07-21T17:35:00Z","receivedAt":"2017-07-21T17:36:49Z","isPatch":false,"sender":{"key":"uwe.hausbrand@gmx.de","avatar":"https://gravatar.com/avatar/232f15ea514b68e4ba8ae775a2b2bec2204e9fa9312a7e12912cb6be69fa8591?d=mp&s=160"},"body":"Yeah, after I checked the code I saw that this is interpreted as\ninteger and fixed my configuration\n\n2017-07-21 16:33 GMT+02:00 Junio C Hamano <gitster@pobox.com>:\n> Uwe Hausbrand <uwe.hausbrand@gmx.de> writes:\n>\n>> seems like there is a bug with \"git rerere gc\" not understanding grace\n>> periods like \"60 days\" defined in the config.\n>>\n>> What I did:\n>>\n>> git config gc.rerereresolved \"60 days\"\n>\n> Let's see how the variable is explained in the documentation.\n>\n>     gc.rerereResolved::\n>             Records of conflicted merge you resolved earlier are\n>             kept for this many days when 'git rerere gc' is run.\n>             The default is 60 days.  See linkgit:git-rerere[1].\n>\n> Notice that \"for this many days\" tries to (and probably\n> unsuccessfully) tell you that this variable is expected to be set to\n> an integer [*1*], counted in \"days\".  IOW, you'd want \"60\" instead.\n>\n> Having said that, it may not be a bad idea to enumerate these\n> \"expected to be an integer that counts in some unit\" variables that\n> are described in a similar way (i.e. look for \"this many\" in\n> Documentation/config.txt), and then for each of them that could be\n> counted in different unit (e.g. it is not outrageously wrong to\n> expect that you could specify that rerere records that are older\n> than 3 months are expired):\n>\n>  - decide what kind of quantity the variable specifies (e.g. \"this\n>    many days\" and \"this many seconds\" variables are giving a\n>    \"timeperiod\").\n>\n>  - keep the code that reacts to an integer without any unit to\n>    behave the same (e.g. \"[gc] rerereresolved = 30\" will keep\n>    meaning \"30 days\");\n>\n>  - extend the code so that when the value given is not an integer,\n>    it tries to parse it as a specification for the expected quantity\n>    (e.g. \"this many days\" and \"this many seconds\" variables would\n>    understand if you said \"60 days\" or \"2 months\")\n>\n>\n> [Footnote]\n>\n>  *1* I think we actually expect a scaled integer whenever we expect\n>      an integral value, so you probably could say \"6k\" to specify\n>      \"6,000 days\"; \"days\" not being any of the recognised unit\n>      suffix like k, M, G, etc. is where \"invalid unit\" comes from.\n"},{"id":"326786","messageId":"20170819203013.3053-3-gitster@pobox.com","threadId":"46435","inReplyTo":"20170819203013.3053-1-gitster@pobox.com","subject":"[PATCH 2/2] rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-19T20:30:13Z","receivedAt":"2017-08-19T20:30:26Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"These two configuration variables are described in the documentation\nto take an expiry period expressed in the number of days:\n\n    gc.rerereResolved::\n\t    Records of conflicted merge you resolved earlier are\n\t    kept for this many days when 'git rerere gc' is run.\n\t    The default is 60 days.\n\n    gc.rerereUnresolved::\n\t    Records of conflicted merge you have not resolved are\n\t    kept for this many days when 'git rerere gc' is run.\n\t    The default is 15 days.\n\nThere is no strong reason not to allow a more general \"approxidate\"\nexpiry specification, e.g. \"5.days.ago\", or \"never\".\n\nTweak the config_get_expiry() helper introduced in the previous step\nto use date.c::parse_expiry_date() to do so.\n\nIn the future, we may find other variables that only allow an\ninteger that specifies \"this many days\" (or other unit of time) and\nallow them to also do the same, and at that point we probably would\nwant to move the helper to a place that is not specific to the\nrerere machinery.  Perhaps config.c would be such a good neutral\nplace, as it will allow git_parse_signed() to go back to static to\nthe file.\n\nBut this will do for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt |  2 ++\n config.c                 |  4 ++--\n config.h                 |  3 +++\n rerere.c                 | 14 ++++++++++++--\n 4 files changed, 19 insertions(+), 4 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d5c9c4cab6..ac95f5f954 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1553,11 +1553,13 @@ gc.<pattern>.reflogExpireUnreachable::\n gc.rerereResolved::\n \tRecords of conflicted merge you resolved earlier are\n \tkept for this many days when 'git rerere gc' is run.\n+\tYou can also use more human-readable \"1.month.ago\", etc.\n \tThe default is 60 days.  See linkgit:git-rerere[1].\n \n gc.rerereUnresolved::\n \tRecords of conflicted merge you have not resolved are\n \tkept for this many days when 'git rerere gc' is run.\n+\tYou can also use more human-readable \"1.month.ago\", etc.\n \tThe default is 15 days.  See linkgit:git-rerere[1].\n \n gitcvs.commitMsgAnnotation::\ndiff --git a/config.c b/config.c\nindex 231f9a750b..ac9071c5cf 100644\n--- a/config.c\n+++ b/config.c\n@@ -769,7 +769,7 @@ static int parse_unit_factor(const char *end, uintmax_t *val)\n \treturn 0;\n }\n \n-static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n+int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n {\n \tif (value && *value) {\n \t\tchar *end;\n@@ -799,7 +799,7 @@ static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n \treturn 0;\n }\n \n-static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n+int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n {\n \tif (value && *value) {\n \t\tchar *end;\ndiff --git a/config.h b/config.h\nindex 0352da117b..039a9295de 100644\n--- a/config.h\n+++ b/config.h\n@@ -215,4 +215,7 @@ struct key_value_info {\n extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));\n extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);\n \n+int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max);\n+int git_parse_signed(const char *value, intmax_t *ret, intmax_t max);\n+\n #endif /* CONFIG_H */\ndiff --git a/rerere.c b/rerere.c\nindex f0b4bce881..8bbdfe8569 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1178,11 +1178,21 @@ static void prune_one(struct rerere_id *id,\n \n static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n {\n-\tint days;\n+\tchar *expiry_string;\n+\tintmax_t days;\n+\ttimestamp_t when;\n \n-\tif (!git_config_get_int(key, &days)) {\n+\tif (git_config_get_string(key, &expiry_string))\n+\t\treturn;\n+\n+\tif (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {\n \t\tconst int scale = 86400;\n \t\t*cutoff = now - days * scale;\n+\t\treturn;\n+\t}\n+\n+\tif (!parse_expiry_date(expiry_string, &when)) {\n+\t\t*cutoff = when;\n \t}\n }\n \n-- \n2.14.1-405-g52c75fc716\n\n"},{"id":"326787","messageId":"20170819203013.3053-1-gitster@pobox.com","threadId":"46435","inReplyTo":"xmqqfudpj1vo.fsf@gitster.mtv.corp.google.com","subject":"[PATCH 0/2] accept non-integer for \"this many days\" expiry specification","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-19T20:30:11Z","receivedAt":"2017-08-19T20:30:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"About a month ago, we wondered why\n\n\t[gc]\n\t\trerereResolved = 5.days\n\ndoes not work and if we want to do something better.  \n\nHere is a pair of patches that attempt to improve the situation.\n\nJunio C Hamano (2):\n  rerere: represent time duration in timestamp_t internally\n  rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved\n\n Documentation/config.txt |  2 ++\n config.c                 |  4 ++--\n config.h                 |  3 +++\n rerere.c                 | 46 +++++++++++++++++++++++++++++++++-------------\n 4 files changed, 40 insertions(+), 15 deletions(-)\n\n-- \n2.14.1-405-g52c75fc716\n\n"},{"id":"326788","messageId":"20170819203013.3053-2-gitster@pobox.com","threadId":"46435","inReplyTo":"20170819203013.3053-1-gitster@pobox.com","subject":"[PATCH 1/2] rerere: represent time duration in timestamp_t internally","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-19T20:30:12Z","receivedAt":"2017-08-19T20:36:50Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The two configuration variables, gc.rerereResolved and\ngc.rerereUnresolved, are measured in days and are passed as such\ninto the prune_one() helper function, which worked in time_t to see\nif an entry in the rerere database is past its expiry.\n\nInstead, have the caller turn the number of days into the expiry\ntimestamp.  Further, use timestamp_t instead of time_t.  This will\nmake it possible to extend the way the configuration variable is\nspelled by using date.c::parse_expiry_date() that gives the expiry\ntimestamp in timestamp_t.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 36 +++++++++++++++++++++++-------------\n 1 file changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 70634d456c..f0b4bce881 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1133,14 +1133,14 @@ int rerere_forget(struct pathspec *pathspec)\n  * Garbage collection support\n  */\n \n-static time_t rerere_created_at(struct rerere_id *id)\n+static timestamp_t rerere_created_at(struct rerere_id *id)\n {\n \tstruct stat st;\n \n \treturn stat(rerere_path(id, \"preimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n \n-static time_t rerere_last_used_at(struct rerere_id *id)\n+static timestamp_t rerere_last_used_at(struct rerere_id *id)\n {\n \tstruct stat st;\n \n@@ -1157,11 +1157,11 @@ static void unlink_rr_item(struct rerere_id *id)\n \tid->collection->status[id->variant] = 0;\n }\n \n-static void prune_one(struct rerere_id *id, time_t now,\n-\t\t      int cutoff_resolve, int cutoff_noresolve)\n+static void prune_one(struct rerere_id *id,\n+\t\t      timestamp_t cutoff_resolve, timestamp_t cutoff_noresolve)\n {\n-\ttime_t then;\n-\tint cutoff;\n+\ttimestamp_t then;\n+\ttimestamp_t cutoff;\n \n \tthen = rerere_last_used_at(id);\n \tif (then)\n@@ -1172,25 +1172,35 @@ static void prune_one(struct rerere_id *id, time_t now,\n \t\t\treturn;\n \t\tcutoff = cutoff_noresolve;\n \t}\n-\tif (then < now - cutoff * 86400)\n+\tif (then < cutoff)\n \t\tunlink_rr_item(id);\n }\n \n+static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n+{\n+\tint days;\n+\n+\tif (!git_config_get_int(key, &days)) {\n+\t\tconst int scale = 86400;\n+\t\t*cutoff = now - days * scale;\n+\t}\n+}\n+\n void rerere_gc(struct string_list *rr)\n {\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n \tDIR *dir;\n \tstruct dirent *e;\n \tint i;\n-\ttime_t now = time(NULL);\n-\tint cutoff_noresolve = 15;\n-\tint cutoff_resolve = 60;\n+\ttimestamp_t now = time(NULL);\n+\ttimestamp_t cutoff_noresolve = now - 15 * 86400;\n+\ttimestamp_t cutoff_resolve = now - 60 * 86400;\n \n \tif (setup_rerere(rr, 0) < 0)\n \t\treturn;\n \n-\tgit_config_get_int(\"gc.rerereresolved\", &cutoff_resolve);\n-\tgit_config_get_int(\"gc.rerereunresolved\", &cutoff_noresolve);\n+\tconfig_get_expiry(\"gc.rerereresolved\", &cutoff_resolve, now);\n+\tconfig_get_expiry(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n \tgit_config(git_default_config, NULL);\n \tdir = opendir(git_path(\"rr-cache\"));\n \tif (!dir)\n@@ -1211,7 +1221,7 @@ void rerere_gc(struct string_list *rr)\n \t\tfor (id.variant = 0, id.collection = rr_dir;\n \t\t     id.variant < id.collection->status_nr;\n \t\t     id.variant++) {\n-\t\t\tprune_one(&id, now, cutoff_resolve, cutoff_noresolve);\n+\t\t\tprune_one(&id, cutoff_resolve, cutoff_noresolve);\n \t\t\tif (id.collection->status[id.variant])\n \t\t\t\tnow_empty = 0;\n \t\t}\n-- \n2.14.1-405-g52c75fc716\n\n"},{"id":"326836","messageId":"d1ce349c-a6ca-796d-7ac1-8c720e48e02f@ramsayjones.plus.com","threadId":"46435","inReplyTo":"20170819203013.3053-3-gitster@pobox.com","subject":"Re: [PATCH 2/2] rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved","fromName":"Ramsay Jones","fromEmail":"ramsay@ramsayjones.plus.com","sentAt":"2017-08-20T21:45:20Z","receivedAt":"2017-08-20T21:45:29Z","isPatch":true,"sender":{"key":"ramsay@ramsayjones.plus.com","avatar":"https://avatars.githubusercontent.com/u/33702710?v=4"},"body":"\n\nOn 19/08/17 21:30, Junio C Hamano wrote:\n> These two configuration variables are described in the documentation\n> to take an expiry period expressed in the number of days:\n> \n>     gc.rerereResolved::\n> \t    Records of conflicted merge you resolved earlier are\n> \t    kept for this many days when 'git rerere gc' is run.\n> \t    The default is 60 days.\n> \n>     gc.rerereUnresolved::\n> \t    Records of conflicted merge you have not resolved are\n> \t    kept for this many days when 'git rerere gc' is run.\n> \t    The default is 15 days.\n> \n> There is no strong reason not to allow a more general \"approxidate\"\n> expiry specification, e.g. \"5.days.ago\", or \"never\".\n> \n> Tweak the config_get_expiry() helper introduced in the previous step\n> to use date.c::parse_expiry_date() to do so.\n> \n> In the future, we may find other variables that only allow an\n> integer that specifies \"this many days\" (or other unit of time) and\n> allow them to also do the same, and at that point we probably would\n> want to move the helper to a place that is not specific to the\n> rerere machinery.  Perhaps config.c would be such a good neutral\n> place, as it will allow git_parse_signed() to go back to static to\n> the file.\n\nYou make git_parse_unsigned() external in this patch, in addition\nto git_parse_signed(), but don't actually call it. Was that intended?\n\nATB,\nRamsay Jones\n\n> \n> But this will do for now.\n> \n> Signed-off-by: Junio C Hamano <gitster@pobox.com>\n> ---\n>  Documentation/config.txt |  2 ++\n>  config.c                 |  4 ++--\n>  config.h                 |  3 +++\n>  rerere.c                 | 14 ++++++++++++--\n>  4 files changed, 19 insertions(+), 4 deletions(-)\n> \n> diff --git a/Documentation/config.txt b/Documentation/config.txt\n> index d5c9c4cab6..ac95f5f954 100644\n> --- a/Documentation/config.txt\n> +++ b/Documentation/config.txt\n> @@ -1553,11 +1553,13 @@ gc.<pattern>.reflogExpireUnreachable::\n>  gc.rerereResolved::\n>  \tRecords of conflicted merge you resolved earlier are\n>  \tkept for this many days when 'git rerere gc' is run.\n> +\tYou can also use more human-readable \"1.month.ago\", etc.\n>  \tThe default is 60 days.  See linkgit:git-rerere[1].\n>  \n>  gc.rerereUnresolved::\n>  \tRecords of conflicted merge you have not resolved are\n>  \tkept for this many days when 'git rerere gc' is run.\n> +\tYou can also use more human-readable \"1.month.ago\", etc.\n>  \tThe default is 15 days.  See linkgit:git-rerere[1].\n>  \n>  gitcvs.commitMsgAnnotation::\n> diff --git a/config.c b/config.c\n> index 231f9a750b..ac9071c5cf 100644\n> --- a/config.c\n> +++ b/config.c\n> @@ -769,7 +769,7 @@ static int parse_unit_factor(const char *end, uintmax_t *val)\n>  \treturn 0;\n>  }\n>  \n> -static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n> +int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n>  {\n>  \tif (value && *value) {\n>  \t\tchar *end;\n> @@ -799,7 +799,7 @@ static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n>  \treturn 0;\n>  }\n>  \n> -static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n> +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n>  {\n>  \tif (value && *value) {\n>  \t\tchar *end;\n> diff --git a/config.h b/config.h\n> index 0352da117b..039a9295de 100644\n> --- a/config.h\n> +++ b/config.h\n> @@ -215,4 +215,7 @@ struct key_value_info {\n>  extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));\n>  extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);\n>  \n> +int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max);\n> +int git_parse_signed(const char *value, intmax_t *ret, intmax_t max);\n> +\n>  #endif /* CONFIG_H */\n> diff --git a/rerere.c b/rerere.c\n> index f0b4bce881..8bbdfe8569 100644\n> --- a/rerere.c\n> +++ b/rerere.c\n> @@ -1178,11 +1178,21 @@ static void prune_one(struct rerere_id *id,\n>  \n>  static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n>  {\n> -\tint days;\n> +\tchar *expiry_string;\n> +\tintmax_t days;\n> +\ttimestamp_t when;\n>  \n> -\tif (!git_config_get_int(key, &days)) {\n> +\tif (git_config_get_string(key, &expiry_string))\n> +\t\treturn;\n> +\n> +\tif (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {\n>  \t\tconst int scale = 86400;\n>  \t\t*cutoff = now - days * scale;\n> +\t\treturn;\n> +\t}\n> +\n> +\tif (!parse_expiry_date(expiry_string, &when)) {\n> +\t\t*cutoff = when;\n>  \t}\n>  }\n>  \n> \n"},{"id":"326849","messageId":"xmqqa82t7ov1.fsf@gitster.mtv.corp.google.com","threadId":"46435","inReplyTo":"d1ce349c-a6ca-796d-7ac1-8c720e48e02f@ramsayjones.plus.com","subject":"Re: [PATCH 2/2] rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-21T00:20:50Z","receivedAt":"2017-08-21T00:20:56Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ramsay Jones <ramsay@ramsayjones.plus.com> writes:\n\n> On 19/08/17 21:30, Junio C Hamano wrote:\n>\n>> In the future, we may find other variables that only allow an\n>> integer that specifies \"this many days\" (or other unit of time) and\n>> allow them to also do the same, and at that point we probably would\n>> want to move the helper to a place that is not specific to the\n>> rerere machinery.  Perhaps config.c would be such a good neutral\n>> place, as it will allow git_parse_signed() to go back to static to\n>> the file.\n>\n> You make git_parse_unsigned() external in this patch, in addition\n> to git_parse_signed(), but don't actually call it. Was that intended?\n\nYes, it was done on purpose for symmetry and was done before I had\nthat \"perhaps this can be moved to config.c\" vision.\n\nPerhaps I should follow up the topic with [PATCH 3/2] to really move\nit to config.c to clean it up.  But not today.\n\nThanks.\n"},{"id":"326972","messageId":"20170822214652.31626-1-gitster@pobox.com","threadId":"46435","inReplyTo":"20170819203013.3053-1-gitster@pobox.com","subject":"[PATCH v2 0/6] accept non-integer for \"this many days\" expiry specification","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:46Z","receivedAt":"2017-08-22T21:47:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"About a month ago, we wondered why\n\n\t[gc]\n\t\trerereResolved = 5.days\n\ndoes not work and if we want to do something better.  \n\nThis is an update to my first attempt.  It turns out that I needed\nmore patches to clean up the tests before I can write new tests for\nthis feature.\n\nThe implementation of the feature itself has not changed much,\nexcept that it now lives in config.c for easier reuse by other\ncodepaths in the future.\n\nJunio C Hamano (6):\n  t4200: give us a clean slate after \"rerere gc\" tests\n  t4200: make \"rerere gc\" test more robust\n  t4200: gather \"rerere gc\" together\n  t4200: parameterize \"rerere gc\" custom expiry test\n  rerere: represent time duration in timestamp_t internally\n  rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved\n\n Documentation/config.txt |  2 ++\n config.c                 | 22 +++++++++++++++++++\n config.h                 |  3 +++\n rerere.c                 | 26 +++++++++++-----------\n t/t4200-rerere.sh        | 57 +++++++++++++++++++++++++++++++++---------------\n 5 files changed, 79 insertions(+), 31 deletions(-)\n\n\nThe interdiff between the original one and this version looks like\nthis.\n\n config.c          | 26 +++++++++++++++++++++++--\n config.h          |  6 +++---\n rerere.c          | 24 ++---------------------\n t/t4200-rerere.sh | 57 +++++++++++++++++++++++++++++++++++++------------------\n 4 files changed, 68 insertions(+), 45 deletions(-)\n\ndiff --git a/config.c b/config.c\nindex ac9071c5cf..ffca15f594 100644\n--- a/config.c\n+++ b/config.c\n@@ -769,7 +769,7 @@ static int parse_unit_factor(const char *end, uintmax_t *val)\n \treturn 0;\n }\n \n-int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n+static int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n {\n \tif (value && *value) {\n \t\tchar *end;\n@@ -799,7 +799,7 @@ int git_parse_signed(const char *value, intmax_t *ret, intmax_t max)\n \treturn 0;\n }\n \n-int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n+static int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max)\n {\n \tif (value && *value) {\n \t\tchar *end;\n@@ -2066,6 +2066,28 @@ int git_config_get_expiry(const char *key, const char **output)\n \treturn ret;\n }\n \n+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)\n+{\n+\tchar *expiry_string;\n+\tintmax_t days;\n+\ttimestamp_t when;\n+\n+\tif (git_config_get_string(key, &expiry_string))\n+\t\treturn 1; /* no such thing */\n+\n+\tif (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {\n+\t\tconst int scale = 86400;\n+\t\t*expiry = now - days * scale;\n+\t\treturn 0;\n+\t}\n+\n+\tif (!parse_expiry_date(expiry_string, &when)) {\n+\t\t*expiry = when;\n+\t\treturn 0;\n+\t}\n+\treturn -1; /* thing exists but cannot be parsed */\n+}\n+\n int git_config_get_untracked_cache(void)\n {\n \tint val = -1;\ndiff --git a/config.h b/config.h\nindex 039a9295de..34ddd3eb8d 100644\n--- a/config.h\n+++ b/config.h\n@@ -205,6 +205,9 @@ extern int git_config_get_max_percent_split_change(void);\n /* This dies if the configured or default date is in the future */\n extern int git_config_get_expiry(const char *key, const char **output);\n \n+/* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n+extern int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n+\n struct key_value_info {\n \tconst char *filename;\n \tint linenr;\n@@ -215,7 +218,4 @@ struct key_value_info {\n extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3)));\n extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr);\n \n-int git_parse_unsigned(const char *value, uintmax_t *ret, uintmax_t max);\n-int git_parse_signed(const char *value, intmax_t *ret, intmax_t max);\n-\n #endif /* CONFIG_H */\ndiff --git a/rerere.c b/rerere.c\nindex 8bbdfe8569..d77235645e 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1176,26 +1176,6 @@ static void prune_one(struct rerere_id *id,\n \t\tunlink_rr_item(id);\n }\n \n-static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n-{\n-\tchar *expiry_string;\n-\tintmax_t days;\n-\ttimestamp_t when;\n-\n-\tif (git_config_get_string(key, &expiry_string))\n-\t\treturn;\n-\n-\tif (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {\n-\t\tconst int scale = 86400;\n-\t\t*cutoff = now - days * scale;\n-\t\treturn;\n-\t}\n-\n-\tif (!parse_expiry_date(expiry_string, &when)) {\n-\t\t*cutoff = when;\n-\t}\n-}\n-\n void rerere_gc(struct string_list *rr)\n {\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n@@ -1209,8 +1189,8 @@ void rerere_gc(struct string_list *rr)\n \tif (setup_rerere(rr, 0) < 0)\n \t\treturn;\n \n-\tconfig_get_expiry(\"gc.rerereresolved\", &cutoff_resolve, now);\n-\tconfig_get_expiry(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n+\tgit_config_get_expiry_in_days(\"gc.rerereresolved\", &cutoff_resolve, now);\n+\tgit_config_get_expiry_in_days(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n \tgit_config(git_default_config, NULL);\n \tdir = opendir(git_path(\"rr-cache\"));\n \tif (!dir)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 1a080e7823..d97d2bebc9 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -239,6 +239,43 @@ test_expect_success 'old records rest in peace' '\n \t! test -f $rr2/preimage\n '\n \n+rerere_gc_custom_expiry_test () {\n+\tfive_days=\"$1\" right_now=\"$2\"\n+\ttest_expect_success \"rerere gc with custom expiry ($five_days, $right_now)\" '\n+\t\trm -fr .git/rr-cache &&\n+\t\trr=.git/rr-cache/$_z40 &&\n+\t\tmkdir -p \"$rr\" &&\n+\t\t>\"$rr/preimage\" &&\n+\t\t>\"$rr/postimage\" &&\n+\n+\t\ttwo_days_ago=$((-2*86400)) &&\n+\t\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n+\t\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n+\n+\t\tfind .git/rr-cache -type f | sort >original &&\n+\n+\t\tgit -c \"gc.rerereresolved=$five_days\" \\\n+\t\t    -c \"gc.rerereunresolved=$five_days\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\ttest_cmp original actual &&\n+\n+\t\tgit -c \"gc.rerereresolved=$five_days\" \\\n+\t\t    -c \"gc.rerereunresolved=$right_now\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\ttest_cmp original actual &&\n+\n+\t\tgit -c \"gc.rerereresolved=$right_now\" \\\n+\t\t    -c \"gc.rerereunresolved=$right_now\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\t>expect &&\n+\t\ttest_cmp expect actual\n+\t'\n+}\n+\n+rerere_gc_custom_expiry_test 5 0\n+\n+rerere_gc_custom_expiry_test 5.days.ago now\n+\n test_expect_success 'setup: file2 added differently in two branches' '\n \tgit reset --hard &&\n \n@@ -419,24 +456,6 @@ count_pre_post () {\n \ttest_line_count = \"$2\" actual\n }\n \n-test_expect_success 'rerere gc' '\n-\tfind .git/rr-cache -type f >original &&\n-\txargs test-chmtime -172800 <original &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n-\ttest_cmp original actual &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n-\ttest_cmp original actual &&\n-\n-\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n-\t>expect &&\n-\ttest_cmp expect actual\n-'\n-\n merge_conflict_resolve () {\n \tgit reset --hard &&\n \ttest_must_fail git merge six.1 &&\n@@ -446,6 +465,8 @@ merge_conflict_resolve () {\n }\n \n test_expect_success 'multiple identical conflicts' '\n+\trm -fr .git/rr-cache &&\n+\tmkdir .git/rr-cache &&\n \tgit reset --hard &&\n \n \ttest_seq 1 6 >early &&\n"},{"id":"326973","messageId":"20170822214652.31626-2-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 1/6] t4200: give us a clean slate after \"rerere gc\" tests","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:47Z","receivedAt":"2017-08-22T21:47:03Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The \"multiple identical conflicts\" test counts the number of entries\nin the rerere database after trying a handful of mergy operations\nand recording their resolutions, but without initializing the rerere\ndatabase to a known state, allowing the state left by previous tests\nto trigger a false failure.  Make it robust by cleaning the database\nbefore it starts.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 2 ++\n 1 file changed, 2 insertions(+)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 1a080e7823..8f5f268baf 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -446,6 +446,8 @@ merge_conflict_resolve () {\n }\n \n test_expect_success 'multiple identical conflicts' '\n+\trm -fr .git/rr-cache &&\n+\tmkdir .git/rr-cache &&\n \tgit reset --hard &&\n \n \ttest_seq 1 6 >early &&\n-- \n2.14.1-427-g5711bb0564\n\n"},{"id":"326974","messageId":"20170822214652.31626-4-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 3/6] t4200: gather \"rerere gc\" together","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:49Z","receivedAt":"2017-08-22T21:47:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Move the \"rerere gc with custom expiry\" test up, so that it is close\nto the more \"rerere gc\" tests.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 54 +++++++++++++++++++++++++++---------------------------\n 1 file changed, 27 insertions(+), 27 deletions(-)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 1e23031cdb..b007b67e9a 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -239,6 +239,33 @@ test_expect_success 'old records rest in peace' '\n \t! test -f $rr2/preimage\n '\n \n+test_expect_success 'rerere gc with custom expiry' '\n+\trm -fr .git/rr-cache &&\n+\trr=.git/rr-cache/$_z40 &&\n+\tmkdir -p \"$rr\" &&\n+\t>\"$rr/preimage\" &&\n+\t>\"$rr/postimage\" &&\n+\n+\ttwo_days_ago=$((-2*86400)) &&\n+\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n+\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n+\n+\tfind .git/rr-cache -type f | sort >original &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n+\ttest_cmp original actual &&\n+\n+\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n+\t>expect &&\n+\ttest_cmp expect actual\n+'\n+\n test_expect_success 'setup: file2 added differently in two branches' '\n \tgit reset --hard &&\n \n@@ -419,33 +446,6 @@ count_pre_post () {\n \ttest_line_count = \"$2\" actual\n }\n \n-test_expect_success 'rerere gc' '\n-\trm -fr .git/rr-cache &&\n-\trr=.git/rr-cache/$_z40 &&\n-\tmkdir -p \"$rr\" &&\n-\t>\"$rr/preimage\" &&\n-\t>\"$rr/postimage\" &&\n-\n-\ttwo_days_ago=$((-2*86400)) &&\n-\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n-\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n-\n-\tfind .git/rr-cache -type f | sort >original &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\ttest_cmp original actual &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\ttest_cmp original actual &&\n-\n-\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\t>expect &&\n-\ttest_cmp expect actual\n-'\n-\n merge_conflict_resolve () {\n \tgit reset --hard &&\n \ttest_must_fail git merge six.1 &&\n-- \n2.14.1-427-g5711bb0564\n\n"},{"id":"326975","messageId":"20170822214652.31626-3-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 2/6] t4200: make \"rerere gc\" test more robust","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:48Z","receivedAt":"2017-08-22T21:47:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The test blindly trusted that there may be _some_ entries left in\nthe rerere database, and used them by updating their timestamps to\nsee if the gc threshold variables are honoured correctly.  This\nwon't work if there is no entries in the database when the test\nbegins.\n\nInstead, clear the rerere database, and populate it with a few known\nentries (which are bogus, but for the purpose of testing \"garbage\ncollection\", it does not matter---we want to make sure we collect\nold cruft, even if the files are corrupt rerere database entries),\nand use them for the expiry test.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 19 ++++++++++++++-----\n 1 file changed, 14 insertions(+), 5 deletions(-)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 8f5f268baf..1e23031cdb 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -420,19 +420,28 @@ count_pre_post () {\n }\n \n test_expect_success 'rerere gc' '\n-\tfind .git/rr-cache -type f >original &&\n-\txargs test-chmtime -172800 <original &&\n+\trm -fr .git/rr-cache &&\n+\trr=.git/rr-cache/$_z40 &&\n+\tmkdir -p \"$rr\" &&\n+\t>\"$rr/preimage\" &&\n+\t>\"$rr/postimage\" &&\n+\n+\ttwo_days_ago=$((-2*86400)) &&\n+\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n+\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n+\n+\tfind .git/rr-cache -type f | sort >original &&\n \n \tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n \ttest_cmp original actual &&\n \n \tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n \ttest_cmp original actual &&\n \n \tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f >actual &&\n+\tfind .git/rr-cache -type f | sort >actual &&\n \t>expect &&\n \ttest_cmp expect actual\n '\n-- \n2.14.1-427-g5711bb0564\n\n"},{"id":"326976","messageId":"20170822214652.31626-5-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 4/6] t4200: parameterize \"rerere gc\" custom expiry test","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:50Z","receivedAt":"2017-08-22T21:47:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The test creates a rerere database entry that is two days old, and\ntries to expire with three different custom expiry configuration\n(keep ones less than 5 days old, keep ones used less than 5 days\nago, and expire everything right now).\n\nWe'll be introducing a different way to spell the same \"5 days\" and\n\"right now\" parameter in a later step; parameterize the test to make\nit easier to test the new spelling when it happens.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n t/t4200-rerere.sh | 58 +++++++++++++++++++++++++++++++------------------------\n 1 file changed, 33 insertions(+), 25 deletions(-)\n\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex b007b67e9a..8d437534f2 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -239,32 +239,40 @@ test_expect_success 'old records rest in peace' '\n \t! test -f $rr2/preimage\n '\n \n-test_expect_success 'rerere gc with custom expiry' '\n-\trm -fr .git/rr-cache &&\n-\trr=.git/rr-cache/$_z40 &&\n-\tmkdir -p \"$rr\" &&\n-\t>\"$rr/preimage\" &&\n-\t>\"$rr/postimage\" &&\n-\n-\ttwo_days_ago=$((-2*86400)) &&\n-\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n-\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n-\n-\tfind .git/rr-cache -type f | sort >original &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=5 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\ttest_cmp original actual &&\n-\n-\tgit -c gc.rerereresolved=5 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\ttest_cmp original actual &&\n+rerere_gc_custom_expiry_test () {\n+\tfive_days=\"$1\" right_now=\"$2\"\n+\ttest_expect_success \"rerere gc with custom expiry ($five_days, $right_now)\" '\n+\t\trm -fr .git/rr-cache &&\n+\t\trr=.git/rr-cache/$_z40 &&\n+\t\tmkdir -p \"$rr\" &&\n+\t\t>\"$rr/preimage\" &&\n+\t\t>\"$rr/postimage\" &&\n+\n+\t\ttwo_days_ago=$((-2*86400)) &&\n+\t\ttest-chmtime =$two_days_ago \"$rr/preimage\" &&\n+\t\ttest-chmtime =$two_days_ago \"$rr/postimage\" &&\n+\n+\t\tfind .git/rr-cache -type f | sort >original &&\n+\n+\t\tgit -c \"gc.rerereresolved=$five_days\" \\\n+\t\t    -c \"gc.rerereunresolved=$five_days\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\ttest_cmp original actual &&\n+\n+\t\tgit -c \"gc.rerereresolved=$five_days\" \\\n+\t\t    -c \"gc.rerereunresolved=$right_now\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\ttest_cmp original actual &&\n+\n+\t\tgit -c \"gc.rerereresolved=$right_now\" \\\n+\t\t    -c \"gc.rerereunresolved=$right_now\" rerere gc &&\n+\t\tfind .git/rr-cache -type f | sort >actual &&\n+\t\t>expect &&\n+\t\ttest_cmp expect actual\n+\t'\n+}\n \n-\tgit -c gc.rerereresolved=0 -c gc.rerereunresolved=0 rerere gc &&\n-\tfind .git/rr-cache -type f | sort >actual &&\n-\t>expect &&\n-\ttest_cmp expect actual\n-'\n+rerere_gc_custom_expiry_test 5 0\n \n test_expect_success 'setup: file2 added differently in two branches' '\n \tgit reset --hard &&\n-- \n2.14.1-427-g5711bb0564\n\n"},{"id":"326977","messageId":"20170822214652.31626-6-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 5/6] rerere: represent time duration in timestamp_t internally","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:51Z","receivedAt":"2017-08-22T21:47:18Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The two configuration variables, gc.rerereResolved and\ngc.rerereUnresolved, are measured in days and are passed as such\ninto the prune_one() helper function, which worked in time_t to see\nif an entry in the rerere database is past its expiry.\n\nInstead, have the caller turn the number of days into the expiry\ntimestamp.  Further, use timestamp_t instead of time_t.  This will\nmake it possible to extend the way the configuration variable is\nspelled by using date.c::parse_expiry_date() that gives the expiry\ntimestamp in timestamp_t.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n rerere.c | 36 +++++++++++++++++++++++-------------\n 1 file changed, 23 insertions(+), 13 deletions(-)\n\ndiff --git a/rerere.c b/rerere.c\nindex 70634d456c..f0b4bce881 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1133,14 +1133,14 @@ int rerere_forget(struct pathspec *pathspec)\n  * Garbage collection support\n  */\n \n-static time_t rerere_created_at(struct rerere_id *id)\n+static timestamp_t rerere_created_at(struct rerere_id *id)\n {\n \tstruct stat st;\n \n \treturn stat(rerere_path(id, \"preimage\"), &st) ? (time_t) 0 : st.st_mtime;\n }\n \n-static time_t rerere_last_used_at(struct rerere_id *id)\n+static timestamp_t rerere_last_used_at(struct rerere_id *id)\n {\n \tstruct stat st;\n \n@@ -1157,11 +1157,11 @@ static void unlink_rr_item(struct rerere_id *id)\n \tid->collection->status[id->variant] = 0;\n }\n \n-static void prune_one(struct rerere_id *id, time_t now,\n-\t\t      int cutoff_resolve, int cutoff_noresolve)\n+static void prune_one(struct rerere_id *id,\n+\t\t      timestamp_t cutoff_resolve, timestamp_t cutoff_noresolve)\n {\n-\ttime_t then;\n-\tint cutoff;\n+\ttimestamp_t then;\n+\ttimestamp_t cutoff;\n \n \tthen = rerere_last_used_at(id);\n \tif (then)\n@@ -1172,25 +1172,35 @@ static void prune_one(struct rerere_id *id, time_t now,\n \t\t\treturn;\n \t\tcutoff = cutoff_noresolve;\n \t}\n-\tif (then < now - cutoff * 86400)\n+\tif (then < cutoff)\n \t\tunlink_rr_item(id);\n }\n \n+static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n+{\n+\tint days;\n+\n+\tif (!git_config_get_int(key, &days)) {\n+\t\tconst int scale = 86400;\n+\t\t*cutoff = now - days * scale;\n+\t}\n+}\n+\n void rerere_gc(struct string_list *rr)\n {\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n \tDIR *dir;\n \tstruct dirent *e;\n \tint i;\n-\ttime_t now = time(NULL);\n-\tint cutoff_noresolve = 15;\n-\tint cutoff_resolve = 60;\n+\ttimestamp_t now = time(NULL);\n+\ttimestamp_t cutoff_noresolve = now - 15 * 86400;\n+\ttimestamp_t cutoff_resolve = now - 60 * 86400;\n \n \tif (setup_rerere(rr, 0) < 0)\n \t\treturn;\n \n-\tgit_config_get_int(\"gc.rerereresolved\", &cutoff_resolve);\n-\tgit_config_get_int(\"gc.rerereunresolved\", &cutoff_noresolve);\n+\tconfig_get_expiry(\"gc.rerereresolved\", &cutoff_resolve, now);\n+\tconfig_get_expiry(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n \tgit_config(git_default_config, NULL);\n \tdir = opendir(git_path(\"rr-cache\"));\n \tif (!dir)\n@@ -1211,7 +1221,7 @@ void rerere_gc(struct string_list *rr)\n \t\tfor (id.variant = 0, id.collection = rr_dir;\n \t\t     id.variant < id.collection->status_nr;\n \t\t     id.variant++) {\n-\t\t\tprune_one(&id, now, cutoff_resolve, cutoff_noresolve);\n+\t\t\tprune_one(&id, cutoff_resolve, cutoff_noresolve);\n \t\t\tif (id.collection->status[id.variant])\n \t\t\t\tnow_empty = 0;\n \t\t}\n-- \n2.14.1-427-g5711bb0564\n\n"},{"id":"326978","messageId":"20170822214652.31626-7-gitster@pobox.com","threadId":"46435","inReplyTo":"20170822214652.31626-1-gitster@pobox.com","subject":"[PATCH v2 6/6] rerere: allow approxidate in gc.rerereResolved/gc.rerereUnresolved","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2017-08-22T21:46:52Z","receivedAt":"2017-08-22T21:47:22Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"These two configuration variables are described in the documentation\nto take an expiry period expressed in the number of days:\n\n    gc.rerereResolved::\n\t    Records of conflicted merge you resolved earlier are\n\t    kept for this many days when 'git rerere gc' is run.\n\t    The default is 60 days.\n\n    gc.rerereUnresolved::\n\t    Records of conflicted merge you have not resolved are\n\t    kept for this many days when 'git rerere gc' is run.\n\t    The default is 15 days.\n\nThere is no strong reason not to allow a more general \"approxidate\"\nexpiry specification, e.g. \"5.days.ago\", or \"never\".\n\nRename the config_get_expiry() helper introduced in the previous\nstep to git_config_get_expiry_in_days() and move it to a more\ngeneric place, config.c, and use date.c::parse_expiry_date() to do\nso.  Give it an ability to allow the caller to tell among three\ncases (i.e. there is no \"gc.rerereResolved\" config, there is and it\nis correctly parsed into the *expiry variable, and there was an\nerror in parsing the given value).  The current caller can work\ncorrectly without using the return value, though.\n\nIn the future, we may find other variables that only allow an\ninteger that specifies \"this many days\" or other unit of time, and\nwhen it happens we may need to drop \"_days\" suffix from the name of\nthe function and instead pass the \"scale\" value as another parameter.\n\nBut this will do for now.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Documentation/config.txt |  2 ++\n config.c                 | 22 ++++++++++++++++++++++\n config.h                 |  3 +++\n rerere.c                 | 14 ++------------\n t/t4200-rerere.sh        |  2 ++\n 5 files changed, 31 insertions(+), 12 deletions(-)\n\ndiff --git a/Documentation/config.txt b/Documentation/config.txt\nindex d5c9c4cab6..ac95f5f954 100644\n--- a/Documentation/config.txt\n+++ b/Documentation/config.txt\n@@ -1553,11 +1553,13 @@ gc.<pattern>.reflogExpireUnreachable::\n gc.rerereResolved::\n \tRecords of conflicted merge you resolved earlier are\n \tkept for this many days when 'git rerere gc' is run.\n+\tYou can also use more human-readable \"1.month.ago\", etc.\n \tThe default is 60 days.  See linkgit:git-rerere[1].\n \n gc.rerereUnresolved::\n \tRecords of conflicted merge you have not resolved are\n \tkept for this many days when 'git rerere gc' is run.\n+\tYou can also use more human-readable \"1.month.ago\", etc.\n \tThe default is 15 days.  See linkgit:git-rerere[1].\n \n gitcvs.commitMsgAnnotation::\ndiff --git a/config.c b/config.c\nindex 231f9a750b..ffca15f594 100644\n--- a/config.c\n+++ b/config.c\n@@ -2066,6 +2066,28 @@ int git_config_get_expiry(const char *key, const char **output)\n \treturn ret;\n }\n \n+int git_config_get_expiry_in_days(const char *key, timestamp_t *expiry, timestamp_t now)\n+{\n+\tchar *expiry_string;\n+\tintmax_t days;\n+\ttimestamp_t when;\n+\n+\tif (git_config_get_string(key, &expiry_string))\n+\t\treturn 1; /* no such thing */\n+\n+\tif (git_parse_signed(expiry_string, &days, maximum_signed_value_of_type(int))) {\n+\t\tconst int scale = 86400;\n+\t\t*expiry = now - days * scale;\n+\t\treturn 0;\n+\t}\n+\n+\tif (!parse_expiry_date(expiry_string, &when)) {\n+\t\t*expiry = when;\n+\t\treturn 0;\n+\t}\n+\treturn -1; /* thing exists but cannot be parsed */\n+}\n+\n int git_config_get_untracked_cache(void)\n {\n \tint val = -1;\ndiff --git a/config.h b/config.h\nindex 0352da117b..34ddd3eb8d 100644\n--- a/config.h\n+++ b/config.h\n@@ -205,6 +205,9 @@ extern int git_config_get_max_percent_split_change(void);\n /* This dies if the configured or default date is in the future */\n extern int git_config_get_expiry(const char *key, const char **output);\n \n+/* parse either \"this many days\" integer, or \"5.days.ago\" approxidate */\n+extern int git_config_get_expiry_in_days(const char *key, timestamp_t *, timestamp_t now);\n+\n struct key_value_info {\n \tconst char *filename;\n \tint linenr;\ndiff --git a/rerere.c b/rerere.c\nindex f0b4bce881..d77235645e 100644\n--- a/rerere.c\n+++ b/rerere.c\n@@ -1176,16 +1176,6 @@ static void prune_one(struct rerere_id *id,\n \t\tunlink_rr_item(id);\n }\n \n-static void config_get_expiry(const char *key, timestamp_t *cutoff, timestamp_t now)\n-{\n-\tint days;\n-\n-\tif (!git_config_get_int(key, &days)) {\n-\t\tconst int scale = 86400;\n-\t\t*cutoff = now - days * scale;\n-\t}\n-}\n-\n void rerere_gc(struct string_list *rr)\n {\n \tstruct string_list to_remove = STRING_LIST_INIT_DUP;\n@@ -1199,8 +1189,8 @@ void rerere_gc(struct string_list *rr)\n \tif (setup_rerere(rr, 0) < 0)\n \t\treturn;\n \n-\tconfig_get_expiry(\"gc.rerereresolved\", &cutoff_resolve, now);\n-\tconfig_get_expiry(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n+\tgit_config_get_expiry_in_days(\"gc.rerereresolved\", &cutoff_resolve, now);\n+\tgit_config_get_expiry_in_days(\"gc.rerereunresolved\", &cutoff_noresolve, now);\n \tgit_config(git_default_config, NULL);\n \tdir = opendir(git_path(\"rr-cache\"));\n \tif (!dir)\ndiff --git a/t/t4200-rerere.sh b/t/t4200-rerere.sh\nindex 8d437534f2..d97d2bebc9 100755\n--- a/t/t4200-rerere.sh\n+++ b/t/t4200-rerere.sh\n@@ -274,6 +274,8 @@ rerere_gc_custom_expiry_test () {\n \n rerere_gc_custom_expiry_test 5 0\n \n+rerere_gc_custom_expiry_test 5.days.ago now\n+\n test_expect_success 'setup: file2 added differently in two branches' '\n \tgit reset --hard &&\n \n-- \n2.14.1-427-g5711bb0564\n\n"}]}