From: Junio C Hamano <hidden> Date: 2016-06-15 23:04:15
When a commit changes a path P that used to be a file to a directory
and create a new path P/X in it, "git show" would say that file P
was removed and file P/X was created for such a commit.
However, if we compare two directories, D1 and D2, where D1 has a
file D1/P in it and D2 has a directory D2/P under which there is a
file D2/P/X, and ask "git diff --no-index D1 D2" to show their
differences, we simply get a refusal "file/directory conflict".
The "diff --no-index" implementation has an underlying machinery
that can make it more in line with the normal Git if it wanted to,
but somehow it is not being exercised. The only thing we need to
do, when we see a file P and a directory P/ (or the other way
around) is to show the removal of a file P and then pretend as if we
are comparing nothing with a whole directory P/, as the code is
fully prepared to express a creation of everything in a directory
(and if the comparison is between a directory P/ and a file P, then
show the creation of the file and then let the existing code remove
everything in P/).
Signed-off-by: Junio C Hamano <redacted>
---
diff-no-index.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,if(get_mode(name1,&mode1)||get_mode(name2,&mode2))return-1;-if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2))-returnerror("file/directory conflict: %s, %s",name1,name2);+if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2)){+structdiff_filespec*d1,*d2;++if(S_ISDIR(mode1)){+/* 2 is file that is created */+d1=noindex_filespec(NULL,0);+d2=noindex_filespec(name2,mode2);+name2=NULL;+mode2=0;+}else{+/* 1 is file that is deleted */+d1=noindex_filespec(name1,mode2);+d2=noindex_filespec(NULL,0);+name1=NULL;+mode1=0;+}+/* emit that file */+diff_queue(&diff_queued_diff,d1,d2);++/* and then let the entire directory created or deleted */+}if(S_ISDIR(mode1)||S_ISDIR(mode2)){structstrbufbuffer1=STRBUF_INIT;
From: Eric Sunshine <hidden> Date: 2016-06-15 23:04:15
On Sun, Mar 22, 2015 at 1:11 AM, Junio C Hamano [off-list ref] wrote:
When a commit changes a path P that used to be a file to a directory
and create a new path P/X in it, "git show" would say that file P
s/create/creates/
More below...
quoted hunk
was removed and file P/X was created for such a commit.
However, if we compare two directories, D1 and D2, where D1 has a
file D1/P in it and D2 has a directory D2/P under which there is a
file D2/P/X, and ask "git diff --no-index D1 D2" to show their
differences, we simply get a refusal "file/directory conflict".
The "diff --no-index" implementation has an underlying machinery
that can make it more in line with the normal Git if it wanted to,
but somehow it is not being exercised. The only thing we need to
do, when we see a file P and a directory P/ (or the other way
around) is to show the removal of a file P and then pretend as if we
are comparing nothing with a whole directory P/, as the code is
fully prepared to express a creation of everything in a directory
(and if the comparison is between a directory P/ and a file P, then
show the creation of the file and then let the existing code remove
everything in P/).
Signed-off-by: Junio C Hamano <redacted>
---
diff-no-index.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,if(get_mode(name1,&mode1)||get_mode(name2,&mode2))return-1;-if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2))-returnerror("file/directory conflict: %s, %s",name1,name2);+if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2)){+structdiff_filespec*d1,*d2;++if(S_ISDIR(mode1)){+/* 2 is file that is created */+d1=noindex_filespec(NULL,0);+d2=noindex_filespec(name2,mode2);+name2=NULL;+mode2=0;+}else{+/* 1 is file that is deleted */+d1=noindex_filespec(name1,mode2);+d2=noindex_filespec(NULL,0);+name1=NULL;+mode1=0;+}+/* emit that file */+diff_queue(&diff_queued_diff,d1,d2);++/* and then let the entire directory created or deleted */
From: Ramsay Jones <hidden> Date: 2016-06-15 23:04:15
On 22/03/15 05:11, Junio C Hamano wrote:
quoted hunk
When a commit changes a path P that used to be a file to a directory
and create a new path P/X in it, "git show" would say that file P
was removed and file P/X was created for such a commit.
However, if we compare two directories, D1 and D2, where D1 has a
file D1/P in it and D2 has a directory D2/P under which there is a
file D2/P/X, and ask "git diff --no-index D1 D2" to show their
differences, we simply get a refusal "file/directory conflict".
The "diff --no-index" implementation has an underlying machinery
that can make it more in line with the normal Git if it wanted to,
but somehow it is not being exercised. The only thing we need to
do, when we see a file P and a directory P/ (or the other way
around) is to show the removal of a file P and then pretend as if we
are comparing nothing with a whole directory P/, as the code is
fully prepared to express a creation of everything in a directory
(and if the comparison is between a directory P/ and a file P, then
show the creation of the file and then let the existing code remove
everything in P/).
Signed-off-by: Junio C Hamano <redacted>
---
diff-no-index.c | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,if(get_mode(name1,&mode1)||get_mode(name2,&mode2))return-1;-if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2))-returnerror("file/directory conflict: %s, %s",name1,name2);+if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2)){+structdiff_filespec*d1,*d2;++if(S_ISDIR(mode1)){+/* 2 is file that is created */+d1=noindex_filespec(NULL,0);+d2=noindex_filespec(name2,mode2);+name2=NULL;+mode2=0;+}else{+/* 1 is file that is deleted */+d1=noindex_filespec(name1,mode2);
-----------------------------------------------------^^^^^
I have not been following the discussion (or even really
studied this patch), but the asymmetry here caught my eye
as I was skimming the email.
[So, if this is actually correct, sorry for the noise!]
ATB,
Ramsay Jones
+ d2 = noindex_filespec(NULL, 0);
+ name1 = NULL;
+ mode1 = 0;
+ }
+ /* emit that file */
+ diff_queue(&diff_queued_diff, d1, d2);
+
+ /* and then let the entire directory created or deleted */
+ }
if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
struct strbuf buffer1 = STRBUF_INIT;
From: Junio C Hamano <hidden> Date: 2016-06-15 23:04:15
On Sun, Mar 22, 2015 at 5:37 AM, Ramsay Jones
[off-list ref] wrote:
On 22/03/15 05:11, Junio C Hamano wrote:
quoted
+ if (S_ISDIR(mode1)) {
+ /* 2 is file that is created */
+ d1 = noindex_filespec(NULL, 0);
+ d2 = noindex_filespec(name2, mode2);
+ name2 = NULL;
+ mode2 = 0;
+ } else {
+ /* 1 is file that is deleted */
+ d1 = noindex_filespec(name1, mode2);
I have not been following the discussion (or even really
studied this patch), but the asymmetry here caught my eye
as I was skimming the email.
Yes, the assymetry is a stupid cut-and-paste error. Thanks for
catching.
There was actually no discussion on this point other than
a tangential mention of the problem to be solved, so you
are up to date as long as you read the proposed log message
in the message you are responding to ;-)
Another thing I noticed while I was playing with this is that
when comparing D1 and D2, a path that was modified is
shown as a patch between a/D1/path and b/D2/path, but
a path that was created or removed shows D1 or D2 on
both sides of the comparison, which we may also want to
fix. This problem appears in the current codebase without
the patch under discussion.
From: Junio C Hamano <hidden> Date: 2016-06-15 23:04:17
Here are a few patches to scratch my itches in "diff --no-index" I
have had for quite some time, but didn't feel strong enough to fix
them myself so far.
The first one is to make "diff File Directory" (and "diff Directory
File") more useful by aligning its behaviour with more mainstream
"diff" implementations. We used to complain that one is a file and
the other is a directory and died without doing anything useful.
Instead, we pretend as if the user asked to compare "diff File
Directory/File" (or vice versa), which is what GNU diff does.
The second one on the other hand is to make it behave more like the
normal "git diff" when comparing two directories, when one directory
has a path as a file while the other directory has the same path as
a directory. GNU diff punts and says "File D1/path is a regular
file while file D2/path is a directory" in such a case, and that is
what "diff --no-index" does, too, but when the normal "git diff"
compares two such trees, we show a removal of the file and creations
of all the files in the directory (or vice versa). With this patch,
we teach "diff --no-index" codepath to do the same, which would be
more in line with the spirit of "diff --no-index", which is to make
"git diff" goodies available to people who compare paths outside
control of any git repository.
Junio C Hamano (2):
diff-no-index: DWIM "diff D F" into "diff D/F F"
diff: align D/F handling of "diff --no-index" with that of normal Git
diff-no-index.c | 66 ++++++++++++++++++++++++++++++++++++++++++++++--
t/t4053-diff-no-index.sh | 34 +++++++++++++++++++++++++
2 files changed, 98 insertions(+), 2 deletions(-)
--
2.3.4-475-g3180e2e
From: Junio C Hamano <hidden> Date: 2016-06-15 23:04:17
When a commit changes a path P that used to be a file to a directory
and creates a new path P/X in it, "git show" would say that file P
was removed and file P/X was created for such a commit.
However, if we compare two directories, D1 and D2, where D1 has a
file D1/P in it and D2 has a directory D2/P under which there is a
file D2/P/X, and ask "git diff --no-index D1 D2" to show their
differences, we simply get a refusal "file/directory conflict".
Surely, that may be what GNU diff does, but we can do better and it
is easy to do so.
Signed-off-by: Junio C Hamano <redacted>
---
diff-no-index.c | 23 +++++++++++++++++++++--
t/t4053-diff-no-index.sh | 12 ++++++++++++
2 files changed, 33 insertions(+), 2 deletions(-)
@@ -97,8 +97,27 @@ static int queue_diff(struct diff_options *o,if(get_mode(name1,&mode1)||get_mode(name2,&mode2))return-1;-if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2))-returnerror("file/directory conflict: %s, %s",name1,name2);+if(mode1&&mode2&&S_ISDIR(mode1)!=S_ISDIR(mode2)){+structdiff_filespec*d1,*d2;++if(S_ISDIR(mode1)){+/* 2 is file that is created */+d1=noindex_filespec(NULL,0);+d2=noindex_filespec(name2,mode2);+name2=NULL;+mode2=0;+}else{+/* 1 is file that is deleted */+d1=noindex_filespec(name1,mode1);+d2=noindex_filespec(NULL,0);+name1=NULL;+mode1=0;+}+/* emit that file */+diff_queue(&diff_queued_diff,d1,d2);++/* and then let the entire directory be created or deleted */+}if(S_ISDIR(mode1)||S_ISDIR(mode2)){structstrbufbuffer1=STRBUF_INIT;
@@ -77,4 +77,16 @@ test_expect_success 'diff D F and diff F D' ')'+test_expect_success'turning a file into a directory''+(+cdnon/git&&+mkdirdee/sub&&+echo1>d/sub&&+echo2>e/sub/file&&+printf"D\td/sub\nA\te/sub/file\n">expect&&+test_must_failgitdiff--no-index--name-statusde>actual&&+test_cmpexpectactual+)+'+ test_done
From: Junio C Hamano <hidden> Date: 2016-06-15 23:04:17
"git diff --no-index" was supposed to be a poor-man's approach to
allow using Git diff goodies outside of a Git repository, without
having to patch mainstream diff implementations.
Unlike a POSIX diff that treats "diff D F" (or "diff F D") as a
request to compare D/F and F (or F and D/F) when D is a directory
and F is a file, however, we did not accept such a command line and
instead barfed with "file/directory conflict".
Imitate what POSIX diff does and append the basename of the file
after the name of the directory before comparing.
Signed-off-by: Junio C Hamano <redacted>
---
diff-no-index.c | 43 +++++++++++++++++++++++++++++++++++++++++++
t/t4053-diff-no-index.sh | 22 ++++++++++++++++++++++
2 files changed, 65 insertions(+)
@@ -182,12 +182,50 @@ static int queue_diff(struct diff_options *o,}}+/* append basename of F to D */+staticvoidappend_basename(structstrbuf*path,constchar*dir,constchar*file)+{+constchar*tail=strrchr(file,'/');++strbuf_addstr(path,dir);+while(path->len&&path->buf[path->len-1]=='/')+path->len--;+strbuf_addch(path,'/');+strbuf_addstr(path,tail?tail+1:file);+}++/*+*DWIM"diff D F"into"diff D/F F"and"diff F D"into"diff F D/F"+*NotethatweappendthebasenameofFtoD/,so"diff a/b/file D"+*becomes"diff a/b/file D/file",not"diff a/b/file D/a/b/file".+*/+staticvoidfixup_paths(constchar**path,structstrbuf*replacement)+{+unsignedintisdir0,isdir1;++if(path[0]==file_from_standard_input||+path[1]==file_from_standard_input)+return;+isdir0=is_directory(path[0]);+isdir1=is_directory(path[1]);+if(isdir0==isdir1)+return;+if(isdir0){+append_basename(replacement,path[0],path[1]);+path[0]=replacement->buf;+}else{+append_basename(replacement,path[1],path[0]);+path[1]=replacement->buf;+}+}+voiddiff_no_index(structrev_info*revs,intargc,constchar**argv,constchar*prefix){inti,prefixlen;constchar*paths[2];+structstrbufreplacement=STRBUF_INIT;diff_setup(&revs->diffopt);for(i=1;i<argc-2;){