git diff for *.cpp files prints nothing in one repository - linux

Once the command git diff *.cpp just stopped printing anything in one repository, despite git diff *.h works fine. git diff works fine for *.cpp files.
What could it be?

The fix is to be careful to run, e.g.:
git diff '*.cpp'
or:
git diff \*.cpp
or more formally:
git diff -- '*.cpp'
or similar.
What's going on here?
Missing from the question: the desired output was a diff for subdir/subfile.cpp, but the top level of the work-tree contained one file named file.cpp or similar.
The problem stems from the fact that you're using a Unix/Linux-style shell, which expands * and other wildcard or glob characters before running the command you enter at the command line. But there is a bit of subtlety here as well.
Because there exists a file named file.cpp in the current directory, when you run:
git diff *.cpp
the shell replaces *.cpp with the names of all the files whose name ends with .cpp, and therefore runs:
git diff file.cpp
Git then dutifully produces the diff—or in this case, no diff since there is no difference—for the one file named file.cpp.
When there are no files named file.cpp or zorg.cpp or similar in the top level directory, however, this shell simply invokes git with arguments diff and *.cpp, as if you had quoted the asterisk. This gives Git the chance to expand the *.cpp argument, and when Git expands it, it does so in a different way than the shell.
Why use --?
The git diff command takes a number of options, such as -s, -p, -w, --name-status, --name-only, and so on.
Depending on what files you have, suppose you want a diff listing for the file named -z in the current directory. If you then run:
git diff -z
Git thinks you mean to supply the -z option, rather than to get a diff listing for the file named -z. A similar problem applies if you want diffs for -z* and the like.
In general, you can work around this problem by using the file name ./-z instead of just -z. Since ./-z does not start with -, Git is not fooled into thinking it's an option. But this problem is more general, and strikes in other cases (other commands) as well. For instance, suppose you have a file named develop and you run:
git checkout develop
Git will think you mean to check out the branch named develop.
All Git commands accept -- as a separator, generally meaning there no more options: anything after this point is an argument instead. For git diff, anything after -- is treated as a pathspec, which includes doing glob expansion, provided the glob characters made it past the shell.
This is what the syntax description in the SYNOPSIS section of the git diff documentation means:
git diff [<options>] [<commit>] [--] [<path>...]
git diff [<options>] --cached [<commit>] [--] [<path>...]
git diff [<options>] <commit> <commit> [--] [<path>...]
git diff [<options>] <blob> <blob>
git diff [<options>] --no-index [--] <path> <path>
The square brackets indicate that something is optional, so all the options are optional. The angle brackets indicate that some argument should be replaced with a string that meets the requirements of the type inside the brackets. The literal -- or other literal options imply that you should type those characters literally—so git diff --cached requires the literal string --cached, for instance. Last, the ... means "repeat the previous as often as you like".
Since the literal string -- is optional, you don't have to enter it—but if you do, everything after it must have the form of a <path>. That form is quite general: almost any character is valid. The documentation is missing a cross-reference to the definition of a pathspec, though (and probably should use <pathspec>, not just <path>, here). The full description of pathspecs is in the gitglossary.

So I have one .cpp file in the repository root directory, and so git diff *.cpp now prints changes only for this file, while git diff '*.cpp' works fine for all files in subdirectories.

Related

git diff --numstat by strings instead of file paths

I'm trying to run git diff --no-index --numstat <string> <string> on Linux (Debian Docker container), but I'm failing in finding a way to achieve this. In essence I want to pass the files' contents as strings instead of their file paths. The goal is to retrieve the stats from the --numstat flag.
This command should be executable outside of a git repository/directory and on Linux. So far, I've found two solutions which lack the former requirements:
git diff --no-index --numstat /dev/fd/3 /dev/fd/4 3<<<$(echo "<string>") 4<<<$(echo "<string>"): This works on MacOS, but fails to work on Linux.
git diff --numstat $(echo <string> | git hash-object -w --stdin) $(echo <string> | git hash-object -w --stdin): which only works inside git repositories (got this partial solution from here)
Certainly there must be a way to achieve this, either via some git command or other bash concepts I'm unaware of. Any help would be great.
Thanks!
The reason that solution 1. isn't working is that /dev/fd/3 and /dev/fd/4 are symlinks and git diff does not follow symlinks but instead uses their link target string as their "content".
The only way to pass a string to git diff directly instead of a file is as stdin - which obviously only works for one of the files. So I see only two possible solutions to your problem:
write the strings to (temporary) files first, then pass them to git diff
use another tool, as suggested by #B--rian in the comment
Another, shorter version of 1. using process substitution would be:
git diff --no-index --numstat <(echo "<string1>") <(echo "<string2>")
Which unfortunately doesn't work either for the same reason/because git diff does not support process substitution, see https://stackoverflow.com/a/49636553/11932806

What's the easiest way to use the output paths from a git command in a subsequent git command?

I far too frequently use the mouse to do things like this:
/home/me-$ git log --name-status -1
commit a10e63af1f4b1b2c28055fed55d4f2bb3225a541
Author: Me <me#me.com>
Date: Tue Aug 18 13:04:04 2015 -0400
XYZ-376 make ctors public
M x/y/z/Class1.java
M x/y/z/Class2.java
/home/me-$ git checkout -- x/y/z/Class2.java # <-- copy/paste with the mouse
I know that some git commands accept wildcards, and this mitigates this problem somewhat, but I'm wondering if there is a way do specifically reference pathspecs, etc. from previous commands.
How can I run commands like this without using the mouse, and without retyping long paths by hand?
I typically use a subshell ($(<command in subshell here...>)) for this.
For example, sometimes I had many files deleted and I had to git rm every one of them.
There's the command git ls-files --deleted that returns the names of all the missing files. I can combine it with git rm like this:
git rm $(git ls-files --deleted)
This is somewhat a bad example, because (as I discovered later), this operation can be achieved much easier with git add --all. But I think it illustrates the point.
In your case, if you wanted to checkout all files that have been changed in the previous commit, it would be hard to parse the output of git log --name-status, because it contains additional information, but you could use something like git diff HEAD^ --name-only instead.
So:
git checkout $(git diff HEAD^ --name-only)
will do it in your example.
One nice thing that I noticed using the $(...) syntax is that it works both in Bash and in PowerShell.
This'd be the kind of thing you run a shell under emacs for, run all your shells in it and have a command to walk back through the buffer looking for patterns in the output.
For retrieving output from a previous command that you didn't capture inside the shell session, you're going to have to get it from your terminal emulator's buffers somehow. The xterm family has a configurable "copy the whole scrollback buffer" thingy, then xclip -o will print the selection and you can pipe it through an extraction filter.
But it's either capture the output within the session or scrape it from the output buffers afterwards, that's everywhere the data's ever been.
On OS X, "Mouseless Copy" supported by iTerm2 (and probably some other terminal emulators) is a workable solution: https://www.iterm2.com/features.html
search for some portion of the string (⌘F)
expand selection right (tab) or left (shift-tab)
paste selection with (option-enter) or copy/paste in the usual way

How to avoid .git files when comparing two folders

I have git installed on my Mac. I am trying to make a diff between files in two different folders.
diff -rq PATH_Folder1/ PATH_Folder2/ > Desktop/DIFF.txt
The results include .git files.
Example:
Files PATH1/abi/cpp/.git/index and PATH2/abi/cpp/.git/index differ.
How can I avoid comparing .git files. I don't require comparing git indices.
You can use the -x option to exclude file patterns. So for this case it would be:
diff -rq -x .git PATH_Folder1/ PATH_Folder2/ > Desktop/DIFF.txt
You could use git diff rather than simple diff, which should know to avoid .git files and the like. Here is the reference for the git diff command.
Even better would be using a graphical diff tool with
$ git difftool -t meld
or
$ git difftool -t kdiff3
Several guides and howtos exist out there. This is a good example.

Removing sensitive data from Git. "fatal: ambiguous argument 'rm'"

I'm trying to run this command:
git filter-branch --force --index-filter 'git rm --cached --ignore-unmatch filename.js' --prune-empty --tag-name-filter cat -- --all
but I keep getting this error:
fatal: ambiguous argument 'rm': unknown revision or path not in the working tree
.
Use '--' to separate paths from revisions, like this:
'git <command> [<revision>...] -- [<file>...]'
It depends on the shell you are using.
On Windows, with msysgit for instance, see issue 477:
Single quotes do not have a special meaning with CMD. Do not expect that they work
the same as with a POSIX shell. Call filter-branch like this:
git filter-branch --commit-filter "GIT_COMMITTER_NAME=void GIT_AUTHOR_NAME=void GIT_COMMITTER_EMAIL=just.a.test#kernel.org GIT_AUTHOR_EMAIL=just.a.test#kernel.org; git commit-tree \"$#\"" HEAD
Multiple lines:
git filter-branch --commit-filter "GIT_COMMITTER_NAME=void \
GIT_AUTHOR_NAME=void \
GIT_COMMITTER_EMAIL=just.a.test#kernel.org \
GIT_AUTHOR_EMAIL=just.a.test#kernel.org; \
git commit-tree \"$#\"" HEAD
As mentioned in "How to pass a programmatically generated list of files to git filter-branch?"
Each argument to the various ...-filters needs to be a single string. That string is saved as a shell variable.
So make sure 'git rm --cached --ignore-unmatch filename.js' is considered a string in the shell you are in.
As Constantine Ketskalo points out in the comments:
Windows 10, PyCharm, GitPython, same command as in question.
Simply changed ' to " inside the string and it worked!
On windows, you have to use double quote " instead of single '

How do you pass a file list to the linux zip command

I'm using Git for version control and unlike SVN I have not come across an inherent means of performing an export of changed files between 2 revisions, branches or tags.
As an alternative I want to use the linux zip command and pass it a set of file names, however the file names are the result of another command git diff. Below is an example of what I am trying to achieve:
zip /home/myhome/releases/files.zip git diff --name-only -a 01-tag 00-tag
However the above does not work as I guess the 'zip' command sees the git operation as part of its command options.
Does someone know how I can make something like the above work?
Thanks
You need to execute the git command in a sub-shell:
zip /home/myhome/releases/files.zip `git diff --name-only -a 01-tag 00-tag`
# another syntax (assuming bash):
zip /home/myhome/releases/files.zip $(git diff --name-only -a 01-tag 00-tag)
Another option is the xargs command:
git diff --name-only -a 01-tag 00-tag | xargs zip /home/myhome/releases/files.zip
If you're in a git shell (bash) you can also do this:
git diff -–name-only commit1 commit2 | zip ../Changes.zip –#
Works for me on Windows and Unix based systems.

Resources