Search code examples
gitdiff

How to git diff with relative paths outside of repository?


I like the format of git diff and the diffstat, but sometimes I would like to use it outside of any git repository, on two arbitrary directories. Doing this introduces some undesired extra directory prefixes into the output, though, which I am having trouble getting rid of.

  1. Put some initial files into directory old (not in any repo).
  2. Copy to directory new and make some changes.
  3. git diff -p --stat old/ new/

This almost works, but unfortunately the diffstat includes {old => new}/ as a path prefix and the diff headers show a/old/... and b/new/..., for example:

 {old => new}/src/config.h |  2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/old/src/config.h b/new/src/config.h
index 8158fcc..7ec16df 100644
--- a/old/src/config.h
+++ b/new/src/config.h
@@ -66,7 +66,7 @@
...

I want the diff to be entirely relative to the directories specified on the command line, thus not naming them in the resulting output at all (i.e. {old => new}/ should not appear and the diff headers should only be a/... and b/...). I do want it to include the relative path within each directory (e.g. src/). i.e. the "correct" output would be:

 src/config.h |  2 +-
 1 files changed, 1 insertions(+), 1 deletions(-)

diff --git a/src/config.h b/src/config.h
index 8158fcc..7ec16df 100644
--- a/src/config.h
+++ b/src/config.h
@@ -66,7 +66,7 @@
...

I would also like this to work even if old and new are not sibling directories -- e.g. I might be comparing old/ and ../foo/bar/new/, and the output should be exactly identical to the above if they have the same contents.

If, instead of old and new, I name the directories a and b (and ensure they're siblings), then I can get the patch part to appear correctly by adding --no-prefix, but this still outputs the wrong diffstat. (This also works if I instead run diff -ur a/ b/, but then there's no diffstat, and it doesn't work with different directory names.)

Is there any trick I'm missing to accomplish this? Note that I'm on a Windows system, so I only have the limited set of posix tools that come with Git for Windows.


Solution

  • The only working solution to this problem that I have found is to follow the suggestion to use sed by @LeGEC :

    diff -u $OLD $NEW | sed -e "s@$OLD/@a/@;s@$NEW/@b/@"
    

    Or with git diff:

    git diff --no-index $OLD $NEW | sed -e "s@$OLD/@@;s@$NEW/@@"
    

    This is still somewhat fragile as it relies on picking a delimiter that's unlikely to appear in either $OLD or $NEW, and these directory names must not appear anywhere in the actual patch content (other than the headers) or it will be corrupted. The latter point means that it's probably a bad idea to actually use "old" and "new" as given in the question; you should use some more unique name. (Though it does at least require a trailing slash, to reduce spurious matches somewhat.)

    It would be possible to design a more complex regex that ensures replacement only in the header, bearing in mind that each appears in two contexts. That's left as an exercise to the reader, since I just use the unique directory names.