1

The manpage of git show says

git show [<options>] [<object>…]

From https://stackoverflow.com/a/7196615

git show some_commit_sha1 -- some_file.c

What does the command mean?

Are both some_commit_sha1 and some_file.c <object> arguments?

Is it the same as

git show -- some_commit_sha1 some_file.c

Thanks.

1 Answers1

2

They're not both <object> arguments, and the documentation here is not very good. However, git show is a user-oriented ("porcelain") command and using it to pipe to other Git commands is not generally good practice in the first place.

In this particular case, git show <commit> -- <path> winds up invoking the equivalent of git log --no-walk --cc <commit> -- <path>. That is, it first runs the same code as git log --no-walk to produce the log message for that particular commit, formatted according to any format directives (none, so this defaults to --pretty=medium). Then it produces a diff listing, even if the commit is a merge commit—by default, git log does not produce a diff listing for merge commits, hence the --cc option.

The various git diff commands—both the porcelain git diff itself, and the plumbing variants git diff-tree, git diff-files, and git diff-index—all allow you to specify particular pathnames or pathspecs. Normally it's pretty clear whether something is a pathname (e.g., README.txt) or a commit-specifier (e.g., master). There are ambiguous cases, though: what if you have a file named master?

To distinguish pathname / pathspec arguments from commit-specifiers in these ambiguous cases, these commands use the -- convention defined by the POSIX specifications for utility commands (see Guideline 10 in particular). That is:

git diff-tree master -- develop

tells git diff-tree that the commit you want is master and the file you care about is named develop, while:

git diff-tree develop -- master

tells git diff-tree that the commit is develop and the file is master, and:

git diff-tree develop master -- release

tells git diff-tree that the two commits are develop and master in that order, while the file is release. Note that git diff-tree takes up to two <tree-ish> arguments: when given only one such argument, the implication is that the one argument is a commit specifier, and Git should diff the commit's parent(s) against the commit.

Conclusions

  1. For reliability (to avoid getting tripped up by various diff configuration items, such as forced use of color sequences via color.diff=always), the command in the answer to which you linked really should read:

    git diff-tree -p <commit> -- <file> | git apply -R
    

    (the -p option to diff-tree turns on the -r option).

  2. The git show documentation, particularly the synopsis line, is inadequate. An improvement, not necessarily complete, would read:

    git show [<options>] [<object>...] [--] [<path>...]
    
torek
  • 448,244
  • 59
  • 642
  • 775
  • Thanks. Could you explain what `git show some_commit_sha1 -- some_file.c | git apply -R` does? –  Jan 25 '19 at 02:52
  • Since `git show` invokes the equivalent of `git log -p --cc` or `git diff-tree -p`, you get the changes from the parent(s) to the child. If there is more than one parent, this combined diff is useless as a patch, and the whole thing produces nothing useful, but if the commit has a single parent, the output from the diff is usable as a patch (see [the old `patch` utility](https://en.wikipedia.org/wiki/Patch_(Unix))), and `git apply` treats it as one. The `-R` flag tells `git apply` that the patch should be reversed, so it's reverse-applied: [continued next comment] – torek Jan 25 '19 at 05:41
  • Where the patch says, e.g., "delete line 30, which used to consist of the following text: ...", Git will *insert* a line, consisting of that text. Where the patch says "insert line 45, consisting of the following text: ..." Git will *delete* line 45, provided it consists of that text. If there is sufficient context, the context is used to locate the places where lines should be removed or inserted, if they have moved. – torek Jan 25 '19 at 05:43
  • Since the diff output shows what someone did *to* one specific file in that commit, the `git apply -R`, which reverse-applies that same patch, attempts to *undo* precisely the same thing, in the same file. That's not always possible, due to additional changes that may happened in since the commit in question, but if it *is* possible, that's what it achieves. Note that this is the same goal as `git revert` except that (1) we look only at one file, and (2) since `git revert` performs a three-way merge, it can succeed in cases where the patch will fail. – torek Jan 25 '19 at 05:44