{"thread":{"id":"11711","subject":"git-clean buglet","startedAt":"2008-01-23T15:14:52Z","lastAt":"2008-03-07T15:24:34Z","messageCount":47,"participants":["Johannes Sixt","Johannes Schindelin","Shawn Bohrer","Junio C Hamano","しらいしななこ","Robin Rosenberg","Karl Hasselström"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"66402","messageId":"479759EC.4010002@viscovery.net","threadId":"11711","inReplyTo":null,"subject":"git-clean buglet","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-23T15:14:52Z","receivedAt":"2008-01-23T15:14:52Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Try this in your favorite git repo:\n\n    git clean -n /\n\nHere, it responds with:\n\n    fatal: oops in prep_exclude\n\nI don't know what the expected behavior should be, but certainly not this.\nMaybe just do nothing like 'git ls-files /'.\n\nI'm not familiar with either builtin-clean.c nor dir.c; perhaps someone\nelse can fix this?\n\n-- Hannes\n"},{"id":"66403","messageId":"47975C11.7050800@viscovery.net","threadId":"11711","inReplyTo":"479759EC.4010002@viscovery.net","subject":"Re: git-clean buglet","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-23T15:24:01Z","receivedAt":"2008-01-23T15:24:01Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Johannes Sixt schrieb:\n> Try this in your favorite git repo:\n> \n>     git clean -n /\n> \n> Here, it responds with:\n> \n>     fatal: oops in prep_exclude\n> \n> I don't know what the expected behavior should be, but certainly not this.\n> Maybe just do nothing like 'git ls-files /'.\n\nWell, 'git ls-files -o /' would be more similar, but that one lists the\nentire disk contents - not what I would have expected, either. Hmm...\n\n-- Hannes\n"},{"id":"66404","messageId":"alpine.LSU.1.00.0801231528520.5731@racer.site","threadId":"11711","inReplyTo":"479759EC.4010002@viscovery.net","subject":"Re: git-clean buglet","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-23T15:29:36Z","receivedAt":"2008-01-23T15:29:36Z","isPatch":false,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Wed, 23 Jan 2008, Johannes Sixt wrote:\n\n> Try this in your favorite git repo:\n> \n>     git clean -n /\n\nThat's an absolute path.  Like almost _all_ git commands, clean only \ntakes relative ones.  You probably meant \"git clean -n\".\n\nHth,\nDscho\n"},{"id":"66405","messageId":"47975FE6.4050709@viscovery.net","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801231528520.5731@racer.site","subject":"Re: git-clean buglet","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-23T15:40:22Z","receivedAt":"2008-01-23T15:40:22Z","isPatch":false,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Johannes Schindelin schrieb:\n> Hi,\n> \n> On Wed, 23 Jan 2008, Johannes Sixt wrote:\n> \n>> Try this in your favorite git repo:\n>>\n>>     git clean -n /\n> \n> That's an absolute path.  Like almost _all_ git commands, clean only \n> takes relative ones.  You probably meant \"git clean -n\".\n\nI know it's an absolute path, but, no, I said\n\n     git clean -n \\*.vcproj\n\non Windows, which the MinGW port internally transforms into\n\n     git clean -n /*.vcproj\n\n(which was not exactly what I meant, but that's a different story).\nAnd this also reports, just like on Linux:\n\n     fatal: oops in prep_exclude\n\nThere remain the questions whether we want to do something about absolute\npaths in general or this oops in particular.\n\n-- Hannes\n"},{"id":"66685","messageId":"1201463731-1963-1-git-send-email-shawn.bohrer@gmail.com","threadId":"11711","inReplyTo":"47975FE6.4050709@viscovery.net","subject":"[PATCH] Fix off by one error in prep_exclude.","fromName":"Shawn Bohrer","fromEmail":"shawn.bohrer@gmail.com","sentAt":"2008-01-27T19:55:31Z","receivedAt":"2008-01-27T19:55:31Z","isPatch":true,"sender":{"key":"shawn.bohrer@gmail.com","avatar":"https://gravatar.com/avatar/6eb093ef7d276306d18366254e0c95ff6a5db58231ac7e82fe78c2800aaae1b6?d=mp&s=160"},"body":"base + current already includes the trailing slash so adding\none removes the first character of the next directory.\n\nSigned-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n---\n\nThis fixes the oops part of the issue Johannes found, but doesn't\naddress the fact that we probably should remove files that aren't\na part of the repository at in the first place.\n\n dir.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 3e345c2..9e5879a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -237,7 +237,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tcurrent = 0;\n \t\t}\n \t\telse {\n-\t\t\tcp = strchr(base + current + 1, '/');\n+\t\t\tcp = strchr(base + current, '/');\n \t\t\tif (!cp)\n \t\t\t\tdie(\"oops in prep_exclude\");\n \t\t\tcp++;\n-- \n1.5.4-rc2.GIT\n"},{"id":"66687","messageId":"alpine.LSU.1.00.0801272043040.23907@racer.site","threadId":"11711","inReplyTo":"1201463731-1963-1-git-send-email-shawn.bohrer@gmail.com","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-27T20:44:57Z","receivedAt":"2008-01-27T20:44:57Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 27 Jan 2008, Shawn Bohrer wrote:\n\n> base + current already includes the trailing slash so adding\n> one removes the first character of the next directory.\n> \n> Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n> ---\n> \n> This fixes the oops part of the issue Johannes found,\n\nhave I?\n\n> but doesn't address the fact that we probably should remove files that \n> aren't a part of the repository at in the first place.\n\nI am sorry, but I cannot begin to see what this commit tries to \naccomplish.  Yes, sure, there is an off-by-one error, and your commit \nmessage says how that was fixed.  But I miss a description what usage it \nwould affect, i.e. when this bug triggers.\n\nI imagine that you would be as lost as me, reading that commit message 6 \nmonths from now, trying to understand why that change was made.\n\nCiao,\nDscho\n"},{"id":"66690","messageId":"20080127211539.GA10993@lintop","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801272043040.23907@racer.site","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Shawn Bohrer","fromEmail":"shawn.bohrer@gmail.com","sentAt":"2008-01-27T21:15:39Z","receivedAt":"2008-01-27T21:15:39Z","isPatch":true,"sender":{"key":"shawn.bohrer@gmail.com","avatar":"https://gravatar.com/avatar/6eb093ef7d276306d18366254e0c95ff6a5db58231ac7e82fe78c2800aaae1b6?d=mp&s=160"},"body":"On Sun, Jan 27, 2008 at 08:44:57PM +0000, Johannes Schindelin wrote:\n> Hi,\n> \n> On Sun, 27 Jan 2008, Shawn Bohrer wrote:\n> \n> > base + current already includes the trailing slash so adding\n> > one removes the first character of the next directory.\n> > \n> > Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n> > ---\n> > \n> > This fixes the oops part of the issue Johannes found,\n> \n> have I?\n \nSorry I should have been more explicit Johannes Sixt reported the issue,\nyou were included simply because you had been involved in the thread.\n\n> > but doesn't address the fact that we probably should remove files that \n> > aren't a part of the repository at in the first place.\n> \n> I am sorry, but I cannot begin to see what this commit tries to \n> accomplish.  Yes, sure, there is an off-by-one error, and your commit \n> message says how that was fixed.  But I miss a description what usage it \n> would affect, i.e. when this bug triggers.\n\nAs far as I can see there are two protential cases that could trigger\nthis bug, but there may be more.  This first was the arguably invalid\ncase the Johannes Sixt reported.\n\ngit clean -n /\n\nThe other case that could trigger this bug and potentially others is if\nsomeone makes their root dircetory a git repository and then uses \"/\" as\nan absolute path.  For example:\n\ncd /\ngit init\ngit clean -n /\n\nYou may argue that both of these cases are invalid and that is fine by\nme, but since I noticed this bug I thought I would send a patch.  If you\nwouldlike I can add these two use cases to the commit message.\n\n--\nShawn\n"},{"id":"66693","messageId":"7v3asiyk2i.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801272043040.23907@racer.site","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-27T22:34:13Z","receivedAt":"2008-01-27T22:34:13Z","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>> but doesn't address the fact that we probably should remove files that \n>> aren't a part of the repository at in the first place.\n>\n> I am sorry, but I cannot begin to see what this commit tries to \n> accomplish.  Yes, sure, there is an off-by-one error, and your commit \n> message says how that was fixed.  But I miss a description what usage it \n> would affect, i.e. when this bug triggers.\n>\n> I imagine that you would be as lost as me, reading that commit message 6 \n> months from now, trying to understand why that change was made.\n\nLikewise.  The message has somewhat to be desired...\n\nIn \"struct exclude_stack\", prep_exclude() and excluded(), the\nconvention for a path is to express the length of directory part\nincluding the trailing slash (e.g. \"foo\" and \"bar/baz\" will get\nbaselen=0 and baselen=4 respectively).\n\nThe variable current and parameter baselen follow that\nconvention in the codepath the patch touches.\n\n\t\telse {\n\t\t\tcp = strchr(base + current + 1, '/');\n\t\t\tif (!cp)\n\t\t\t\tdie(\"oops in prep_exclude\");\n\t\t\tcp++;\n\t\t}\n\t\tstk->prev = dir->exclude_stack;\n\t\tstk->baselen = cp - base;\n\nis about coming up with the next value for current (which is\ntaken from stk->baselen) to dig one more level.\n\nIf base=\"foo/a/boo\" and current=4 (i.e. we are looking at\n\"foo/\"), at the point, scanning from (base+current) as Shawn\nBohrer's patch suggests means the scan begins at \"a/boo\" to find\nthe next slash.  The existing code skips one letter ('a') and\nstarts scanning from \"/boo\".\n\nThe only case this microoptimization makes difference is when an\ninput is malformed and has double-slash (i.e. path component\nwhose length is zero), like \"foo//boo\".\n\nPerhaps the \"oops part of the issue Johannes found\" had a caller\nthat feeds such an incorrect input?\n"},{"id":"66698","messageId":"20080128003404.GA18276@lintop","threadId":"11711","inReplyTo":"7v3asiyk2i.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Shawn Bohrer","fromEmail":"shawn.bohrer@gmail.com","sentAt":"2008-01-28T00:34:04Z","receivedAt":"2008-01-28T00:34:04Z","isPatch":true,"sender":{"key":"shawn.bohrer@gmail.com","avatar":"https://gravatar.com/avatar/6eb093ef7d276306d18366254e0c95ff6a5db58231ac7e82fe78c2800aaae1b6?d=mp&s=160"},"body":"On Sun, Jan 27, 2008 at 02:34:13PM -0800, Junio C Hamano wrote:\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> but doesn't address the fact that we probably should remove files that \n> >> aren't a part of the repository at in the first place.\n> >\n> > I am sorry, but I cannot begin to see what this commit tries to \n> > accomplish.  Yes, sure, there is an off-by-one error, and your commit \n> > message says how that was fixed.  But I miss a description what usage it \n> > would affect, i.e. when this bug triggers.\n> >\n> > I imagine that you would be as lost as me, reading that commit message 6 \n> > months from now, trying to understand why that change was made.\n> \n> Likewise.  The message has somewhat to be desired...\n\nAgreed I'll resend with a improved message.\n \n> In \"struct exclude_stack\", prep_exclude() and excluded(), the\n> convention for a path is to express the length of directory part\n> including the trailing slash (e.g. \"foo\" and \"bar/baz\" will get\n> baselen=0 and baselen=4 respectively).\n> \n> The variable current and parameter baselen follow that\n> convention in the codepath the patch touches.\n> \n> \t\telse {\n> \t\t\tcp = strchr(base + current + 1, '/');\n> \t\t\tif (!cp)\n> \t\t\t\tdie(\"oops in prep_exclude\");\n> \t\t\tcp++;\n> \t\t}\n> \t\tstk->prev = dir->exclude_stack;\n> \t\tstk->baselen = cp - base;\n> \n> is about coming up with the next value for current (which is\n> taken from stk->baselen) to dig one more level.\n> \n> If base=\"foo/a/boo\" and current=4 (i.e. we are looking at\n> \"foo/\"), at the point, scanning from (base+current) as Shawn\n> Bohrer's patch suggests means the scan begins at \"a/boo\" to find\n> the next slash.  The existing code skips one letter ('a') and\n> starts scanning from \"/boo\".\n> \n> The only case this microoptimization makes difference is when an\n> input is malformed and has double-slash (i.e. path component\n> whose length is zero), like \"foo//boo\".\n\nGood catch, I didn't think of this case but this indeed will cause\nthe same issue.\n \n> Perhaps the \"oops part of the issue Johannes found\" had a caller\n> that feeds such an incorrect input?\n\nNope the problem Johannes Sixt was having was that he mistakenly ran\n\ngit clean -n /*foo\n\nNow that isn't what he meant to do, but I figured it might be possible\nthat someone has their whole filesystem in a git repository, or maybe\nis using some sort of chroot on their repository.  Your malformed\npaths guess is probably much more likely to occur.\n\n--\nShawn\n"},{"id":"66699","messageId":"1201480650-19716-1-git-send-email-shawn.bohrer@gmail.com","threadId":"11711","inReplyTo":"20080128003404.GA18276@lintop","subject":"[PATCH] Fix off by one error in prep_exclude.","fromName":"Shawn Bohrer","fromEmail":"shawn.bohrer@gmail.com","sentAt":"2008-01-28T00:37:30Z","receivedAt":"2008-01-28T00:37:30Z","isPatch":true,"sender":{"key":"shawn.bohrer@gmail.com","avatar":"https://gravatar.com/avatar/6eb093ef7d276306d18366254e0c95ff6a5db58231ac7e82fe78c2800aaae1b6?d=mp&s=160"},"body":"In \"struct exclude_stack\", prep_exclude() and excluded(), the\nconvention for a path is to express the length of directory part\nincluding the trailing slash (e.g. \"foo\" and \"bar/baz\" will get\nbaselen=0 and baselen=4 respectively).\n\nThe variable current and parameter baselen follow that\nconvention in the codepath the following patch touches.\n\n                else {\n                        cp = strchr(base + current + 1, '/');\n                        if (!cp)\n                                die(\"oops in prep_exclude\");\n                        cp++;\n                }\n                stk->prev = dir->exclude_stack;\n                stk->baselen = cp - base;\n\nis about coming up with the next value for current (which is\ntaken from stk->baselen) to dig one more level.\n\nIf base=\"foo/bar/boo\" and current=4 (i.e. we are looking at\n\"foo/\"), the current code (base + current + 1) will begin scanning\nfor the next slash at ar/boo skipping one letter ('b').  This\npatch starts the scanning at bar/boo/\n\nThis only causes a problem when a path component has a length of\nzero which can happen when the user provides an absolute path to\na file or directory in the root directory (i.e. \"/\", or \"/foo\"),\nor if the input is malformed and contains a double-slash such\nas \"foo//boo\".\n\nSigned-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n---\n dir.c |    2 +-\n 1 files changed, 1 insertions(+), 1 deletions(-)\n\ndiff --git a/dir.c b/dir.c\nindex 3e345c2..9e5879a 100644\n--- a/dir.c\n+++ b/dir.c\n@@ -237,7 +237,7 @@ static void prep_exclude(struct dir_struct *dir, const char *base, int baselen)\n \t\t\tcurrent = 0;\n \t\t}\n \t\telse {\n-\t\t\tcp = strchr(base + current + 1, '/');\n+\t\t\tcp = strchr(base + current, '/');\n \t\t\tif (!cp)\n \t\t\t\tdie(\"oops in prep_exclude\");\n \t\t\tcp++;\n-- \n1.5.4.rc5.1.g813e\n"},{"id":"66704","messageId":"7vodb6wtix.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"20080128003404.GA18276@lintop","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T02:52:54Z","receivedAt":"2008-01-28T02:52:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Shawn Bohrer <shawn.bohrer@gmail.com> writes:\n\n> Nope the problem Johannes Sixt was having was that he mistakenly ran\n>\n> git clean -n /*foo\n>\n> Now that isn't what he meant to do, but I figured it might be possible\n> that someone has their whole filesystem in a git repository, or maybe\n> is using some sort of chroot on their repository.  Your malformed\n> paths guess is probably much more likely to occur.\n\nThat is not a user error from the syntax point of view (although\nit might be from the semantics point of view).  I think the\ncaller of the excluded() function (that is probably somewhere in\nbuiltin-clean.c -- I did not check) is responsible for not\nsupplying such a path to the called function.\n"},{"id":"66712","messageId":"479D805E.3000209@viscovery.net","threadId":"11711","inReplyTo":"7vodb6wtix.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-28T07:12:30Z","receivedAt":"2008-01-28T07:12:30Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> Shawn Bohrer <shawn.bohrer@gmail.com> writes:\n> \n>> Nope the problem Johannes Sixt was having was that he mistakenly ran\n>>\n>> git clean -n /*foo\n>>\n>> Now that isn't what he meant to do, but I figured it might be possible\n>> that someone has their whole filesystem in a git repository, or maybe\n>> is using some sort of chroot on their repository.  Your malformed\n>> paths guess is probably much more likely to occur.\n> \n> That is not a user error from the syntax point of view (although\n> it might be from the semantics point of view).  I think the\n> caller of the excluded() function (that is probably somewhere in\n> builtin-clean.c -- I did not check) is responsible for not\n> supplying such a path to the called function.\n\nThe \"problem\" is not only with git-clean, but also in others, like\ngit-ls-files. Try this in you favorite repository:\n\n   $ git ls-files -o /*bin\n\nThe output does not make a lot of sense. (Here it lists the contents of\n/bin and /sbin.) Not that it hurts with ls-files, but\n\n   $ git clean -f /\n\nis basically a synonym for\n\n   $ rm -rf /\n\n-- Hannes\n"},{"id":"66721","messageId":"7vprvmuykw.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"479D805E.3000209@viscovery.net","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T08:46:39Z","receivedAt":"2008-01-28T08:46:39Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n> The \"problem\" is not only with git-clean, but also in others, like\n> git-ls-files. Try this in you favorite repository:\n>\n>    $ git ls-files -o /*bin\n>\n> The output does not make a lot of sense. (Here it lists the contents of\n> /bin and /sbin.) Not that it hurts with ls-files, but\n>\n>    $ git clean -f /\n>\n> is basically a synonym for\n>\n>    $ rm -rf /\n\nYeah, /*bin is not inside the repository so it should not even\nbe reported as \"others\".  Shouldn't the commands detect this and\nreject feeding such paths outside the work tree to the core,\nwhich always expect you to talk about paths inside?\n"},{"id":"66724","messageId":"479D9ADE.6010003@viscovery.net","threadId":"11711","inReplyTo":"7vprvmuykw.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-28T09:05:34Z","receivedAt":"2008-01-28T09:05:34Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> Johannes Sixt <j.sixt@viscovery.net> writes:\n> \n>> The \"problem\" is not only with git-clean, but also in others, like\n>> git-ls-files. Try this in you favorite repository:\n>>\n>>    $ git ls-files -o /*bin\n>>\n>> The output does not make a lot of sense. (Here it lists the contents of\n>> /bin and /sbin.) Not that it hurts with ls-files, but\n>>\n>>    $ git clean -f /\n>>\n>> is basically a synonym for\n>>\n>>    $ rm -rf /\n> \n> Yeah, /*bin is not inside the repository so it should not even\n> be reported as \"others\".  Shouldn't the commands detect this and\n> reject feeding such paths outside the work tree to the core,\n> which always expect you to talk about paths inside?\n\nThat's what I had expected. But look:\n\n   $ git ls-files -o /\n   [... tons of file names ...]\n\n   $ git ls-files -o ..\n   fatal: '..' is outside repository\n\n   $ git clean -n /    # with Shawn's patch\n   Would remove /bin/\n   [... etc ...]\n\n   $ git clean -n ..\n   fatal: '..' is outside repository\n\nSome mechanism for this is already there; it's just not complete enough.\n\n-- Hannes\n"},{"id":"66725","messageId":"7vlk6auwxs.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"479D9ADE.6010003@viscovery.net","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T09:22:07Z","receivedAt":"2008-01-28T09:22:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n> Junio C Hamano schrieb:\n> ...\n>> Yeah, /*bin is not inside the repository so it should not even\n>> be reported as \"others\".  Shouldn't the commands detect this and\n>> reject feeding such paths outside the work tree to the core,\n>> which always expect you to talk about paths inside?\n>\n> That's what I had expected. But look:\n> ...\n> Some mechanism for this is already there; it's just not complete enough.\n\nExactly. That was what I've been getting at from the beginning\nof this thread --- fix for that incompleteness is what's needed.\n"},{"id":"66738","messageId":"alpine.LSU.1.00.0801281159150.23907@racer.site","threadId":"11711","inReplyTo":"1201480650-19716-1-git-send-email-shawn.bohrer@gmail.com","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-28T11:59:50Z","receivedAt":"2008-01-28T11:59:50Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Sun, 27 Jan 2008, Shawn Bohrer wrote:\n\n> In \"struct exclude_stack\", prep_exclude() and excluded(), the\n> convention for a path is to express the length of directory part\n> including the trailing slash (e.g. \"foo\" and \"bar/baz\" will get\n> baselen=0 and baselen=4 respectively).\n> \n> The variable current and parameter baselen follow that\n> convention in the codepath the following patch touches.\n> \n>                 else {\n>                         cp = strchr(base + current + 1, '/');\n>                         if (!cp)\n>                                 die(\"oops in prep_exclude\");\n>                         cp++;\n>                 }\n>                 stk->prev = dir->exclude_stack;\n>                 stk->baselen = cp - base;\n> \n> is about coming up with the next value for current (which is\n> taken from stk->baselen) to dig one more level.\n> \n> If base=\"foo/bar/boo\" and current=4 (i.e. we are looking at\n> \"foo/\"), the current code (base + current + 1) will begin scanning\n> for the next slash at ar/boo skipping one letter ('b').  This\n> patch starts the scanning at bar/boo/\n> \n> This only causes a problem when a path component has a length of\n> zero which can happen when the user provides an absolute path to\n> a file or directory in the root directory (i.e. \"/\", or \"/foo\"),\n> or if the input is malformed and contains a double-slash such\n> as \"foo//boo\".\n> \n> Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n\nI'll try to remember even 6 months from now that this was the \"git clean \n-n /\" problem ;-)\n\nCiao,\nDscho\n"},{"id":"66739","messageId":"7vd4rmtavb.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801281159150.23907@racer.site","subject":"Re: [PATCH] Fix off by one error in prep_exclude.","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-28T12:04:08Z","receivedAt":"2008-01-28T12:04:08Z","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>> ...\n>> This only causes a problem when a path component has a length of\n>> zero which can happen when the user provides an absolute path to\n>> a file or directory in the root directory (i.e. \"/\", or \"/foo\"),\n>> or if the input is malformed and contains a double-slash such\n>> as \"foo//boo\".\n>> \n>> Signed-off-by: Shawn Bohrer <shawn.bohrer@gmail.com>\n>\n> I'll try to remember even 6 months from now that this was the \"git clean \n> -n /\" problem ;-)\n\nActually the quoted part of the message clearly tells that the\npatch is touching the wrong code.  It should not blame the user\nbut the caller of the function that did not check such an input,\nwhich is where the fix should be in.\n"},{"id":"66741","messageId":"alpine.LSU.1.00.0801281210440.23907@racer.site","threadId":"11711","inReplyTo":"479D9ADE.6010003@viscovery.net","subject":"[RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-28T12:33:48Z","receivedAt":"2008-01-28T12:33:48Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWithout this fix, \"git ls-files --others /\" would list _all_ files,\nexcept for those tracked in the current repository.  Worse, \"git clean /\"\nwould start removing them.\n\nNoticed by Johannes Sixt.\n\nIncidentally, it fixes some strange code in builtin-mv.c by yours truly,\nwhere a slash was added to \"dst\" but then ignored, and instead taken from\nthe source path.  This triggered the new check for absolute paths.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\tOn Mon, 28 Jan 2008, Johannes Sixt wrote:\n\n\t> Junio C Hamano schrieb:\n\t> > Johannes Sixt <j.sixt@viscovery.net> writes:\n\t> > \n\t> >> The \"problem\" is not only with git-clean, but also in others, \n\t> >> like git-ls-files. Try this in you favorite repository:\n\t> >>\n\t> >>    $ git ls-files -o /*bin\n\t> >>\n\t> >> The output does not make a lot of sense. (Here it lists the \n\t> >> contents of /bin and /sbin.) Not that it hurts with ls-files, \n\t> >> but\n\t> >>\n\t> >>    $ git clean -f /\n\t> >>\n\t> >> is basically a synonym for\n\t> >>\n\t> >>    $ rm -rf /\n\t> > \n\t> > Yeah, /*bin is not inside the repository so it should not even \n\t> > be reported as \"others\".  Shouldn't the commands detect this \n\t> > and reject feeding such paths outside the work tree to the \n\t> > core, which always expect you to talk about paths inside?\n\t> \n\t> That's what I had expected. But look:\n\t> \n\t>    $ git ls-files -o /\n\t>    [... tons of file names ...]\n\t> \n\t>    $ git ls-files -o ..\n\t>    fatal: '..' is outside repository\n\t> \n\t>    $ git clean -n /    # with Shawn's patch\n\t>    Would remove /bin/\n\t>    [... etc ...]\n\t> \n\t>    $ git clean -n ..\n\t>    fatal: '..' is outside repository\n\t> \n\t> Some mechanism for this is already there; it's just not complete \n\t> enough.\n\n\tThis patch cannot be applied as-is: t3101 is failing (t7001 is \n\tfixed by the builtin-mv.c part).\n\n\tThe failure of t3101 has something to do with ls-tree filtering \n\tout invalid paths; I maintain that this behaviour is wrong to \n\tbegin with.\n\n\tSo the help I am requesting is this: so late in the game for 1.5.4 \n\tI would hate to introduce a change in prefix_path(), because it \n\taffects apparently too much.  However, the \"git clean /\" bug is a \n\treal one, and should at least be prevented.  What to do?\n\n builtin-mv.c |    4 ++--\n setup.c      |    2 ++\n 2 files changed, 4 insertions(+), 2 deletions(-)\n\ndiff --git a/builtin-mv.c b/builtin-mv.c\nindex 990e213..94f6dd2 100644\n--- a/builtin-mv.c\n+++ b/builtin-mv.c\n@@ -164,7 +164,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \n \t\t\t\tdst = add_slash(dst);\n-\t\t\t\tdst_len = strlen(dst) - 1;\n+\t\t\t\tdst_len = strlen(dst);\n \n \t\t\t\tfor (j = 0; j < last - first; j++) {\n \t\t\t\t\tconst char *path =\n@@ -172,7 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tsource[argc + j] = path;\n \t\t\t\t\tdestination[argc + j] =\n \t\t\t\t\t\tprefix_path(dst, dst_len,\n-\t\t\t\t\t\t\tpath + length);\n+\t\t\t\t\t\t\tpath + length + 1);\n \t\t\t\t\tmodes[argc + j] = INDEX;\n \t\t\t\t}\n \t\t\t\targc += last - first;\ndiff --git a/setup.c b/setup.c\nindex 2174e78..5a4aadc 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -13,6 +13,8 @@ const char *get_current_prefix()\n const char *prefix_path(const char *prefix, int len, const char *path)\n {\n \tconst char *orig = path;\n+\tif (is_absolute_path(path))\n+\t\tdie(\"no absolute paths allowed: '%s'\", path);\n \tfor (;;) {\n \t\tchar c;\n \t\tif (*path != '.')\n-- \n1.5.4.rc5.15.g8231f\n"},{"id":"66750","messageId":"alpine.LSU.1.00.0801281503350.23907@racer.site","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801281210440.23907@racer.site","subject":"[PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-28T15:05:42Z","receivedAt":"2008-01-28T15:05:42Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"\nWithout this fix, \"git ls-files --others /\" would list _all_ files,\nexcept for those tracked in the current repository.  Worse, \"git clean /\"\nwould start removing them.\n\nNoticed by Johannes Sixt.\n\nIncidentally, it fixes some strange code in builtin-mv.c by yours truly,\nwhere a slash was added to \"dst\" but then ignored, and instead taken from\nthe source path.  This triggered the new check for absolute paths.\n\nA test in t3101 started failing, too, because it tested ls-tree with\nnot-really-absolute paths (expecting the leading \"/\" to be ignored).\nThose paths were changed to relative paths.\n\nSigned-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>\n---\n\n\tOn Mon, 28 Jan 2008, Johannes Schindelin wrote:\n\n\t> The failure of t3101 has something to do with ls-tree filtering \n\t> out invalid paths; I maintain that this behaviour is wrong to \n\t> begin with.\n\n\tThis patch fixes the test.\n\n\tBut as this fix illustrates, it is a change in semantics: where \n\tearlier\n\n\t\tgit ls-tree /README\n\n\twas allowed, it is no longer.\n\n\tComments?\n\n builtin-mv.c               |    4 ++--\n setup.c                    |    2 ++\n t/t3101-ls-tree-dirname.sh |    2 +-\n 3 files changed, 5 insertions(+), 3 deletions(-)\n\ndiff --git a/builtin-mv.c b/builtin-mv.c\nindex 990e213..94f6dd2 100644\n--- a/builtin-mv.c\n+++ b/builtin-mv.c\n@@ -164,7 +164,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \n \t\t\t\tdst = add_slash(dst);\n-\t\t\t\tdst_len = strlen(dst) - 1;\n+\t\t\t\tdst_len = strlen(dst);\n \n \t\t\t\tfor (j = 0; j < last - first; j++) {\n \t\t\t\t\tconst char *path =\n@@ -172,7 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tsource[argc + j] = path;\n \t\t\t\t\tdestination[argc + j] =\n \t\t\t\t\t\tprefix_path(dst, dst_len,\n-\t\t\t\t\t\t\tpath + length);\n+\t\t\t\t\t\t\tpath + length + 1);\n \t\t\t\t\tmodes[argc + j] = INDEX;\n \t\t\t\t}\n \t\t\t\targc += last - first;\ndiff --git a/setup.c b/setup.c\nindex 2174e78..5a4aadc 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -13,6 +13,8 @@ const char *get_current_prefix()\n const char *prefix_path(const char *prefix, int len, const char *path)\n {\n \tconst char *orig = path;\n+\tif (is_absolute_path(path))\n+\t\tdie(\"no absolute paths allowed: '%s'\", path);\n \tfor (;;) {\n \t\tchar c;\n \t\tif (*path != '.')\ndiff --git a/t/t3101-ls-tree-dirname.sh b/t/t3101-ls-tree-dirname.sh\nindex 39fe267..cc5b982 100755\n--- a/t/t3101-ls-tree-dirname.sh\n+++ b/t/t3101-ls-tree-dirname.sh\n@@ -120,7 +120,7 @@ EOF\n # having 1.txt and path3\n test_expect_success \\\n     'ls-tree filter odd names' \\\n-    'git ls-tree $tree 1.txt /1.txt //1.txt path3/1.txt /path3/1.txt //path3//1.txt path3 /path3/ path3// >current &&\n+    'git ls-tree $tree 1.txt ./1.txt .//1.txt path3/1.txt ./path3/1.txt .//path3//1.txt path3 ./path3/ path3// >current &&\n      cat >expected <<\\EOF &&\n 100644 blob X\t1.txt\n 100644 blob X\tpath3/1.txt\n-- \n1.5.4.rc5.15.g8231f\n"},{"id":"66768","messageId":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801281210440.23907@racer.site","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T01:23:12Z","receivedAt":"2008-01-29T01:23:12Z","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> Without this fix, \"git ls-files --others /\" would list _all_ files,\n> except for those tracked in the current repository.  Worse, \"git clean /\"\n> would start removing them.\n> ...\n> \tThis patch cannot be applied as-is: t3101 is failing (t7001 is \n> \tfixed by the builtin-mv.c part).\n>\n> \tThe failure of t3101 has something to do with ls-tree filtering \n> \tout invalid paths; I maintain that this behaviour is wrong to \n> \tbegin with.\n>\n> \tSo the help I am requesting is this: so late in the game for 1.5.4 \n> \tI would hate to introduce a change in prefix_path(), because it \n> \taffects apparently too much.  However, the \"git clean /\" bug is a \n> \treal one, and should at least be prevented.  What to do?\n> ...\n> diff --git a/setup.c b/setup.c\n> index 2174e78..5a4aadc 100644\n> --- a/setup.c\n> +++ b/setup.c\n> @@ -13,6 +13,8 @@ const char *get_current_prefix()\n>  const char *prefix_path(const char *prefix, int len, const char *path)\n>  {\n>  \tconst char *orig = path;\n> +\tif (is_absolute_path(path))\n> +\t\tdie(\"no absolute paths allowed: '%s'\", path);\n>  \tfor (;;) {\n>  \t\tchar c;\n>  \t\tif (*path != '.')\n\nIf we are touching the prefix_path(), I think we should try to\nmake its \"ambiguous path rejection\" more complete.\n\nCurrently, we:\n\n - Remove \".\" path component (i.e. the directory leading part\n   specified) from the input;\n\n - Remove \"..\" path component and strip one level of the prefix;\n\nonly from the beginning.  So if you give nonsense pathspec from\nthe command line, you can end up calling prefix_path() with things\nlike \"/README\", \"/absolute/path/to//repository/tracked/file\", and\n\"fo//o/../o\".\n\nAnd not passing such ambiguous path like \"fo//o\" to the core\nlevel but sanitizing matters.  Then core level can always do\nmemcmp() with \"fo/o\" to see they are talking about the same\npath.\n\nI suspect that the right approach might be something like the\nattached patch.  It introduces a version of prefix_path() that\nsanitizes path (but not prefix part, which comes from git itself\nand hopefully there should not be a need to sanitize it) while\ndoing the prefixing.  It also strips the leading absolute path\nto the repository by comparing it with the value of work_tree.\n\nA few things to note.\n\n * Your mv fix is rolled in.\n\n * This allows you to name a in-repository file as `pwd`/file,\n   or `pwd`//file (iow, double-slash is also sanitized).  It may\n   kill the bird in another thread nearby.\n\n * get_pathspec() drops paths outside of repository, so the\n   caller may end up getting a smaller number of paths than it\n   originally gave it.  If an existing caller expects the same\n   number of paths to come back, it needs to be adjusted (I did\n   not check).  We could alternatively die() but I couldn't\n   decide which one is a better behaviour.\n\nThis is not to be applied (especially before auditing the\ncallers), but to be thought about.  Although it passes all the\ntests...\n\n\n\n builtin-mv.c |    4 +-\n setup.c      |  152 ++++++++++++++++++++++++++++++++++++++++++----------------\n 2 files changed, 112 insertions(+), 44 deletions(-)\n\ndiff --git a/builtin-mv.c b/builtin-mv.c\nindex 990e213..94f6dd2 100644\n--- a/builtin-mv.c\n+++ b/builtin-mv.c\n@@ -164,7 +164,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \n \t\t\t\tdst = add_slash(dst);\n-\t\t\t\tdst_len = strlen(dst) - 1;\n+\t\t\t\tdst_len = strlen(dst);\n \n \t\t\t\tfor (j = 0; j < last - first; j++) {\n \t\t\t\t\tconst char *path =\n@@ -172,7 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tsource[argc + j] = path;\n \t\t\t\t\tdestination[argc + j] =\n \t\t\t\t\t\tprefix_path(dst, dst_len,\n-\t\t\t\t\t\t\tpath + length);\n+\t\t\t\t\t\t\tpath + length + 1);\n \t\t\t\t\tmodes[argc + j] = INDEX;\n \t\t\t\t}\n \t\t\t\targc += last - first;\ndiff --git a/setup.c b/setup.c\nindex adede16..fdc6459 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -4,51 +4,114 @@\n static int inside_git_dir = -1;\n static int inside_work_tree = -1;\n \n-const char *prefix_path(const char *prefix, int len, const char *path)\n+static int sanitary_path_copy(char *dst, const char *src)\n {\n-\tconst char *orig = path;\n+\tchar *dst0 = dst;\n+\n+\tif (*src == '/') {\n+\t\t*dst++ = '/';\n+\t\twhile (*src == '/')\n+\t\t\tsrc++;\n+\t}\n+\n \tfor (;;) {\n-\t\tchar c;\n-\t\tif (*path != '.')\n-\t\t\tbreak;\n-\t\tc = path[1];\n-\t\t/* \".\" */\n-\t\tif (!c) {\n-\t\t\tpath++;\n-\t\t\tbreak;\n+\t\tchar c = *src;\n+\n+\t\t/*\n+\t\t * A path component that begins with . could be\n+\t\t * special:\n+\t\t * (1) \".\" and ends   -- ignore and terminate.\n+\t\t * (2) \"./\"           -- ignore them, eat slash and continue.\n+\t\t * (3) \"..\" and ends  -- strip one and terminate.\n+\t\t * (4) \"../\"          -- strip one, eat slash and continue.\n+\t\t */\n+\t\tif (c == '.') {\n+\t\t\tswitch (src[1]) {\n+\t\t\tcase '\\0':\n+\t\t\t\t/* (1) */\n+\t\t\t\tsrc++;\n+\t\t\t\tbreak;\n+\t\t\tcase '/':\n+\t\t\t\t/* (2) */\n+\t\t\t\tsrc += 2;\n+\t\t\t\twhile (*src == '/')\n+\t\t\t\t\tsrc++;\n+\t\t\t\tcontinue;\n+\t\t\tcase '.':\n+\t\t\t\tswitch (src[2]) {\n+\t\t\t\tcase '\\0':\n+\t\t\t\t\t/* (3) */\n+\t\t\t\t\tsrc += 2;\n+\t\t\t\t\tgoto up_one;\n+\t\t\t\tcase '/':\n+\t\t\t\t\t/* (4) */\n+\t\t\t\t\tsrc += 3;\n+\t\t\t\t\twhile (*src == '/')\n+\t\t\t\t\t\tsrc++;\n+\t\t\t\t\tgoto up_one;\n+\t\t\t\t}\n+\t\t\t}\n \t\t}\n-\t\t/* \"./\" */\n+\n+\t\t/* copy up to the next '/', and eat all '/' */\n+\t\twhile ((c = *src++) != '\\0' && c != '/')\n+\t\t\t*dst++ = c;\n \t\tif (c == '/') {\n-\t\t\tpath += 2;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (c != '.')\n+\t\t\t*dst++ = c;\n+\t\t\twhile (c == '/')\n+\t\t\t\tc = *src++;\n+\t\t\tsrc--;\n+\t\t} else if (!c)\n \t\t\tbreak;\n-\t\tc = path[2];\n-\t\tif (!c)\n-\t\t\tpath += 2;\n-\t\telse if (c == '/')\n-\t\t\tpath += 3;\n-\t\telse\n-\t\t\tbreak;\n-\t\t/* \"..\" and \"../\" */\n-\t\t/* Remove last component of the prefix */\n-\t\tdo {\n-\t\t\tif (!len)\n-\t\t\t\tdie(\"'%s' is outside repository\", orig);\n-\t\t\tlen--;\n-\t\t} while (len && prefix[len-1] != '/');\n \t\tcontinue;\n+\n+\tup_one:\n+\t\t/*\n+\t\t * dst0..dst is prefix portion, and dst[-1] is '/';\n+\t\t * go up one level.\n+\t\t */\n+\t\tdst -= 2; /* go past trailing '/' if any */\n+\t\tif (dst < dst0)\n+\t\t\treturn -1;\n+\t\twhile (1) {\n+\t\t\tif (dst <= dst0)\n+\t\t\t\tbreak;\n+\t\t\tc = *dst--;\n+\t\t\tif (c == '/') {\n+\t\t\t\tdst += 2;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n \t}\n-\tif (len) {\n-\t\tint speclen = strlen(path);\n-\t\tchar *n = xmalloc(speclen + len + 1);\n+\t*dst = '\\0';\n+\treturn 0;\n+}\n \n-\t\tmemcpy(n, prefix, len);\n-\t\tmemcpy(n + len, path, speclen+1);\n-\t\tpath = n;\n+const char *prefix_path(const char *prefix, int len, const char *path)\n+{\n+\tconst char *orig = path;\n+\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n+\tif (*orig == '/')\n+\t\tstrcpy(sanitized, path);\n+\telse {\n+\t\tif (len)\n+\t\t\tmemcpy(sanitized, prefix, len);\n+\t\tstrcpy(sanitized + len, path);\t\t\n \t}\n-\treturn path;\n+\tif (sanitary_path_copy(sanitized, sanitized))\n+\t\tgoto error_out;\n+\tif (*orig == '/') {\n+\t\tconst char *work_tree = get_git_work_tree();\n+\t\tsize_t len = strlen(work_tree);\n+\t\tif (strncmp(sanitized, work_tree, len) ||\n+\t\t    (sanitized[len] != '\\0' && sanitized[len] != '/')) {\n+\t\terror_out:\n+\t\t\terror(\"'%s' is outside repository\", orig);\n+\t\t\tfree(sanitized);\n+\t\t\treturn NULL;\n+\t\t}\n+\t}\n+\treturn sanitized;\n }\n \n /*\n@@ -114,7 +177,7 @@ void verify_non_filename(const char *prefix, const char *arg)\n const char **get_pathspec(const char *prefix, const char **pathspec)\n {\n \tconst char *entry = *pathspec;\n-\tconst char **p;\n+\tconst char **src, **dst;\n \tint prefixlen;\n \n \tif (!prefix && !entry)\n@@ -128,12 +191,17 @@ const char **get_pathspec(const char *prefix, const char **pathspec)\n \t}\n \n \t/* Otherwise we have to re-write the entries.. */\n-\tp = pathspec;\n+\tsrc = pathspec;\n+\tdst = pathspec;\n \tprefixlen = prefix ? strlen(prefix) : 0;\n-\tdo {\n-\t\t*p = prefix_path(prefix, prefixlen, entry);\n-\t} while ((entry = *++p) != NULL);\n-\treturn (const char **) pathspec;\n+\twhile (*src) {\n+\t\tconst char *p = prefix_path(prefix, prefixlen, *src);\n+\t\tif (p)\n+\t\t\t*(dst++) = p;\n+\t\tsrc++;\n+\t}\n+\t*dst = NULL;\n+\treturn pathspec;\n }\n \n /*\n"},{"id":"66772","messageId":"7vd4rls817.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T02:03:00Z","receivedAt":"2008-01-29T02:03:00Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"This is on top of the original \"sanitary_path_copy()\" patch to\nfix the case where the user gives `pwd`/foobar (or just `pwd`)\nto us.  After making sure the leading part matches with the work\ntree, we need to strip that to make the result relative to the\nwork tree.\n\n setup.c |    5 +++++\n 1 files changed, 5 insertions(+), 0 deletions(-)\n\ndiff --git a/setup.c b/setup.c\nindex 156d417..0688e7b 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -110,6 +110,9 @@ const char *prefix_path(const char *prefix, int len, const char *path)\n \t\t\tfree(sanitized);\n \t\t\treturn NULL;\n \t\t}\n+\t\tsanitized += len;\n+\t\tif (*sanitized == '/')\n+\t\t\tsanitized++;\n \t}\n \treturn sanitized;\n }\n@@ -201,6 +204,8 @@ const char **get_pathspec(const char *prefix, const char **pathspec)\n \t\tsrc++;\n \t}\n \t*dst = NULL;\n+\tif (!*pathspec)\n+\t\treturn NULL;\n \treturn pathspec;\n }\n \n"},{"id":"66773","messageId":"7vbq75s80t.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T02:03:14Z","receivedAt":"2008-01-29T02:03:14Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I suspect that the right approach might be something like the\n> attached patch.  It introduces a version of prefix_path() that\n> sanitizes path (but not prefix part, which comes from git itself\n> and hopefully there should not be a need to sanitize it) while\n> doing the prefixing.  It also strips the leading absolute path\n> to the repository by comparing it with the value of work_tree.\n>\n> A few things to note.\n>\n>  * Your mv fix is rolled in.\n>\n>  * This allows you to name a in-repository file as `pwd`/file,\n>    or `pwd`//file (iow, double-slash is also sanitized).  It may\n>    kill the bird in another thread nearby.\n>\n>  * get_pathspec() drops paths outside of repository, so the\n>    caller may end up getting a smaller number of paths than it\n>    originally gave it.  If an existing caller expects the same\n>    number of paths to come back, it needs to be adjusted (I did\n>    not check).  We could alternatively die() but I couldn't\n>    decide which one is a better behaviour.\n\nThis is the kind of \"fixing existing callers\" that would be\nneeded if we take this approach.\n\n builtin-ls-files.c |   11 ++++++++++-\n 1 files changed, 10 insertions(+), 1 deletions(-)\n\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex 0f0ab2d..3801cf4 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -572,8 +572,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)\n \tpathspec = get_pathspec(prefix, argv + i);\n \n \t/* Verify that the pathspec matches the prefix */\n-\tif (pathspec)\n+\tif (pathspec) {\n+\t\tif (argc != i) {\n+\t\t\tint cnt;\n+\t\t\tfor (cnt = 0; pathspec[cnt]; cnt++)\n+\t\t\t\t;\n+\t\t\tif (cnt != (argc - i))\n+\t\t\t\texit(1); /* error message already given */\n+\t\t}\n \t\tprefix = verify_pathspec(prefix);\n+\t} else if (argc != i)\n+\t\texit(1); /* error message already given */\n \n \t/* Treat unmatching pathspec elements as errors */\n \tif (pathspec && error_unmatch) {\n"},{"id":"66777","messageId":"alpine.LSU.1.00.0801290234590.23907@racer.site","threadId":"11711","inReplyTo":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-29T02:37:59Z","receivedAt":"2008-01-29T02:37:59Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 28 Jan 2008, Junio C Hamano wrote:\n\n> If we are touching the prefix_path(), I think we should try to make its \n> \"ambiguous path rejection\" more complete.\n\nI should have made more clear that I tried to avoid exactly that before \n1.5.4, I guess.\n\n> This is not to be applied (especially before auditing the callers), but \n> to be thought about.  Although it passes all the tests...\n\nIt certainly is tempting.\n\n\n> +\t\t\twhile (c == '/')\n> +\t\t\t\tc = *src++;\n> +\t\t\tsrc--;\n\nThis is ugly.  I would like this better:\n\n\t\t\twhile (src[1] == '/')\n\t\t\t\tsrc++;\n\n> +const char *prefix_path(const char *prefix, int len, const char *path)\n> +{\n> +\tconst char *orig = path;\n> +\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n\nThere _has_ to be a way to avoid malloc()ing things that will _never_ be \nfree()d again with every second patch ;-)\n\nCiao,\nDscho\n"},{"id":"66779","messageId":"7v7ihts61v.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"alpine.LSU.1.00.0801290234590.23907@racer.site","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T02:45:48Z","receivedAt":"2008-01-29T02:45:48Z","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>> This is not to be applied (especially before auditing the callers), but \n>> to be thought about.  Although it passes all the tests...\n>\n> It certainly is tempting.\n>\n>\n>> +\t\t\twhile (c == '/')\n>> +\t\t\t\tc = *src++;\n>> +\t\t\tsrc--;\n>\n> This is ugly.  I would like this better:\n>\n> \t\t\twhile (src[1] == '/')\n> \t\t\t\tsrc++;\n\nWhatever.  That was just for discussion.\n\n>> +const char *prefix_path(const char *prefix, int len, const char *path)\n>> +{\n>> +\tconst char *orig = path;\n>> +\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n>\n> There _has_ to be a way to avoid malloc()ing things that will _never_ be \n> free()d again with every second patch ;-)\n\nHuh?  prefix_path() already allocates for rewritten pathspec\nentries; this is nothing new.\n"},{"id":"66783","messageId":"alpine.LSU.1.00.0801290255050.23907@racer.site","threadId":"11711","inReplyTo":"7v7ihts61v.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Schindelin","fromEmail":"johannes.schindelin@gmx.de","sentAt":"2008-01-29T02:59:58Z","receivedAt":"2008-01-29T02:59:58Z","isPatch":true,"sender":{"key":"johannes.schindelin@gmx.de","avatar":"https://avatars.githubusercontent.com/u/127790?v=4"},"body":"Hi,\n\nOn Mon, 28 Jan 2008, Junio C Hamano wrote:\n\n> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:\n> \n> >> +const char *prefix_path(const char *prefix, int len, const char *path)\n> >> +{\n> >> +\tconst char *orig = path;\n> >> +\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n> >\n> > There _has_ to be a way to avoid malloc()ing things that will _never_ \n> > be free()d again with every second patch ;-)\n> \n> Huh?  prefix_path() already allocates for rewritten pathspec entries; \n> this is nothing new.\n\nRight.  It is nothing new.  Except that we now allocate for more paths \nthan before.\n\nAt the same time as introducing a new feature (path normalisation), we \ncould introduce another change, which would introduce a function \ncleanup_prefixed_pathspecs(), which would free all path that were \nmalloc()ed.\n\nCiao,\nDscho\n"},{"id":"66815","messageId":"7v7ihtqfm8.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7vbq75s80t.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T07:02:07Z","receivedAt":"2008-01-29T07:02:07Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Ok, the three patches from this afternoon, squashed together\ninto one, seems to give us quite a nice property.\n\nHere is an demonstration.\n\n t/t7010-setup.sh |  117 ++++++++++++++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 117 insertions(+), 0 deletions(-)\n create mode 100755 t/t7010-setup.sh\n\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nnew file mode 100755\nindex 0000000..da20ba5\n--- /dev/null\n+++ b/t/t7010-setup.sh\n@@ -0,0 +1,117 @@\n+#!/bin/sh\n+\n+test_description='setup taking and sanitizing funny paths'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\n+\tmkdir -p a/b/c a/e &&\n+\tD=$(pwd) &&\n+\t>a/b/c/d &&\n+\t>a/e/f\n+\n+'\n+\n+test_expect_success 'git add (absolute)' '\n+\n+\tgit add \"$D/a/b/c/d\" &&\n+\tgit ls-files >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+\n+test_expect_success 'git add (funny relative)' '\n+\n+\trm -f .git/index &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit add \"../e/./f\"\n+\t) &&\n+\tgit ls-files >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git rm (absolute)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\tgit rm -f --cached \"$D/a/b/c/d\" &&\n+\tgit ls-files >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git rm (funny relative)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit rm -f --cached \"../e/./f\"\n+\t) &&\n+\tgit ls-files >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (absolute)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\tgit ls-files \"$D/a/e/../b\" >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #1)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit ls-files \"../b/c\"\n+\t)  >current &&\n+\techo c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #2)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit ls-files --full-name \"../e/f\"\n+\t)  >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #3)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tif git ls-files \"../e/f\"\n+\t\tthen\n+\t\t\techo Gaah, should have failed\n+\t\t\texit 1\n+\t\telse\n+\t\t\t: happy\n+\t\tfi\n+\t)\n+\n+'\n+\n+test_done\n"},{"id":"66816","messageId":"479ED3AE.5000403@viscovery.net","threadId":"11711","inReplyTo":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-29T07:20:14Z","receivedAt":"2008-01-29T07:20:14Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> +static int sanitary_path_copy(char *dst, const char *src)\n>  {\n> -\tconst char *orig = path;\n> +\tchar *dst0 = dst;\n> +\n> +\tif (*src == '/') {\n> +\t\t*dst++ = '/';\n> +\t\twhile (*src == '/')\n> +\t\t\tsrc++;\n> +\t}\n\nAdvance notice: In this function, tests of the kind *src == '/' need to be\nturned into is_dir_sep(*src) when we port to Windows.\n\n> +\t\t/* copy up to the next '/', and eat all '/' */\n> +\t\twhile ((c = *src++) != '\\0' && c != '/')\n> +\t\t\t*dst++ = c;\n>  \t\tif (c == '/') {\n> -\t\t\tpath += 2;\n> -\t\t\tcontinue;\n> -\t\t}\n> -\t\tif (c != '.')\n> +\t\t\t*dst++ = c;\n\n\t\t\t*dst++ = '/';\n\nwill be needed on Windows to sanitize all is_dir_sep(c) to '/'.\n\n> +\t\t\twhile (c == '/')\n> +\t\t\t\tc = *src++;\n> +\t\t\tsrc--;\n> +\t\t} else if (!c)\n>  \t\t\tbreak;\n...\n> +const char *prefix_path(const char *prefix, int len, const char *path)\n> +{\n> +\tconst char *orig = path;\n> +\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n> +\tif (*orig == '/')\n\n\tif (is_absolute_path(*orig))\n\n> +\t\tstrcpy(sanitized, path);\n> +\telse {\n> +\t\tif (len)\n> +\t\t\tmemcpy(sanitized, prefix, len);\n> +\t\tstrcpy(sanitized + len, path);\t\t\n>  \t}\n> -\treturn path;\n> +\tif (sanitary_path_copy(sanitized, sanitized))\n> +\t\tgoto error_out;\n> +\tif (*orig == '/') {\n\nDitto.\n\n> +\t\tconst char *work_tree = get_git_work_tree();\n> +\t\tsize_t len = strlen(work_tree);\n> +\t\tif (strncmp(sanitized, work_tree, len) ||\n> +\t\t    (sanitized[len] != '\\0' && sanitized[len] != '/')) {\n> +\t\terror_out:\n> +\t\t\terror(\"'%s' is outside repository\", orig);\n> +\t\t\tfree(sanitized);\n> +\t\t\treturn NULL;\n> +\t\t}\n> +\t}\n> +\treturn sanitized;\n>  }\n\nI appreciate this new sanitary_copy_path() because I expect that we will\nneed at least one less #ifdef __MINGW32__/#endif compared to our current\nWindows port.\n\n-- Hannes\n"},{"id":"66818","messageId":"7v3ashqedx.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"479ED3AE.5000403@viscovery.net","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T07:28:42Z","receivedAt":"2008-01-29T07:28:42Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n> I appreciate this new sanitary_copy_path() because I expect that we will\n> need at least one less #ifdef __MINGW32__/#endif compared to our current\n> Windows port.\n\nI would have expected that the whole function would have\nplatform specific implementation to deal with Windows, not just\nifdef sprinkled everywhere that says \"Oh, on this platform\ndirectory separator is a backslash\".\n\nEspecially I have no idea how would that drive lettter stuff\nwould/should work.  When you are in C:\\Documents\\Panda\\, how\nwould you express D:\\Movie\\Porn\\My Favorite.mpg as a relative\npath?\n"},{"id":"66819","messageId":"479ED92E.4020709@viscovery.net","threadId":"11711","inReplyTo":"7v3ashqedx.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Johannes Sixt","fromEmail":"j.sixt@viscovery.net","sentAt":"2008-01-29T07:43:42Z","receivedAt":"2008-01-29T07:43:42Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Junio C Hamano schrieb:\n> Johannes Sixt <j.sixt@viscovery.net> writes:\n> \n>> I appreciate this new sanitary_copy_path() because I expect that we will\n>> need at least one less #ifdef __MINGW32__/#endif compared to our current\n>> Windows port.\n> \n> I would have expected that the whole function would have\n> platform specific implementation to deal with Windows, not just\n> ifdef sprinkled everywhere that says \"Oh, on this platform\n> directory separator is a backslash\".\n\nI agree. Therefore, we would have *one* conditionalized definition of\nis_dir_sep() upfront, and use that in place of c == '/'.\n\nThe #ifdef I'm addressing above is one that we had to introduce because in\nthe old implementation of prefix_path() on Windows we had to rewrite the\npath more often than on *nix due to '\\\\' => '/' conversion. In your new\nimplementation this rewriting now always takes place, but on *nix it more\noften turns out to be an identity operation.\n\n> Especially I have no idea how would that drive lettter stuff\n> would/should work.  When you are in C:\\Documents\\Panda\\, how\n> would you express D:\\Movie\\Porn\\My Favorite.mpg as a relative\n> path?\n\nYou can't. But when would this be necessary?\n\n-- Hannes\n"},{"id":"66821","messageId":"7vve5dox0o.fsf_-_@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7v7ihtqfm8.fsf@gitster.siamese.dyndns.org","subject":"[PATCH] setup: sanitize absolute and funny paths in get_pathspec()","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T08:29:11Z","receivedAt":"2008-01-29T08:29:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"The prefix_path() function called from get_pathspec() is\nresponsible for translating list of user-supplied pathspecs to\nlist of pathspecs that is relative to the root of the work\ntree.  When working inside a subdirectory, the user-supplied\npathspecs are taken to be relative to the current subdirectory.\n\nAmong special path components in pathspecs, we used to accept\nand interpret only \".\" (\"the directory\", meaning a no-op) and\n\"..\"  (\"up one level\") at the beginning.  Everything else was\npassed through as-is.\n\nFor example, if you are in Documentation/ directory of the\nproject, you can name Documentation/howto/maintain-git.txt as:\n\n    howto/maintain-git.txt\n    ../Documentation/howto/maitain-git.txt\n    ../././Documentation/howto/maitain-git.txt\n\nbut not as:\n\n    howto/./maintain-git.txt\n    $(pwd)/howto/maintain-git.txt\n\nThis patch updates prefix_path() in several ways:\n\n - If the pathspec is not absolute, prefix (i.e. the current\n   subdirectory relative to the root of the work tree, with\n   terminating slash, if not empty) and the pathspec is\n   concatenated first and used in the next step.  Otherwise,\n   that absolute pathspec is used in the next step.\n\n - Then special path components \".\" (no-op) and \"..\" (up one\n   level) are interpreted to simplify the path.  It is an error\n   to have too many \"..\" to cause the intermediate result to\n   step outside of the input to this step.\n\n - If the original pathspec was not absolute, the result from\n   the previous step is the resulting \"sanitized\" pathspec.\n   Otherwise, the result from the previous step is still\n   absolute, and it is an error if it does not begin with the\n   directory that corresponds to the root of the work tree.  The\n   directory is stripped away from the result and is returned.\n\n - In any case, the resulting pathspec in the array\n   get_pathspec() returns omit the ones that caused errors.\n\nWith this patch, the last two examples also behave as expected.\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n\n * This is a squash of the handful patches I sent out with some\n   clean-ups, to conclude the day for me.  Perhaps this will\n   appear in 'pu' before 1.5.4 if I really am bored but not very\n   likely.  I am pretty much bogged down with day-job these days\n   during the week...\n\n builtin-ls-files.c |   11 +++-\n builtin-mv.c       |    4 +-\n setup.c            |  158 ++++++++++++++++++++++++++++++++++++++--------------\n t/t7010-setup.sh   |  117 ++++++++++++++++++++++++++++++++++++++\n 4 files changed, 245 insertions(+), 45 deletions(-)\n create mode 100755 t/t7010-setup.sh\n\ndiff --git a/builtin-ls-files.c b/builtin-ls-files.c\nindex 0f0ab2d..3801cf4 100644\n--- a/builtin-ls-files.c\n+++ b/builtin-ls-files.c\n@@ -572,8 +572,17 @@ int cmd_ls_files(int argc, const char **argv, const char *prefix)\n \tpathspec = get_pathspec(prefix, argv + i);\n \n \t/* Verify that the pathspec matches the prefix */\n-\tif (pathspec)\n+\tif (pathspec) {\n+\t\tif (argc != i) {\n+\t\t\tint cnt;\n+\t\t\tfor (cnt = 0; pathspec[cnt]; cnt++)\n+\t\t\t\t;\n+\t\t\tif (cnt != (argc - i))\n+\t\t\t\texit(1); /* error message already given */\n+\t\t}\n \t\tprefix = verify_pathspec(prefix);\n+\t} else if (argc != i)\n+\t\texit(1); /* error message already given */\n \n \t/* Treat unmatching pathspec elements as errors */\n \tif (pathspec && error_unmatch) {\ndiff --git a/builtin-mv.c b/builtin-mv.c\nindex 990e213..94f6dd2 100644\n--- a/builtin-mv.c\n+++ b/builtin-mv.c\n@@ -164,7 +164,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t}\n \n \t\t\t\tdst = add_slash(dst);\n-\t\t\t\tdst_len = strlen(dst) - 1;\n+\t\t\t\tdst_len = strlen(dst);\n \n \t\t\t\tfor (j = 0; j < last - first; j++) {\n \t\t\t\t\tconst char *path =\n@@ -172,7 +172,7 @@ int cmd_mv(int argc, const char **argv, const char *prefix)\n \t\t\t\t\tsource[argc + j] = path;\n \t\t\t\t\tdestination[argc + j] =\n \t\t\t\t\t\tprefix_path(dst, dst_len,\n-\t\t\t\t\t\t\tpath + length);\n+\t\t\t\t\t\t\tpath + length + 1);\n \t\t\t\t\tmodes[argc + j] = INDEX;\n \t\t\t\t}\n \t\t\t\targc += last - first;\ndiff --git a/setup.c b/setup.c\nindex adede16..23c9a11 100644\n--- a/setup.c\n+++ b/setup.c\n@@ -4,51 +4,118 @@\n static int inside_git_dir = -1;\n static int inside_work_tree = -1;\n \n-const char *prefix_path(const char *prefix, int len, const char *path)\n+static int sanitary_path_copy(char *dst, const char *src)\n {\n-\tconst char *orig = path;\n+\tchar *dst0 = dst;\n+\n+\tif (*src == '/') {\n+\t\t*dst++ = '/';\n+\t\twhile (*src == '/')\n+\t\t\tsrc++;\n+\t}\n+\n \tfor (;;) {\n-\t\tchar c;\n-\t\tif (*path != '.')\n-\t\t\tbreak;\n-\t\tc = path[1];\n-\t\t/* \".\" */\n-\t\tif (!c) {\n-\t\t\tpath++;\n-\t\t\tbreak;\n+\t\tchar c = *src;\n+\n+\t\t/*\n+\t\t * A path component that begins with . could be\n+\t\t * special:\n+\t\t * (1) \".\" and ends   -- ignore and terminate.\n+\t\t * (2) \"./\"           -- ignore them, eat slash and continue.\n+\t\t * (3) \"..\" and ends  -- strip one and terminate.\n+\t\t * (4) \"../\"          -- strip one, eat slash and continue.\n+\t\t */\n+\t\tif (c == '.') {\n+\t\t\tswitch (src[1]) {\n+\t\t\tcase '\\0':\n+\t\t\t\t/* (1) */\n+\t\t\t\tsrc++;\n+\t\t\t\tbreak;\n+\t\t\tcase '/':\n+\t\t\t\t/* (2) */\n+\t\t\t\tsrc += 2;\n+\t\t\t\twhile (*src == '/')\n+\t\t\t\t\tsrc++;\n+\t\t\t\tcontinue;\n+\t\t\tcase '.':\n+\t\t\t\tswitch (src[2]) {\n+\t\t\t\tcase '\\0':\n+\t\t\t\t\t/* (3) */\n+\t\t\t\t\tsrc += 2;\n+\t\t\t\t\tgoto up_one;\n+\t\t\t\tcase '/':\n+\t\t\t\t\t/* (4) */\n+\t\t\t\t\tsrc += 3;\n+\t\t\t\t\twhile (*src == '/')\n+\t\t\t\t\t\tsrc++;\n+\t\t\t\t\tgoto up_one;\n+\t\t\t\t}\n+\t\t\t}\n \t\t}\n-\t\t/* \"./\" */\n+\n+\t\t/* copy up to the next '/', and eat all '/' */\n+\t\twhile ((c = *src++) != '\\0' && c != '/')\n+\t\t\t*dst++ = c;\n \t\tif (c == '/') {\n-\t\t\tpath += 2;\n-\t\t\tcontinue;\n-\t\t}\n-\t\tif (c != '.')\n+\t\t\t*dst++ = c;\n+\t\t\twhile (c == '/')\n+\t\t\t\tc = *src++;\n+\t\t\tsrc--;\n+\t\t} else if (!c)\n \t\t\tbreak;\n-\t\tc = path[2];\n-\t\tif (!c)\n-\t\t\tpath += 2;\n-\t\telse if (c == '/')\n-\t\t\tpath += 3;\n-\t\telse\n-\t\t\tbreak;\n-\t\t/* \"..\" and \"../\" */\n-\t\t/* Remove last component of the prefix */\n-\t\tdo {\n-\t\t\tif (!len)\n-\t\t\t\tdie(\"'%s' is outside repository\", orig);\n-\t\t\tlen--;\n-\t\t} while (len && prefix[len-1] != '/');\n \t\tcontinue;\n+\n+\tup_one:\n+\t\t/*\n+\t\t * dst0..dst is prefix portion, and dst[-1] is '/';\n+\t\t * go up one level.\n+\t\t */\n+\t\tdst -= 2; /* go past trailing '/' if any */\n+\t\tif (dst < dst0)\n+\t\t\treturn -1;\n+\t\twhile (1) {\n+\t\t\tif (dst <= dst0)\n+\t\t\t\tbreak;\n+\t\t\tc = *dst--;\n+\t\t\tif (c == '/') {\n+\t\t\t\tdst += 2;\n+\t\t\t\tbreak;\n+\t\t\t}\n+\t\t}\n \t}\n-\tif (len) {\n-\t\tint speclen = strlen(path);\n-\t\tchar *n = xmalloc(speclen + len + 1);\n+\t*dst = '\\0';\n+\treturn 0;\n+}\n \n-\t\tmemcpy(n, prefix, len);\n-\t\tmemcpy(n + len, path, speclen+1);\n-\t\tpath = n;\n+const char *prefix_path(const char *prefix, int len, const char *path)\n+{\n+\tconst char *orig = path;\n+\tchar *sanitized = xmalloc(len + strlen(path) + 1);\n+\tif (*orig == '/')\n+\t\tstrcpy(sanitized, path);\n+\telse {\n+\t\tif (len)\n+\t\t\tmemcpy(sanitized, prefix, len);\n+\t\tstrcpy(sanitized + len, path);\n \t}\n-\treturn path;\n+\tif (sanitary_path_copy(sanitized, sanitized))\n+\t\tgoto error_out;\n+\tif (*orig == '/') {\n+\t\tconst char *work_tree = get_git_work_tree();\n+\t\tsize_t len = strlen(work_tree);\n+\t\tsize_t total = strlen(sanitized) + 1;\n+\t\tif (strncmp(sanitized, work_tree, len) ||\n+\t\t    (sanitized[len] != '\\0' && sanitized[len] != '/')) {\n+\t\terror_out:\n+\t\t\terror(\"'%s' is outside repository\", orig);\n+\t\t\tfree(sanitized);\n+\t\t\treturn NULL;\n+\t\t}\n+\t\tif (sanitized[len] == '/')\n+\t\t\tlen++;\n+\t\tmemmove(sanitized, sanitized + len, total - len);\n+\t}\n+\treturn sanitized;\n }\n \n /*\n@@ -114,7 +181,7 @@ void verify_non_filename(const char *prefix, const char *arg)\n const char **get_pathspec(const char *prefix, const char **pathspec)\n {\n \tconst char *entry = *pathspec;\n-\tconst char **p;\n+\tconst char **src, **dst;\n \tint prefixlen;\n \n \tif (!prefix && !entry)\n@@ -128,12 +195,19 @@ const char **get_pathspec(const char *prefix, const char **pathspec)\n \t}\n \n \t/* Otherwise we have to re-write the entries.. */\n-\tp = pathspec;\n+\tsrc = pathspec;\n+\tdst = pathspec;\n \tprefixlen = prefix ? strlen(prefix) : 0;\n-\tdo {\n-\t\t*p = prefix_path(prefix, prefixlen, entry);\n-\t} while ((entry = *++p) != NULL);\n-\treturn (const char **) pathspec;\n+\twhile (*src) {\n+\t\tconst char *p = prefix_path(prefix, prefixlen, *src);\n+\t\tif (p)\n+\t\t\t*(dst++) = p;\n+\t\tsrc++;\n+\t}\n+\t*dst = NULL;\n+\tif (!*pathspec)\n+\t\treturn NULL;\n+\treturn pathspec;\n }\n \n /*\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nnew file mode 100755\nindex 0000000..da20ba5\n--- /dev/null\n+++ b/t/t7010-setup.sh\n@@ -0,0 +1,117 @@\n+#!/bin/sh\n+\n+test_description='setup taking and sanitizing funny paths'\n+\n+. ./test-lib.sh\n+\n+test_expect_success setup '\n+\n+\tmkdir -p a/b/c a/e &&\n+\tD=$(pwd) &&\n+\t>a/b/c/d &&\n+\t>a/e/f\n+\n+'\n+\n+test_expect_success 'git add (absolute)' '\n+\n+\tgit add \"$D/a/b/c/d\" &&\n+\tgit ls-files >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+\n+test_expect_success 'git add (funny relative)' '\n+\n+\trm -f .git/index &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit add \"../e/./f\"\n+\t) &&\n+\tgit ls-files >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git rm (absolute)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\tgit rm -f --cached \"$D/a/b/c/d\" &&\n+\tgit ls-files >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git rm (funny relative)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit rm -f --cached \"../e/./f\"\n+\t) &&\n+\tgit ls-files >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (absolute)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\tgit ls-files \"$D/a/e/../b\" >current &&\n+\techo a/b/c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #1)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit ls-files \"../b/c\"\n+\t)  >current &&\n+\techo c/d >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #2)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tgit ls-files --full-name \"../e/f\"\n+\t)  >current &&\n+\techo a/e/f >expect &&\n+\tdiff -u expect current\n+\n+'\n+\n+test_expect_success 'git ls-files (relative #3)' '\n+\n+\trm -f .git/index &&\n+\tgit add a &&\n+\t(\n+\t\tcd a/b &&\n+\t\tif git ls-files \"../e/f\"\n+\t\tthen\n+\t\t\techo Gaah, should have failed\n+\t\t\texit 1\n+\t\telse\n+\t\t\t: happy\n+\t\tfi\n+\t)\n+\n+'\n+\n+test_done\n-- \n1.5.4.rc5\n"},{"id":"66823","messageId":"7vprvlowwg.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"479ED92E.4020709@viscovery.net","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-29T08:31:43Z","receivedAt":"2008-01-29T08:31:43Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Johannes Sixt <j.sixt@viscovery.net> writes:\n\n> The #ifdef I'm addressing above is one that we had to introduce because in\n> the old implementation of prefix_path() on Windows we had to rewrite the\n> path more often than on *nix due to '\\\\' => '/' conversion. In your new\n> implementation this rewriting now always takes place, but on *nix it more\n> often turns out to be an identity operation.\n\nI am not sure what you mean by \"\\\\\" => '/', but you are right.\nWe should be able to optimize the common case of prefix=none and\nno special path components found in path, which should be an\nidentity function.\n\n>> Especially I have no idea how would that drive lettter stuff\n>> would/should work.  When you are in C:\\Documents\\Panda\\, how\n>> would you express D:\\Movie\\Porn\\My Favorite.mpg as a relative\n>> path?\n>\n> You can't. But when would this be necessary?\n\nI don't know.  That's why I asked.\n"},{"id":"66860","messageId":"200801292158.m0TLwA7u019321@mi0.bluebottle.com","threadId":"11711","inReplyTo":"7vwspts9vj.fsf@gitster.siamese.dyndns.org","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"しらいしななこ","fromEmail":"nanako3@bluebottle.com","sentAt":"2008-01-29T21:53:48Z","receivedAt":"2008-01-29T21:53:48Z","isPatch":true,"sender":{"key":"nanako3@lavabit.com","avatar":"https://gravatar.com/avatar/3777b9e201c5883a62b1a6fdf7c53f2d712d1d80989146063ea861e33aad72a8?d=mp&s=160"},"body":"Quoting Junio C Hamano <gitster@pobox.com>:\n\n> Currently, we:\n>\n>  - Remove \".\" path component (i.e. the directory leading part\n>    specified) from the input;\n>\n>  - Remove \"..\" path component and strip one level of the prefix;\n>\n> only from the beginning.  So if you give nonsense pathspec from\n> the command line, you can end up calling prefix_path() with things\n> like \"/README\", \"/absolute/path/to//repository/tracked/file\", and\n> \"fo//o/../o\".\n>\n> And not passing such ambiguous path like \"fo//o\" to the core\n> level but sanitizing matters.  Then core level can always do\n> memcmp() with \"fo/o\" to see they are talking about the same\n> path.\n\nI may be mistaken but I think \"fo//o\" and \"fo//o/\" are returned as two different strings \"fo/o\" and \"fo/o/\" from your patch. Shouldn't you clean-up the second one to \"fo/o\", too?\n\n-- \nNanako Shiraishi\nhttp://ivory.ap.teacup.com/nanako3/\n\n----------------------------------------------------------------------\nFree pop3 email with a spam filter.\nhttp://www.bluebottle.com/tag/5\n"},{"id":"66875","messageId":"7vhcgwkurj.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"200801292158.m0TLwA7u019321@mi0.bluebottle.com","subject":"Re: [RFH/PATCH] prefix_path(): disallow absolute paths","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-01-30T00:43:44Z","receivedAt":"2008-01-30T00:43:44Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"しらいしななこ  <nanako3@bluebottle.com> writes:\n\n> Quoting Junio C Hamano <gitster@pobox.com>:\n>...\n>> And not passing such ambiguous path like \"fo//o\" to the core\n>> level but sanitizing matters.  Then core level can always do\n>> memcmp() with \"fo/o\" to see they are talking about the same\n>> path.\n>\n> I may be mistaken but I think \"fo//o\" and \"fo//o/\" are\n> returned as two different strings \"fo/o\" and \"fo/o/\" from your\n> patch. Shouldn't you clean-up the second one to \"fo/o\", too?\n\nThat certainly is a potentially possible sanitization, and I\nactually thought about it when I did these patches.\n\nHowever, I chose not to because I was not sure if some core\nfunctions want to differentiate pathspecs \"foo/\" and \"foo\".  The\nformer says \"I want to make sure that 'foo' is a directory, and\nwant to affect everything under it\", the latter says \"I do not\ncare if 'foo' is a blob or a directory, but I want to affect it\n(if a blob) or everything under it (if a directory)\".\n\nIn fact, stripping trailing slashes off would break this pair:\n\n\tgit ls-files --error-unmatch Makefile/\n\tgit ls-files --error-unmatch Makefile\n\nThings like \"git add Makefile/\" relies on the former to fail\nloudly.  So the answer is no, we do not want to clean it up.\n\nIncidentally, I notice that in addition to the squashed patch\nfrom yesterday, we would need to teach \"error-unmatch\" code that\nit should trigger when get_pathspec() returns a pathspec that\nhas fewer number of paths than its input.\n\nIt should be a pretty straightforward patch, but I haven't\nlooked into fixing it.  I'm lazy and I'd rather have other\npeople to do the fixing for me.  Hint, hint... ;-)\n\nBy the way, please keep your lines to reasonable length by\nwrapping in your e-mailed messages.\n"},{"id":"67003","messageId":"200802010507.05421.robin.rosenberg.lists@dewire.com","threadId":"11711","inReplyTo":"7vve5dox0o.fsf_-_@gitster.siamese.dyndns.org","subject":"[PATCH] Make blame accept absolute paths","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-02-01T04:07:04Z","receivedAt":"2008-02-01T04:07:04Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"\nBlame did not always use prefix_path.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n builtin-blame.c |    4 +---\n 1 files changed, 1 insertions(+), 3 deletions(-)\n\nThis is a followup to \"setup: sanitize absolute and funny paths in get_pathspec()\"\nThanks Junio for fixing this wish of mine.\n\ndiff --git a/builtin-blame.c b/builtin-blame.c\nindex c7e6887..f88c32a 100644\n--- a/builtin-blame.c\n+++ b/builtin-blame.c\n@@ -1894,9 +1894,7 @@ static unsigned parse_score(const char *arg)\n \n static const char *add_prefix(const char *prefix, const char *path)\n {\n-\tif (!prefix || !prefix[0])\n-\t\treturn path;\n-\treturn prefix_path(prefix, strlen(prefix), path);\n+\treturn prefix_path(prefix, prefix ? strlen(prefix) : 0, path);\n }\n \n /*\n-- \n1.5.4.rc4.25.g81cc\n"},{"id":"67004","messageId":"200802010534.55925.robin.rosenberg.lists@dewire.com","threadId":"11711","inReplyTo":"7vve5dox0o.fsf_-_@gitster.siamese.dyndns.org","subject":"[PATCH] More test cases for sanitized path names","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-02-01T04:34:55Z","receivedAt":"2008-02-01T04:34:55Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Verify a few more commands and pathname variants.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n t/t7010-setup.sh |   39 +++++++++++++++++++++++++++++++++++++++\n 1 files changed, 39 insertions(+), 0 deletions(-)\n\nThese are a few testcases from my earlier attempt at this. The\nlog and commit cases succeeded with Junios version, but not \nblame and some of the nastier versions for git add (same\nprinciple for all commands, just that I use add as an example)\n\n-- robin\n\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nindex da20ba5..60c4a46 100755\n--- a/t/t7010-setup.sh\n+++ b/t/t7010-setup.sh\n@@ -114,4 +114,43 @@ test_expect_success 'git ls-files (relative #3)' '\n \n '\n \n+test_expect_success 'commit using absolute path names' '\n+\tgit commit -m \"foo\" &&\n+\techo aa >>a/b/c/d &&\n+\tgit commit -m \"aa\" \"$(pwd)/a/b/c/d\"\n+'\n+\n+test_expect_success 'log using absolute path names' '\n+\techo bb >>a/b/c/d &&\n+\tgit commit -m \"bb\" $(pwd)/a/b/c/d &&\n+\n+\tgit log a/b/c/d >f1.txt &&\n+\tgit log \"$(pwd)/a/b/c/d\" >f2.txt &&\n+\tdiff -u f1.txt f2.txt\n+'\n+\n+test_expect_success 'blame using absolute path names' '\n+\tgit blame a/b/c/d >f1.txt &&\n+\tgit blame \"$(pwd)/a/b/c/d\" >f2.txt &&\n+\tdiff -u f1.txt f2.txt\n+'\n+\n+test_expect_failure 'add a directory outside the work tree' '\n+\td1=\"$(cd .. ; pwd)\" &&\n+\tgit add \"$d1\"\n+\techo $?\n+'\n+\n+test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n+\tf=\"$(pwd)x\" &&\n+\ttouch \"$f\" &&\n+\tgit add \"$f\"\n+)'\n+\n+test_expect_failure 'add a file outside the work tree, nasty case 2' '(\n+\tf=\"$(pwd|sed \"s/.$//\")x\" &&\n+\ttouch \"$f\" &&\n+\tgit add \"$f\"\n+)'\n+\n test_done\n-- \n1.5.4.rc4.25.g81cc\n"},{"id":"67009","messageId":"7vabmlb0y0.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"200802010534.55925.robin.rosenberg.lists@dewire.com","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T07:17:11Z","receivedAt":"2008-02-01T07:17:11Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n\n> +test_expect_failure 'add a directory outside the work tree' '\n> +\td1=\"$(cd .. ; pwd)\" &&\n> +\tgit add \"$d1\"\n> +\techo $?\n> +'\n\nThis test will always fail as the final exit status is that of\n\"echo\", which will exit with success and you are expecting a\nfailure.\n\n> +test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n> +\tf=\"$(pwd)x\" &&\n> +\ttouch \"$f\" &&\n> +\tgit add \"$f\"\n> +)'\n\nYou are in the directory \"t/trash\", and try to add t/trashx, so\nthis should fail and you would want to make sure it fails.\n\nBut this has a few problems:\n\n * First, the obvious one.  You are creating a garbage file\n   outside of t/trash directory.  Don't.  If you need to, dig a\n   test directory one level lower inside t/trash and play around\n   there.\n\n * In general you should stay away from test_expect_failure.  If\n   any of the command in && chain fails, it fails the whole\n   thing, but you cannot tell if the sequence failed at the\n   command you expected to fail or something else that is much\n   earlier.  For example, it may be that somebody created t/trashx\n   file in the source tree that is read-only, and the comand\n   that failed in the sequence could be 'touch' before the\n   command you are testing.\n\n   Instead, write it like (after fixing it not to go outside\n   t/trash):\n\n\ttest_expect_success 'add a path outside repo (1)' '\n\n\t\tfile=path_to_outside_repo &&\n                touch \"$file\" &&\n\t\t! git add \"$f\"\n\n\t'\n\nI'd like to make the _first_ patch after 1.5.4 to be a fix-up\nfor tests that misuse test_expect_failure.  After that, we can\nuse test_expect_failure to mark tests that ought to pass but\ndon't because of bugs in the commands.  That way, people who are\nabsolutely bored can grep for test_expect_failure to see what\nexisting issues to tackle ;-).\n"},{"id":"67016","messageId":"200802011010.41938.robin.rosenberg.lists@dewire.com","threadId":"11711","inReplyTo":"7vabmlb0y0.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-02-01T09:10:40Z","receivedAt":"2008-02-01T09:10:40Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"fredagen den 1 februari 2008 skrev Junio C Hamano:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n> \n> > +test_expect_failure 'add a directory outside the work tree' '\n> > +\td1=\"$(cd .. ; pwd)\" &&\n> > +\tgit add \"$d1\"\n> > +\techo $?\n> > +'\nOops. Remove the echo $?. It still fails, i.e. git add succeeds when\nit shouldn't. I was double checking it just before sending the patch.\n\n> > +test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n> > +\tf=\"$(pwd)x\" &&\n> > +\ttouch \"$f\" &&\n> > +\tgit add \"$f\"\n> > +)'\n> \n> You are in the directory \"t/trash\", and try to add t/trashx, so\n> this should fail and you would want to make sure it fails.\n> \n> But this has a few problems:\n> \n>  * First, the obvious one.  You are creating a garbage file\n>    outside of t/trash directory.  Don't.  If you need to, dig a\n>    test directory one level lower inside t/trash and play around\n>    there.\nCan we move the default trash one level down for all tests? That\nwould give us one free level to play with.\n\n>  * In general you should stay away from test_expect_failure.  If\nI respect that.\n[...]\n> I'd like to make the _first_ patch after 1.5.4 to be a fix-up\n> for tests that misuse test_expect_failure.  After that, we can\n> use test_expect_failure to mark tests that ought to pass but\n> don't because of bugs in the commands.  That way, people who are\n> absolutely bored can grep for test_expect_failure to see what\n> existing issues to tackle ;-).\n\nIf I recall things properly there are lots of test that test for success\nrather than checking that the command does what it should.\n\nUpdate follows.\n\n-- robin\n\n>From 11a52821ca81096987f53c29bf1b9ce373fe7fd4 Mon Sep 17 00:00:00 2001\nFrom: Robin Rosenberg <robin.rosenberg@dewire.com>\nDate: Fri, 1 Feb 2008 05:29:38 +0100\nSubject: [PATCH] More test cases for sanitized path names\n\nVerify a few more commands and pathname variants.\n\nSigned-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n---\n t/t7010-setup.sh |   42 ++++++++++++++++++++++++++++++++++++++++++\n 1 files changed, 42 insertions(+), 0 deletions(-)\n\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nindex da20ba5..ef30099 100755\n--- a/t/t7010-setup.sh\n+++ b/t/t7010-setup.sh\n@@ -4,6 +4,10 @@ test_description='setup taking and sanitizing funny paths'\n \n . ./test-lib.sh\n \n+rm -rf .git\n+test_create_repo repo\n+cd repo\n+\n test_expect_success setup '\n \n \tmkdir -p a/b/c a/e &&\n@@ -114,4 +118,42 @@ test_expect_success 'git ls-files (relative #3)' '\n \n '\n \n+test_expect_success 'commit using absolute path names' '\n+\tgit commit -m \"foo\" &&\n+\techo aa >>a/b/c/d &&\n+\tgit commit -m \"aa\" \"$(pwd)/a/b/c/d\"\n+'\n+\n+test_expect_success 'log using absolute path names' '\n+\techo bb >>a/b/c/d &&\n+\tgit commit -m \"bb\" $(pwd)/a/b/c/d &&\n+\n+\tgit log a/b/c/d >f1.txt &&\n+\tgit log \"$(pwd)/a/b/c/d\" >f2.txt &&\n+\tdiff -u f1.txt f2.txt\n+'\n+\n+test_expect_success 'blame using absolute path names' '\n+\tgit blame a/b/c/d >f1.txt &&\n+\tgit blame \"$(pwd)/a/b/c/d\" >f2.txt &&\n+\tdiff -u f1.txt f2.txt\n+'\n+\n+test_expect_success 'add a directory outside the work tree' '\n+\td1=\"$(cd .. ; pwd)\" &&\n+\t! git add \"$d1\"\n+'\n+\n+test_expect_success 'add a file outside the work tree, nasty case 1' '(\n+\tf=\"$(pwd)x\" &&\n+\ttouch \"$f\" &&\n+\t! git add \"$f\"\n+)'\n+\n+test_expect_success 'add a file outside the work tree, nasty case 2' '(\n+\tf=\"$(pwd|sed \"s/.$//\")x\" &&\n+\ttouch \"$f\" &&\n+\t! git add \"$f\"\n+)'\n+\n test_done\n-- \n1.5.4.rc4.25.g81cc\n"},{"id":"67017","messageId":"20080201091618.GA32734@diana.vm.bytemark.co.uk","threadId":"11711","inReplyTo":"7vabmlb0y0.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Karl Hasselström","fromEmail":"kha@treskal.com","sentAt":"2008-02-01T09:16:18Z","receivedAt":"2008-02-01T09:16:18Z","isPatch":true,"sender":{"key":"kha@treskal.com","avatar":"https://gravatar.com/avatar/f0120c734b5279b345075a28521e1ac66acb20c9913ffe9bf6ae97e53f7f3f13?d=mp&s=160"},"body":"On 2008-01-31 23:17:11 -0800, Junio C Hamano wrote:\n\n> I'd like to make the _first_ patch after 1.5.4 to be a fix-up for\n> tests that misuse test_expect_failure. After that, we can use\n> test_expect_failure to mark tests that ought to pass but don't\n> because of bugs in the commands.\n\nThis is precisely what StGit's test suite does, and it has a very nice\nproperty (beyond looking tidy): it's possible to first commit a new\n(failing) test, and then commit a fix for the bug that made the test\nfail, and have the test suite pass at every step.\n\n-- \nKarl Hasselström, kha@treskal.com\n      www.treskal.com/kalle\n"},{"id":"67018","messageId":"7vwspp9f9e.fsf_-_@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7vabmlb0y0.fsf@gitster.siamese.dyndns.org","subject":"[PATCH for post 1.5.4] Sane use of test_expect_failure","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T09:50:53Z","receivedAt":"2008-02-01T09:50:53Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Originally, test_expect_failure was designed to be the opposite\nof test_expect_success, but this was a bad decision.  Most tests\nrun a series of commands that leads to the single command that\nneeds to be tested, like this:\n\n    test_expect_{success,failure} 'test title' '\n\tsetup1 &&\n        setup2 &&\n        setup3 &&\n        what is to be tested\n    '\n\nAnd expecting a failure exit from the whole sequence misses the\npoint of writing tests.  Your setup$N that are supposed to\nsucceed may have failed without even reaching what you are\ntrying to test.  The only valid use of test_expect_failure is to\ncheck a trivial single command that is expected to fail, which\nis a minority in tests of Porcelain-ish commands.\n\nThis large-ish patch rewrites all uses of test_expect_failure to\nuse test_expect_success and rewrites the condition of what is\ntested, like this:\n\n    test_expect_success 'test title' '\n\tsetup1 &&\n        setup2 &&\n        setup3 &&\n        ! this command should fail\n    '\n\ntest_expect_failure is redefined to serve as a reminder that\nthat test *should* succeed but due to a known breakage in git it\ncurrently does not pass.  So if git-foo command should create a\nfile 'bar' but you discovered a bug that it doesn't, you can\nwrite a test like this:\n\n    test_expect_failure 'git-foo should create bar' '\n        rm -f bar &&\n        git foo &&\n        test -f bar\n    '\n\nThis construct acts similar to test_expect_success, but instead\nof reporting \"ok/FAIL\" like test_expect_success does, the\noutcome is reported as \"FIXED/still broken\".\n\nSigned-off-by: Junio C Hamano <gitster@pobox.com>\n---\n Junio C Hamano <gitster@pobox.com> writes:\n\n > I'd like to make the _first_ patch after 1.5.4 to be a fix-up\n > for tests that misuse test_expect_failure.  After that, we can\n > use test_expect_failure to mark tests that ought to pass but\n > don't because of bugs in the commands.  That way, people who are\n > absolutely bored can grep for test_expect_failure to see what\n > existing issues to tackle ;-).\n\n This turned out to be a huge patch.  I tried to be careful by\n keeping the conversion mostly mechanical, but I am not sure\n about some of the git-svn and git-cvsserver tests, some of which\n may already have been using test_expect_failure to mark a known\n breakage.\n\n Eyeballing by area experts are very much appreciated.\n\n t/README                                  |   14 +--\n t/t0000-basic.sh                          |   30 ++++--\n t/t0030-stripspace.sh                     |   40 ++++----\n t/t0040-parse-options.sh                  |    4 +-\n t/t1000-read-tree-m-3way.sh               |  161 ++++++++++++++++-------------\n t/t1200-tutorial.sh                       |    8 +-\n t/t1300-repo-config.sh                    |   39 ++++---\n t/t1302-repo-version.sh                   |    5 +-\n t/t1400-update-ref.sh                     |   24 ++--\n t/t2000-checkout-cache-clash.sh           |    4 +-\n t/t2002-checkout-cache-u.sh               |    4 +-\n t/t2008-checkout-subdir.sh                |   16 ++--\n t/t2100-update-cache-badpath.sh           |    4 +-\n t/t3020-ls-files-error-unmatch.sh         |    4 +-\n t/t3200-branch.sh                         |   36 ++++---\n t/t3210-pack-refs.sh                      |   26 +++---\n t/t3400-rebase.sh                         |    4 +-\n t/t3403-rebase-skip.sh                    |    6 +-\n t/t3600-rm.sh                             |   17 ++--\n t/t4103-apply-binary.sh                   |   68 +++++++------\n t/t4113-apply-ending.sh                   |    8 +-\n t/t5300-pack-object.sh                    |    4 +-\n t/t5302-pack-index.sh                     |   32 +++---\n t/t5401-update-hooks.sh                   |    8 +-\n t/t5402-post-merge-hook.sh                |    4 +-\n t/t5500-fetch-pack.sh                     |    4 +-\n t/t5510-fetch.sh                          |   12 +-\n t/t5530-upload-pack-error.sh              |   14 +--\n t/t5600-clone-fail-cleanup.sh             |   12 +-\n t/t5710-info-alternate.sh                 |   14 ++-\n t/t6023-merge-file.sh                     |   12 +-\n t/t6024-recursive-merge.sh                |    2 +-\n t/t6025-merge-symlinks.sh                 |   21 ++--\n t/t6101-rev-parse-parents.sh              |    2 +-\n t/t6300-for-each-ref.sh                   |    8 +-\n t/t7001-mv.sh                             |    4 +-\n t/t7002-grep.sh                           |    4 +-\n t/t7004-tag.sh                            |   36 +++---\n t/t7101-reset.sh                          |   24 ++--\n t/t7501-commit.sh                         |   40 ++++----\n t/t7503-pre-commit-hook.sh                |    4 +-\n t/t7504-commit-msg-hook.sh                |    8 +-\n t/t9100-git-svn-basic.sh                  |   28 +++---\n t/t9106-git-svn-commit-diff-clobber.sh    |   18 ++--\n t/t9106-git-svn-dcommit-clobber-series.sh |    4 +-\n t/t9300-fast-import.sh                    |   24 ++--\n t/t9400-git-cvsserver-server.sh           |   30 +++--\n t/test-lib.sh                             |   30 +++++-\n 48 files changed, 498 insertions(+), 427 deletions(-)\n\ndiff --git a/t/README b/t/README\nindex 36f2517..73ed11b 100644\n--- a/t/README\n+++ b/t/README\n@@ -160,14 +160,12 @@ library for your script to use.\n \n  - test_expect_failure <message> <script>\n \n-   This is the opposite of test_expect_success.  If <script>\n-   yields success, test is considered a failure.\n-\n-   Example:\n-\n-\ttest_expect_failure \\\n-\t    'git-update-index without --add should fail adding.' \\\n-\t    'git-update-index should-be-empty'\n+   This is NOT the opposite of test_expect_success, but is used\n+   to mark a test that demonstrates a known breakage.  Unlike\n+   the usual test_expect_success tests, which say \"ok\" on\n+   success and \"FAIL\" on failure, this will say \"FIXED\" on\n+   success and \"still broken\" on failure.  Failures from these\n+   tests won't cause -i (immediate) to stop.\n \n  - test_debug <script>\n \ndiff --git a/t/t0000-basic.sh b/t/t0000-basic.sh\nindex 4e49d59..cd0de50 100755\n--- a/t/t0000-basic.sh\n+++ b/t/t0000-basic.sh\n@@ -47,12 +47,24 @@ test_expect_success \\\n     'test $(wc -l < full-of-directories) = 3'\n \n ################################################################\n+# Test harness\n+test_expect_success 'success is reported like this' '\n+    :\n+'\n+test_expect_failure 'pretend we have a known breakage' '\n+    false\n+'\n+test_expect_failure 'pretend we have fixed a known breakage' '\n+    :\n+'\n+\n+################################################################\n # Basics of the basics\n \n # updating a new file without --add should fail.\n-test_expect_failure \\\n-    'git update-index without --add should fail adding.' \\\n-    'git update-index should-be-empty'\n+test_expect_success 'git update-index without --add should fail adding.' '\n+    ! git update-index should-be-empty\n+'\n \n # and with --add it should succeed, even if it is empty (it used to fail).\n test_expect_success \\\n@@ -70,9 +82,9 @@ test_expect_success \\\n \n # Removing paths.\n rm -f should-be-empty full-of-directories\n-test_expect_failure \\\n-    'git update-index without --remove should fail removing.' \\\n-    'git update-index should-be-empty'\n+test_expect_success 'git update-index without --remove should fail removing.' '\n+    ! git update-index should-be-empty\n+'\n \n test_expect_success \\\n     'git update-index with --remove should be able to remove.' \\\n@@ -204,9 +216,9 @@ test_expect_success \\\n     'put invalid objects into the index.' \\\n     'git update-index --index-info < badobjects'\n \n-test_expect_failure \\\n-    'writing this tree without --missing-ok.' \\\n-    'git write-tree'\n+test_expect_success 'writing this tree without --missing-ok.' '\n+    ! git write-tree\n+'\n \n test_expect_success \\\n     'writing this tree with --missing-ok.' \\\ndiff --git a/t/t0030-stripspace.sh b/t/t0030-stripspace.sh\nindex cad95f3..818c862 100755\n--- a/t/t0030-stripspace.sh\n+++ b/t/t0030-stripspace.sh\n@@ -243,14 +243,14 @@ test_expect_success \\\n     test `printf \"$ttt$sss$sss$sss\" | git stripspace | wc -l` -gt 0\n '\n \n-test_expect_failure \\\n+test_expect_success \\\n     'text plus spaces without newline at end should not show spaces' '\n-    printf \"$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    printf \"$ttt$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    printf \"$ttt$ttt$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    printf \"$ttt$sss$sss\" | git stripspace | grep -q \"  \" ||\n-    printf \"$ttt$ttt$sss$sss\" | git stripspace | grep -q \"  \" ||\n-    printf \"$ttt$sss$sss$sss\" | git stripspace | grep -q \"  \"\n+    ! (printf \"$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (printf \"$ttt$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (printf \"$ttt$ttt$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (printf \"$ttt$sss$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (printf \"$ttt$ttt$sss$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (printf \"$ttt$sss$sss$sss\" | git stripspace | grep -q \"  \")\n '\n \n test_expect_success \\\n@@ -280,14 +280,14 @@ test_expect_success \\\n     git diff expect actual\n '\n \n-test_expect_failure \\\n+test_expect_success \\\n     'text plus spaces at end should not show spaces' '\n-    echo \"$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    echo \"$ttt$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    echo \"$ttt$ttt$ttt$sss\" | git stripspace | grep -q \"  \" ||\n-    echo \"$ttt$sss$sss\" | git stripspace | grep -q \"  \" ||\n-    echo \"$ttt$ttt$sss$sss\" | git stripspace | grep -q \"  \" ||\n-    echo \"$ttt$sss$sss$sss\" | git stripspace | grep -q \"  \"\n+    ! (echo \"$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (echo \"$ttt$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (echo \"$ttt$ttt$ttt$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (echo \"$ttt$sss$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (echo \"$ttt$ttt$sss$sss\" | git stripspace | grep -q \"  \") &&\n+    ! (echo \"$ttt$sss$sss$sss\" | git stripspace | grep -q \"  \")\n '\n \n test_expect_success \\\n@@ -339,13 +339,13 @@ test_expect_success \\\n     git diff expect actual\n '\n \n-test_expect_failure \\\n+test_expect_success \\\n     'spaces without newline at end should not show spaces' '\n-    printf \"\" | git stripspace | grep -q \" \" ||\n-    printf \"$sss\" | git stripspace | grep -q \" \" ||\n-    printf \"$sss$sss\" | git stripspace | grep -q \" \" ||\n-    printf \"$sss$sss$sss\" | git stripspace | grep -q \" \" ||\n-    printf \"$sss$sss$sss$sss\" | git stripspace | grep -q \" \"\n+    ! (printf \"\" | git stripspace | grep -q \" \") &&\n+    ! (printf \"$sss\" | git stripspace | grep -q \" \") &&\n+    ! (printf \"$sss$sss\" | git stripspace | grep -q \" \") &&\n+    ! (printf \"$sss$sss$sss\" | git stripspace | grep -q \" \") &&\n+    ! (printf \"$sss$sss$sss$sss\" | git stripspace | grep -q \" \")\n '\n \n test_expect_success \\\ndiff --git a/t/t0040-parse-options.sh b/t/t0040-parse-options.sh\nindex 0a3b55d..2ecc283 100755\n--- a/t/t0040-parse-options.sh\n+++ b/t/t0040-parse-options.sh\n@@ -87,9 +87,9 @@ test_expect_success 'unambiguously abbreviated option with \"=\"' '\n \tgit diff expect output\n '\n \n-test_expect_failure 'ambiguously abbreviated option' '\n+test_expect_success 'ambiguously abbreviated option' '\n \ttest-parse-options --strin 123;\n-        test $? != 129\n+        test $? = 129\n '\n \n cat > expect << EOF\ndiff --git a/t/t1000-read-tree-m-3way.sh b/t/t1000-read-tree-m-3way.sh\nindex 37add1b..6c065bf 100755\n--- a/t/t1000-read-tree-m-3way.sh\n+++ b/t/t1000-read-tree-m-3way.sh\n@@ -210,12 +210,12 @@ DF (file) when tree B require DF to be a directory by having DF/DF\n \n END_OF_CASE_TABLE\n \n-test_expect_failure \\\n-    '1 - must not have an entry not in A.' \\\n-    \"rm -f .git/index XX &&\n+test_expect_success '1 - must not have an entry not in A.' \"\n+     rm -f .git/index XX &&\n      echo XX >XX &&\n      git update-index --add XX &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '2 - must match B in !O && !A && B case.' \\\n@@ -248,13 +248,14 @@ test_expect_success \\\n      echo extra >>AN &&\n      git read-tree -m $tree_O $tree_A $tree_B\"\n \n-test_expect_failure \\\n-    '3 (fail) - must match A in !O && A && !B case.' \\\n-    \"rm -f .git/index AN &&\n+test_expect_success \\\n+    '3 (fail) - must match A in !O && A && !B case.' \"\n+     rm -f .git/index AN &&\n      cp .orig-A/AN AN &&\n      echo extra >>AN &&\n      git update-index --add AN &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '4 - must match and be up-to-date in !O && A && B && A!=B case.' \\\n@@ -264,21 +265,23 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \\\n-    \"rm -f .git/index AA &&\n+test_expect_success \\\n+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \"\n+     rm -f .git/index AA &&\n      cp .orig-A/AA AA &&\n      git update-index --add AA &&\n      echo extra >>AA &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \\\n-    \"rm -f .git/index AA &&\n+test_expect_success \\\n+    '4 (fail) - must match and be up-to-date in !O && A && B && A!=B case.' \"\n+     rm -f .git/index AA &&\n      cp .orig-A/AA AA &&\n      echo extra >>AA &&\n      git update-index --add AA &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '5 - must match in !O && A && B && A==B case.' \\\n@@ -297,34 +300,38 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '5 (fail) - must match A in !O && A && B && A==B case.' \\\n-    \"rm -f .git/index LL &&\n+test_expect_success \\\n+    '5 (fail) - must match A in !O && A && B && A==B case.' \"\n+     rm -f .git/index LL &&\n      cp .orig-A/LL LL &&\n      echo extra >>LL &&\n      git update-index --add LL &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '6 - must not exist in O && !A && !B case' \\\n-    \"rm -f .git/index DD &&\n+test_expect_success \\\n+    '6 - must not exist in O && !A && !B case' \"\n+     rm -f .git/index DD &&\n      echo DD >DD\n      git update-index --add DD &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '7 - must not exist in O && !A && B && O!=B case' \\\n-    \"rm -f .git/index DM &&\n+test_expect_success \\\n+    '7 - must not exist in O && !A && B && O!=B case' \"\n+     rm -f .git/index DM &&\n      cp .orig-B/DM DM &&\n      git update-index --add DM &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '8 - must not exist in O && !A && B && O==B case' \\\n-    \"rm -f .git/index DN &&\n+test_expect_success \\\n+    '8 - must not exist in O && !A && B && O==B case' \"\n+     rm -f .git/index DN &&\n      cp .orig-B/DN DN &&\n      git update-index --add DN &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '9 - must match and be up-to-date in O && A && !B && O!=A case' \\\n@@ -334,21 +341,23 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \\\n-    \"rm -f .git/index MD &&\n+test_expect_success \\\n+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \"\n+     rm -f .git/index MD &&\n      cp .orig-A/MD MD &&\n      git update-index --add MD &&\n      echo extra >>MD &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \\\n-    \"rm -f .git/index MD &&\n+test_expect_success \\\n+    '9 (fail) - must match and be up-to-date in O && A && !B && O!=A case' \"\n+     rm -f .git/index MD &&\n      cp .orig-A/MD MD &&\n      echo extra >>MD &&\n      git update-index --add MD &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '10 - must match and be up-to-date in O && A && !B && O==A case' \\\n@@ -358,21 +367,23 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \\\n-    \"rm -f .git/index ND &&\n+test_expect_success \\\n+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \"\n+     rm -f .git/index ND &&\n      cp .orig-A/ND ND &&\n      git update-index --add ND &&\n      echo extra >>ND &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \\\n-    \"rm -f .git/index ND &&\n+test_expect_success \\\n+    '10 (fail) - must match and be up-to-date in O && A && !B && O==A case' \"\n+     rm -f .git/index ND &&\n      cp .orig-A/ND ND &&\n      echo extra >>ND &&\n      git update-index --add ND &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '11 - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \\\n@@ -382,21 +393,23 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \\\n-    \"rm -f .git/index MM &&\n+test_expect_success \\\n+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \"\n+     rm -f .git/index MM &&\n      cp .orig-A/MM MM &&\n      git update-index --add MM &&\n      echo extra >>MM &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \\\n-    \"rm -f .git/index MM &&\n+test_expect_success \\\n+    '11 (fail) - must match and be up-to-date in O && A && B && O!=A && O!=B && A!=B case' \"\n+     rm -f .git/index MM &&\n      cp .orig-A/MM MM &&\n      echo extra >>MM &&\n      git update-index --add MM &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '12 - must match A in O && A && B && O!=A && A==B case' \\\n@@ -415,13 +428,14 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '12 (fail) - must match A in O && A && B && O!=A && A==B case' \\\n-    \"rm -f .git/index SS &&\n+test_expect_success \\\n+    '12 (fail) - must match A in O && A && B && O!=A && A==B case' \"\n+     rm -f .git/index SS &&\n      cp .orig-A/SS SS &&\n      echo extra >>SS &&\n      git update-index --add SS &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '13 - must match A in O && A && B && O!=A && O==B case' \\\n@@ -457,21 +471,23 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \\\n-    \"rm -f .git/index NM &&\n+test_expect_success \\\n+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \"\n+     rm -f .git/index NM &&\n      cp .orig-A/NM NM &&\n      git update-index --add NM &&\n      echo extra >>NM &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n-test_expect_failure \\\n-    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \\\n-    \"rm -f .git/index NM &&\n+test_expect_success \\\n+    '14 (fail) - must match and be up-to-date in O && A && B && O==A && O!=B case' \"\n+     rm -f .git/index NM &&\n      cp .orig-A/NM NM &&\n      echo extra >>NM &&\n      git update-index --add NM &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n test_expect_success \\\n     '15 - must match A in O && A && B && O==A && O==B case' \\\n@@ -490,13 +506,14 @@ test_expect_success \\\n      git read-tree -m $tree_O $tree_A $tree_B &&\n      check_result\"\n \n-test_expect_failure \\\n-    '15 (fail) - must match A in O && A && B && O==A && O==B case' \\\n-    \"rm -f .git/index NN &&\n+test_expect_success \\\n+    '15 (fail) - must match A in O && A && B && O==A && O==B case' \"\n+     rm -f .git/index NN &&\n      cp .orig-A/NN NN &&\n      echo extra >>NN &&\n      git update-index --add NN &&\n-     git read-tree -m $tree_O $tree_A $tree_B\"\n+     ! git read-tree -m $tree_O $tree_A $tree_B\n+\"\n \n # #16\n test_expect_success \\\ndiff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh\nindex 991d3c5..dcb3108 100755\n--- a/t/t1200-tutorial.sh\n+++ b/t/t1200-tutorial.sh\n@@ -101,8 +101,8 @@ echo \"Play, play, play\" >>hello\n echo \"Lots of fun\" >>example\n git commit -m 'Some fun.' -i hello example\n \n-test_expect_failure 'git resolve now fails' '\n-\tgit merge -m \"Merge work in mybranch\" mybranch\n+test_expect_success 'git resolve now fails' '\n+\t! git merge -m \"Merge work in mybranch\" mybranch\n '\n \n cat > hello << EOF\n@@ -156,6 +156,8 @@ test_expect_success 'git show-branch' 'cmp show-branch2.expect show-branch2.outp\n \n test_expect_success 'git repack' 'git repack'\n test_expect_success 'git prune-packed' 'git prune-packed'\n-test_expect_failure '-> only packed objects' 'find -type f .git/objects/[0-9a-f][0-9a-f]'\n+test_expect_success '-> only packed objects' '\n+\t! find -type f .git/objects/[0-9a-f][0-9a-f]\n+'\n \n test_done\ndiff --git a/t/t1300-repo-config.sh b/t/t1300-repo-config.sh\nindex 42eac2a..a786c5c 100755\n--- a/t/t1300-repo-config.sh\n+++ b/t/t1300-repo-config.sh\n@@ -181,8 +181,9 @@ test_expect_success 'non-match' \\\n test_expect_success 'non-match value' \\\n \t'test wow = $(git config --get nextsection.nonewline !for)'\n \n-test_expect_failure 'ambiguous get' \\\n-\t'git config --get nextsection.nonewline'\n+test_expect_success 'ambiguous get' '\n+\t! git config --get nextsection.nonewline\n+'\n \n test_expect_success 'get multivar' \\\n \t'git config --get-all nextsection.nonewline'\n@@ -202,13 +203,17 @@ EOF\n \n test_expect_success 'multivar replace' 'cmp .git/config expect'\n \n-test_expect_failure 'ambiguous value' 'git config nextsection.nonewline'\n+test_expect_success 'ambiguous value' '\n+\t! git config nextsection.nonewline\n+'\n \n-test_expect_failure 'ambiguous unset' \\\n-\t'git config --unset nextsection.nonewline'\n+test_expect_success 'ambiguous unset' '\n+\t! git config --unset nextsection.nonewline\n+'\n \n-test_expect_failure 'invalid unset' \\\n-\t'git config --unset somesection.nonewline'\n+test_expect_success 'invalid unset' '\n+\t! git config --unset somesection.nonewline\n+'\n \n git config --unset nextsection.nonewline \"wow3$\"\n \n@@ -224,7 +229,7 @@ EOF\n \n test_expect_success 'multivar unset' 'cmp .git/config expect'\n \n-test_expect_failure 'invalid key' 'git config inval.2key blabla'\n+test_expect_success 'invalid key' '! git config inval.2key blabla'\n \n test_expect_success 'correct key' 'git config 123456.a123 987'\n \n@@ -382,8 +387,9 @@ EOF\n \n test_expect_success \"rename succeeded\" \"git diff expect .git/config\"\n \n-test_expect_failure \"rename non-existing section\" \\\n-\t'git config --rename-section branch.\"world domination\" branch.drei'\n+test_expect_success \"rename non-existing section\" '\n+\t! git config --rename-section branch.\"world domination\" branch.drei\n+'\n \n test_expect_success \"rename succeeded\" \"git diff expect .git/config\"\n \n@@ -494,14 +500,14 @@ test_expect_success bool '\n         done &&\n \tcmp expect result'\n \n-test_expect_failure 'invalid bool (--get)' '\n+test_expect_success 'invalid bool (--get)' '\n \n \tgit config bool.nobool foobar &&\n-\tgit config --bool --get bool.nobool'\n+\t! git config --bool --get bool.nobool'\n \n-test_expect_failure 'invalid bool (set)' '\n+test_expect_success 'invalid bool (set)' '\n \n-\tgit config --bool bool.nobool foobar'\n+\t! git config --bool bool.nobool foobar'\n \n rm .git/config\n \n@@ -562,8 +568,9 @@ EOF\n \n test_expect_success 'quoting' 'cmp .git/config expect'\n \n-test_expect_failure 'key with newline' 'git config key.with\\\\\\\n-newline 123'\n+test_expect_success 'key with newline' '\n+\t! git config \"key.with\n+newline\" 123'\n \n test_expect_success 'value with newline' 'git config key.sub value.with\\\\\\\n newline'\ndiff --git a/t/t1302-repo-version.sh b/t/t1302-repo-version.sh\nindex 37fc1c8..9be0770 100755\n--- a/t/t1302-repo-version.sh\n+++ b/t/t1302-repo-version.sh\n@@ -40,7 +40,8 @@ test_expect_success 'gitdir required mode on normal repos' '\n \t(git apply --check --index test.patch &&\n \tcd test && git apply --check --index ../test.patch)'\n \n-test_expect_failure 'gitdir required mode on unsupported repo' '\n-\t(cd test2 && git apply --check --index ../test.patch)'\n+test_expect_success 'gitdir required mode on unsupported repo' '\n+\t(cd test2 && ! git apply --check --index ../test.patch)\n+'\n \n test_done\ndiff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh\nindex 71ab2dd..78cd412 100755\n--- a/t/t1400-update-ref.sh\n+++ b/t/t1400-update-ref.sh\n@@ -51,23 +51,23 @@ test_expect_success \\\n \t test $B\"' = $(cat .git/'\"$m\"')'\n rm -f .git/$m\n \n-test_expect_failure \\\n-\t'(not) create HEAD with old sha1' \\\n-\t\"git update-ref HEAD $A $B\"\n-test_expect_failure \\\n-\t\"(not) prior created .git/$m\" \\\n-\t\"test -f .git/$m\"\n+test_expect_success '(not) create HEAD with old sha1' \"\n+\t! git update-ref HEAD $A $B\n+\"\n+test_expect_success \"(not) prior created .git/$m\" \"\n+\t! test -f .git/$m\n+\"\n rm -f .git/$m\n \n test_expect_success \\\n \t\"create HEAD\" \\\n \t\"git update-ref HEAD $A\"\n-test_expect_failure \\\n-\t'(not) change HEAD with wrong SHA1' \\\n-\t\"git update-ref HEAD $B $Z\"\n-test_expect_failure \\\n-\t\"(not) changed .git/$m\" \\\n-\t\"test $B\"' = $(cat .git/'\"$m\"')'\n+test_expect_success '(not) change HEAD with wrong SHA1' \"\n+\t! git update-ref HEAD $B $Z\n+\"\n+test_expect_success \"(not) changed .git/$m\" \"\n+\t! test $B\"' = $(cat .git/'\"$m\"')\n+'\n rm -f .git/$m\n \n : a repository with working tree always has reflog these days...\ndiff --git a/t/t2000-checkout-cache-clash.sh b/t/t2000-checkout-cache-clash.sh\nindex ac84335..5141fab 100755\n--- a/t/t2000-checkout-cache-clash.sh\n+++ b/t/t2000-checkout-cache-clash.sh\n@@ -36,9 +36,9 @@ mkdir path0\n date >path0/file0\n date >path1\n \n-test_expect_failure \\\n+test_expect_success \\\n     'git checkout-index without -f should fail on conflicting work tree.' \\\n-    'git checkout-index -a'\n+    '! git checkout-index -a'\n \n test_expect_success \\\n     'git checkout-index with -f should succeed.' \\\ndiff --git a/t/t2002-checkout-cache-u.sh b/t/t2002-checkout-cache-u.sh\nindex f7a0055..0f441bc 100755\n--- a/t/t2002-checkout-cache-u.sh\n+++ b/t/t2002-checkout-cache-u.sh\n@@ -16,12 +16,12 @@ echo frotz >path0 &&\n git update-index --add path0 &&\n t=$(git write-tree)'\n \n-test_expect_failure \\\n+test_expect_success \\\n 'without -u, git checkout-index smudges stat information.' '\n rm -f path0 &&\n git read-tree $t &&\n git checkout-index -f -a &&\n-git diff-files | diff - /dev/null'\n+! git diff-files | diff - /dev/null'\n \n test_expect_success \\\n 'with -u, git checkout-index picks up stat information from new files.' '\ndiff --git a/t/t2008-checkout-subdir.sh b/t/t2008-checkout-subdir.sh\nindex f78945e..4a723dc 100755\n--- a/t/t2008-checkout-subdir.sh\n+++ b/t/t2008-checkout-subdir.sh\n@@ -67,16 +67,16 @@ test_expect_success 'checkout with simple prefix' '\n \n '\n \n-test_expect_failure 'relative path outside tree should fail' \\\n-\t'git checkout HEAD -- ../../Makefile'\n+test_expect_success 'relative path outside tree should fail' \\\n+\t'! git checkout HEAD -- ../../Makefile'\n \n-test_expect_failure 'incorrect relative path to file should fail (1)' \\\n-\t'git checkout HEAD -- ../file0'\n+test_expect_success 'incorrect relative path to file should fail (1)' \\\n+\t'! git checkout HEAD -- ../file0'\n \n-test_expect_failure 'incorrect relative path should fail (2)' \\\n-\t'( cd dir1 && git checkout HEAD -- ./file0 )'\n+test_expect_success 'incorrect relative path should fail (2)' \\\n+\t'( cd dir1 && ! git checkout HEAD -- ./file0 )'\n \n-test_expect_failure 'incorrect relative path should fail (3)' \\\n-\t'( cd dir1 && git checkout HEAD -- ../../file0 )'\n+test_expect_success 'incorrect relative path should fail (3)' \\\n+\t'( cd dir1 && ! git checkout HEAD -- ../../file0 )'\n \n test_done\ndiff --git a/t/t2100-update-cache-badpath.sh b/t/t2100-update-cache-badpath.sh\nindex 04a1ed1..9beaecd 100755\n--- a/t/t2100-update-cache-badpath.sh\n+++ b/t/t2100-update-cache-badpath.sh\n@@ -44,8 +44,8 @@ date >path1/file1\n \n for p in path0/file0 path1/file1 path2 path3\n do\n-\ttest_expect_failure \\\n+\ttest_expect_success \\\n \t    \"git update-index to add conflicting path $p should fail.\" \\\n-\t    \"git update-index --add -- $p\"\n+\t    \"! git update-index --add -- $p\"\n done\n test_done\ndiff --git a/t/t3020-ls-files-error-unmatch.sh b/t/t3020-ls-files-error-unmatch.sh\nindex c83f820..f4da869 100755\n--- a/t/t3020-ls-files-error-unmatch.sh\n+++ b/t/t3020-ls-files-error-unmatch.sh\n@@ -15,9 +15,9 @@ touch foo bar\n git update-index --add foo bar\n git-commit -m \"add foo bar\"\n \n-test_expect_failure \\\n+test_expect_success \\\n     'git ls-files --error-unmatch should fail with unmatched path.' \\\n-    'git ls-files --error-unmatch foo bar-does-not-match'\n+    '! git ls-files --error-unmatch foo bar-does-not-match'\n \n test_expect_success \\\n     'git ls-files --error-unmatch should succeed eith matched paths.' \\\ndiff --git a/t/t3200-branch.sh b/t/t3200-branch.sh\nindex ef1eeb7..fe353ff 100755\n--- a/t/t3200-branch.sh\n+++ b/t/t3200-branch.sh\n@@ -17,10 +17,11 @@ test_expect_success \\\n      git-commit -m \"Initial commit.\" &&\n      HEAD=$(git rev-parse --verify HEAD)'\n \n-test_expect_failure \\\n-    'git branch --help should not have created a bogus branch' \\\n-    'git branch --help </dev/null >/dev/null 2>/dev/null || :\n-     test -f .git/refs/heads/--help'\n+test_expect_success \\\n+    'git branch --help should not have created a bogus branch' '\n+     git branch --help </dev/null >/dev/null 2>/dev/null;\n+     ! test -f .git/refs/heads/--help\n+'\n \n test_expect_success \\\n     'git branch abc should create a branch' \\\n@@ -71,17 +72,17 @@ test_expect_success \\\n         git branch -m n/n n\n         test -f .git/logs/refs/heads/n'\n \n-test_expect_failure \\\n-    'git branch -m o/o o should fail when o/p exists' \\\n-       'git branch o/o &&\n+test_expect_success 'git branch -m o/o o should fail when o/p exists' '\n+        git branch o/o &&\n         git branch o/p &&\n-        git branch -m o/o o'\n+        ! git branch -m o/o o\n+'\n \n-test_expect_failure \\\n-    'git branch -m q r/q should fail when r exists' \\\n-       'git branch q &&\n-         git branch r &&\n-         git branch -m q r/q'\n+test_expect_success 'git branch -m q r/q should fail when r exists' '\n+        git branch q &&\n+        git branch r &&\n+        ! git branch -m q r/q\n+'\n \n mv .git/config .git/config-saved\n \n@@ -108,12 +109,13 @@ test_expect_success 'config information was renamed, too' \\\n \t\"test $(git config branch.s.dummy) = Hello &&\n \t ! git config branch.s/s/dummy\"\n \n-test_expect_failure \\\n-    'git branch -m u v should fail when the reflog for u is a symlink' \\\n-    'git branch -l u &&\n+test_expect_success \\\n+    'git branch -m u v should fail when the reflog for u is a symlink' '\n+     git branch -l u &&\n      mv .git/logs/refs/heads/u real-u &&\n      ln -s real-u .git/logs/refs/heads/u &&\n-     git branch -m u v'\n+     ! git branch -m u v\n+'\n \n test_expect_success 'test tracking setup via --track' \\\n     'git config remote.local.url . &&\ndiff --git a/t/t3210-pack-refs.sh b/t/t3210-pack-refs.sh\nindex 4ddc634..b64ccfb 100755\n--- a/t/t3210-pack-refs.sh\n+++ b/t/t3210-pack-refs.sh\n@@ -39,12 +39,12 @@ test_expect_success \\\n      git show-ref b >result &&\n      diff expect result'\n \n-test_expect_failure \\\n-    'git branch c/d should barf if branch c exists' \\\n-    'git branch c &&\n+test_expect_success 'git branch c/d should barf if branch c exists' '\n+     git branch c &&\n      git pack-refs --all &&\n-     rm .git/refs/heads/c &&\n-     git branch c/d'\n+     rm -f .git/refs/heads/c &&\n+     ! git branch c/d\n+'\n \n test_expect_success \\\n     'see if a branch still exists after git pack-refs --prune' \\\n@@ -54,11 +54,11 @@ test_expect_success \\\n      git show-ref e >result &&\n      diff expect result'\n \n-test_expect_failure \\\n-    'see if git pack-refs --prune remove ref files' \\\n-    'git branch f &&\n+test_expect_success 'see if git pack-refs --prune remove ref files' '\n+     git branch f &&\n      git pack-refs --all --prune &&\n-     ls .git/refs/heads/f'\n+     ! test -f .git/refs/heads/f\n+'\n \n test_expect_success \\\n     'git branch g should work when git branch g/h has been deleted' \\\n@@ -69,11 +69,11 @@ test_expect_success \\\n      git pack-refs --all &&\n      git branch -d g'\n \n-test_expect_failure \\\n-    'git branch i/j/k should barf if branch i exists' \\\n-    'git branch i &&\n+test_expect_success 'git branch i/j/k should barf if branch i exists' '\n+     git branch i &&\n      git pack-refs --all --prune &&\n-     git branch i/j/k'\n+     ! git branch i/j/k\n+'\n \n test_expect_success \\\n     'test git branch k after branch k/l/m and k/lm have been deleted' \\\ndiff --git a/t/t3400-rebase.sh b/t/t3400-rebase.sh\nindex 95e33b5..496f4ec 100755\n--- a/t/t3400-rebase.sh\n+++ b/t/t3400-rebase.sh\n@@ -42,9 +42,9 @@ test_expect_success \\\n test_expect_success 'rebase against master' '\n      git rebase master'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'the rebase operation should not have destroyed author information' \\\n-    'git log | grep \"Author:\" | grep \"<>\"'\n+    '! git log | grep \"Author:\" | grep \"<>\"'\n \n test_expect_success 'rebase after merge master' '\n      git reset --hard topic &&\ndiff --git a/t/t3403-rebase-skip.sh b/t/t3403-rebase-skip.sh\nindex 657f681..0a26099 100755\n--- a/t/t3403-rebase-skip.sh\n+++ b/t/t3403-rebase-skip.sh\n@@ -31,8 +31,8 @@ test_expect_success setup '\n \tgit branch skip-merge skip-reference\n \t'\n \n-test_expect_failure 'rebase with git am -3 (default)' '\n-\tgit rebase master\n+test_expect_success 'rebase with git am -3 (default)' '\n+\t! git rebase master\n '\n \n test_expect_success 'rebase --skip with am -3' '\n@@ -53,7 +53,7 @@ test_expect_success 'rebase moves back to skip-reference' '\n \n test_expect_success 'checkout skip-merge' 'git checkout -f skip-merge'\n \n-test_expect_failure 'rebase with --merge' 'git rebase --merge master'\n+test_expect_success 'rebase with --merge' '! git rebase --merge master'\n \n test_expect_success 'rebase --skip with --merge' '\n \tgit rebase --skip\ndiff --git a/t/t3600-rm.sh b/t/t3600-rm.sh\nindex b1ee622..f542f0a 100755\n--- a/t/t3600-rm.sh\n+++ b/t/t3600-rm.sh\n@@ -59,15 +59,16 @@ test_expect_success \\\n      echo \"other content\" > foo\n      git rm --cached foo'\n \n-test_expect_failure \\\n-    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' \\\n-    'echo content > foo\n+test_expect_success \\\n+    'Test that git rm --cached foo fails if the index matches neither the file nor HEAD' '\n+     echo content > foo\n      git add foo\n      git commit -m foo\n      echo \"other content\" > foo\n      git add foo\n      echo \"yet another content\" > foo\n-     git rm --cached foo'\n+     ! git rm --cached foo\n+'\n \n test_expect_success \\\n     'Test that git rm --cached -f foo works in case where --cached only did not' \\\n@@ -106,9 +107,9 @@ embedded'\"\n \n if test \"$test_failed_remove\" = y; then\n chmod a-w .\n-test_expect_failure \\\n+test_expect_success \\\n     'Test that \"git rm -f\" fails if its rm fails' \\\n-    'git rm -f baz'\n+    '! git rm -f baz'\n chmod 775 .\n else\n     test_expect_success 'skipping removal failure (perhaps running as root?)' :\n@@ -212,8 +213,8 @@ test_expect_success 'Recursive with -r -f' '\n \t! test -d frotz\n '\n \n-test_expect_failure 'Remove nonexistent file returns nonzero exit status' '\n-\tgit rm nonexistent\n+test_expect_success 'Remove nonexistent file returns nonzero exit status' '\n+\t! git rm nonexistent\n '\n \n test_done\ndiff --git a/t/t4103-apply-binary.sh b/t/t4103-apply-binary.sh\nindex 74f06ec..7c25634 100755\n--- a/t/t4103-apply-binary.sh\n+++ b/t/t4103-apply-binary.sh\n@@ -46,21 +46,25 @@ test_expect_success 'stat binary diff (copy) -- should not fail.' \\\n \t'git-checkout master\n \t git apply --stat --summary C.diff'\n \n-test_expect_failure 'check binary diff -- should fail.' \\\n-\t'git-checkout master\n-\t git apply --check B.diff'\n-\n-test_expect_failure 'check binary diff (copy) -- should fail.' \\\n-\t'git-checkout master\n-\t git apply --check C.diff'\n-\n-test_expect_failure 'check incomplete binary diff with replacement -- should fail.' \\\n-\t'git-checkout master\n-\t git apply --check --allow-binary-replacement B.diff'\n+test_expect_success 'check binary diff -- should fail.' \\\n+\t'git-checkout master &&\n+\t ! git apply --check B.diff'\n+\n+test_expect_success 'check binary diff (copy) -- should fail.' \\\n+\t'git-checkout master &&\n+\t ! git apply --check C.diff'\n+\n+test_expect_success \\\n+\t'check incomplete binary diff with replacement -- should fail.' '\n+\tgit-checkout master &&\n+\t! git apply --check --allow-binary-replacement B.diff\n+'\n \n-test_expect_failure 'check incomplete binary diff with replacement (copy) -- should fail.' \\\n-\t'git-checkout master\n-\t git apply --check --allow-binary-replacement C.diff'\n+test_expect_success \\\n+    'check incomplete binary diff with replacement (copy) -- should fail.' '\n+\t git-checkout master &&\n+\t ! git apply --check --allow-binary-replacement C.diff\n+'\n \n test_expect_success 'check binary diff with replacement.' \\\n \t'git-checkout master\n@@ -73,42 +77,42 @@ test_expect_success 'check binary diff with replacement (copy).' \\\n # Now we start applying them.\n \n do_reset () {\n-\trm -f file?\n-\tgit-reset --hard\n+\trm -f file? &&\n+\tgit-reset --hard &&\n \tgit-checkout -f master\n }\n \n-test_expect_failure 'apply binary diff -- should fail.' \\\n-\t'do_reset\n-\t git apply B.diff'\n+test_expect_success 'apply binary diff -- should fail.' \\\n+\t'do_reset &&\n+\t ! git apply B.diff'\n \n-test_expect_failure 'apply binary diff -- should fail.' \\\n-\t'do_reset\n-\t git apply --index B.diff'\n+test_expect_success 'apply binary diff -- should fail.' \\\n+\t'do_reset &&\n+\t ! git apply --index B.diff'\n \n-test_expect_failure 'apply binary diff (copy) -- should fail.' \\\n-\t'do_reset\n-\t git apply C.diff'\n+test_expect_success 'apply binary diff (copy) -- should fail.' \\\n+\t'do_reset &&\n+\t ! git apply C.diff'\n \n-test_expect_failure 'apply binary diff (copy) -- should fail.' \\\n-\t'do_reset\n-\t git apply --index C.diff'\n+test_expect_success 'apply binary diff (copy) -- should fail.' \\\n+\t'do_reset &&\n+\t ! git apply --index C.diff'\n \n test_expect_success 'apply binary diff without replacement.' \\\n-\t'do_reset\n+\t'do_reset &&\n \t git apply BF.diff'\n \n test_expect_success 'apply binary diff without replacement (copy).' \\\n-\t'do_reset\n+\t'do_reset &&\n \t git apply CF.diff'\n \n test_expect_success 'apply binary diff.' \\\n-\t'do_reset\n+\t'do_reset &&\n \t git apply --allow-binary-replacement --index BF.diff &&\n \t test -z \"$(git diff --name-status binary)\"'\n \n test_expect_success 'apply binary diff (copy).' \\\n-\t'do_reset\n+\t'do_reset &&\n \t git apply --allow-binary-replacement --index CF.diff &&\n \t test -z \"$(git diff --name-status binary)\"'\n \ndiff --git a/t/t4113-apply-ending.sh b/t/t4113-apply-ending.sh\nindex 1c6bec0..d741039 100755\n--- a/t/t4113-apply-ending.sh\n+++ b/t/t4113-apply-ending.sh\n@@ -29,8 +29,8 @@ test_expect_success setup \\\n \n # test\n \n-test_expect_failure 'apply at the end' \\\n-    'git apply --index test-patch'\n+test_expect_success 'apply at the end' \\\n+    '! git apply --index test-patch'\n \n cat >test-patch <<\\EOF\n diff a/file b/file\n@@ -47,7 +47,7 @@ b\n c'\n git update-index file\n \n-test_expect_failure 'apply at the beginning' \\\n-\t'git apply --index test-patch'\n+test_expect_success 'apply at the beginning' \\\n+\t'! git apply --index test-patch'\n \n test_done\ndiff --git a/t/t5300-pack-object.sh b/t/t5300-pack-object.sh\nindex 6e594bf..4f350dd 100755\n--- a/t/t5300-pack-object.sh\n+++ b/t/t5300-pack-object.sh\n@@ -264,8 +264,8 @@ test_expect_success \\\n      cp -f\t.git/objects/9d/235ed07cd19811a6ceb342de82f190e49c9f68 \\\n \t\t.git/objects/c8/2de19312b6c3695c0c18f70709a6c535682a67'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'make sure index-pack detects the SHA1 collision' \\\n-    'git-index-pack -o bad.idx test-3.pack'\n+    '! git-index-pack -o bad.idx test-3.pack'\n \n test_done\ndiff --git a/t/t5302-pack-index.sh b/t/t5302-pack-index.sh\nindex 2a2878b..67b9a7b 100755\n--- a/t/t5302-pack-index.sh\n+++ b/t/t5302-pack-index.sh\n@@ -42,9 +42,9 @@ test_expect_success \\\n     'both packs should be identical' \\\n     'cmp \"test-1-${pack1}.pack\" \"test-2-${pack2}.pack\"'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'index v1 and index v2 should be different' \\\n-    'cmp \"test-1-${pack1}.idx\" \"test-2-${pack2}.idx\"'\n+    '! cmp \"test-1-${pack1}.idx\" \"test-2-${pack2}.idx\"'\n \n test_expect_success \\\n     'index-pack with index version 1' \\\n@@ -78,9 +78,9 @@ test_expect_success \\\n     'git verify-pack -v \"test-3-${pack3}.pack\"'\n \n test \"$have_64bits\" &&\n-test_expect_failure \\\n+test_expect_success \\\n     '64-bit offsets: should be different from previous index v2 results' \\\n-    'cmp \"test-2-${pack2}.idx\" \"test-3-${pack3}.idx\"'\n+    '! cmp \"test-2-${pack2}.idx\" \"test-3-${pack3}.idx\"'\n \n test \"$have_64bits\" &&\n test_expect_success \\\n@@ -112,22 +112,22 @@ test_expect_success \\\n \t  bs=1 count=20 conv=notrunc &&\n        git cat-file blob \"$delta_sha1\" > blob_2 )'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v1] 3) corrupted delta happily returned wrong data' \\\n-    'cmp blob_1 blob_2'\n+    '! cmp blob_1 blob_2'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v1] 4) confirm that the pack is actually corrupted' \\\n-    'git fsck --full $commit'\n+    '! git fsck --full $commit'\n \n test_expect_success \\\n     '[index v1] 5) pack-objects happily reuses corrupted data' \\\n     'pack4=$(git pack-objects test-4 <obj-list) &&\n      test -f \"test-4-${pack1}.pack\"'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v1] 6) newly created pack is BAD !' \\\n-    'git verify-pack -v \"test-4-${pack1}.pack\"'\n+    '! git verify-pack -v \"test-4-${pack1}.pack\"'\n \n test_expect_success \\\n     '[index v2] 1) stream pack to repository' \\\n@@ -150,16 +150,16 @@ test_expect_success \\\n \t  bs=1 count=20 conv=notrunc &&\n        git cat-file blob \"$delta_sha1\" > blob_4 )'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v2] 3) corrupted delta happily returned wrong data' \\\n-    'cmp blob_3 blob_4'\n+    '! cmp blob_3 blob_4'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v2] 4) confirm that the pack is actually corrupted' \\\n-    'git fsck --full $commit'\n+    '! git fsck --full $commit'\n \n-test_expect_failure \\\n+test_expect_success \\\n     '[index v2] 5) pack-objects refuses to reuse corrupted data' \\\n-    'git pack-objects test-5 <obj-list'\n+    '! git pack-objects test-5 <obj-list'\n \n test_done\ndiff --git a/t/t5401-update-hooks.sh b/t/t5401-update-hooks.sh\nindex 9734fc5..9a12024 100755\n--- a/t/t5401-update-hooks.sh\n+++ b/t/t5401-update-hooks.sh\n@@ -60,8 +60,8 @@ echo STDERR post-update >&2\n EOF\n chmod u+x victim/.git/hooks/post-update\n \n-test_expect_failure push '\n-\tgit-send-pack --force ./victim/.git master tofail >send.out 2>send.err\n+test_expect_success push '\n+    ! git-send-pack --force ./victim/.git master tofail >send.out 2>send.err\n '\n \n test_expect_success 'updated as expected' '\n@@ -112,8 +112,8 @@ test_expect_success 'all *-receive hook args are empty' '\n \t! test -s victim/.git/post-receive.args\n '\n \n-test_expect_failure 'send-pack produced no output' '\n-\ttest -s send.out\n+test_expect_success 'send-pack produced no output' '\n+\t! test -s send.out\n '\n \n cat <<EOF >expect\ndiff --git a/t/t5402-post-merge-hook.sh b/t/t5402-post-merge-hook.sh\nindex 1c4b0b3..1394047 100755\n--- a/t/t5402-post-merge-hook.sh\n+++ b/t/t5402-post-merge-hook.sh\n@@ -30,9 +30,9 @@ EOF\n     chmod u+x clone${clone}/.git/hooks/post-merge\n done\n \n-test_expect_failure 'post-merge does not run for up-to-date ' '\n+test_expect_success 'post-merge does not run for up-to-date ' '\n         GIT_DIR=clone1/.git git merge $commit0 &&\n-\ttest -e clone1/.git/post-merge.args\n+\t! test -f clone1/.git/post-merge.args\n '\n \n test_expect_success 'post-merge runs as expected ' '\ndiff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh\nindex 7b6798d..788b4a5 100755\n--- a/t/t5500-fetch-pack.sh\n+++ b/t/t5500-fetch-pack.sh\n@@ -176,7 +176,7 @@ test_expect_success \"deepening fetch in shallow repo\" \\\n test_expect_success \"clone shallow object count\" \\\n \t\"test \\\"count: 18\\\" = \\\"$(grep count count.shallow)\\\"\"\n \n-test_expect_failure \"pull in shallow repo with missing merge base\" \\\n-\t\"(cd shallow; git pull --depth 4 .. A)\"\n+test_expect_success \"pull in shallow repo with missing merge base\" \\\n+\t\"(cd shallow && ! git pull --depth 4 .. A)\"\n \n test_done\ndiff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh\nindex 02882c1..9b948c1 100755\n--- a/t/t5510-fetch.sh\n+++ b/t/t5510-fetch.sh\n@@ -95,7 +95,7 @@ test_expect_success 'fetch following tags' '\n \n '\n \n-test_expect_failure 'fetch must not resolve short tag name' '\n+test_expect_success 'fetch must not resolve short tag name' '\n \n \tcd \"$D\" &&\n \n@@ -103,11 +103,11 @@ test_expect_failure 'fetch must not resolve short tag name' '\n \tcd five &&\n \tgit init &&\n \n-\tgit fetch .. anno:five\n+\t! git fetch .. anno:five\n \n '\n \n-test_expect_failure 'fetch must not resolve short remote name' '\n+test_expect_success 'fetch must not resolve short remote name' '\n \n \tcd \"$D\" &&\n \tgit-update-ref refs/remotes/six/HEAD HEAD\n@@ -116,7 +116,7 @@ test_expect_failure 'fetch must not resolve short remote name' '\n \tcd six &&\n \tgit init &&\n \n-\tgit fetch .. six:six\n+\t! git fetch .. six:six\n \n '\n \n@@ -139,10 +139,10 @@ test_expect_success 'create bundle 2' '\n \tgit bundle create bundle2 master~2..master\n '\n \n-test_expect_failure 'unbundle 1' '\n+test_expect_success 'unbundle 1' '\n \tcd \"$D/bundle\" &&\n \tgit checkout -b some-branch &&\n-\tgit fetch \"$D/bundle1\" master:master\n+\t! git fetch \"$D/bundle1\" master:master\n '\n \n test_expect_success 'bundle 1 has only 3 files ' '\ndiff --git a/t/t5530-upload-pack-error.sh b/t/t5530-upload-pack-error.sh\nindex cc8949e..8b05091 100755\n--- a/t/t5530-upload-pack-error.sh\n+++ b/t/t5530-upload-pack-error.sh\n@@ -26,9 +26,8 @@ test_expect_success 'setup and corrupt repository' '\n \n '\n \n-test_expect_failure 'fsck fails' '\n-\n-\tgit fsck\n+test_expect_success 'fsck fails' '\n+\t! git fsck\n '\n \n test_expect_success 'upload-pack fails due to error in pack-objects' '\n@@ -46,9 +45,8 @@ test_expect_success 'corrupt repo differently' '\n \n '\n \n-test_expect_failure 'fsck fails' '\n-\n-\tgit fsck\n+test_expect_success 'fsck fails' '\n+\t! git fsck\n '\n test_expect_success 'upload-pack fails due to error in rev-list' '\n \n@@ -66,9 +64,9 @@ test_expect_success 'create empty repository' '\n \n '\n \n-test_expect_failure 'fetch fails' '\n+test_expect_success 'fetch fails' '\n \n-\tgit fetch .. master\n+\t! git fetch .. master\n \n '\n \ndiff --git a/t/t5600-clone-fail-cleanup.sh b/t/t5600-clone-fail-cleanup.sh\nindex 1776b37..acf34ce 100755\n--- a/t/t5600-clone-fail-cleanup.sh\n+++ b/t/t5600-clone-fail-cleanup.sh\n@@ -11,13 +11,13 @@ remove the directory before attempting a clone again.'\n \n . ./test-lib.sh\n \n-test_expect_failure \\\n+test_expect_success \\\n     'clone of non-existent source should fail' \\\n-    'git-clone foo bar'\n+    '! git-clone foo bar'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'failed clone should not leave a directory' \\\n-    'cd bar'\n+    '! test -d bar'\n \n # Need a repo to clone\n test_create_repo foo\n@@ -27,9 +27,9 @@ test_create_repo foo\n \n # source repository given to git-clone should be relative to the\n # current path not to the target dir\n-test_expect_failure \\\n+test_expect_success \\\n     'clone of non-existent (relative to $PWD) source should fail' \\\n-    'git-clone ../foo baz'\n+    '! git-clone ../foo baz'\n \n test_expect_success \\\n     'clone should work now that source exists' \\\ndiff --git a/t/t5710-info-alternate.sh b/t/t5710-info-alternate.sh\nindex 1908dc8..910ccb4 100755\n--- a/t/t5710-info-alternate.sh\n+++ b/t/t5710-info-alternate.sh\n@@ -87,10 +87,10 @@ test_valid_repo\"\n \n cd \"$base_dir\"\n \n-test_expect_failure 'that info/alternates is necessary' \\\n+test_expect_success 'that info/alternates is necessary' \\\n 'cd C &&\n-rm .git/objects/info/alternates &&\n-test_valid_repo'\n+rm -f .git/objects/info/alternates &&\n+! (test_valid_repo)'\n \n cd \"$base_dir\"\n \n@@ -101,9 +101,11 @@ test_valid_repo'\n \n cd \"$base_dir\"\n \n-test_expect_failure 'that relative alternate is only possible for current dir' \\\n-'cd D &&\n-test_valid_repo'\n+test_expect_success \\\n+    'that relative alternate is only possible for current dir' '\n+    cd D &&\n+    ! (test_valid_repo)\n+'\n \n cd \"$base_dir\"\n \ndiff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh\nindex ae3b6f2..8641996 100755\n--- a/t/t6023-merge-file.sh\n+++ b/t/t6023-merge-file.sh\n@@ -66,8 +66,8 @@ test_expect_success \"merge result added missing LF\" \\\n \t\"git diff test.txt test2.txt\"\n \n cp test.txt backup.txt\n-test_expect_failure \"merge with conflicts\" \\\n-\t\"git merge-file test.txt orig.txt new3.txt\"\n+test_expect_success \"merge with conflicts\" \\\n+\t\"! git merge-file test.txt orig.txt new3.txt\"\n \n cat > expect.txt << EOF\n <<<<<<< test.txt\n@@ -89,8 +89,8 @@ EOF\n test_expect_success \"expected conflict markers\" \"git diff test.txt expect.txt\"\n \n cp backup.txt test.txt\n-test_expect_failure \"merge with conflicts, using -L\" \\\n-\t\"git merge-file -L 1 -L 2 test.txt orig.txt new3.txt\"\n+test_expect_success \"merge with conflicts, using -L\" \\\n+\t\"! git merge-file -L 1 -L 2 test.txt orig.txt new3.txt\"\n \n cat > expect.txt << EOF\n <<<<<<< 1\n@@ -113,8 +113,8 @@ test_expect_success \"expected conflict markers, with -L\" \\\n \t\"git diff test.txt expect.txt\"\n \n sed \"s/ tu / TU /\" < new1.txt > new5.txt\n-test_expect_failure \"conflict in removed tail\" \\\n-\t\"git merge-file -p orig.txt new1.txt new5.txt > out\"\n+test_expect_success \"conflict in removed tail\" \\\n+\t\"! git merge-file -p orig.txt new1.txt new5.txt > out\"\n \n cat > expect << EOF\n Dominus regit me,\ndiff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh\nindex c154f03..149ea85 100755\n--- a/t/t6024-recursive-merge.sh\n+++ b/t/t6024-recursive-merge.sh\n@@ -60,7 +60,7 @@ git update-index a1 &&\n GIT_AUTHOR_DATE=\"2006-12-12 23:00:08\" git commit -m F\n '\n \n-test_expect_failure \"combined merge conflicts\" \"git merge -m final G\"\n+test_expect_success \"combined merge conflicts\" \"! git merge -m final G\"\n \n cat > expect << EOF\n <<<<<<< HEAD:a1\ndiff --git a/t/t6025-merge-symlinks.sh b/t/t6025-merge-symlinks.sh\nindex 950c2e9..6004deb 100755\n--- a/t/t6025-merge-symlinks.sh\n+++ b/t/t6025-merge-symlinks.sh\n@@ -30,30 +30,29 @@ echo plain-file > symlink &&\n git add symlink &&\n git-commit -m b-file'\n \n-test_expect_failure \\\n+test_expect_success \\\n 'merge master into b-symlink, which has a different symbolic link' '\n-! git-checkout b-symlink ||\n-git-merge master'\n+git-checkout b-symlink &&\n+! git-merge master'\n \n test_expect_success \\\n 'the merge result must be a file' '\n test -f symlink'\n \n-test_expect_failure \\\n+test_expect_success \\\n 'merge master into b-file, which has a file instead of a symbolic link' '\n-! (git-reset --hard &&\n-git-checkout b-file) ||\n-git-merge master'\n+git-reset --hard && git-checkout b-file &&\n+! git-merge master'\n \n test_expect_success \\\n 'the merge result must be a file' '\n test -f symlink'\n \n-test_expect_failure \\\n+test_expect_success \\\n 'merge b-file, which has a file instead of a symbolic link, into master' '\n-! (git-reset --hard &&\n-git-checkout master) ||\n-git-merge b-file'\n+git-reset --hard &&\n+git-checkout master &&\n+! git-merge b-file'\n \n test_expect_success \\\n 'the merge result must be a file' '\ndiff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh\nindex 0724864..2328b69 100755\n--- a/t/t6101-rev-parse-parents.sh\n+++ b/t/t6101-rev-parse-parents.sh\n@@ -26,7 +26,7 @@ test_expect_success 'final^1^1^1 = final^^^' \"test $(git rev-parse final^1^1^1)\n test_expect_success 'final^1^2' \"test $(git rev-parse start2) = $(git rev-parse final^1^2)\"\n test_expect_success 'final^1^2 != final^1^1' \"test $(git rev-parse final^1^2) != $(git rev-parse final^1^1)\"\n test_expect_success 'final^1^3 not valid' \"if git rev-parse --verify final^1^3; then false; else :; fi\"\n-test_expect_failure '--verify start2^1' 'git rev-parse --verify start2^1'\n+test_expect_success '--verify start2^1' '! git rev-parse --verify start2^1'\n test_expect_success '--verify start2^0' 'git rev-parse --verify start2^0'\n \n test_expect_success 'repack for next test' 'git repack -a -d'\ndiff --git a/t/t6300-for-each-ref.sh b/t/t6300-for-each-ref.sh\nindex 8a23aaf..f46ec93 100755\n--- a/t/t6300-for-each-ref.sh\n+++ b/t/t6300-for-each-ref.sh\n@@ -43,8 +43,8 @@ test_expect_success 'Check atom names are valid' '\n \ttest -z \"$bad\"\n '\n \n-test_expect_failure 'Check invalid atoms names are errors' '\n-\tgit-for-each-ref --format=\"%(INVALID)\" refs/heads\n+test_expect_success 'Check invalid atoms names are errors' '\n+\t! git-for-each-ref --format=\"%(INVALID)\" refs/heads\n '\n \n test_expect_success 'Check format specifiers are ignored in naming date atoms' '\n@@ -63,8 +63,8 @@ test_expect_success 'Check valid format specifiers for date fields' '\n \tgit-for-each-ref --format=\"%(authordate:rfc2822)\" refs/heads\n '\n \n-test_expect_failure 'Check invalid format specifiers are errors' '\n-\tgit-for-each-ref --format=\"%(authordate:INVALID)\" refs/heads\n+test_expect_success 'Check invalid format specifiers are errors' '\n+\t! git-for-each-ref --format=\"%(authordate:INVALID)\" refs/heads\n '\n \n cat >expected <<\\EOF\ndiff --git a/t/t7001-mv.sh b/t/t7001-mv.sh\nindex b730c90..b1243b4 100755\n--- a/t/t7001-mv.sh\n+++ b/t/t7001-mv.sh\n@@ -78,9 +78,9 @@ test_expect_success \\\n      git diff-tree -r -M --name-status  HEAD^ HEAD | \\\n      grep \"^R100..*path2/README..*path1/path2/README\"'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'do not move directory over existing directory' \\\n-    'mkdir path0 && mkdir path0/path2 && git mv path2 path0'\n+    'mkdir path0 && mkdir path0/path2 && ! git mv path2 path0'\n \n test_expect_success \\\n     'move into \".\"' \\\ndiff --git a/t/t7002-grep.sh b/t/t7002-grep.sh\nindex 68b2b92..8d3a8eb 100755\n--- a/t/t7002-grep.sh\n+++ b/t/t7002-grep.sh\n@@ -107,8 +107,8 @@ do\n \t\tdiff expected actual\n \t'\n \n-        test_expect_failure \"grep -c $L (no /dev/null)\" '\n-\t\tgit grep -c test $H | grep -q \"/dev/null\"\n+        test_expect_success \"grep -c $L (no /dev/null)\" '\n+\t\t! git grep -c test $H | grep -q /dev/null\n         '\n \n done\ndiff --git a/t/t7004-tag.sh b/t/t7004-tag.sh\nindex df496a9..75cd33b 100755\n--- a/t/t7004-tag.sh\n+++ b/t/t7004-tag.sh\n@@ -26,8 +26,8 @@ test_expect_success 'listing all tags in an empty tree should output nothing' '\n \ttest `git-tag | wc -l` -eq 0\n '\n \n-test_expect_failure 'looking for a tag in an empty tree should fail' \\\n-\t'tag_exists mytag'\n+test_expect_success 'looking for a tag in an empty tree should fail' \\\n+\t'! (tag_exists mytag)'\n \n test_expect_success 'creating a tag in an empty tree should fail' '\n \t! git-tag mynotag &&\n@@ -83,9 +83,9 @@ test_expect_success \\\n \n # special cases for creating tags:\n \n-test_expect_failure \\\n+test_expect_success \\\n \t'trying to create a tag with the name of one existing should fail' \\\n-\t'git tag mytag'\n+\t'! git tag mytag'\n \n test_expect_success \\\n \t'trying to create a tag with a non-valid name should fail' '\n@@ -146,8 +146,8 @@ test_expect_success \\\n \t! tag_exists myhead\n '\n \n-test_expect_failure 'trying to delete an already deleted tag should fail' \\\n-\t'git-tag -d mytag'\n+test_expect_success 'trying to delete an already deleted tag should fail' \\\n+\t'! git-tag -d mytag'\n \n # listing various tags with pattern matching:\n \n@@ -265,16 +265,16 @@ test_expect_success \\\n \ttest $(git rev-parse non-annotated-tag) = $(git rev-parse HEAD)\n '\n \n-test_expect_failure 'trying to verify an unknown tag should fail' \\\n-\t'git-tag -v unknown-tag'\n+test_expect_success 'trying to verify an unknown tag should fail' \\\n+\t'! git-tag -v unknown-tag'\n \n-test_expect_failure \\\n+test_expect_success \\\n \t'trying to verify a non-annotated and non-signed tag should fail' \\\n-\t'git-tag -v non-annotated-tag'\n+\t'! git-tag -v non-annotated-tag'\n \n-test_expect_failure \\\n+test_expect_success \\\n \t'trying to verify many non-annotated or unknown tags, should fail' \\\n-\t'git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'\n+\t'! git-tag -v unknown-tag1 non-annotated-tag unknown-tag2'\n \n # creating annotated tags:\n \n@@ -1027,21 +1027,21 @@ test_expect_success \\\n \n # try to sign with bad user.signingkey\n git config user.signingkey BobTheMouse\n-test_expect_failure \\\n+test_expect_success \\\n \t'git-tag -s fails if gpg is misconfigured' \\\n-\t'git tag -s -m tail tag-gpg-failure'\n+\t'! git tag -s -m tail tag-gpg-failure'\n git config --unset user.signingkey\n \n # try to verify without gpg:\n \n rm -rf gpghome\n-test_expect_failure \\\n+test_expect_success \\\n \t'verify signed tag fails when public key is not present' \\\n-\t'git-tag -v signed-tag'\n+\t'! git-tag -v signed-tag'\n \n-test_expect_failure \\\n+test_expect_success \\\n \t'git-tag -a fails if tag annotation is empty' '\n-\tGIT_EDITOR=cat git tag -a initial-comment\n+\t! (GIT_EDITOR=cat git tag -a initial-comment)\n '\n \n test_expect_success \\\ndiff --git a/t/t7101-reset.sh b/t/t7101-reset.sh\nindex 66d4043..0d9874b 100755\n--- a/t/t7101-reset.sh\n+++ b/t/t7101-reset.sh\n@@ -36,28 +36,28 @@ test_expect_success \\\n     'test -d path0 &&\n      test -f path0/COPYING'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking lack of path1/path2/COPYING' \\\n-    'test -f path1/path2/COPYING'\n+    '! test -f path1/path2/COPYING'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking lack of path1/COPYING' \\\n-    'test -f path1/COPYING'\n+    '! test -f path1/COPYING'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking lack of COPYING' \\\n-    'test -f COPYING'\n+    '! test -f COPYING'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking checking lack of path1/COPYING-TOO' \\\n-    'test -f path0/COPYING-TOO'\n+    '! test -f path0/COPYING-TOO'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking lack of path1/path2' \\\n-    'test -d path1/path2'\n+    '! test -d path1/path2'\n \n-test_expect_failure \\\n+test_expect_success \\\n     'checking lack of path1' \\\n-    'test -d path1'\n+    '! test -d path1'\n \n test_done\ndiff --git a/t/t7501-commit.sh b/t/t7501-commit.sh\nindex d1a415a..21dcf55 100755\n--- a/t/t7501-commit.sh\n+++ b/t/t7501-commit.sh\n@@ -17,49 +17,49 @@ test_expect_success \\\n \t git-add file && \\\n \t git-status | grep 'Initial commit'\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"fail initial amend\" \\\n-\t\"git-commit --amend\"\n+\t\"! git-commit --amend\"\n \n test_expect_success \\\n \t\"initial commit\" \\\n \t\"git-commit -m initial\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"invalid options 1\" \\\n-\t\"git-commit -m foo -m bar -F file\"\n+\t\"! git-commit -m foo -m bar -F file\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"invalid options 2\" \\\n-\t\"git-commit -C HEAD -m illegal\"\n+\t\"! git-commit -C HEAD -m illegal\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"using paths with -a\" \\\n \t\"echo King of the bongo >file &&\n-\tgit-commit -m foo -a file\"\n+\t! git-commit -m foo -a file\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"using paths with --interactive\" \\\n \t\"echo bong-o-bong >file &&\n-\techo 7 | git-commit -m foo --interactive file\"\n+\t! echo 7 | git-commit -m foo --interactive file\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"using invalid commit with -C\" \\\n-\t\"git-commit -C bogus\"\n+\t\"! git-commit -C bogus\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"testing nothing to commit\" \\\n-\t\"git-commit -m initial\"\n+\t\"! git-commit -m initial\"\n \n test_expect_success \\\n \t\"next commit\" \\\n \t\"echo 'bongo bongo bongo' >file \\\n \t git-commit -m next -a\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"commit message from non-existing file\" \\\n \t\"echo 'more bongo: bongo bongo bongo bongo' >file && \\\n-\t git-commit -F gah -a\"\n+\t ! git-commit -F gah -a\"\n \n # Empty except stray tabs and spaces on a few lines.\n sed -e 's/@$//' >msg <<EOF\n@@ -68,9 +68,9 @@ sed -e 's/@$//' >msg <<EOF\n   @\n Signed-off-by: hula\n EOF\n-test_expect_failure \\\n+test_expect_success \\\n \t\"empty commit message\" \\\n-\t\"git-commit -F msg -a\"\n+\t\"! git-commit -F msg -a\"\n \n test_expect_success \\\n \t\"commit message from file\" \\\n@@ -88,10 +88,10 @@ test_expect_success \\\n \t\"amend commit\" \\\n \t\"VISUAL=./editor git-commit --amend\"\n \n-test_expect_failure \\\n+test_expect_success \\\n \t\"passing -m and -F\" \\\n \t\"echo 'enough with the bongos' >file && \\\n-\t git-commit -F msg -m amending .\"\n+\t ! git-commit -F msg -m amending .\"\n \n test_expect_success \\\n \t\"using message from other commit\" \\\ndiff --git a/t/t7503-pre-commit-hook.sh b/t/t7503-pre-commit-hook.sh\nindex d787cac..2dd5a5e 100755\n--- a/t/t7503-pre-commit-hook.sh\n+++ b/t/t7503-pre-commit-hook.sh\n@@ -52,11 +52,11 @@ cat > \"$HOOK\" <<EOF\n exit 1\n EOF\n \n-test_expect_failure 'with failing hook' '\n+test_expect_success 'with failing hook' '\n \n \techo \"another\" >> file &&\n \tgit add file &&\n-\tgit commit -m \"another\"\n+\t! git commit -m \"another\"\n \n '\n \ndiff --git a/t/t7504-commit-msg-hook.sh b/t/t7504-commit-msg-hook.sh\nindex 751b113..eff36aa 100755\n--- a/t/t7504-commit-msg-hook.sh\n+++ b/t/t7504-commit-msg-hook.sh\n@@ -98,20 +98,20 @@ cat > \"$HOOK\" <<EOF\n exit 1\n EOF\n \n-test_expect_failure 'with failing hook' '\n+test_expect_success 'with failing hook' '\n \n \techo \"another\" >> file &&\n \tgit add file &&\n-\tgit commit -m \"another\"\n+\t! git commit -m \"another\"\n \n '\n \n-test_expect_failure 'with failing hook (editor)' '\n+test_expect_success 'with failing hook (editor)' '\n \n \techo \"more another\" >> file &&\n \tgit add file &&\n \techo \"more another\" > FAKE_MSG &&\n-\tGIT_EDITOR=\"$FAKE_EDITOR\" git commit\n+\t! (GIT_EDITOR=\"$FAKE_EDITOR\" git commit)\n \n '\n \ndiff --git a/t/t9100-git-svn-basic.sh b/t/t9100-git-svn-basic.sh\nindex 614cf50..1078f87 100755\n--- a/t/t9100-git-svn-basic.sh\n+++ b/t/t9100-git-svn-basic.sh\n@@ -56,19 +56,19 @@ test_expect_success \"$name\" \"\n \n \n name='detect node change from file to directory #1'\n-test_expect_failure \"$name\" \"\n+test_expect_success \"$name\" \"\n \tmkdir dir/new_file &&\n \tmv dir/file dir/new_file/file &&\n \tmv dir/new_file dir/file &&\n \tgit update-index --remove dir/file &&\n \tgit update-index --add dir/file/file &&\n-\tgit commit -m '$name'  &&\n-\tgit-svn set-tree --find-copies-harder --rmdir \\\n+\tgit commit -m '$name' &&\n+\t! git-svn set-tree --find-copies-harder --rmdir \\\n \t\tremotes/git-svn..mybranch\" || true\n \n \n name='detect node change from directory to file #1'\n-test_expect_failure \"$name\" \"\n+test_expect_success \"$name\" \"\n \trm -rf dir '$GIT_DIR'/index &&\n \tgit checkout -f -b mybranch2 remotes/git-svn &&\n \tmv bar/zzz zzz &&\n@@ -77,12 +77,12 @@ test_expect_failure \"$name\" \"\n \tgit update-index --remove -- bar/zzz &&\n \tgit update-index --add -- bar &&\n \tgit commit -m '$name' &&\n-\tgit-svn set-tree --find-copies-harder --rmdir \\\n+\t! git-svn set-tree --find-copies-harder --rmdir \\\n \t\tremotes/git-svn..mybranch2\" || true\n \n \n name='detect node change from file to directory #2'\n-test_expect_failure \"$name\" \"\n+test_expect_success \"$name\" \"\n \trm -f '$GIT_DIR'/index &&\n \tgit checkout -f -b mybranch3 remotes/git-svn &&\n \trm bar/zzz &&\n@@ -91,12 +91,12 @@ test_expect_failure \"$name\" \"\n \techo yyy > bar/zzz/yyy &&\n \tgit update-index --add bar/zzz/yyy &&\n \tgit commit -m '$name' &&\n-\tgit-svn set-tree --find-copies-harder --rmdir \\\n+\t! git-svn set-tree --find-copies-harder --rmdir \\\n \t\tremotes/git-svn..mybranch3\" || true\n \n \n name='detect node change from directory to file #2'\n-test_expect_failure \"$name\" \"\n+test_expect_success \"$name\" \"\n \trm -f '$GIT_DIR'/index &&\n \tgit checkout -f -b mybranch4 remotes/git-svn &&\n \trm -rf dir &&\n@@ -105,7 +105,7 @@ test_expect_failure \"$name\" \"\n \techo asdf > dir &&\n \tgit update-index --add -- dir &&\n \tgit commit -m '$name' &&\n-\tgit-svn set-tree --find-copies-harder --rmdir \\\n+\t! git-svn set-tree --find-copies-harder --rmdir \\\n \t\tremotes/git-svn..mybranch4\" || true\n \n \n@@ -213,18 +213,18 @@ EOF\n \n test_expect_success \"$name\" \"git diff a expected\"\n \n-test_expect_failure 'exit if remote refs are ambigious' \"\n+test_expect_success 'exit if remote refs are ambigious' \"\n         git config --add svn-remote.svn.fetch \\\n                               bar:refs/remotes/git-svn &&\n-        git-svn migrate\n-        \"\n+        ! git-svn migrate\n+\"\n \n-test_expect_failure 'exit if init-ing a would clobber a URL' \"\n+test_expect_success 'exit if init-ing a would clobber a URL' \"\n         svnadmin create ${PWD}/svnrepo2 &&\n         svn mkdir -m 'mkdir bar' ${svnrepo}2/bar &&\n         git config --unset svn-remote.svn.fetch \\\n                                 '^bar:refs/remotes/git-svn$' &&\n-        git-svn init ${svnrepo}2/bar\n+        ! git-svn init ${svnrepo}2/bar\n         \"\n \n test_expect_success \\\ndiff --git a/t/t9106-git-svn-commit-diff-clobber.sh b/t/t9106-git-svn-commit-diff-clobber.sh\nindex 79b7968..f74ab12 100755\n--- a/t/t9106-git-svn-commit-diff-clobber.sh\n+++ b/t/t9106-git-svn-commit-diff-clobber.sh\n@@ -24,11 +24,11 @@ test_expect_success 'commit change from svn side' \"\n \trm -rf t.svn\n \t\"\n \n-test_expect_failure 'commit conflicting change from git' \"\n+test_expect_success 'commit conflicting change from git' \"\n \techo second line from git >> file &&\n \tgit commit -a -m 'second line from git' &&\n-\tgit-svn commit-diff -r1 HEAD~1 HEAD $svnrepo\n-\t\" || true\n+\t! git-svn commit-diff -r1 HEAD~1 HEAD $svnrepo\n+\"\n \n test_expect_success 'commit complementing change from git' \"\n \tgit reset --hard HEAD~1 &&\n@@ -39,7 +39,7 @@ test_expect_success 'commit complementing change from git' \"\n \tgit-svn commit-diff -r2 HEAD~1 HEAD $svnrepo\n \t\"\n \n-test_expect_failure 'dcommit fails to commit because of conflict' \"\n+test_expect_success 'dcommit fails to commit because of conflict' \"\n \tgit-svn init $svnrepo &&\n \tgit-svn fetch &&\n \tgit reset --hard refs/remotes/git-svn &&\n@@ -52,8 +52,8 @@ test_expect_failure 'dcommit fails to commit because of conflict' \"\n \trm -rf t.svn &&\n \techo 'fourth line from git' >> file &&\n \tgit commit -a -m 'fourth line from git' &&\n-\tgit-svn dcommit\n-\t\" || true\n+\t! git-svn dcommit\n+\t\"\n \n test_expect_success 'dcommit does the svn equivalent of an index merge' \"\n \tgit reset --hard refs/remotes/git-svn &&\n@@ -76,15 +76,15 @@ test_expect_success 'commit another change from svn side' \"\n \trm -rf t.svn\n \t\"\n \n-test_expect_failure 'multiple dcommit from git-svn will not clobber svn' \"\n+test_expect_success 'multiple dcommit from git-svn will not clobber svn' \"\n \tgit reset --hard refs/remotes/git-svn &&\n \techo new file >> new-file &&\n \tgit update-index --add new-file &&\n \tgit commit -a -m 'new file' &&\n \techo clobber > file &&\n \tgit commit -a -m 'clobber' &&\n-\tgit svn dcommit\n-\t\" || true\n+\t! git svn dcommit\n+\t\"\n \n \n test_expect_success 'check that rebase really failed' 'test -d .dotest'\ndiff --git a/t/t9106-git-svn-dcommit-clobber-series.sh b/t/t9106-git-svn-dcommit-clobber-series.sh\nindex 7452546..ca8a00e 100755\n--- a/t/t9106-git-svn-dcommit-clobber-series.sh\n+++ b/t/t9106-git-svn-dcommit-clobber-series.sh\n@@ -54,10 +54,10 @@ test_expect_success 'change file but in unrelated area' \"\n \t\ttest x\\\"\\`sed -n -e 61p < file\\`\\\" = x6611\n \t\"\n \n-test_expect_failure 'attempt to dcommit with a dirty index' '\n+test_expect_success 'attempt to dcommit with a dirty index' '\n \techo foo >>file &&\n \tgit add file &&\n-\tgit svn dcommit\n+\t! git svn dcommit\n '\n \n test_done\ndiff --git a/t/t9300-fast-import.sh b/t/t9300-fast-import.sh\nindex 0595041..cceedbb 100755\n--- a/t/t9300-fast-import.sh\n+++ b/t/t9300-fast-import.sh\n@@ -165,9 +165,9 @@ from refs/heads/master\n M 755 0000000000000000000000000000000000000001 zero1\n \n INPUT_END\n-test_expect_failure \\\n-    'B: fail on invalid blob sha1' \\\n-    'git-fast-import <input'\n+test_expect_success 'B: fail on invalid blob sha1' '\n+    ! git-fast-import <input\n+'\n rm -f .git/objects/pack_* .git/objects/index_*\n \n cat >input <<INPUT_END\n@@ -180,9 +180,9 @@ COMMIT\n from refs/heads/master\n \n INPUT_END\n-test_expect_failure \\\n-    'B: fail on invalid branch name \".badbranchname\"' \\\n-    'git-fast-import <input'\n+test_expect_success 'B: fail on invalid branch name \".badbranchname\"' '\n+    ! git-fast-import <input\n+'\n rm -f .git/objects/pack_* .git/objects/index_*\n \n cat >input <<INPUT_END\n@@ -195,9 +195,9 @@ COMMIT\n from refs/heads/master\n \n INPUT_END\n-test_expect_failure \\\n-    'B: fail on invalid branch name \"bad[branch]name\"' \\\n-    'git-fast-import <input'\n+test_expect_success 'B: fail on invalid branch name \"bad[branch]name\"' '\n+    ! git-fast-import <input\n+'\n rm -f .git/objects/pack_* .git/objects/index_*\n \n cat >input <<INPUT_END\n@@ -339,9 +339,9 @@ COMMIT\n from refs/heads/branch^0\n \n INPUT_END\n-test_expect_failure \\\n-    'E: rfc2822 date, --date-format=raw' \\\n-    'git-fast-import --date-format=raw <input'\n+test_expect_success 'E: rfc2822 date, --date-format=raw' '\n+    ! git-fast-import --date-format=raw <input\n+'\n test_expect_success \\\n     'E: rfc2822 date, --date-format=rfc2822' \\\n     'git-fast-import --date-format=rfc2822 <input'\ndiff --git a/t/t9400-git-cvsserver-server.sh b/t/t9400-git-cvsserver-server.sh\nindex 75d1ce4..0a20971 100755\n--- a/t/t9400-git-cvsserver-server.sh\n+++ b/t/t9400-git-cvsserver-server.sh\n@@ -156,15 +156,19 @@ test_expect_success 'req_Root (strict paths)' \\\n   'cat request-anonymous | git-cvsserver --strict-paths pserver $SERVERDIR >log 2>&1 &&\n    tail -n1 log | grep -q \"^I LOVE YOU$\"'\n \n-test_expect_failure 'req_Root failure (strict-paths)' \\\n-  'cat request-anonymous | git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1'\n+test_expect_success 'req_Root failure (strict-paths)' '\n+    ! cat request-anonymous |\n+    git-cvsserver --strict-paths pserver $WORKDIR >log 2>&1\n+'\n \n test_expect_success 'req_Root (w/o strict-paths)' \\\n   'cat request-anonymous | git-cvsserver pserver $WORKDIR/ >log 2>&1 &&\n    tail -n1 log | grep -q \"^I LOVE YOU$\"'\n \n-test_expect_failure 'req_Root failure (w/o strict-paths)' \\\n-  'cat request-anonymous | git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1'\n+test_expect_success 'req_Root failure (w/o strict-paths)' '\n+    ! cat request-anonymous |\n+    git-cvsserver pserver $WORKDIR/gitcvs >log 2>&1\n+'\n \n cat >request-base  <<EOF\n BEGIN AUTH REQUEST\n@@ -179,8 +183,10 @@ test_expect_success 'req_Root (base-path)' \\\n   'cat request-base | git-cvsserver --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&\n    tail -n1 log | grep -q \"^I LOVE YOU$\"'\n \n-test_expect_failure 'req_Root failure (base-path)' \\\n-  'cat request-anonymous | git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1'\n+test_expect_success 'req_Root failure (base-path)' '\n+    ! cat request-anonymous |\n+    git-cvsserver --strict-paths --base-path $WORKDIR pserver $SERVERDIR >log 2>&1\n+'\n \n GIT_DIR=\"$SERVERDIR\" git config --bool gitcvs.enabled false || exit 1\n \n@@ -188,9 +194,8 @@ test_expect_success 'req_Root (export-all)' \\\n   'cat request-anonymous | git-cvsserver --export-all pserver $WORKDIR >log 2>&1 &&\n    tail -n1 log | grep -q \"^I LOVE YOU$\"'\n \n-test_expect_failure 'req_Root failure (export-all w/o whitelist)' \\\n-  'cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 ||\n-   false'\n+test_expect_success 'req_Root failure (export-all w/o whitelist)' \\\n+  '! (cat request-anonymous | git-cvsserver --export-all pserver >log 2>&1 || false)'\n \n test_expect_success 'req_Root (everything together)' \\\n   'cat request-base | git-cvsserver --export-all --strict-paths --base-path $WORKDIR/ pserver $SERVERDIR >log 2>&1 &&\n@@ -290,15 +295,16 @@ test_expect_success 'cvs update (update existing file)' \\\n \n cd \"$WORKDIR\"\n #TODO: cvsserver doesn't support update w/o -d\n-test_expect_failure \"cvs update w/o -d doesn't create subdir (TODO)\" \\\n-  'mkdir test &&\n+test_expect_failure \"cvs update w/o -d doesn't create subdir (TODO)\" '\n+   mkdir test &&\n    echo >test/empty &&\n    git add test &&\n    git commit -q -m \"Single Subdirectory\" &&\n    git push gitcvs.git >/dev/null &&\n    cd cvswork &&\n    GIT_CONFIG=\"$git_config\" cvs -Q update &&\n-   test ! -d test'\n+   test ! -d test\n+'\n \n cd \"$WORKDIR\"\n test_expect_success 'cvs update (subdirectories)' \\\ndiff --git a/t/test-lib.sh b/t/test-lib.sh\nindex 142540e..9a3c0b4 100644\n--- a/t/test-lib.sh\n+++ b/t/test-lib.sh\n@@ -139,6 +139,8 @@ fi\n \n test_failure=0\n test_count=0\n+test_fixed=0\n+test_broken=0\n \n trap 'echo >&5 \"FATAL: Unexpected exit with code $?\"; exit 1' exit\n \n@@ -171,6 +173,17 @@ test_failure_ () {\n \ttest \"$immediate\" = \"\" || { trap - exit; exit 1; }\n }\n \n+test_known_broken_ok_ () {\n+\ttest_count=$(expr \"$test_count\" + 1)\n+\ttest_fixed=$(($test_fixed+1))\n+\tsay_color \"\" \"  FIXED $test_count: $@\"\n+}\n+\n+test_known_broken_failure_ () {\n+\ttest_count=$(expr \"$test_count\" + 1)\n+\ttest_broken=$(($test_broken+1))\n+\tsay_color skip \"  still broken $test_count: $@\"\n+}\n \n test_debug () {\n \ttest \"$debug\" = \"\" || eval \"$1\"\n@@ -211,13 +224,13 @@ test_expect_failure () {\n \terror \"bug in the test script: not 2 parameters to test-expect-failure\"\n \tif ! test_skip \"$@\"\n \tthen\n-\t\tsay >&3 \"expecting failure: $2\"\n+\t\tsay >&3 \"checking known breakage: $2\"\n \t\ttest_run_ \"$2\"\n-\t\tif [ \"$?\" = 0 -a \"$eval_ret\" != 0 -a \"$eval_ret\" -lt 129 ]\n+\t\tif [ \"$?\" = 0 -a \"$eval_ret\" = 0 ]\n \t\tthen\n-\t\t\ttest_ok_ \"$1\"\n+\t\t\ttest_known_broken_ok_ \"$1\"\n \t\telse\n-\t\t\ttest_failure_ \"$@\"\n+\t\t    test_known_broken_failure_ \"$1\"\n \t\tfi\n \tfi\n \techo >&3 \"\"\n@@ -274,6 +287,15 @@ test_create_repo () {\n \n test_done () {\n \ttrap - exit\n+\n+\tif test \"$test_fixed\" != 0\n+\tthen\n+\t\tsay_color pass \"fixed $test_fixed known breakage(s)\"\n+\tfi\n+\tif test \"$test_broken\" != 0\n+\tthen\n+\t\tsay_color error \"still have $test_broken known breakage(s)\"\n+\tfi\n \tcase \"$test_failure\" in\n \t0)\n \t\t# We could:\n"},{"id":"67022","messageId":"7v63x99dt9.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"200802011010.41938.robin.rosenberg.lists@dewire.com","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T10:22:10Z","receivedAt":"2008-02-01T10:22:10Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n\n>> > +test_expect_failure 'add a directory outside the work tree' '\n>> > +\td1=\"$(cd .. ; pwd)\" &&\n>> > +\tgit add \"$d1\"\n>> > +\techo $?\n>> > +'\n>\n> Oops. Remove the echo $?. It still fails, i.e. git add succeeds when\n> it shouldn't. I was double checking it just before sending the patch.\n\nAh, you found breakages.\n\nI could not quite tell what you meant by these tests with\ntest_expect_failure, either they were misuse or \"currently fails\nbut they shouldn't\".  Coming up with a small reproducible\nfailure case is 50% of solving the problem.  That's very\nappreciated.\n\nIn any case, I think the large-ish test framework update patch I\nsent tonight should go in very early post 1.5.4 cycle, so plesae\nuse the new-and-improved test_expect_failure to mark these\nreproducible failure cases.  Also, there is no need for hurry\nfor you to just send test cases without fixes.  When I say I do\nnot take patches early, I do mean it --- I do not even take _my_\nown patches like the one you are following up on.  I've sent\nquite a many of them, and I think some are on 'pu' or 'offcuts',\nbut most are only in the list archive.  If nobody cares deeply\nenough about them to test and resend with Tested-by: , I am not\nplanning to go back to pick them up.\n\n>>  * First, the obvious one.  You are creating a garbage file\n>>    outside of t/trash directory.  Don't.  If you need to, dig a\n>>    test directory one level lower inside t/trash and play around\n>>    there.\n>\n> Can we move the default trash one level down for all tests? That\n> would give us one free level to play with.\n\nI'd rather not.  Most tests do not have to step outside and it\nis very handy to debug any breakage they find by always being\nable to go there with \"cd t/trash\".\n"},{"id":"67023","messageId":"7v1w7x9cgf.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7v63x99dt9.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T10:51:28Z","receivedAt":"2008-02-01T10:51:28Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n>\n>>> > +test_expect_failure 'add a directory outside the work tree' '\n>>> > +\td1=\"$(cd .. ; pwd)\" &&\n>>> > +\tgit add \"$d1\"\n>>> > +\techo $?\n>>> > +'\n>>\n>> Oops. Remove the echo $?. It still fails, i.e. git add succeeds when\n>> it shouldn't. I was double checking it just before sending the patch.\n>\n> Ah, you found breakages.\n\nI haven't looked at the code, but I suspect that \"git add\" and\nanything that uses the same logic as \"ls-files --error-unmatch\"\nwould still not work with the setup patch.\n\nThe updated get_pathspec() issues a warning message and returns\nthe result that omits paths outside of the work tree.  It does\nnot die (and it is intentional, by the way).  The callers that\nexpect to always receive the same number of paths in the return\nvalue as argv+i they pass to get_pathspec() should be updated to\nnotice that they got less than they passed in, if they care\nabout this error condition, and --error-unmatch codepath is one\nof them.  I did not touch that in the weatherbaloon patch.\n"},{"id":"67024","messageId":"7vtzkt7x02.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7v1w7x9cgf.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T11:10:37Z","receivedAt":"2008-02-01T11:10:37Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Junio C Hamano <gitster@pobox.com> writes:\n\n> I haven't looked at the code, but I suspect that \"git add\" and\n> anything that uses the same logic as \"ls-files --error-unmatch\"\n> would still not work with the setup patch.\n\nOk, I looked at the code.\n\nI already had a fix for \"ls-files --error-unmatch\" in the\nweatherbaloon patch.  The logic to complain and fail nonsense\npaths was needed for \"add\".\n\nThe attached is on top of the previous one and your test case\nupdates.  We may want to also add tests for --error-unmatch.\n\nIt is very tempting to enhance pathspec API in such a way that\nit is not just a NULL terminated array of (char*) pointers, but\na pointer to a richer structure that knows the number of\nelements and which strings need to be freed (and we would invent\na \"free_pathspec()\" function to free them) and such, but that is\nan independent surgery that is outside of the scope of flying\nweatherbaloons.\n\n---\n\n builtin-add.c    |   12 ++++++++++++\n t/t7010-setup.sh |   20 ++++++++++++++------\n 2 files changed, 26 insertions(+), 6 deletions(-)\n\ndiff --git a/builtin-add.c b/builtin-add.c\nindex 4a91e3e..820110e 100644\n--- a/builtin-add.c\n+++ b/builtin-add.c\n@@ -228,6 +228,18 @@ int cmd_add(int argc, const char **argv, const char *prefix)\n \t\tgoto finish;\n \t}\n \n+\tif (*argv) {\n+\t\t/* Was there an invalid path? */\n+\t\tif (pathspec) {\n+\t\t\tint num;\n+\t\t\tfor (num = 0; pathspec[num]; num++)\n+\t\t\t\t; /* just counting */\n+\t\t\tif (argc != num)\n+\t\t\t\texit(1); /* error message already given */\n+\t\t} else\n+\t\t\texit(1); /* error message already given */\n+\t}\n+\n \tfill_directory(&dir, pathspec, ignored_too);\n \n \tif (show_only) {\ndiff --git a/t/t7010-setup.sh b/t/t7010-setup.sh\nindex 60c4a46..e809e0e 100755\n--- a/t/t7010-setup.sh\n+++ b/t/t7010-setup.sh\n@@ -135,20 +135,28 @@ test_expect_success 'blame using absolute path names' '\n \tdiff -u f1.txt f2.txt\n '\n \n-test_expect_failure 'add a directory outside the work tree' '\n+test_expect_success 'setup deeper work tree' '\n+\ttest_create_repo tester\n+'\n+\n+test_expect_success 'add a directory outside the work tree' '(\n+\tcd tester &&\n \td1=\"$(cd .. ; pwd)\" &&\n \tgit add \"$d1\"\n-\techo $?\n-'\n+)'\n \n-test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n+test_expect_success 'add a file outside the work tree, nasty case 1' '(\n+\tcd tester &&\n \tf=\"$(pwd)x\" &&\n+\techo \"$f\" &&\n \ttouch \"$f\" &&\n \tgit add \"$f\"\n )'\n \n-test_expect_failure 'add a file outside the work tree, nasty case 2' '(\n-\tf=\"$(pwd|sed \"s/.$//\")x\" &&\n+test_expect_success 'add a file outside the work tree, nasty case 2' '(\n+\tcd tester &&\n+\tf=\"$(pwd | sed \"s/.$//\")x\" &&\n+\techo \"$f\" &&\n \ttouch \"$f\" &&\n \tgit add \"$f\"\n )'\n"},{"id":"67032","messageId":"200802011517.28895.robin.rosenberg.lists@dewire.com","threadId":"11711","inReplyTo":"7v63x99dt9.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg.lists@dewire.com","sentAt":"2008-02-01T14:17:27Z","receivedAt":"2008-02-01T14:17:27Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"fredagen den 1 februari 2008 skrev Junio C Hamano:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n> \n> >> > +test_expect_failure 'add a directory outside the work tree' '\n> >> > +\td1=\"$(cd .. ; pwd)\" &&\n> >> > +\tgit add \"$d1\"\n> >> > +\techo $?\n> >> > +'\n> >\n> > Oops. Remove the echo $?. It still fails, i.e. git add succeeds when\n> > it shouldn't. I was double checking it just before sending the patch.\n> \n> Ah, you found breakages.\n> \n> I could not quite tell what you meant by these tests with\n> test_expect_failure, either they were misuse or \"currently fails\n> but they shouldn't\".  Coming up with a small reproducible\n> failure case is 50% of solving the problem.  That's very\n> appreciated.\n> \n> In any case, I think the large-ish test framework update patch I\n> sent tonight should go in very early post 1.5.4 cycle, so plesae\n> use the new-and-improved test_expect_failure to mark these\n> reproducible failure cases.  Also, there is no need for hurry\n> for you to just send test cases without fixes.  When I say I do\n> not take patches early, I do mean it --- I do not even take _my_\n> own patches like the one you are following up on.  I've sent\n> quite a many of them, and I think some are on 'pu' or 'offcuts',\n> but most are only in the list archive.  If nobody cares deeply\n> enough about them to test and resend with Tested-by: , I am not\n> planning to go back to pick them up.\n\nI had those test cases on my machine from my earlier work on absolute\npath names so I just ran them with your code and extracted those that\nfailed and put them into your testsuite instead. That's why I sent them\nso early. Read them as comments on your patches, \"oh you need to cover\nthis too\". It was simply very convenient for me, and hopefull for you,\nto supply them in  patch form.\n\nThe reasone my code for absolute path names wasn't re-submitted was\nbecause I had some test cases that didn't pass. I don't really care how\nthe problem is solved.\n\n> > Can we move the default trash one level down for all tests? That\n> > would give us one free level to play with.\n> \n> I'd rather not.  Most tests do not have to step outside and it\n> is very handy to debug any breakage they find by always being\n> able to go there with \"cd t/trash\".\n\nThe change would be to \"cd t/trash/repo\" instead. Not much different.\n\n> The updated get_pathspec() issues a warning message and returns\n> the result that omits paths outside of the work tree.  It does\n> not die (and it is intentional, by the way).  The callers that\n> expect to always receive the same number of paths in the return\n> value as argv+i they pass to get_pathspec() should be updated to\n> notice that they got less than they passed in, if they care\n> about this error condition, and --error-unmatch codepath is one\n> of them.  I did not touch that in the weatherbaloon patch.\n\nGit add in the current version on an absolute path outside the repo\nfails, so I think the updated one should. It really doesn't make sense.\n\nls-files outside the repo doesn't make sense either, but then the \ncurrent version exits with 0 so I can't make the same reference to \nexisting behaviour there. It just might break someone's script.\n\n-- robin\n"},{"id":"67037","messageId":"7vprvg8tac.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"200802011517.28895.robin.rosenberg.lists@dewire.com","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-01T17:45:31Z","receivedAt":"2008-02-01T17:45:31Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n\n> fredagen den 1 februari 2008 skrev Junio C Hamano:\n>\n>> Ah, you found breakages.\n>> \n>> I could not quite tell what you meant by these tests with\n>> test_expect_failure, either they were misuse or \"currently fails\n>> but they shouldn't\".  Coming up with a small reproducible\n>> failure case is 50% of solving the problem.  That's very\n>> appreciated.\n>> ...\n> I had those test cases on my machine from my earlier work on absolute\n> path names so I just ran them with your code and extracted those that\n> failed and put them into your testsuite instead. That's why I sent them\n> so early...\n\nYeah, I initially did not realize that was what you were doing,\nhence the above \"I could not quite tell, ... but thanks now I\ngot it\".\n"},{"id":"67099","messageId":"7vhcgr3c5w.fsf_-_@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"7vwspp9f9e.fsf_-_@gitster.siamese.dyndns.org","subject":"Re: [PATCH] Sane use of test_expect_failure","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-02-02T10:06:35Z","receivedAt":"2008-02-02T10:06:35Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"As I promised, a patch to revamp test_expect_failure semantics\nhas been applied to 'master' and pushed out.\n\nThe rule used to be that test_expect_failure is to see if the\ncommand sequence exits with non-zero status.  It was tempting to\nincorrectly use it like this:\n\n    test_expect_failure 'this should fail' '\n\tsetup1 &&\n        setup2 &&\n        setup3 &&\n        what you expect to fail\n    '\n\nbut was very error prone, because the failure can come from the\nearlier \"setup\" stages.\n\nThe new world order is that test_expect_failure is used to mark\na known breakage, so that people can run \"git grep t/\" to see if\nthere are things to work on.\n\nWe have an example in cvsserver test:\n\n    #TODO: cvsserver doesn't support update w/o -d\n    test_expect_failure \"cvs update w/o -d doesn't create subdir (TODO)\" '\n       ...\n       test ! -d test\n    '\n\nIf git-cvsserver did not have this bug, this should succeed, but\nthere is a known breakage that is waiting to be fixed.\n\nI may have missed tests that were using test_expect_failure to\nmark known bug that need to be fixed and converted that to\ntest_expect_success to check an error exit status from the last\ncommand in the sequence.  IOW, a mistranslation of the above\nmight have done:\n\n    test_expect_success \"cvs update w/o -d doesn't create subdir (TODO)\" '\n       ...\n       test -d test\n    '\n\nwhich would be wrong.  Fixing the bug would then \"break\" this\ntest.\n\nCould people who added test_expect_failure in the past that this\npatch updated, look them over to catch such a misconversion\nplease?\n"},{"id":"71324","messageId":"7vwsof2b8l.fsf@gitster.siamese.dyndns.org","threadId":"11711","inReplyTo":"200802010534.55925.robin.rosenberg.lists@dewire.com","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2008-03-07T08:23:54Z","receivedAt":"2008-03-07T08:23:54Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n\n> Verify a few more commands and pathname variants.\n>\n> Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n> ---\n>  t/t7010-setup.sh |   39 +++++++++++++++++++++++++++++++++++++++\n>  1 files changed, 39 insertions(+), 0 deletions(-)\n>\n> These are a few testcases from my earlier attempt at this. The\n> log and commit cases succeeded with Junios version, but not \n> blame and some of the nastier versions for git add (same\n> principle for all commands, just that I use add as an example)\n\nI am very sorry about replying to an ancient topic, but I think I misread\nyour patch.\n\n> +test_expect_failure 'add a directory outside the work tree' '\n> +\td1=\"$(cd .. ; pwd)\" &&\n> +\tgit add \"$d1\"\n> +\techo $?\n> +'\n\nWhat I think I misunderstood was that you _wanted_ this (after removing\nthe \"echo\", which was a mistake, which we already talked about) to fail.\nSomehow I ended up committing test_expect_success, which I think was a\nmistake, and I am asking for a sanity-check.\n\nLikewise for the other two tests.  These \"add outside\" should fail, right?\n\n> +test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n> +\tf=\"$(pwd)x\" &&\n> +\ttouch \"$f\" &&\n> +\tgit add \"$f\"\n> +)'\n> +\n> +test_expect_failure 'add a file outside the work tree, nasty case 2' '(\n> +\tf=\"$(pwd|sed \"s/.$//\")x\" &&\n> +\ttouch \"$f\" &&\n> +\tgit add \"$f\"\n> +)'\n> +\n>  test_done\n\n"},{"id":"71352","messageId":"200803071624.35369.robin.rosenberg@dewire.com","threadId":"11711","inReplyTo":"7vwsof2b8l.fsf@gitster.siamese.dyndns.org","subject":"Re: [PATCH] More test cases for sanitized path names","fromName":"Robin Rosenberg","fromEmail":"robin.rosenberg@dewire.com","sentAt":"2008-03-07T15:24:34Z","receivedAt":"2008-03-07T15:24:34Z","isPatch":true,"sender":{"key":"robin.rosenberg@dewire.com","avatar":"https://avatars.githubusercontent.com/u/46357?v=4"},"body":"Den Friday 07 March 2008 09.23.54 skrev Junio C Hamano:\n> Robin Rosenberg <robin.rosenberg.lists@dewire.com> writes:\n> > Verify a few more commands and pathname variants.\n> >\n> > Signed-off-by: Robin Rosenberg <robin.rosenberg@dewire.com>\n> > ---\n> >  t/t7010-setup.sh |   39 +++++++++++++++++++++++++++++++++++++++\n> >  1 files changed, 39 insertions(+), 0 deletions(-)\n> >\n> > These are a few testcases from my earlier attempt at this. The\n> > log and commit cases succeeded with Junios version, but not\n> > blame and some of the nastier versions for git add (same\n> > principle for all commands, just that I use add as an example)\n>\n> I am very sorry about replying to an ancient topic, but I think I misread\n> your patch.\n>\n> > +test_expect_failure 'add a directory outside the work tree' '\n> > +\td1=\"$(cd .. ; pwd)\" &&\n> > +\tgit add \"$d1\"\n> > +\techo $?\n> > +'\n>\n> What I think I misunderstood was that you _wanted_ this (after removing\n> the \"echo\", which was a mistake, which we already talked about) to fail.\n> Somehow I ended up committing test_expect_success, which I think was a\n> mistake, and I am asking for a sanity-check.\nYes, it should fail, so according to your filosophy, the test should be \nreverted, i.e. ! git add \"$d1 and that negated test should pass.\n\n> Likewise for the other two tests.  These \"add outside\" should fail, right?\n>\n> > +test_expect_failure 'add a file outside the work tree, nasty case 1' '(\n> > +\tf=\"$(pwd)x\" &&\n> > +\ttouch \"$f\" &&\n> > +\tgit add \"$f\"\n> > +)'\n> > +\n> > +test_expect_failure 'add a file outside the work tree, nasty case 2' '(\n> > +\tf=\"$(pwd|sed \"s/.$//\")x\" &&\n> > +\ttouch \"$f\" &&\n> > +\tgit add \"$f\"\n> > +)'\n> > +\n> >  test_done\n\nYes.\n\n-- robin\n"}]}