All of lore.kernel.org
 help / color / mirror / Atom feed
* [PATCH] Add `git diff2`, a GNU diff workalike
@ 2007-02-16  4:01 Johannes Schindelin
  2007-02-16  8:06 ` Junio C Hamano
       [not found] ` <7vtzxknrzp.fsf@assigned-by-dhcp.cox.net>
  0 siblings, 2 replies; 5+ messages in thread
From: Johannes Schindelin @ 2007-02-16  4:01 UTC (permalink / raw)
  To: git, junkio


Git does have a wonderful diff engine. For example, colored diffs
really shine, and there are other useful options like --check,
--patch-with-stat, etc. I always dreamt of using this diff engine
also outside of a git repository.

With this commit, you can say

	git diff2 file1 file2

to compare the (possibly untracked) files "file1" and "file2", and

	git diff2 dir1 dir2

to compare the directories "dir1" and "dir2".

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---
 .gitignore      |    1 +
 Makefile        |    1 +
 builtin-diff2.c |  163 +++++++++++++++++++++++++++++++++++++++++++++++++++++++
 builtin.h       |    1 +
 diff.c          |    3 +-
 git.c           |    1 +
 6 files changed, 169 insertions(+), 1 deletions(-)
 create mode 100644 builtin-diff2.c

diff --git a/.gitignore b/.gitignore
index 9b5502b..9809a7d 100644
--- a/.gitignore
+++ b/.gitignore
@@ -32,6 +32,7 @@ git-cvsserver
 git-daemon
 git-describe
 git-diff
+git-diff2
 git-diff-files
 git-diff-index
 git-diff-tree
diff --git a/Makefile b/Makefile
index ea2b4f5..94b1c75 100644
--- a/Makefile
+++ b/Makefile
@@ -284,6 +284,7 @@ BUILTIN_OBJS = \
 	builtin-count-objects.o \
 	builtin-describe.o \
 	builtin-diff.o \
+	builtin-diff2.o \
 	builtin-diff-files.o \
 	builtin-diff-index.o \
 	builtin-diff-tree.o \
diff --git a/builtin-diff2.c b/builtin-diff2.c
new file mode 100644
index 0000000..1de82c1
--- /dev/null
+++ b/builtin-diff2.c
@@ -0,0 +1,163 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "path-list.h"
+
+static const char diff2_usage[] = "git diff2 [diff-opts] file1 file2";
+
+static int read_directory(const char *path, struct path_list *list)
+{
+	DIR *dir;
+	struct dirent *e;
+
+	if (!(dir = opendir(path)))
+		return error("Could not open directory %s", path);
+
+	while ((e = readdir(dir)))
+		if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
+			path_list_insert(xstrdup(e->d_name), list);
+
+	closedir(dir);
+	return 0;
+}
+
+static int queue_diff(struct diff_options *o,
+		const char *name1, const char *name2)
+{
+	struct stat st;
+	int mode1 = 0, mode2 = 0;
+
+	if (name1) {
+		if (stat(name1, &st))
+			return error("Could not access '%s'", name1);
+		mode1 = st.st_mode;
+	}
+	if (name2) {
+		if (stat(name2, &st))
+			return error("Could not access '%s'", name1);
+		mode2 = st.st_mode;
+	}
+
+	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
+		return error("file/directory conflict: %s, %s", name1, name2);
+
+	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
+		char buffer1[PATH_MAX], buffer2[PATH_MAX];
+		struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
+		int len1 = 0, len2 = 0, i1, i2, ret = 0;
+
+		if (name1 && read_directory(name1, &p1))
+			return -1;
+		if (name2 && read_directory(name2, &p2)) {
+			path_list_clear(&p1, 0);
+			return -1;
+		}
+
+		if (name1) {
+			len1 = strlen(name1);
+			if (len1 > 0 && name1[len1 - 1] == '/')
+				len1--;
+			memcpy(buffer1, name1, len1);
+			buffer1[len1++] = '/';
+		}
+
+		if (name2) {
+			len2 = strlen(name2);
+			if (len2 > 0 && name2[len2 - 1] == '/')
+				len2--;
+			memcpy(buffer2, name2, len2);
+			buffer2[len2++] = '/';
+		}
+
+		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
+			const char *n1, *n2;
+			int comp;
+
+			if (i1 == p1.nr)
+				comp = 1;
+			else if (i2 == p2.nr)
+				comp = -1;
+			else
+				comp = strcmp(p1.items[i1].path,
+					p2.items[i2].path);
+
+			if (comp > 0)
+				n1 = NULL;
+			else {
+				n1 = buffer1;
+				strncpy(buffer1 + len1, p1.items[i1++].path,
+						PATH_MAX - len1);
+			}
+
+			if (comp < 0)
+				n2 = NULL;
+			else {
+				n2 = buffer2;
+				strncpy(buffer2 + len2, p2.items[i2++].path,
+						PATH_MAX - len2);
+			}
+
+			ret = queue_diff(o, n1, n2);
+		}
+		path_list_clear(&p1, 0);
+		path_list_clear(&p2, 0);
+
+		return ret;
+	} else {
+		struct diff_filespec *d1, *d2;
+
+		if (o->reverse_diff) {
+			unsigned tmp;
+			const char *tmp_c;
+			tmp = mode1; mode1 = mode2; mode2 = tmp;
+			tmp_c = name1; name1 = name2; name2 = tmp_c;
+		}
+
+		if (!name1)
+			name1 = "/dev/null";
+		if (!name2)
+			name2 = "/dev/null";
+		d1 = alloc_filespec(name1);
+		d2 = alloc_filespec(name2);
+		fill_filespec(d1, null_sha1, mode1);
+		fill_filespec(d2, null_sha1, mode2);
+
+		diff_queue(&diff_queued_diff, d1, d2);
+		return 0;
+	}
+}
+
+int cmd_diff2(int argc, char **argv, char **envp)
+{
+	struct diff_options options;
+	int i, i2;
+        int nongit = 0;
+
+        setup_git_directory_gently(&nongit);
+	git_config(git_diff_ui_config);
+
+	diff_setup(&options);
+	for (i = 1; i < argc; ) {
+		if (!strcmp("--", argv[i])) {
+			i++;
+			break;
+		}
+		i2 = diff_opt_parse(&options,
+				(const char **)argv + i, argc - i);
+		if (!i2)
+			break;
+		i += i2;
+	}
+	if (diff_setup_done(&options) < 0)
+		die("diff_setup_done failed");
+	if (!options.output_format)
+		options.output_format = DIFF_FORMAT_PATCH;
+
+	if (argc - i != 2)
+		usage(diff2_usage);
+
+	queue_diff(&options, argv[i], argv[i + 1]);
+	diffcore_std(&options);
+	diff_flush(&options);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index f8e1425..72f94ec 100644
--- a/builtin.h
+++ b/builtin.h
@@ -29,6 +29,7 @@ extern int cmd_describe(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
+extern int cmd_diff2(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fetch__tool(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
diff --git a/diff.c b/diff.c
index c2d9abe..be73621 100644
--- a/diff.c
+++ b/diff.c
@@ -2443,7 +2443,8 @@ static void diff_resolve_rename_copy(void)
 				p->status = DIFF_STATUS_RENAMED;
 		}
 		else if (hashcmp(p->one->sha1, p->two->sha1) ||
-			 p->one->mode != p->two->mode)
+			 p->one->mode != p->two->mode ||
+			 is_null_sha1(p->one->sha1))
 			p->status = DIFF_STATUS_MODIFIED;
 		else {
 			/* This is a "no-change" entry and should not
diff --git a/git.c b/git.c
index be04fb5..5e6b355 100644
--- a/git.c
+++ b/git.c
@@ -249,6 +249,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
 		{ "describe", cmd_describe, RUN_SETUP },
 		{ "diff", cmd_diff, RUN_SETUP | USE_PAGER },
+		{ "diff2", cmd_diff2, USE_PAGER },
 		{ "diff-files", cmd_diff_files, RUN_SETUP },
 		{ "diff-index", cmd_diff_index, RUN_SETUP },
 		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
-- 
1.5.0.2137.g20bab

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* Re: [PATCH] Add `git diff2`, a GNU diff workalike
  2007-02-16  4:01 [PATCH] Add `git diff2`, a GNU diff workalike Johannes Schindelin
@ 2007-02-16  8:06 ` Junio C Hamano
  2007-02-16 14:01   ` Johannes Schindelin
       [not found] ` <7vtzxknrzp.fsf@assigned-by-dhcp.cox.net>
  1 sibling, 1 reply; 5+ messages in thread
From: Junio C Hamano @ 2007-02-16  8:06 UTC (permalink / raw)
  To: Johannes Schindelin; +Cc: git

Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:

> Git does have a wonderful diff engine. For example, colored diffs
> really shine, and there are other useful options like --check,
> --patch-with-stat, etc. I always dreamt of using this diff engine
> also outside of a git repository.
>
> With this commit, you can say
>
> 	git diff2 file1 file2

Why diff2?  I would have expected this to be a new low-level
sibling of diff-files and diff-index, which in turn hook into
the overall "diff" driver.

> +static int queue_diff(struct diff_options *o,
> +		const char *name1, const char *name2)
> +{
> +	struct stat st;
> +	int mode1 = 0, mode2 = 0;
> +
> +	if (name1) {
> +		if (stat(name1, &st))
> +			return error("Could not access '%s'", name1);
> +		mode1 = st.st_mode;
> +	}
> +	if (name2) {
> +		if (stat(name2, &st))
> +			return error("Could not access '%s'", name1);
> +		mode2 = st.st_mode;
> +	}

I am still debating myself if these should be lstat(2) instead
of stat(2).  The former is more consistent with what git does.

> +	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
> +		return error("file/directory conflict: %s, %s", name1, name2);

If a/frotz is a file and b/frotz/nitfol is there, I do not think
we show an error; we say "a/frotz" was removed (see notes below,
though).

> +	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
> +		char buffer1[PATH_MAX], buffer2[PATH_MAX];
> +		struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
> +		int len1 = 0, len2 = 0, i1, i2, ret = 0;
> +
> +		if (name1 && read_directory(name1, &p1))
> +			return -1;
> +		if (name2 && read_directory(name2, &p2)) {
> +			path_list_clear(&p1, 0);
> +			return -1;
> +		}

I suspect your favorite path-list might not be optimal for this
kind of codeflow; wouldn't reading everything in an expanding
array and sorting them with a single qsort() after reading one
directory more efficient?

> +		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
> +			const char *n1, *n2;
> +			int comp;
> +
> +			if (i1 == p1.nr)
> +				comp = 1;
> +			else if (i2 == p2.nr)
> +				comp = -1;
> +			else
> +				comp = strcmp(p1.items[i1].path,
> +					p2.items[i2].path);

I think you would want to be consistent with how git sorts paths
here.  In particular, you can always pretend that a path that is
a directory has '/' at the end.  Which (as a side effect) means
that you do not have to worry about a/frotz and b/frotz/nitfol,
because element from p1 will be "frotz" and the one from p2 will
be "frotz/" in this case.  You will never feed queue_diff() with
the same name with d/f conflicts that way.

> +int cmd_diff2(int argc, char **argv, char **envp)
> +{

The rest looks quite straightforward use of diffcore API, done
very cleanly.

> diff --git a/diff.c b/diff.c
> index c2d9abe..be73621 100644
> --- a/diff.c
> +++ b/diff.c
> @@ -2443,7 +2443,8 @@ static void diff_resolve_rename_copy(void)
>  				p->status = DIFF_STATUS_RENAMED;
>  		}
>  		else if (hashcmp(p->one->sha1, p->two->sha1) ||
> -			 p->one->mode != p->two->mode)
> +			 p->one->mode != p->two->mode ||
> +			 is_null_sha1(p->one->sha1))
>  			p->status = DIFF_STATUS_MODIFIED;

I didn't look, but you might also need to teach diffcore-rename
that two objects both with null object names are not equal.

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] Add `git diff2`, a GNU diff workalike
  2007-02-16  8:06 ` Junio C Hamano
@ 2007-02-16 14:01   ` Johannes Schindelin
  2007-02-16 14:20     ` Johannes Schindelin
  0 siblings, 1 reply; 5+ messages in thread
From: Johannes Schindelin @ 2007-02-16 14:01 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi,

On Fri, 16 Feb 2007, Junio C Hamano wrote:

> Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> 
> > Git does have a wonderful diff engine. For example, colored diffs
> > really shine, and there are other useful options like --check,
> > --patch-with-stat, etc. I always dreamt of using this diff engine
> > also outside of a git repository.
> >
> > With this commit, you can say
> >
> > 	git diff2 file1 file2
> 
> Why diff2?  I would have expected this to be a new low-level sibling of 
> diff-files and diff-index, which in turn hook into the overall "diff" 
> driver.

I planned this. However, I could not think of any sane way to handle the 
case in an intuitive manner where you want to compare two _tracked_ files. 
How would you do that in a consistent manner?

BTW I also realized that `git diff bla` will _not_ complain when the file 
bla exists, but is untracked. Neither will it show a diff. I did not come 
around to fix that yet.

> > +static int queue_diff(struct diff_options *o,
> > +		const char *name1, const char *name2)
> > +{
> > +	struct stat st;
> > +	int mode1 = 0, mode2 = 0;
> > +
> > +	if (name1) {
> > +		if (stat(name1, &st))
> > +			return error("Could not access '%s'", name1);
> > +		mode1 = st.st_mode;
> > +	}
> > +	if (name2) {
> > +		if (stat(name2, &st))
> > +			return error("Could not access '%s'", name1);
> > +		mode2 = st.st_mode;
> > +	}
> 
> I am still debating myself if these should be lstat(2) instead of 
> stat(2).  The former is more consistent with what git does.

Well, it should probably be lstat(), with an addremove when one or both 
files are links. But they could be identical also.

> > +	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
> > +		return error("file/directory conflict: %s, %s", name1, name2);
> 
> If a/frotz is a file and b/frotz/nitfol is there, I do not think we show 
> an error; we say "a/frotz" was removed (see notes below, though).

Good point. Will fix.

> > +	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
> > +		char buffer1[PATH_MAX], buffer2[PATH_MAX];
> > +		struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
> > +		int len1 = 0, len2 = 0, i1, i2, ret = 0;
> > +
> > +		if (name1 && read_directory(name1, &p1))
> > +			return -1;
> > +		if (name2 && read_directory(name2, &p2)) {
> > +			path_list_clear(&p1, 0);
> > +			return -1;
> > +		}
> 
> I suspect your favorite path-list might not be optimal for this kind of 
> codeflow; wouldn't reading everything in an expanding array and sorting 
> them with a single qsort() after reading one directory more efficient?

You are right. But for the moment, I'll leave it, since it is one of the 
least performance critical parts of diff2, _and_ the use of path-list is 
really easy.

> > +		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
> > +			const char *n1, *n2;
> > +			int comp;
> > +
> > +			if (i1 == p1.nr)
> > +				comp = 1;
> > +			else if (i2 == p2.nr)
> > +				comp = -1;
> > +			else
> > +				comp = strcmp(p1.items[i1].path,
> > +					p2.items[i2].path);
> 
> I think you would want to be consistent with how git sorts paths
> here.  In particular, you can always pretend that a path that is
> a directory has '/' at the end.

At that point, the entries in p1 and p2 are from readdir(), so I do not 
know the type (e->d_type is non-portable according to my man-page here).

BTW I had a memory leak in path_list_insert(xstrdup(...

> The rest looks quite straightforward use of diffcore API, done
> very cleanly.

Thanks!

> > diff --git a/diff.c b/diff.c
> > index c2d9abe..be73621 100644
> > --- a/diff.c
> > +++ b/diff.c
> > @@ -2443,7 +2443,8 @@ static void diff_resolve_rename_copy(void)
> >  				p->status = DIFF_STATUS_RENAMED;
> >  		}
> >  		else if (hashcmp(p->one->sha1, p->two->sha1) ||
> > -			 p->one->mode != p->two->mode)
> > +			 p->one->mode != p->two->mode ||
> > +			 is_null_sha1(p->one->sha1))
> >  			p->status = DIFF_STATUS_MODIFIED;
> 
> I didn't look, but you might also need to teach diffcore-rename
> that two objects both with null object names are not equal.

But would not every diff be marked as a rename then?

Ciao,
Dscho

^ permalink raw reply	[flat|nested] 5+ messages in thread

* Re: [PATCH] Add `git diff2`, a GNU diff workalike
  2007-02-16 14:01   ` Johannes Schindelin
@ 2007-02-16 14:20     ` Johannes Schindelin
  0 siblings, 0 replies; 5+ messages in thread
From: Johannes Schindelin @ 2007-02-16 14:20 UTC (permalink / raw)
  To: Junio C Hamano; +Cc: git

Hi,

On Fri, 16 Feb 2007, Johannes Schindelin wrote:

> On Fri, 16 Feb 2007, Junio C Hamano wrote:
> 
> > Johannes Schindelin <Johannes.Schindelin@gmx.de> writes:
> > 
> > > +	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
> > > +		return error("file/directory conflict: %s, %s", name1, name2);
> > 
> > If a/frotz is a file and b/frotz/nitfol is there, I do not think we show 
> > an error; we say "a/frotz" was removed (see notes below, though).
> 
> Good point. Will fix.

Thinking about this again, I do not know of any patch implementation which 
removes a directory which just became empty, so dir->file is a real 
problem. Also, if dir is empty, another problem looms.

So at least the dir->non-dir case should be an error.

BTW I just realized that diff2 as-is will output a diff header for _every_ 
file pair, even if they do compare equally. Actually, I like it, so I 
don't want to imitate GNU diff behaviour here.

Ciao,
Dscho

P.S.: Here is a quick-fix patch on top of my original (tested with 
dir->file (error!), file->dir, link->link, link->null, file->link):

 builtin-diff2.c |   16 +++++++++++-----
 1 files changed, 11 insertions(+), 5 deletions(-)

diff --git a/builtin-diff2.c b/builtin-diff2.c
index 1de82c1..234dabb 100644
--- a/builtin-diff2.c
+++ b/builtin-diff2.c
@@ -15,7 +15,7 @@ static int read_directory(const char *path, struct path_list *list)
 
 	while ((e = readdir(dir)))
 		if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
-			path_list_insert(xstrdup(e->d_name), list);
+			path_list_insert(e->d_name, list);
 
 	closedir(dir);
 	return 0;
@@ -28,18 +28,24 @@ static int queue_diff(struct diff_options *o,
 	int mode1 = 0, mode2 = 0;
 
 	if (name1) {
-		if (stat(name1, &st))
+		if (lstat(name1, &st))
 			return error("Could not access '%s'", name1);
 		mode1 = st.st_mode;
 	}
 	if (name2) {
-		if (stat(name2, &st))
+		if (lstat(name2, &st))
 			return error("Could not access '%s'", name1);
 		mode2 = st.st_mode;
 	}
 
-	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
-		return error("file/directory conflict: %s, %s", name1, name2);
+	if (mode1 && mode2) {
+		if (S_ISDIR(mode1) && !S_ISDIR(mode2))
+			return error("Cannot handle dir->file: %s -> %s",
+				name1, name2);
+		if (!S_ISDIR(mode1) && S_ISDIR(mode2))
+			return queue_diff(o, name1, NULL) ||
+				queue_diff(o, NULL, name2);
+	}
 
 	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
 		char buffer1[PATH_MAX], buffer2[PATH_MAX];

^ permalink raw reply related	[flat|nested] 5+ messages in thread

* [PATCH] Add `git diff2`, a GNU diff workalike
       [not found] ` <7vtzxknrzp.fsf@assigned-by-dhcp.cox.net>
@ 2007-02-18 11:44   ` Johannes Schindelin
  0 siblings, 0 replies; 5+ messages in thread
From: Johannes Schindelin @ 2007-02-18 11:44 UTC (permalink / raw)
  To: Junio C Hamano, git


Git really has a wonderful diff engine. For example, colored diffs
really shine, and there are other useful options like --check,
--check, etc. I always dreamt of using this diff engine also outside
of a git repository.

With this commit, you can say

	git diff2 file1 file2

to compare the (possibly untracked) files "file1" and "file2", and

	git diff2 dir1 dir2

to compare the directories "dir1" and "dir2".

Signed-off-by: Johannes Schindelin <johannes.schindelin@gmx.de>
---

	Junio tricked me into writing documentation, so be gentle.

	Back to bed.

 .gitignore                  |    1 +
 Documentation/cmd-list.perl |    1 +
 Documentation/git-diff2.txt |   63 +++++++++++++++++
 Makefile                    |    1 +
 builtin-diff2.c             |  163 +++++++++++++++++++++++++++++++++++++++++++
 builtin.h                   |    1 +
 diff.c                      |    3 +-
 git.c                       |    1 +
 8 files changed, 233 insertions(+), 1 deletions(-)
 create mode 100644 Documentation/git-diff2.txt
 create mode 100644 builtin-diff2.c

diff --git a/.gitignore b/.gitignore
index f15155d..7c653fb 100644
--- a/.gitignore
+++ b/.gitignore
@@ -31,6 +31,7 @@ git-cvsimport
 git-cvsserver
 git-daemon
 git-diff
+git-diff2
 git-diff-files
 git-diff-index
 git-diff-tree
diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl
index a2d6268..e2b0bd3 100755
--- a/Documentation/cmd-list.perl
+++ b/Documentation/cmd-list.perl
@@ -90,6 +90,7 @@ git-describe                            mainporcelain
 git-diff-files                          plumbinginterrogators
 git-diff-index                          plumbinginterrogators
 git-diff                                mainporcelain
+git-diff2                               mainporcelain
 git-diff-tree                           plumbinginterrogators
 git-fast-import				ancillarymanipulators
 git-fetch                               mainporcelain
diff --git a/Documentation/git-diff2.txt b/Documentation/git-diff2.txt
new file mode 100644
index 0000000..8909119
--- /dev/null
+++ b/Documentation/git-diff2.txt
@@ -0,0 +1,63 @@
+git-diff2(1)
+===========
+
+NAME
+----
+git-diff2 - Show changes between files, directories and symlinks
+
+
+SYNOPSIS
+--------
+'git-diff2' [ --diff-options ] <path1> <path2>
+
+DESCRIPTION
+-----------
+Show changes between two files, directories or symlinks.
+
+'git-diff2' [--options] [--] <path1> <path2>::
+
+	This command operates independently of a git repository.
+	It is meant to bring the full power of git's diff engine
+	to your files, without having to check them into a git
+	repository first.
+
+
+OPTIONS
+-------
+include::diff-options.txt[]
+
+<path1> <path2>::
+	The <paths> parameters are used to determine which files
+	should be compared against each other.
+
+
+EXAMPLES
+--------
+
+See the differences README.new has relative to README::
++
+------------
+$ git diff2 README README.new
+------------
++
+
+Check if there is white space breakage in the changes leading from
+old/ to new/::
++
+------------
+$ git diff2 --check old/ new/
+------------
++
+
+Author
+------
+Written by Johannes Schindelin <johannes.schindelin@gmx.de>
+
+Documentation
+--------------
+Documentation by Johannes Schindelin and the git-list <git@vger.kernel.org>.
+
+GIT
+---
+Part of the gitlink:git[7] suite
+
diff --git a/Makefile b/Makefile
index ebecbbd..39a1a09 100644
--- a/Makefile
+++ b/Makefile
@@ -278,6 +278,7 @@ BUILTIN_OBJS = \
 	builtin-count-objects.o \
 	builtin-describe.o \
 	builtin-diff.o \
+	builtin-diff2.o \
 	builtin-diff-files.o \
 	builtin-diff-index.o \
 	builtin-diff-tree.o \
diff --git a/builtin-diff2.c b/builtin-diff2.c
new file mode 100644
index 0000000..1de82c1
--- /dev/null
+++ b/builtin-diff2.c
@@ -0,0 +1,163 @@
+#include "cache.h"
+#include "diff.h"
+#include "diffcore.h"
+#include "path-list.h"
+
+static const char diff2_usage[] = "git diff2 [diff-opts] file1 file2";
+
+static int read_directory(const char *path, struct path_list *list)
+{
+	DIR *dir;
+	struct dirent *e;
+
+	if (!(dir = opendir(path)))
+		return error("Could not open directory %s", path);
+
+	while ((e = readdir(dir)))
+		if (strcmp(".", e->d_name) && strcmp("..", e->d_name))
+			path_list_insert(xstrdup(e->d_name), list);
+
+	closedir(dir);
+	return 0;
+}
+
+static int queue_diff(struct diff_options *o,
+		const char *name1, const char *name2)
+{
+	struct stat st;
+	int mode1 = 0, mode2 = 0;
+
+	if (name1) {
+		if (stat(name1, &st))
+			return error("Could not access '%s'", name1);
+		mode1 = st.st_mode;
+	}
+	if (name2) {
+		if (stat(name2, &st))
+			return error("Could not access '%s'", name1);
+		mode2 = st.st_mode;
+	}
+
+	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2))
+		return error("file/directory conflict: %s, %s", name1, name2);
+
+	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
+		char buffer1[PATH_MAX], buffer2[PATH_MAX];
+		struct path_list p1 = {NULL, 0, 0, 1}, p2 = {NULL, 0, 0, 1};
+		int len1 = 0, len2 = 0, i1, i2, ret = 0;
+
+		if (name1 && read_directory(name1, &p1))
+			return -1;
+		if (name2 && read_directory(name2, &p2)) {
+			path_list_clear(&p1, 0);
+			return -1;
+		}
+
+		if (name1) {
+			len1 = strlen(name1);
+			if (len1 > 0 && name1[len1 - 1] == '/')
+				len1--;
+			memcpy(buffer1, name1, len1);
+			buffer1[len1++] = '/';
+		}
+
+		if (name2) {
+			len2 = strlen(name2);
+			if (len2 > 0 && name2[len2 - 1] == '/')
+				len2--;
+			memcpy(buffer2, name2, len2);
+			buffer2[len2++] = '/';
+		}
+
+		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
+			const char *n1, *n2;
+			int comp;
+
+			if (i1 == p1.nr)
+				comp = 1;
+			else if (i2 == p2.nr)
+				comp = -1;
+			else
+				comp = strcmp(p1.items[i1].path,
+					p2.items[i2].path);
+
+			if (comp > 0)
+				n1 = NULL;
+			else {
+				n1 = buffer1;
+				strncpy(buffer1 + len1, p1.items[i1++].path,
+						PATH_MAX - len1);
+			}
+
+			if (comp < 0)
+				n2 = NULL;
+			else {
+				n2 = buffer2;
+				strncpy(buffer2 + len2, p2.items[i2++].path,
+						PATH_MAX - len2);
+			}
+
+			ret = queue_diff(o, n1, n2);
+		}
+		path_list_clear(&p1, 0);
+		path_list_clear(&p2, 0);
+
+		return ret;
+	} else {
+		struct diff_filespec *d1, *d2;
+
+		if (o->reverse_diff) {
+			unsigned tmp;
+			const char *tmp_c;
+			tmp = mode1; mode1 = mode2; mode2 = tmp;
+			tmp_c = name1; name1 = name2; name2 = tmp_c;
+		}
+
+		if (!name1)
+			name1 = "/dev/null";
+		if (!name2)
+			name2 = "/dev/null";
+		d1 = alloc_filespec(name1);
+		d2 = alloc_filespec(name2);
+		fill_filespec(d1, null_sha1, mode1);
+		fill_filespec(d2, null_sha1, mode2);
+
+		diff_queue(&diff_queued_diff, d1, d2);
+		return 0;
+	}
+}
+
+int cmd_diff2(int argc, char **argv, char **envp)
+{
+	struct diff_options options;
+	int i, i2;
+        int nongit = 0;
+
+        setup_git_directory_gently(&nongit);
+	git_config(git_diff_ui_config);
+
+	diff_setup(&options);
+	for (i = 1; i < argc; ) {
+		if (!strcmp("--", argv[i])) {
+			i++;
+			break;
+		}
+		i2 = diff_opt_parse(&options,
+				(const char **)argv + i, argc - i);
+		if (!i2)
+			break;
+		i += i2;
+	}
+	if (diff_setup_done(&options) < 0)
+		die("diff_setup_done failed");
+	if (!options.output_format)
+		options.output_format = DIFF_FORMAT_PATCH;
+
+	if (argc - i != 2)
+		usage(diff2_usage);
+
+	queue_diff(&options, argv[i], argv[i + 1]);
+	diffcore_std(&options);
+	diff_flush(&options);
+	return 0;
+}
diff --git a/builtin.h b/builtin.h
index 57e8741..b872ecc 100644
--- a/builtin.h
+++ b/builtin.h
@@ -29,6 +29,7 @@ extern int cmd_describe(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_files(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_index(int argc, const char **argv, const char *prefix);
 extern int cmd_diff(int argc, const char **argv, const char *prefix);
+extern int cmd_diff2(int argc, const char **argv, const char *prefix);
 extern int cmd_diff_tree(int argc, const char **argv, const char *prefix);
 extern int cmd_fmt_merge_msg(int argc, const char **argv, const char *prefix);
 extern int cmd_for_each_ref(int argc, const char **argv, const char *prefix);
diff --git a/diff.c b/diff.c
index 12c8b2b..701880a 100644
--- a/diff.c
+++ b/diff.c
@@ -2406,7 +2406,8 @@ static void diff_resolve_rename_copy(void)
 				p->status = DIFF_STATUS_RENAMED;
 		}
 		else if (hashcmp(p->one->sha1, p->two->sha1) ||
-			 p->one->mode != p->two->mode)
+			 p->one->mode != p->two->mode ||
+			 is_null_sha1(p->one->sha1))
 			p->status = DIFF_STATUS_MODIFIED;
 		else {
 			/* This is a "no-change" entry and should not
diff --git a/git.c b/git.c
index 4dd1967..dfeddf2 100644
--- a/git.c
+++ b/git.c
@@ -238,6 +238,7 @@ static void handle_internal_command(int argc, const char **argv, char **envp)
 		{ "count-objects", cmd_count_objects, RUN_SETUP },
 		{ "describe", cmd_describe, RUN_SETUP },
 		{ "diff", cmd_diff, RUN_SETUP | USE_PAGER },
+		{ "diff2", cmd_diff2, USE_PAGER },
 		{ "diff-files", cmd_diff_files, RUN_SETUP },
 		{ "diff-index", cmd_diff_index, RUN_SETUP },
 		{ "diff-tree", cmd_diff_tree, RUN_SETUP },
-- 
1.5.0.rc4.2449.ga6f47

^ permalink raw reply related	[flat|nested] 5+ messages in thread

end of thread, other threads:[~2007-02-18 11:44 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2007-02-16  4:01 [PATCH] Add `git diff2`, a GNU diff workalike Johannes Schindelin
2007-02-16  8:06 ` Junio C Hamano
2007-02-16 14:01   ` Johannes Schindelin
2007-02-16 14:20     ` Johannes Schindelin
     [not found] ` <7vtzxknrzp.fsf@assigned-by-dhcp.cox.net>
2007-02-18 11:44   ` Johannes Schindelin

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.