{"thread":{"id":"22837","subject":"[PATCH] prune: honor --expire=never","startedAt":"2010-02-26T21:59:16Z","lastAt":"2010-02-27T03:50:04Z","messageCount":6,"participants":["Adam Simpkins","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"135785","messageId":"20100226215916.GF5116@facebook.com","threadId":"22837","inReplyTo":null,"subject":"[PATCH] prune: honor --expire=never","fromName":"Adam Simpkins","fromEmail":"simpkins@facebook.com","sentAt":"2010-02-26T21:59:16Z","receivedAt":"2010-02-26T21:59:16Z","isPatch":true,"sender":{"key":"simpkins@facebook.com","avatar":null},"body":"Previously, prune treated an expiration time of 0 to mean that no\nexpire argument was supplied, and everything should be pruned.  As a\nresult, \"prune --expire=never\" would prune all unreachable objects,\nregardless of their timestamps.\n\nprune can be called with --expire=never automatically by gc, when the\ngc.pruneExpire configuration is set to \"never\".\n\nSigned-off-by: Adam Simpkins <simpkins@facebook.com>\n---\n builtin-prune.c  |    7 +++++--\n t/t5304-prune.sh |   32 ++++++++++++++++++++++++++++++++\n 2 files changed, 37 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex 4675f60..ce43271 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -7,6 +7,8 @@\n #include \"parse-options.h\"\n #include \"dir.h\"\n \n+#define ALWAYS_EXPIRE ((unsigned int)-1)\n+\n static const char * const prune_usage[] = {\n \t\"git prune [-n] [-v] [--expire <time>] [--] [<head>...]\",\n \tNULL\n@@ -18,7 +20,7 @@ static unsigned long expire;\n static int prune_tmp_object(const char *path, const char *filename)\n {\n \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n-\tif (expire) {\n+\tif (expire != ALWAYS_EXPIRE) {\n \t\tstruct stat st;\n \t\tif (lstat(fullpath, &st))\n \t\t\treturn error(\"Could not stat '%s'\", fullpath);\n@@ -34,7 +36,7 @@ static int prune_tmp_object(const char *path, const char *filename)\n static int prune_object(char *path, const char *filename, const unsigned char *sha1)\n {\n \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n-\tif (expire) {\n+\tif (expire != ALWAYS_EXPIRE) {\n \t\tstruct stat st;\n \t\tif (lstat(fullpath, &st))\n \t\t\treturn error(\"Could not stat '%s'\", fullpath);\n@@ -139,6 +141,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t};\n \tchar *s;\n \n+\texpire = ALWAYS_EXPIRE;\n \tsave_commit_buffer = 0;\n \tread_replace_refs = 0;\n \tinit_revisions(&revs, prefix);\ndiff --git a/t/t5304-prune.sh b/t/t5304-prune.sh\nindex 3c6687a..e2ed13d 100755\n--- a/t/t5304-prune.sh\n+++ b/t/t5304-prune.sh\n@@ -148,6 +148,38 @@ test_expect_success 'gc --prune=<date>' '\n \n '\n \n+test_expect_success 'gc --prune=never' '\n+\n+\tadd_blob &&\n+\tgit gc --prune=never &&\n+\ttest -f $BLOB_FILE &&\n+\tgit gc --prune=now &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n+test_expect_success 'gc respects gc.pruneExpire=never' '\n+\n+\tgit config gc.pruneExpire never &&\n+\tadd_blob &&\n+\tgit gc &&\n+\ttest -f $BLOB_FILE &&\n+\tgit config gc.pruneExpire now &&\n+\tgit gc &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n+test_expect_success 'prune --expire=never' '\n+\n+\tadd_blob &&\n+\tgit prune --expire=never &&\n+\ttest -f $BLOB_FILE &&\n+\tgit prune &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n test_expect_success 'gc: prune old objects after local clone' '\n \tadd_blob &&\n \ttest-chmtime =-$((2*$week+1)) $BLOB_FILE &&\n-- \n1.6.3.3\n"},{"id":"135787","messageId":"7v4ol3ilri.fsf@alter.siamese.dyndns.org","threadId":"22837","inReplyTo":"20100226215916.GF5116@facebook.com","subject":"Re: [PATCH] prune: honor --expire=never","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2010-02-27T00:07:45Z","receivedAt":"2010-02-27T00:07:45Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Adam Simpkins <simpkins@facebook.com> writes:\n\n> diff --git a/builtin-prune.c b/builtin-prune.c\n> index 4675f60..ce43271 100644\n> --- a/builtin-prune.c\n> +++ b/builtin-prune.c\n> @@ -7,6 +7,8 @@\n>  #include \"parse-options.h\"\n>  #include \"dir.h\"\n>  \n> +#define ALWAYS_EXPIRE ((unsigned int)-1)\n> ...\n> @@ -34,7 +36,7 @@ static int prune_tmp_object(const char *path, const char *filename)\n>  static int prune_object(char *path, const char *filename, const unsigned char *sha1)\n>  {\n>  \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n> -\tif (expire) {\n> +\tif (expire != ALWAYS_EXPIRE) {\n\nWouldn't it be a lot simpler to initialize expire to \"now\" for the default\ncase, and remove all these \"if (expire)\"?  I think that is how the logic\nto expire reflog entries work, which I think is saner.\n\nWhile you are at it, you might want to think about a way to unify what\nparse_opt_approxidate_cb() and parse_expire_cfg_value() do.  The latter\nknows about \"expire = false\" but the former doesn't, which is a slight\ninconsistency.\n"},{"id":"135788","messageId":"20100227012130.GA28452@facebook.com","threadId":"22837","inReplyTo":"7v4ol3ilri.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH] prune: honor --expire=never","fromName":"Adam Simpkins","fromEmail":"simpkins@facebook.com","sentAt":"2010-02-27T01:21:30Z","receivedAt":"2010-02-27T01:21:30Z","isPatch":true,"sender":{"key":"simpkins@facebook.com","avatar":null},"body":"On Fri, Feb 26, 2010 at 04:07:45PM -0800, Junio C Hamano wrote:\n> Adam Simpkins <simpkins@facebook.com> writes:\n> \n> > diff --git a/builtin-prune.c b/builtin-prune.c\n> > index 4675f60..ce43271 100644\n> > --- a/builtin-prune.c\n> > +++ b/builtin-prune.c\n> > @@ -7,6 +7,8 @@\n> >  #include \"parse-options.h\"\n> >  #include \"dir.h\"\n> >  \n> > +#define ALWAYS_EXPIRE ((unsigned int)-1)\n> > ...\n> > @@ -34,7 +36,7 @@ static int prune_tmp_object(const char *path, const char *filename)\n> >  static int prune_object(char *path, const char *filename, const unsigned char *sha1)\n> >  {\n> >  \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n> > -\tif (expire) {\n> > +\tif (expire != ALWAYS_EXPIRE) {\n> \n> Wouldn't it be a lot simpler to initialize expire to \"now\" for the default\n> case, and remove all these \"if (expire)\"?\n\nSure, I can submit an updated patch to do that.  It does slightly\nchange the behavior of \"git prune\" with no --expire argument though:\n\n  - Objects with an mtime in the future will no longer be pruned.\n  - We'll call lstat() all of the unreachable objects, even though it\n    isn't really necessary.\n\nHowever, the code is indeed simpler, if you don't think either of\nthese changes matter.\n\n> I think that is how the logic\n> to expire reflog entries work, which I think is saner.\n\nHmm.  reflog appears to have the same bug when parsing the\ngc.reflogexpire and gc.reflogexpireunreachable options.  Setting\neither of these to \"never\" or \"false\" results in the default\nexpiration time.  (However, using --expire=never on the command line\nworks correctly.)\n\nI'll submit a separate patch for that.\n\n\n> While you are at it, you might want to think about a way to unify what\n> parse_opt_approxidate_cb() and parse_expire_cfg_value() do.  The latter\n> knows about \"expire = false\" but the former doesn't, which is a slight\n> inconsistency.\n\nSure, I'll look into it and submit a patch.\n\n-- \nAdam Simpkins\nsimpkins@facebook.com\n"},{"id":"135794","messageId":"1267242604-5215-1-git-send-email-simpkins@facebook.com","threadId":"22837","inReplyTo":"20100227012130.GA28452@facebook.com","subject":"[PATCH 1/3] prune: honor --expire=never","fromName":"Adam Simpkins","fromEmail":"simpkins@facebook.com","sentAt":"2010-02-27T03:50:02Z","receivedAt":"2010-02-27T03:50:02Z","isPatch":true,"sender":{"key":"simpkins@facebook.com","avatar":null},"body":"Previously, prune treated an expiration time of 0 to mean that no\nexpire argument was supplied, and everything should be pruned.  As a\nresult, \"prune --expire=never\" would prune all unreachable objects,\nregardless of their timestamp.\n\nprune can be called with --expire=never automatically by gc, when the\ngc.pruneExpire configuration is set to \"never\".\n\nSigned-off-by: Adam Simpkins <simpkins@facebook.com>\n---\n builtin-prune.c  |   25 +++++++++++--------------\n t/t5304-prune.sh |   32 ++++++++++++++++++++++++++++++++\n 2 files changed, 43 insertions(+), 14 deletions(-)\n\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex 4675f60..f22bcf6 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -18,13 +18,11 @@ static unsigned long expire;\n static int prune_tmp_object(const char *path, const char *filename)\n {\n \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n-\tif (expire) {\n-\t\tstruct stat st;\n-\t\tif (lstat(fullpath, &st))\n-\t\t\treturn error(\"Could not stat '%s'\", fullpath);\n-\t\tif (st.st_mtime > expire)\n-\t\t\treturn 0;\n-\t}\n+\tstruct stat st;\n+\tif (lstat(fullpath, &st))\n+\t\treturn error(\"Could not stat '%s'\", fullpath);\n+\tif (st.st_mtime > expire)\n+\t\treturn 0;\n \tprintf(\"Removing stale temporary file %s\\n\", fullpath);\n \tif (!show_only)\n \t\tunlink_or_warn(fullpath);\n@@ -34,13 +32,11 @@ static int prune_tmp_object(const char *path, const char *filename)\n static int prune_object(char *path, const char *filename, const unsigned char *sha1)\n {\n \tconst char *fullpath = mkpath(\"%s/%s\", path, filename);\n-\tif (expire) {\n-\t\tstruct stat st;\n-\t\tif (lstat(fullpath, &st))\n-\t\t\treturn error(\"Could not stat '%s'\", fullpath);\n-\t\tif (st.st_mtime > expire)\n-\t\t\treturn 0;\n-\t}\n+\tstruct stat st;\n+\tif (lstat(fullpath, &st))\n+\t\treturn error(\"Could not stat '%s'\", fullpath);\n+\tif (st.st_mtime > expire)\n+\t\treturn 0;\n \tif (show_only || verbose) {\n \t\tenum object_type type = sha1_object_info(sha1, NULL);\n \t\tprintf(\"%s %s\\n\", sha1_to_hex(sha1),\n@@ -139,6 +135,7 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t};\n \tchar *s;\n \n+\texpire = (unsigned long)-1;\n \tsave_commit_buffer = 0;\n \tread_replace_refs = 0;\n \tinit_revisions(&revs, prefix);\ndiff --git a/t/t5304-prune.sh b/t/t5304-prune.sh\nindex 3c6687a..e2ed13d 100755\n--- a/t/t5304-prune.sh\n+++ b/t/t5304-prune.sh\n@@ -148,6 +148,38 @@ test_expect_success 'gc --prune=<date>' '\n \n '\n \n+test_expect_success 'gc --prune=never' '\n+\n+\tadd_blob &&\n+\tgit gc --prune=never &&\n+\ttest -f $BLOB_FILE &&\n+\tgit gc --prune=now &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n+test_expect_success 'gc respects gc.pruneExpire=never' '\n+\n+\tgit config gc.pruneExpire never &&\n+\tadd_blob &&\n+\tgit gc &&\n+\ttest -f $BLOB_FILE &&\n+\tgit config gc.pruneExpire now &&\n+\tgit gc &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n+test_expect_success 'prune --expire=never' '\n+\n+\tadd_blob &&\n+\tgit prune --expire=never &&\n+\ttest -f $BLOB_FILE &&\n+\tgit prune &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n test_expect_success 'gc: prune old objects after local clone' '\n \tadd_blob &&\n \ttest-chmtime =-$((2*$week+1)) $BLOB_FILE &&\n-- \n1.6.3.3\n"},{"id":"135793","messageId":"1267242604-5215-2-git-send-email-simpkins@facebook.com","threadId":"22837","inReplyTo":"1267242604-5215-1-git-send-email-simpkins@facebook.com","subject":"[PATCH 2/3] reflog: honor gc.reflogexpire=never","fromName":"Adam Simpkins","fromEmail":"simpkins@facebook.com","sentAt":"2010-02-27T03:50:03Z","receivedAt":"2010-02-27T03:50:03Z","isPatch":true,"sender":{"key":"simpkins@facebook.com","avatar":null},"body":"Previously, if gc.reflogexpire or gc.reflogexpire were set to \"never\"\nor \"false\", the builtin default values were used instead.\n\nSigned-off-by: Adam Simpkins <simpkins@facebook.com>\n---\n builtin-reflog.c  |    6 ++----\n t/t1410-reflog.sh |   41 +++++++++++++++++++++++++++++++++++++++++\n 2 files changed, 43 insertions(+), 4 deletions(-)\n\ndiff --git a/builtin-reflog.c b/builtin-reflog.c\nindex 7498210..64e45bd 100644\n--- a/builtin-reflog.c\n+++ b/builtin-reflog.c\n@@ -530,16 +530,14 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \tint i, status, do_all;\n \tint explicit_expiry = 0;\n \n+\tdefault_reflog_expire_unreachable = now - 30 * 24 * 3600;\n+\tdefault_reflog_expire = now - 90 * 24 * 3600;\n \tgit_config(reflog_expire_config, NULL);\n \n \tsave_commit_buffer = 0;\n \tdo_all = status = 0;\n \tmemset(&cb, 0, sizeof(cb));\n \n-\tif (!default_reflog_expire_unreachable)\n-\t\tdefault_reflog_expire_unreachable = now - 30 * 24 * 3600;\n-\tif (!default_reflog_expire)\n-\t\tdefault_reflog_expire = now - 90 * 24 * 3600;\n \tcb.expire_total = default_reflog_expire;\n \tcb.expire_unreachable = default_reflog_expire_unreachable;\n \ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 80af6b9..25046c4 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -214,4 +214,45 @@ test_expect_success 'delete' '\n \n '\n \n+test_expect_success 'rewind2' '\n+\n+\ttest_tick && git reset --hard HEAD~2 &&\n+\tloglen=$(wc -l <.git/logs/refs/heads/master) &&\n+\ttest $loglen = 4\n+\n+'\n+\n+test_expect_success '--expire=never' '\n+\n+\tgit reflog expire --verbose \\\n+\t\t--expire=never \\\n+\t\t--expire-unreachable=never \\\n+\t\t--all &&\n+\tloglen=$(wc -l <.git/logs/refs/heads/master) &&\n+\ttest $loglen = 4\n+\n+'\n+\n+test_expect_success 'gc.reflogexpire=never' '\n+\n+\tgit config gc.reflogexpire never &&\n+\tgit config gc.reflogexpireunreachable never &&\n+\tgit reflog expire --verbose --all &&\n+\tloglen=$(wc -l <.git/logs/refs/heads/master) &&\n+\ttest $loglen = 4\n+'\n+\n+test_expect_success 'gc.reflogexpire=false' '\n+\n+\tgit config gc.reflogexpire false &&\n+\tgit config gc.reflogexpireunreachable false &&\n+\tgit reflog expire --verbose --all &&\n+\tloglen=$(wc -l <.git/logs/refs/heads/master) &&\n+\ttest $loglen = 4 &&\n+\n+\tgit config --unset gc.reflogexpire &&\n+\tgit config --unset gc.reflogexpireunreachable\n+\n+'\n+\n test_done\n-- \n1.6.3.3\n"},{"id":"135795","messageId":"1267242604-5215-3-git-send-email-simpkins@facebook.com","threadId":"22837","inReplyTo":"1267242604-5215-2-git-send-email-simpkins@facebook.com","subject":"[PATCH 3/3] clean up parsing of expiration dates","fromName":"Adam Simpkins","fromEmail":"simpkins@facebook.com","sentAt":"2010-02-27T03:50:04Z","receivedAt":"2010-02-27T03:50:04Z","isPatch":true,"sender":{"key":"simpkins@facebook.com","avatar":null},"body":"The gc, prune, and reflog command all take arguments that control\nexpiring of old objects/log entries.  This cleans up the parsing of\nthese arguments, and centralizes the logic so the commands behave\nconsistently.\n\nTwo new functions have been added for parsing expiration dates:\nparse_expire_date() is similar to approxidate_careful(), but also\naccepts the value \"false\" as a synonym for \"never\".\ngit_config_expire_date() calls parse_expire_date(), and prints an\nerror message if the value is invalid.  A new OPT_EXPIRE_DATE option\ntype has also been added to parse-options.h for parsing expiration\ndate command line arguments.\n\nSigned-off-by: Adam Simpkins <simpkins@facebook.com>\n---\n builtin-gc.c      |    9 ++++-----\n builtin-prune.c   |    4 ++--\n builtin-reflog.c  |   27 +++++++++++----------------\n cache.h           |    2 ++\n config.c          |   12 ++++++++++++\n date.c            |   10 ++++++++++\n parse-options.c   |   17 +++++++++++++++++\n parse-options.h   |    4 ++++\n t/t1410-reflog.sh |   11 +++++++++++\n t/t5304-prune.sh  |   36 +++++++++++++++++++++++++++++++++++-\n 10 files changed, 108 insertions(+), 24 deletions(-)\n\ndiff --git a/builtin-gc.c b/builtin-gc.c\nindex c304638..3921e6e 100644\n--- a/builtin-gc.c\n+++ b/builtin-gc.c\n@@ -57,11 +57,10 @@ static int gc_config(const char *var, const char *value, void *cb)\n \t\treturn 0;\n \t}\n \tif (!strcmp(var, \"gc.pruneexpire\")) {\n-\t\tif (value && strcmp(value, \"now\")) {\n-\t\t\tunsigned long now = approxidate(\"now\");\n-\t\t\tif (approxidate(value) >= now)\n-\t\t\t\treturn error(\"Invalid %s: '%s'\", var, value);\n-\t\t}\n+\t\t/* Make sure the value is valid */\n+\t\tunsigned long expire_date;\n+\t\tif (git_config_expire_date(&expire_date, var, value))\n+\t\t\treturn -1;\n \t\treturn git_config_string(&prune_expire, var, value);\n \t}\n \treturn git_default_config(var, value, cb);\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex f22bcf6..e8d25a6 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -129,8 +129,8 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\t    \"do not remove, show only\"),\n \t\tOPT_BOOLEAN('v', NULL, &verbose,\n \t\t\t\"report pruned objects\"),\n-\t\tOPT_DATE(0, \"expire\", &expire,\n-\t\t\t \"expire objects older than <time>\"),\n+\t\tOPT_EXPIRE_DATE(0, \"expire\", &expire,\n+\t\t\t\t\"expire objects older than <time>\"),\n \t\tOPT_END()\n \t};\n \tchar *s;\ndiff --git a/builtin-reflog.c b/builtin-reflog.c\nindex 64e45bd..2d2a9aa 100644\n--- a/builtin-reflog.c\n+++ b/builtin-reflog.c\n@@ -425,18 +425,6 @@ static struct reflog_expire_cfg *find_cfg_ent(const char *pattern, size_t len)\n \treturn ent;\n }\n \n-static int parse_expire_cfg_value(const char *var, const char *value, unsigned long *expire)\n-{\n-\tif (!value)\n-\t\treturn config_error_nonbool(var);\n-\tif (!strcmp(value, \"never\") || !strcmp(value, \"false\")) {\n-\t\t*expire = 0;\n-\t\treturn 0;\n-\t}\n-\t*expire = approxidate(value);\n-\treturn 0;\n-}\n-\n /* expiry timer slot */\n #define EXPIRE_TOTAL   01\n #define EXPIRE_UNREACH 02\n@@ -453,11 +441,11 @@ static int reflog_expire_config(const char *var, const char *value, void *cb)\n \n \tif (!strcmp(lastdot, \".reflogexpire\")) {\n \t\tslot = EXPIRE_TOTAL;\n-\t\tif (parse_expire_cfg_value(var, value, &expire))\n+\t\tif (git_config_expire_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else if (!strcmp(lastdot, \".reflogexpireunreachable\")) {\n \t\tslot = EXPIRE_UNREACH;\n-\t\tif (parse_expire_cfg_value(var, value, &expire))\n+\t\tif (git_config_expire_date(&expire, var, value))\n \t\t\treturn -1;\n \t} else\n \t\treturn git_default_config(var, value, cb);\n@@ -546,11 +534,18 @@ static int cmd_reflog_expire(int argc, const char **argv, const char *prefix)\n \t\tif (!strcmp(arg, \"--dry-run\") || !strcmp(arg, \"-n\"))\n \t\t\tcb.dry_run = 1;\n \t\telse if (!prefixcmp(arg, \"--expire=\")) {\n-\t\t\tcb.expire_total = approxidate(arg + 9);\n+\t\t\tif (git_config_expire_date(&cb.expire_total,\n+\t\t\t\t\t\t   \"--expire\", arg + 9)) {\n+\t\t\t\tusage(reflog_expire_usage);\n+\t\t\t}\n \t\t\texplicit_expiry |= EXPIRE_TOTAL;\n \t\t}\n \t\telse if (!prefixcmp(arg, \"--expire-unreachable=\")) {\n-\t\t\tcb.expire_unreachable = approxidate(arg + 21);\n+\t\t\tif (git_config_expire_date(&cb.expire_unreachable,\n+\t\t\t\t\t\t   \"--expire-unreachable\",\n+\t\t\t\t\t\t   arg + 21)) {\n+\t\t\t\tusage(reflog_expire_usage);\n+\t\t\t}\n \t\t\texplicit_expiry |= EXPIRE_UNREACH;\n \t\t}\n \t\telse if (!strcmp(arg, \"--stale-fix\"))\ndiff --git a/cache.h b/cache.h\nindex d478eff..981987e 100644\n--- a/cache.h\n+++ b/cache.h\n@@ -766,6 +766,7 @@ void datestamp(char *buf, int bufsize);\n unsigned long approxidate_careful(const char *, int *);\n unsigned long approxidate_relative(const char *date, const struct timeval *now);\n enum date_mode parse_date_format(const char *format);\n+unsigned long parse_expire_date(const char *value, int *error_ret);\n \n #define IDENT_WARN_ON_NO_NAME  1\n #define IDENT_ERROR_ON_NO_NAME 2\n@@ -926,6 +927,7 @@ extern int git_config_bool_or_int(const char *, const char *, int *);\n extern int git_config_bool(const char *, const char *);\n extern int git_config_string(const char **, const char *, const char *);\n extern int git_config_pathname(const char **, const char *, const char *);\n+extern int git_config_expire_date(unsigned long *, const char *, const char *);\n extern int git_config_set(const char *, const char *);\n extern int git_config_set_multivar(const char *, const char *, const char *, int);\n extern int git_config_rename_section(const char *, const char *);\ndiff --git a/config.c b/config.c\nindex 6963fbe..47df868 100644\n--- a/config.c\n+++ b/config.c\n@@ -361,6 +361,18 @@ int git_config_pathname(const char **dest, const char *var, const char *value)\n \treturn 0;\n }\n \n+int git_config_expire_date(unsigned long *dest, const char *var, const char *value)\n+{\n+\tif (!value)\n+\t\treturn config_error_nonbool(var);\n+\tint error_code = 0;\n+\t*dest = parse_expire_date(value, &error_code);\n+\tif (error_code) {\n+\t\treturn error(\"Invalid value for %s: '%s'\", var, value);\n+\t}\n+\treturn 0;\n+}\n+\n static int git_default_core_config(const char *var, const char *value)\n {\n \t/* This needs a better name */\ndiff --git a/date.c b/date.c\nindex 002aa3c..1191c9d 100644\n--- a/date.c\n+++ b/date.c\n@@ -673,6 +673,16 @@ void datestamp(char *buf, int bufsize)\n \tdate_string(now, offset, buf, bufsize);\n }\n \n+unsigned long parse_expire_date(const char *value, int *error_ret)\n+{\n+\tif (!strcmp(value, \"never\") || !strcmp(value, \"false\")) {\n+\t\tif (error_ret)\n+\t\t\t*error_ret = 0;\n+\t\treturn 0;\n+\t}\n+\treturn approxidate_careful(value, error_ret);\n+}\n+\n /*\n  * Relative time update (eg \"2 days ago\").  If we haven't set the time\n  * yet, we need to set it from current time.\ndiff --git a/parse-options.c b/parse-options.c\nindex d218122..431d367 100644\n--- a/parse-options.c\n+++ b/parse-options.c\n@@ -599,6 +599,23 @@ int parse_opt_approxidate_cb(const struct option *opt, const char *arg,\n \treturn 0;\n }\n \n+int parse_opt_expire_date_cb(const struct option *opt, const char *arg,\n+\t\t\t     int unset)\n+{\n+\tint error_code = 0;\n+\n+\tif (unset) {\n+\t\t*(unsigned long *)(opt->value) = 0;\n+\t\treturn 0;\n+\t}\n+\n+\t*(unsigned long *)(opt->value) = parse_expire_date(arg, &error_code);\n+\tif (error_code) {\n+\t\treturn opterror(opt, \"expects a date or \\\"false\\\"\", 0);\n+\t}\n+\treturn 0;\n+}\n+\n int parse_opt_verbosity_cb(const struct option *opt, const char *arg,\n \t\t\t   int unset)\n {\ndiff --git a/parse-options.h b/parse-options.h\nindex 0c99691..8f446cc 100644\n--- a/parse-options.h\n+++ b/parse-options.h\n@@ -128,6 +128,9 @@ struct option {\n #define OPT_DATE(s, l, v, h) \\\n \t{ OPTION_CALLBACK, (s), (l), (v), \"time\",(h), 0, \\\n \t  parse_opt_approxidate_cb }\n+#define OPT_EXPIRE_DATE(s, l, v, h) \\\n+\t{ OPTION_CALLBACK, (s), (l), (v), \"time\",(h), 0, \\\n+\t  parse_opt_expire_date_cb }\n #define OPT_CALLBACK(s, l, v, a, h, f) \\\n \t{ OPTION_CALLBACK, (s), (l), (v), (a), (h), 0, (f) }\n #define OPT_NUMBER_CALLBACK(v, h, f) \\\n@@ -187,6 +190,7 @@ extern int parse_options_end(struct parse_opt_ctx_t *ctx);\n /*----- some often used options -----*/\n extern int parse_opt_abbrev_cb(const struct option *, const char *, int);\n extern int parse_opt_approxidate_cb(const struct option *, const char *, int);\n+extern int parse_opt_expire_date_cb(const struct option *, const char *, int);\n extern int parse_opt_verbosity_cb(const struct option *, const char *, int);\n extern int parse_opt_with_commit(const struct option *, const char *, int);\n extern int parse_opt_tertiary(const struct option *, const char *, int);\ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 25046c4..d6641fa 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -233,6 +233,17 @@ test_expect_success '--expire=never' '\n \n '\n \n+test_expect_success '--expire=false' '\n+\n+\tgit reflog expire --verbose \\\n+\t\t--expire=false \\\n+\t\t--expire-unreachable=false \\\n+\t\t--all &&\n+\tloglen=$(wc -l <.git/logs/refs/heads/master) &&\n+\ttest $loglen = 4\n+\n+'\n+\n test_expect_success 'gc.reflogexpire=never' '\n \n \tgit config gc.reflogexpire never &&\ndiff --git a/t/t5304-prune.sh b/t/t5304-prune.sh\nindex e2ed13d..43e5b1d 100755\n--- a/t/t5304-prune.sh\n+++ b/t/t5304-prune.sh\n@@ -166,7 +166,21 @@ test_expect_success 'gc respects gc.pruneExpire=never' '\n \ttest -f $BLOB_FILE &&\n \tgit config gc.pruneExpire now &&\n \tgit gc &&\n-\ttest ! -f $BLOB_FILE\n+\ttest ! -f $BLOB_FILE &&\n+\tgit config --unset gc.pruneExpire\n+\n+'\n+\n+test_expect_success 'gc respects gc.pruneExpire=false' '\n+\n+\tgit config gc.pruneExpire false &&\n+\tadd_blob &&\n+\tgit gc &&\n+\ttest -f $BLOB_FILE &&\n+\tgit config gc.pruneExpire now &&\n+\tgit gc &&\n+\ttest ! -f $BLOB_FILE &&\n+\tgit config --unset gc.pruneExpire\n \n '\n \n@@ -180,6 +194,26 @@ test_expect_success 'prune --expire=never' '\n \n '\n \n+test_expect_success 'prune --expire=false' '\n+\n+\tadd_blob &&\n+\tgit prune --expire=false &&\n+\ttest -f $BLOB_FILE &&\n+\tgit prune &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n+test_expect_success 'prune --no-expire' '\n+\n+\tadd_blob &&\n+\tgit prune --no-expire &&\n+\ttest -f $BLOB_FILE &&\n+\tgit prune &&\n+\ttest ! -f $BLOB_FILE\n+\n+'\n+\n test_expect_success 'gc: prune old objects after local clone' '\n \tadd_blob &&\n \ttest-chmtime =-$((2*$week+1)) $BLOB_FILE &&\n-- \n1.6.3.3\n"}]}