{"thread":{"id":"10915","subject":"[RFC] Alternates and broken repos: A pack and prune scheme to avoid them","startedAt":"2007-11-18T11:25:52Z","lastAt":"2007-11-29T20:59:55Z","messageCount":14,"participants":["Johannes Sixt","Junio C Hamano","Johannes Schindelin","Jeff King"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"60230","messageId":"200711181225.52288.johannes.sixt@telecom.at","threadId":"10915","inReplyTo":null,"subject":"[RFC] Alternates and broken repos: A pack and prune scheme to avoid them","fromName":"Johannes Sixt","fromEmail":"johannes.sixt@telecom.at","sentAt":"2007-11-18T11:25:52Z","receivedAt":"2007-11-18T11:25:52Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"As you know, repo.or.cz uses alternates in order to reduce the space that the \nrepositories of forked projects require.\n\nRecently, it happened that a fork (4msysgit.git) became broken because it was \nusing an object that was pruned away from the repository that it was \nborrowing from (mingw.git). This happened even though 4msysgit did not use \nthe branch of mingw.git that was rebased and whose objects were pruned. The \nreason is that a merge in 4msysgit.git resulted in a blob that was also in \nthe rebased branch.\n\nTo avoid such situations I propose to introduce \"attic\" packs. They contain \nobjects that are unreachable by the local set of refs. Otherwise they are \nused like regular packs.\n\ngit-repack produces \"attic\" packs like this:\n\n- Places objects of the local object store that are unreachable in an \"attic\" \npack.\n- Copies objects that are reachable but borrowed from an alternate and are \nonly in the alternates' \"attic\" packs into the local regular pack.\n\ngit-prune removes \"attic\" packs.\n\nThen the strategy of garbage collection can be arranged in the following way:\n\n- Repack by starting at the \"most complete\" repo and work towards the \"most \nborrowing\" ones. During this phase \"attic\" packs are created. Borrowing repos \nget a chance to salvage objects before the alternates prune them away.\n\n- Prune by starting at the \"most borrowing\" repo and work towards the \"most \ncomplete\" ones. During this phase the \"attic\" packs are cleaned up.\n\nWhat do you think? Is this a way for a solution?\n\n-- Hannes\n"},{"id":"60243","messageId":"7v3av3wg7h.fsf@gitster.siamese.dyndns.org","threadId":"10915","inReplyTo":"200711181225.52288.johannes.sixt@telecom.at","subject":"Re: [RFC] Alternates and broken repos: A pack and prune scheme to avoid them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-18T18:39:46Z","receivedAt":"2007-11-18T18:39:46Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <johannes.sixt@telecom.at> writes:\n\n> Then the strategy of garbage collection can be arranged in the following way:\n>\n> - Repack by starting at the \"most complete\" repo and work towards the \"most \n> borrowing\" ones. During this phase \"attic\" packs are created. Borrowing repos \n> get a chance to salvage objects before the alternates prune them away.\n>\n> - Prune by starting at the \"most borrowing\" repo and work towards the \"most \n> complete\" ones. During this phase the \"attic\" packs are cleaned up.\n>\n> What do you think? Is this a way for a solution?\n\nI would imagine that would work as long as it can be controlled\nwhen all the involved repositories are repacked and pruned, such\nas on repo.or.cz case (but on the other hand it is not really\ncontrolled well there and that is the reason you wrote the\nmessage X-<).\n"},{"id":"60250","messageId":"200711182101.53936.johannes.sixt@telecom.at","threadId":"10915","inReplyTo":"7v3av3wg7h.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFC] Alternates and broken repos: A pack and prune scheme to avoid them","fromName":"Johannes Sixt","fromEmail":"johannes.sixt@telecom.at","sentAt":"2007-11-18T20:01:53Z","receivedAt":"2007-11-18T20:01:53Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"On Sunday 18 November 2007 19:39, Junio C Hamano wrote:\n> Johannes Sixt <johannes.sixt@telecom.at> writes:\n> > Then the strategy of garbage collection can be arranged in the following\n> > way:\n> >\n> > - Repack by starting at the \"most complete\" repo and work towards the\n> > \"most borrowing\" ones. During this phase \"attic\" packs are created.\n> > Borrowing repos get a chance to salvage objects before the alternates\n> > prune them away.\n> >\n> > - Prune by starting at the \"most borrowing\" repo and work towards the\n> > \"most complete\" ones. During this phase the \"attic\" packs are cleaned up.\n> >\n> > What do you think? Is this a way for a solution?\n>\n> I would imagine that would work as long as it can be controlled\n> when all the involved repositories are repacked and pruned, such\n> as on repo.or.cz case (but on the other hand it is not really\n> controlled well there and that is the reason you wrote the\n> message X-<).\n\nWell, I think in many situations pack and prune can be controlled. To be \nprecise, if alternates are used pack and prune *must* be controlled. \nCurrently, the control is very simple: \"don't prune\" (and I don't recall ATM \nwhat you must not do when you repack).\n\nAnyway, judging from the responses so far it seems that people can live \nwith \"don't prune\" (or not using alternates) ;-) Repositories getting broken \nthis way isn't exactly my itch, either, so... I spelled out a possible \nsolution if someone wants to pick up the topic.\n\n-- Hannes\n"},{"id":"60251","messageId":"7v7ikfuxfk.fsf@gitster.siamese.dyndns.org","threadId":"10915","inReplyTo":"200711182101.53936.johannes.sixt@telecom.at","subject":"Re: [RFC] Alternates and broken repos: A pack and prune scheme to avoid them","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-18T20:10:39Z","receivedAt":"2007-11-18T20:10:39Z","isPatch":false,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <johannes.sixt@telecom.at> writes:\n\n> On Sunday 18 November 2007 19:39, Junio C Hamano wrote:\n> ...\n>> I would imagine that would work as long as it can be controlled\n>> when all the involved repositories are repacked and pruned, such\n>> as on repo.or.cz case (but on the other hand it is not really\n>> controlled well there and that is the reason you wrote the\n>> message X-<).\n>\n> Well, I think in many situations pack and prune can be controlled. To be \n> precise, if alternates are used pack and prune *must* be controlled.\n> Currently, the control is very simple: \"don't prune\" (and I don't recall ATM \n> what you must not do when you repack).\n>\n> Anyway, judging from the responses so far it seems that people can live \n> with \"don't prune\" (or not using alternates) ;-)\n\nBecause my point was not \"don't prune is good enough\", I think\nyou are judging from too small number of responses (in fact,\nzero).\n\nMy point was that even the existing setup that is well known to\nthe public (i.e. repo.or.cz) does not seem to be controlled, and\nadding a nicer mechanism (e.g. I do not think there currently is\na canned way to prepare a pack that contains only unreachable\nobjects --- you need to script it anew) for a better control may\nnot help the situation much, unless it is actually used.\n"},{"id":"61360","messageId":"Pine.LNX.4.64.0711290340470.27959@racer.site","threadId":"10915","inReplyTo":"7v7ikfuxfk.fsf@gitster.siamese.dyndns.org","subject":"[PATCH/RFC] Teach repack to optionally retain otherwise lost objects","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T03:41:42Z","receivedAt":"2007-11-29T03:41:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWhen specifying --attic=<prefix>, the objects that would be lost when\ncalling repack with -d will be put into a packfile (or multiple\npackfiles), using the file name prefix <prefix>.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tThis implements the idea of Hannes.\n\n\tThe plan for repo.or.cz is now to invoke repack with\n\t\"--attic=attic\" and copied attic-*.{idx,pack} to all the forks'\n\tobject stores, then delete the original attic-*{.idx,pack}.\n\n\tThe beauty of that approach is that the order in which the\n\trepositories are repacked is no longer important.\n\n\tThis patch is marked RFC since there is a severe bottleneck\n\there: the new pack's index is sorted and made unique and every\n\tSHA-1 displayed twice, then the old pack's index is sorted and\n\tmade unique.  Then the combined result is sorted and only the\n\tnow-unique SHA-1s are actually packed.\n\n\t(The sort is not necessary if there is only _one_ pack.\n\tHowever, we cannot guarantee that.)\n\n\tOf course, this is quick 'n dirty, and the price to be paid\n\tis a substantial performance hit: in my tests, linux-2.6.git\n\tneeded half a second to show its pack's index, but that\n\tsed 's/^.* //' | sort | uniq | sed p mantra needs 19 seconds.\n\n\tThe obvious thing is to exploit the fact that the pack indices\n\tare already sorted:\n\n\tI started patch git-show-index so it takes an argument\n\t--missing-objects, followed by the new pack index file names,\n\tfollowed by --, followed be the old pack index file names.\n\n\tThen it would traverse all of them simultaneously, outputting\n\tonly the SHA-1s of objects that are in an old pack, but not\n\tin any of the new packs.\n\n\tTwo issues: there might be a whole lot of pack files (Pasky\n\ttold me today that in one instance there were 416 pack files!)\n\tand that might well exceed the maximum number of open files.\n\n\tSecond issue: there are two different pack index formats, and\n\tthe code is not easily refactored AFAICT.\n\n\tProbably a better method would be not to read the files\n\tsimultaneously, but fill a \"struct decorate *\" with objects\n\t(which can be faked, as we do not really need to parse them)\n\tof the new packs, and then only use decorate_lookup() to determine\n\tfor all old packs' objects if they are present in the new ones.\n\n\tThe latter approach would allow for a relatively easy refactoring\n\tof show-index.c; just provide a callback for each entry.\n\n\tHowever, I am way too tired today to do it.\n\n\tBesides, a completely different idea just struck me: before\n\trepacking, .git/objects/pack/* could be _hard linked_ to the\n\tforkee's object stores.  Then nothing in git-repack's code\n\tneeds to be changed.\n\n\tOh, well.  I just wasted 1.5 hours.\n\n Documentation/git-repack.txt |    4 +++\n git-repack.sh                |   40 +++++++++++++++++++++++++++++++++----\n t/t5303-repack-attic.sh      |   44 ++++++++++++++++++++++++++++++++++++++++++\n 3 files changed, 83 insertions(+), 5 deletions(-)\n create mode 100644 t/t5303-repack-attic.sh\n\ndiff --git a/Documentation/git-repack.txt b/Documentation/git-repack.txt\nindex 12e2079..ec2c2bf 100644\n--- a/Documentation/git-repack.txt\n+++ b/Documentation/git-repack.txt\n@@ -84,6 +84,10 @@ OPTIONS\n \tIf specified,  multiple packfiles may be created.\n \tThe default is unlimited.\n \n+--attic=<prefix>::\n+\tPut all objects that would/will be lost when running with `-d`\n+\tinto its own packfile(s), with file name prefix `<prefix>`.\n+\n \n Configuration\n -------------\ndiff --git a/git-repack.sh b/git-repack.sh\nindex e18eb3f..f83d6f0 100755\n--- a/git-repack.sh\n+++ b/git-repack.sh\n@@ -18,12 +18,13 @@ window=         size of the window used for delta compression\n window-memory=  same as the above, but limit memory size instead of entries count\n depth=          limits the maximum delta depth\n max-pack-size=  maximum size of each packfile\n+attic=          pack no-longer-packed used objects into an \"attic\" pack\n \"\n SUBDIRECTORY_OK='Yes'\n . git-sh-setup\n \n no_update_info= all_into_one= remove_redundant= keep_unreachable=\n-local= quiet= no_reuse= extra=\n+local= quiet= no_reuse= extra= attic=\n while test $# != 0\n do\n \tcase \"$1\" in\n@@ -37,6 +38,8 @@ do\n \t-l)\tlocal=--local ;;\n \t--max-pack-size|--window|--window-memory|--depth)\n \t\textra=\"$extra $1=$2\"; shift ;;\n+\t--attic)\n+\t\tattic=\"$2\"; shift ;;\n \t--) shift; break;;\n \t*)\tusage ;;\n \tesac\n@@ -119,6 +122,36 @@ for name in $names ; do\n \trm -f \"$PACKDIR/old-pack-$name.pack\" \"$PACKDIR/old-pack-$name.idx\"\n done\n \n+new_existing=\n+for e in $existing\n+do\n+\tcase \"$ fullbases \" in\n+\t*\" $e \"*) ;;\n+\t*)\n+\t\tnew_existing=\"$new_existing $e\"\n+\t\t;;\n+\tesac\n+done\n+existing=\"$new_existing\"\n+\n+if test ! -z \"$attic\"\n+then\n+\t# Find the objects which were in the existing packs, but are no\n+\t# longer in the new ones.\n+\t#\n+\t# This could be much more efficient.\n+\t(for name in $names\n+\t do\n+\t\tgit show-index < $PACKDIR/pack-$name.idx\n+\t done | sed 's/^.* //' | sort | uniq | sed p &&\n+\t for e in $existing\n+\t do\n+\t\tgit show-index < \"$PACKDIR/$e.idx\"\n+\t done | sed 's/^.* //' | sort | uniq) | sort | uniq -u |\n+\tgit pack-objects --non-empty \"$attic\" ||\n+\t\tdie \"Could not create attic '$attic'.\"\n+fi\n+\n if test \"$remove_redundant\" = t\n then\n \t# We know $existing are all redundant.\n@@ -128,10 +161,7 @@ then\n \t\t( cd \"$PACKDIR\" &&\n \t\t  for e in $existing\n \t\t  do\n-\t\t\tcase \" $fullbases \" in\n-\t\t\t*\" $e \"*) ;;\n-\t\t\t*)\trm -f \"$e.pack\" \"$e.idx\" \"$e.keep\" ;;\n-\t\t\tesac\n+\t\t\trm -f \"$e.pack\" \"$e.idx\" \"$e.keep\"\n \t\t  done\n \t\t)\n \tfi\ndiff --git a/t/t5303-repack-attic.sh b/t/t5303-repack-attic.sh\nnew file mode 100644\nindex 0000000..9777748\n--- /dev/null\n+++ b/t/t5303-repack-attic.sh\n@@ -0,0 +1,44 @@\n+#!/bin/sh\n+#\n+# Copyright (c) 2007 Johannes E. Schindelin\n+#\n+\n+test_description='repack with an attic pack'\n+. ./test-lib.sh\n+\n+test_expect_success 'setup' '\n+\n+\techo \"Ten weary, footsore wanderers,\" > file &&\n+\tgit add file &&\n+\ttest_tick &&\n+\tgit commit -m initial &&\n+\techo \"all in a woeful plight,\" >> file &&\n+\ttest_tick &&\n+\tgit commit -m second file &&\n+\techo \"sought shelter in a wayside-inn\" >> file &&\n+\ttest_tick &&\n+\tgit commit -m third file &&\n+\techo \"one dark and lonely night.\" >> file &&\n+\ttest_tick &&\n+\tgit commit -m fourth file &&\n+\techo \">>Nine rooms, no more<<, the landlord said,\" >> file &&\n+\ttest_tick && git commit -m fifth file &&\n+\tgit repack -a -d &&\n+\t! ls .git/objects/??/*\n+\n+'\n+\n+test_expect_success 'create attic pack' '\n+\n+\tLAST_VERSION=$(git rev-parse --verify HEAD:file) &&\n+\tgit reset --hard HEAD^ &&\n+\trm .git/logs/HEAD .git/logs/refs/heads/master &&\n+\tgit cat-file blob $LAST_VERSION &&\n+\tgit repack --attic=attic -a -d &&\n+\t! git cat-file blob $LAST_VERSION &&\n+\ttest -f attic-*.idx &&\n+\tcat attic-*.idx | git show-index | grep $LAST_VERSION\n+\n+'\n+\n+test_done\n-- \n1.5.3.6.2066.g09421\n"},{"id":"61367","messageId":"7vaboxy3va.fsf@gitster.siamese.dyndns.org","threadId":"10915","inReplyTo":"Pine.LNX.4.64.0711290340470.27959@racer.site","subject":"Re: [PATCH/RFC] Teach repack to optionally retain otherwise lost objects","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-29T06:15:05Z","receivedAt":"2007-11-29T06:15:05Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> \tBesides, a completely different idea just struck me: before\n> \trepacking, .git/objects/pack/* could be _hard linked_ to the\n> \tforkee's object stores.  Then nothing in git-repack's code\n> \tneeds to be changed.\n>\n> \tOh, well.  I just wasted 1.5 hours.\n\nYour 1.5 hours was spent wisely to come up with that idea ;-).\n\nTo make sure I understand your idea correctly, the procedure to repack a\nrepository in a fork-friendly way is:\n\n (1) find the project directly forked from you;\n\n (2) hardlink all packs under your object store to their object store;\n\n (3) repack -a -l and prune.\n\nI think that would work as long as you do the above as a unit and handle\none repository at a time.  Otherwise I think you risk losing necessary\nobjects when hierarchical forks are involved.  E.g.  if you have a\nproject X that has a fork Y which in turn has fork Z.\n\n\t* Step 1 is run for X, Y and Z.\n        * Step 2 is run for Y and Z.\n        * Step 3 is run for Z.\n\nAt this point, Z is still borrowing objects from Y and X through Y, and\nit will not keep objects it is borrowing from X through Y.  Then if the\nprocedure is intermixed like this, a bad thing happens.\n\n\t* Step 2 is run for X.\n\t* Step 3 is run for Y.\n\t* Step 3 is run for X.\n\nStep 3 for Y would lose objects Y was borrowing from X that were not\nused by Y itself.  At this point, Z is still usable as the objects it is\nborrowing from X though Y have not been pruned from X.  But Step 3 for X\nwill lose them, rendering Z unusable.\n"},{"id":"61389","messageId":"Pine.LNX.4.64.0711291146090.27959@racer.site","threadId":"10915","inReplyTo":"7vaboxy3va.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH/RFC] Teach repack to optionally retain otherwise lost objects","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T11:57:26Z","receivedAt":"2007-11-29T11:57:26Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 28 Nov 2007, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> > \tBesides, a completely different idea just struck me: before\n> > \trepacking, .git/objects/pack/* could be _hard linked_ to the\n> > \tforkee's object stores.  Then nothing in git-repack's code\n> > \tneeds to be changed.\n> >\n> > \tOh, well.  I just wasted 1.5 hours.\n> \n> Your 1.5 hours was spent wisely to come up with that idea ;-).\n\nThanks ;-)\n\n> To make sure I understand your idea correctly, the procedure to repack a \n> repository in a fork-friendly way is:\n> \n>  (1) find the project directly forked from you;\n> \n>  (2) hardlink all packs under your object store to their object store;\n> \n>  (3) repack -a -l and prune.\n\nYep.\n\n> I think that would work as long as you do the above as a unit and handle\n> one repository at a time.\n\nExactly.  See\n\nhttp://repo.or.cz/w/repo.git?a=commitdiff;h=fba501deabd349afbe3b8bf89f385889889e04ac\n\nfor a tired proposal.\n\nNote that \"prune\" is not (yet) an option, since it could possibly destroy \nobjects which are needed in an ongoing push operation.\n\nHowever, we could do exactly the same as with reflogs: introduce a grace \nperiod (with loose objects, we can use the ctime...)\n\n> Otherwise I think you risk losing necessary objects when hierarchical \n> forks are involved.  E.g.  if you have a project X that has a fork Y \n> which in turn has fork Z.\n\nWell, in theory you could also iterate over all projects and hard link the \npacks/objects of their alternates, and _then_ iterate and repack.  But it \nis simpler and more obvious in the case of repo.or.cz to do all in one \niteration, because we can order the repository names easily so that \nforkees come first, _and_ we have an easy way to find out what are the \nforks of a project.\n\nCiao,\nDscho\n"},{"id":"61398","messageId":"Pine.LNX.4.64.0711291419350.27959@racer.site","threadId":"10915","inReplyTo":"Pine.LNX.4.64.0711291146090.27959@racer.site","subject":"[PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T14:21:23Z","receivedAt":"2007-11-29T14:21:23Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nEarlier, 'git prune' would prune all loose unreachable objects.\nThis could be quite dangerous, as the objects could be used in\nan ongoing operation.\n\nThis patch adds a mode to expire only loose, unreachable objects\nwhich are older than a certain time.  For example, by\n\n\tgit prune --expire 14.days\n\nyou can prune only those objects which are loose, unreachable\nand older than 14 days (and thus probably outdated).\n\nThe implementation uses st.st_mtime rather than st.st_ctime,\nbecause it can be tested better, using 'touch -d <time>' (and\nomitting the test when the platform does not support that\ncommand line switch).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tOn Thu, 29 Nov 2007, Johannes Schindelin wrote:\n\n\t> Note that \"prune\" is not (yet) an option [for repo.or.cz], since \n\t> it could possibly destroy objects which are needed in an ongoing \n\t> push operation.\n\t> \n\t> However, we could do exactly the same as with reflogs: introduce \n\t> a grace period (with loose objects, we can use the ctime...)\n\n\tand this patch does that (except using mtime as ctime, for reasons \n\texplained in the commit message.\n\n\tObviously, this patch is asking for a cousin, changing\n\tgit-gc to use this option, and maybe introduce a config\n\tvariable gc.pruneAge.\n\n Documentation/git-prune.txt |    5 ++++-\n builtin-prune.c             |   21 ++++++++++++++++++++-\n t/t1410-reflog.sh           |   18 ++++++++++++++++++\n 3 files changed, 42 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 0ace233..9835bdb 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database\n \n SYNOPSIS\n --------\n-'git-prune' [-n] [--] [<head>...]\n+'git-prune' [-n] [--expire <expire>] [--] [<head>...]\n \n DESCRIPTION\n -----------\n@@ -31,6 +31,9 @@ OPTIONS\n \\--::\n \tDo not interpret any more arguments as options.\n \n+\\--expire <time>::\n+\tOnly expire loose objects older than <time>.\n+\n <head>...::\n \tIn addition to objects\n \treachable from any of our references, keep objects\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex 44df59e..b5e7684 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -7,15 +7,24 @@\n \n static const char prune_usage[] = \"git-prune [-n]\";\n static int show_only;\n+static unsigned long expire;\n \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 \tif (show_only) {\n \t\tenum object_type type = sha1_object_info(sha1, NULL);\n \t\tprintf(\"%s %s\\n\", sha1_to_hex(sha1),\n \t\t       (type > 0) ? typename(type) : \"unknown\");\n \t} else\n-\t\tunlink(mkpath(\"%s/%s\", path, filename));\n+\t\tunlink(fullpath);\n \treturn 0;\n }\n \n@@ -85,6 +94,16 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\tshow_only = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(arg, \"--expire\")) {\n+\t\t\tif (++i < argc) {\n+\t\t\t\texpire = approxidate(argv[i]);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\telse if (!prefixcmp(arg, \"--expire=\")) {\n+\t\t\texpire = approxidate(arg + 9);\n+\t\t\tcontinue;\n+\t\t}\n \t\tusage(prune_usage);\n \t}\n \ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 12a53ed..f093802 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -201,4 +201,22 @@ test_expect_success 'delete' '\n \t! grep dragon < output\n '\n \n+test_expect_success 'prune --expire' '\n+\n+\tBLOB=$(echo aleph | git hash-object -w --stdin) &&\n+\tBLOB_FILE=.git/objects/$(echo $BLOB | sed \"s/^../&\\//\") &&\n+\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n+\ttest -f $BLOB_FILE &&\n+\tgit reset --hard &&\n+\tif touch -d \"Jan 1 1970\" $BLOB_FILE\n+\tthen\n+\t\tgit prune --expire 1.day &&\n+\t\ttest 19 = $(git count-objects | sed \"s/ .*//\") &&\n+\t\t! test -f $BLOB_FILE\n+\telse\n+\t\tsay \"Skipping test due to non-working touch -d\"\n+\tfi\n+\n+'\n+\n test_done\n-- \n1.5.3.6.2087.g788ea4\n"},{"id":"61399","messageId":"474ECE2A.9050700@viscovery.net","threadId":"10915","inReplyTo":"Pine.LNX.4.64.0711291419350.27959@racer.site","subject":"Re: [PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2007-11-29T14:35:22Z","receivedAt":"2007-11-29T14:35:22Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Johannes Schindelin schrieb:\n> +test_expect_success 'prune --expire' '\n> +\n> +\tBLOB=$(echo aleph | git hash-object -w --stdin) &&\n> +\tBLOB_FILE=.git/objects/$(echo $BLOB | sed \"s/^../&\\//\") &&\n> +\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n> +\ttest -f $BLOB_FILE &&\n> +\tgit reset --hard &&\n\nHere you could throw in:\n\n\tgit prune --expire=1.hour.ago &&\n\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n\ttest -f $BLOB_FILE &&\n\nto test that the object is not pruned (and the alternate --expire syntax).\n\n> +\tif touch -d \"Jan 1 1970\" $BLOB_FILE\n> +\tthen\n> +\t\tgit prune --expire 1.day &&\n> +\t\ttest 19 = $(git count-objects | sed \"s/ .*//\") &&\n> +\t\t! test -f $BLOB_FILE\n> +\telse\n> +\t\tsay \"Skipping test due to non-working touch -d\"\n> +\tfi\n\n-- Hannes\n"},{"id":"61402","messageId":"20071129151211.GB32296@coredump.intra.peff.net","threadId":"10915","inReplyTo":"Pine.LNX.4.64.0711291419350.27959@racer.site","subject":"Re: [PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Jeff King","fromEmail":"peff@peff.net","sentAt":"2007-11-29T15:12:11Z","receivedAt":"2007-11-29T15:12:11Z","isPatch":true,"sender":{"key":"peff@peff.net","avatar":"https://avatars.githubusercontent.com/u/45925?v=4"},"body":"On Thu, Nov 29, 2007 at 02:21:23PM +0000, Johannes Schindelin wrote:\n\n> This patch adds a mode to expire only loose, unreachable objects\n> which are older than a certain time.  For example, by\n> \n> \tgit prune --expire 14.days\n> \n> you can prune only those objects which are loose, unreachable\n> and older than 14 days (and thus probably outdated).\n\nDoes this now make git-prune safe for automatic running?\n\nI suppose you could still be actively manipulating refs that point to\nvery old objects.\n\n-Peff\n"},{"id":"61403","messageId":"Pine.LNX.4.64.0711291519030.27959@racer.site","threadId":"10915","inReplyTo":"474ECE2A.9050700@viscovery.net","subject":"[PATCH v2] Add \"--expire <time>\" option to 'git prune'","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T15:22:02Z","receivedAt":"2007-11-29T15:22:02Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nEarlier, 'git prune' would prune all loose unreachable objects.\nThis could be quite dangerous, as the objects could be used in\nan ongoing operation.\n\nThis patch adds a mode to expire only loose, unreachable objects\nwhich are older than a certain time.  For example, by\n\n\tgit prune --expire 14.days\n\nyou can prune only those objects which are loose, unreachable\nand older than 14 days (and thus probably outdated).\n\nThe implementation uses st.st_mtime rather than st.st_ctime,\nbecause it can be tested better, using 'touch -d <time>' (and\nomitting the test when the platform does not support that\ncommand line switch).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tOn Thu, 29 Nov 2007, Johannes Sixt wrote:\n\n\t> Johannes Schindelin schrieb:\n\t> > +test_expect_success 'prune --expire' '\n\t> > +\n\t> > +\tBLOB=$(echo aleph | git hash-object -w --stdin) &&\n\t> > +\tBLOB_FILE=.git/objects/$(echo $BLOB | sed \"s/^../&\\//\") &&\n\t> > +\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n\t> > +\ttest -f $BLOB_FILE &&\n\t> > +\tgit reset --hard &&\n\t> \n\t> Here you could throw in:\n\t> \n\t> \tgit prune --expire=1.hour.ago &&\n\t> \ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n\t> \ttest -f $BLOB_FILE &&\n\t> \n\t> to test that the object is not pruned (and the alternate \n\t> --expire syntax).\n\n\tGood idea!\n\n Documentation/git-prune.txt |    5 ++++-\n builtin-prune.c             |   21 ++++++++++++++++++++-\n t/t1410-reflog.sh           |   21 +++++++++++++++++++++\n 3 files changed, 45 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 0ace233..9835bdb 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database\n \n SYNOPSIS\n --------\n-'git-prune' [-n] [--] [<head>...]\n+'git-prune' [-n] [--expire <expire>] [--] [<head>...]\n \n DESCRIPTION\n -----------\n@@ -31,6 +31,9 @@ OPTIONS\n \\--::\n \tDo not interpret any more arguments as options.\n \n+\\--expire <time>::\n+\tOnly expire loose objects older than <time>.\n+\n <head>...::\n \tIn addition to objects\n \treachable from any of our references, keep objects\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex 44df59e..b5e7684 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -7,15 +7,24 @@\n \n static const char prune_usage[] = \"git-prune [-n]\";\n static int show_only;\n+static unsigned long expire;\n \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 \tif (show_only) {\n \t\tenum object_type type = sha1_object_info(sha1, NULL);\n \t\tprintf(\"%s %s\\n\", sha1_to_hex(sha1),\n \t\t       (type > 0) ? typename(type) : \"unknown\");\n \t} else\n-\t\tunlink(mkpath(\"%s/%s\", path, filename));\n+\t\tunlink(fullpath);\n \treturn 0;\n }\n \n@@ -85,6 +94,16 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\tshow_only = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(arg, \"--expire\")) {\n+\t\t\tif (++i < argc) {\n+\t\t\t\texpire = approxidate(argv[i]);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\telse if (!prefixcmp(arg, \"--expire=\")) {\n+\t\t\texpire = approxidate(arg + 9);\n+\t\t\tcontinue;\n+\t\t}\n \t\tusage(prune_usage);\n \t}\n \ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 12a53ed..3924dc4 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -201,4 +201,25 @@ test_expect_success 'delete' '\n \t! grep dragon < output\n '\n \n+test_expect_success 'prune --expire' '\n+\n+\tBLOB=$(echo aleph | git hash-object -w --stdin) &&\n+\tBLOB_FILE=.git/objects/$(echo $BLOB | sed \"s/^../&\\//\") &&\n+\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n+\ttest -f $BLOB_FILE &&\n+\tgit reset --hard &&\n+\tgit prune --expire=1.hour.ago &&\n+\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n+\ttest -f $BLOB_FILE &&\n+\tif touch -d \"Jan 1 1970\" $BLOB_FILE\n+\tthen\n+\t\tgit prune --expire 1.day &&\n+\t\ttest 19 = $(git count-objects | sed \"s/ .*//\") &&\n+\t\t! test -f $BLOB_FILE\n+\telse\n+\t\tsay \"Skipping test due to non-working touch -d\"\n+\tfi\n+\n+'\n+\n test_done\n-- \n1.5.3.6.2088.g8c260\n"},{"id":"61405","messageId":"Pine.LNX.4.64.0711291613260.27959@racer.site","threadId":"10915","inReplyTo":"20071129151211.GB32296@coredump.intra.peff.net","subject":"Re: [PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T16:13:47Z","receivedAt":"2007-11-29T16:13:47Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Thu, 29 Nov 2007, Jeff King wrote:\n\n> On Thu, Nov 29, 2007 at 02:21:23PM +0000, Johannes Schindelin wrote:\n> \n> > This patch adds a mode to expire only loose, unreachable objects\n> > which are older than a certain time.  For example, by\n> > \n> > \tgit prune --expire 14.days\n> > \n> > you can prune only those objects which are loose, unreachable\n> > and older than 14 days (and thus probably outdated).\n> \n> Does this now make git-prune safe for automatic running?\n> \n> I suppose you could still be actively manipulating refs that point to\n> very old objects.\n\nThat's why I want to have it configurable from git-gc.\n\nCiao,\nDscho\n"},{"id":"61420","messageId":"7vlk8gvmts.fsf@gitster.siamese.dyndns.org","threadId":"10915","inReplyTo":"Pine.LNX.4.64.0711291419350.27959@racer.site","subject":"Re: [PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2007-11-29T20:06:07Z","receivedAt":"2007-11-29T20:06:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\n> The implementation uses st.st_mtime rather than st.st_ctime,\n> because it can be tested better, using 'touch -d <time>' (and\n> omitting the test when the platform does not support that\n> command line switch).\n\nBut I think you can use more portable -t for setting mtime to\n1970/01/01, but I had a feeling that earlier we were bitten by\nnon-portability of \"touch\" and introduced test-chmtime.\n"},{"id":"61424","messageId":"Pine.LNX.4.64.0711292058110.27959@racer.site","threadId":"10915","inReplyTo":"7vlk8gvmts.fsf@gitster.siamese.dyndns.org","subject":"[PATCH] Add \"--expire <time>\" option to 'git prune'","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2007-11-29T20:59:55Z","receivedAt":"2007-11-29T20:59:55Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nEarlier, 'git prune' would prune all loose unreachable objects.\nThis could be quite dangerous, as the objects could be used in\nan ongoing operation.\n\nThis patch adds a mode to expire only loose, unreachable objects\nwhich are older than a certain time.  For example, by\n\n\tgit prune --expire 14.days\n\nyou can prune only those objects which are loose, unreachable\nand older than 14 days (and thus probably outdated).\n\nThe implementation uses st.st_mtime rather than st.st_ctime,\nbecause it can be tested better, using 'touch -d <time>' (and\nomitting the test when the platform does not support that\ncommand line switch).\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tOn Thu, 29 Nov 2007, Junio C Hamano wrote:\n\n\t> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n\t> \n\t> > The implementation uses st.st_mtime rather than st.st_ctime,\n\t> > because it can be tested better, using 'touch -d <time>' (and\n\t> > omitting the test when the platform does not support that\n\t> > command line switch).\n\t> \n\t> But I think you can use more portable -t for setting mtime to\n\t> 1970/01/01, but I had a feeling that earlier we were bitten by\n\t> non-portability of \"touch\" and introduced test-chmtime.\n\n\tSomehow that slipped by me.  This patch uses test-chmtime.\n\n Documentation/git-prune.txt |    5 ++++-\n builtin-prune.c             |   21 ++++++++++++++++++++-\n t/t1410-reflog.sh           |   17 +++++++++++++++++\n 3 files changed, 41 insertions(+), 2 deletions(-)\n\ndiff --git a/Documentation/git-prune.txt b/Documentation/git-prune.txt\nindex 0ace233..9835bdb 100644\n--- a/Documentation/git-prune.txt\n+++ b/Documentation/git-prune.txt\n@@ -8,7 +8,7 @@ git-prune - Prune all unreachable objects from the object database\n \n SYNOPSIS\n --------\n-'git-prune' [-n] [--] [<head>...]\n+'git-prune' [-n] [--expire <expire>] [--] [<head>...]\n \n DESCRIPTION\n -----------\n@@ -31,6 +31,9 @@ OPTIONS\n \\--::\n \tDo not interpret any more arguments as options.\n \n+\\--expire <time>::\n+\tOnly expire loose objects older than <time>.\n+\n <head>...::\n \tIn addition to objects\n \treachable from any of our references, keep objects\ndiff --git a/builtin-prune.c b/builtin-prune.c\nindex 44df59e..b5e7684 100644\n--- a/builtin-prune.c\n+++ b/builtin-prune.c\n@@ -7,15 +7,24 @@\n \n static const char prune_usage[] = \"git-prune [-n]\";\n static int show_only;\n+static unsigned long expire;\n \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 \tif (show_only) {\n \t\tenum object_type type = sha1_object_info(sha1, NULL);\n \t\tprintf(\"%s %s\\n\", sha1_to_hex(sha1),\n \t\t       (type > 0) ? typename(type) : \"unknown\");\n \t} else\n-\t\tunlink(mkpath(\"%s/%s\", path, filename));\n+\t\tunlink(fullpath);\n \treturn 0;\n }\n \n@@ -85,6 +94,16 @@ int cmd_prune(int argc, const char **argv, const char *prefix)\n \t\t\tshow_only = 1;\n \t\t\tcontinue;\n \t\t}\n+\t\tif (!strcmp(arg, \"--expire\")) {\n+\t\t\tif (++i < argc) {\n+\t\t\t\texpire = approxidate(argv[i]);\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t}\n+\t\telse if (!prefixcmp(arg, \"--expire=\")) {\n+\t\t\texpire = approxidate(arg + 9);\n+\t\t\tcontinue;\n+\t\t}\n \t\tusage(prune_usage);\n \t}\n \ndiff --git a/t/t1410-reflog.sh b/t/t1410-reflog.sh\nindex 12a53ed..4a17573 100755\n--- a/t/t1410-reflog.sh\n+++ b/t/t1410-reflog.sh\n@@ -201,4 +201,21 @@ test_expect_success 'delete' '\n \t! grep dragon < output\n '\n \n+test_expect_success 'prune --expire' '\n+\n+\tBLOB=$(echo aleph | git hash-object -w --stdin) &&\n+\tBLOB_FILE=.git/objects/$(echo $BLOB | sed \"s/^../&\\//\") &&\n+\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n+\ttest -f $BLOB_FILE &&\n+\tgit reset --hard &&\n+\tgit prune --expire=1.hour.ago &&\n+\ttest 20 = $(git count-objects | sed \"s/ .*//\") &&\n+\ttest -f $BLOB_FILE &&\n+\ttest-chmtime -86400 $BLOB_FILE &&\n+\tgit prune --expire 1.day &&\n+\ttest 19 = $(git count-objects | sed \"s/ .*//\") &&\n+\t! test -f $BLOB_FILE\n+\n+'\n+\n test_done\n-- \n1.5.3.6.2088.g8c260\n"}]}