How can I run `git diff --staged` with Fugitive? - vim-fugitive

The command :Gdiff is equivalent to running git diff on that file.
What's the equivalent for git diff --staged or git diff --cached?

I've found a way to do this. Run :Git, you should get a window with contents like the following:
# Head: master
# Merge: origin/master
# Help: g?
#
# Staged (1)
# M example.txt
#
Scroll down to the staged file, example.txt, and press dd. This will open a diff view, comparing what's in HEAD and what's in the index. You'll notice on the bar on the bottom that both the filenames are special Fugitive filenames.
Also while in :Git preview window, you can press g?, which will list all the mappings valid in the current context.

While vim-fugitive does not supply direct analogues for git diff --staged or git diff --cached, it does supply a general-purpose Vim command for piping the output of arbitrary git commands into read-only Vim buffers: :Git!.
Inapplicable Answers
Before we get to that, let's explicitly restate the question. git diff --staged and git diff --cached are synonyms for the same underlying operation: diffing the contents of the index (the set of all staged changes) against the contents of the HEAD (the most recent commit for the current branch), typically for reviewing changes prior to commit. The stated question then becomes:
What is the most effective means of reviewing all staged changes in vim-fugitive?
It should be clear that the currently accepted self-answer fails to address this question. The next highest rated self-answer is no better.
:Gstatus bindings only apply to the file on the current line and hence cannot by definition be used to review all staged changes. Moreover, the :Gstatus D binding doesn't even review all staged changes for the file on the current line. It only diffs the index and working tree copies of that file, rather than diffing the index and most recently committed copies of that file (which is an altogether different beast).
:Gdiff HEAD is similarly inapplicable. It only diffs the most recently committed and working tree copies of the file corresponding to the current buffer. :Gdiff without an argument is equivalent to the :Gstatus D binding, again diffing the index and working tree copies of that file. Neither reviews all staged changes.
Applicable Answers
emaniacs struck the closest to a working solution with this comment to the latter answer:
:Git diff --staged
Now we're approximating the truth!
:Git pipes the output of the passed git command to the current external pager, permitting a liesurely review of all staged changes external to Vim. But there's the rub: external to Vim. That means no Vim bindings, buffers, or syntax highlighting. Ideally, we'd prefer a read-only Vim buffer syntax highlighting the output of git diff --staged. Can we do this?
The Solution
We can, or Tim Pope isn't the Vim Pope. The !-suffixed variant of :Git does just that, permitting a liesurely review of all staged changes within Vim complete with Vim-based syntax highlighting of change differences:
:Git! diff --staged
Yeah. It's pretty awesomeness.
But let's go a step farther. In the time-honoured tradition of slothful slackers everywhere, let's define a new Vim command :Greview encapsulating this operation and a new binding <leader>gr running this command. Just stash the following into your .vimrc:
command Greview :Git! diff --staged
nnoremap <leader>gr :Greview<cr>
Assuming <leader> to be ,, reviewing all staged changes reduces to ,gr. It couldn't get any Vimmier.

:Gdiff HEAD
Gdiff takes an revision argument. So you can pass it HEAD. This is not equivalent to git diff --staged, but it can serve a similar purpose.

Update: 3/28/2017,
Current version of fugitive will do this automatically when you press D on a file.
if the file is staged, only staged changes will be showed in the diff.
If the file is not staged, then only change that are not staged will be visible.

As already noted, Gdiff, Gdiff : or Gdiff :0 gives you the diff with the index,
Gdiff - or Gdiff HEAD gives the diff with the HEAD.
Triple-split approach
So doing a diff first with : then with - show 3 diff-panes in vim:
HEAD
index ("cached" or "staged")
working tree
command! -bar Gvstage :Gvdiff -|Gvdiff : " vertical 3-split
command! -bar Gsstage :Gsdiff -|Gsdiff : " horizontal 3-split
Of course you can also just to Gvdiff - if you're already in diff mode.
Now pushing and getting changes is sligthly more complicated with 3 open windows, however, you can diffput from index to working tree and vice-versa easily, as on the HEAD modifiable is off, so it can never be targeted.
Otherwise, you can add some shortcuts for the diffput and diffget commands, knowing that they can take a "buffer specifier", which can be a pattern (see :help merge) or the buffer number. I modified the previous commands to save the initial buffer's number and use patterns for the others:
command! -bar Gvstage :let t:working_copy=bufnr('%')|Gvdiff -|Gvdiff : " vertical 3-split
command! -bar Gsstage :let t:working_copy=bufnr('%')|Gsdiff -|Gsdiff : " horizontal 3-split
nnoremap <Leader>hg :diffget fugitive://*/.git//[0-9a-f][0-9a-f]*/<CR> " HEAD get
nnoremap <Leader>ig :diffget fugitive://*/.git//0/<CR> " index get
nnoremap <Leader>ip :diffput fugitive://*/.git//0/<CR> " index put
nnoremap <Leader>wg :diffget <C-R>=t:working_copy<CR><CR> " work get
nnoremap <Leader>wp :diffput <C-R>=t:working_copy<CR><CR> " work put
Difftool approach
Alternately, if you just want a nice vimdiff view of what is staged instead of a patch, let me sugget:
command! Greview :exec "Git difftool --tool=vimdiff --staged " . fugitive#buffer().path()
This starts a new instance of vim, so when you quit it you go back to your tabs and windows you already had opened, which is perfect. This if faster (at least for me) than transitioning through the git status window, but has the drawback that you can't edit the staged file.

Use :Gtabedit #:% | Gdiff :.
This is better than the other answers because it opens in split view just like :Gdiff, rather than dumping diff syntax into a single buffer. When you're done, just :tabc to get back.
Explanation
Gtabedit open a new tab and edit a fugitive object:
from commit # (HEAD), a specific file :, the current file %.
Gdiff diff the current buffer against another fugitive object:
from the index : (you can specify the file again with :% but it's not needed).
Bonus
We now have fugitive equivalents for git diff (:Gdiff) and git diff --staged (the command above). To get the behaviour of git show on the current file, use :Gtabedit #~:% | Gdiff #.
References
https://github.com/tpope/vim-fugitive/issues/837#issuecomment-247985612
:help fugitive-object

In case you somebody stumbled on this question.
:Gdiff --staged
will do.. :)

Related

How to stage specific lines to git

I want to be able stage specific lines of code that match a pattern (MARKETING_VERSION in my case).
I've got the awk command which will show me the lines that match the pattern MARKETING_VERSION but I don't know how to stage the lines from that result to git.
awk '/MARKETING_VERSION/{print NR}' exampleFile.txt
the result in terminal is
1191
1245
How can I use this result to stage those specific lines in that file to git?
I know you can use git add -p but I want to use this in a shell script so I need a non-interactive version.
TIA
What git add -p <file> does is, very roughly, this:
tmpfile=$(mktemp)
tf2=$(mktemp)
tf3=$(mktemp)
git diff <file> > $tmpfile
while [ -s $tmpfile ]; do
extract first diff hunk from $tmpfile to $tf2 and rest to $tf3
show you $tf2, ask if you want to include this hunk
(with options to edit the hunk, etc); repeat until ready
if you say to *add* the hunk, run git apply --cached $tf2
cat < $tf3 > $tf2
done
rm -f $tmpfile $tf2 $tf3
That is, git add -p uses git apply --cached (a specialized sub-variant of git apply --index that ignores the working tree copy of the file). The key takeaway you need, from the above, is this: There are three versions of the file!
The first one (completely ignored here) is frozen for all time and is in the HEAD commit.
The second one is in Git's index aka staging area. That's used by git diff above as the "old version".
The third one is in your working tree. That's used by git diff above as the "new version".
The patches that Git lets you take or skip are simply the result of comparing the "old" (index) and "new" (working tree) version. If you take some patch, Git updates the in-index copy by applying the patch.
Hence, if there are some set of lines in the working tree version (say, lines 100 through 110 inclusive) that you'd like to use to replace some other set of lines (say, lines 90 through 92 inclusive) in the index version, the way to construct that is:
extract the index version;
scrape out lines 1-89 from the index version; concatenate lines 100-110 from the working tree version; concatenate lines 93-end from the index version, all into a temporary file;
replace the index copy with the temporary file.
To read the index version, use git show or git cat-file -p with the name of the index version of the file. If the file's name is path/to/file, the index version's name is :path/to/file (short for :0:path/to/file: we want the copy in slot zero; there must not be a copy in slots 1, 2, or 3 so that there is a copy in slot 0; you can simply attempt to read it from slot zero, and if that fails, assume the file either isn't in the index, or is conflicted).
Reading the working tree file (some select subset of lines) is left as an exercise, as is the concatenation part, and any error checking you wish to include.
Assuming the final resulting file is in a temporary file named $tf (as a shell variable), to update the index copy, you must first make sure an appropriate blob hash ID exists:
hash=$(git hash-object -w -t blob --path="$path" -- "$tf")
for instance (this assumes you want to run the usual .gitattribute filters, if any, and know that the path is $path). Then, if that goes well, use that hash ID with git update-index:
git update-index --cacheinfo "$mode,$hash,$path"
where $mode is either 100644 or 100755 as appropriate for the file. If you don't want to change the mode, you can read the previous mode with git ls-files --cached or similar. Otherwise, provided core.fileMode is true, read the mode from the working tree copy of the file, to match the behavior of git add: convert "has any executable bit set" to 100755 and "has no executable bit set" to 100644. When core.fileMode is false—use git config --get --type bool core.filemode to read it—git add uses the existing mode for this add-patch case.)

How to only list .png files that have been modified in Git

How would one go about only listing the png files that have been modified in the current branch on Git?
My goal is to copy those files to a different directory (I need to send an email).
Suppose I have:
$ git status
On branch update_assessment_pt1
Your branch is up-to-date with 'upstream/devel'.
Changes to be committed:
(use "git reset HEAD <file>..." to unstage)
new file: assessment/LWR/validation/HbepR1/analysis/hbepr1_plot.py
deleted: assessment/LWR/validation/HbepR1/doc/figures/AxialPowerProfile.pdf
deleted: assessment/LWR/validation/HbepR1/doc/figures/AxialProfile.pdf
deleted: assessment/LWR/validation/HbepR1/doc/figures/CladDisp.pdf
deleted: assessment/LWR/validation/HbepR1/doc/figures/FissionGas.pdf
modified: assessment/LWR/validation/HbepR1/doc/figures/FissionGas.png
deleted: assessment/LWR/validation/HbepR1/doc/figures/InterGasPress.pdf
deleted: assessment/LWR/validation/HbepR1/doc/figures/Mesh.pdf
deleted: assessment/LWR/validation/HbepR1/doc/figures/Power.pdf
modified: assessment/LWR/validation/HbepR1/doc/figures/Power.png
new file: assessment/LWR/validation/IFA_431/analysis/ifa431_plot.py
modified: assessment/LWR/validation/IFA_431/doc/figures/431_bol_rod_power.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r1.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r2.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r3.png
How would I go about getting the following, so I can copy those files?
modified: assessment/LWR/validation/HbepR1/doc/figures/FissionGas.png
modified: assessment/LWR/validation/HbepR1/doc/figures/Power.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431_bol_rod_power.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r1.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r2.png
modified: assessment/LWR/validation/IFA_431/doc/figures/431r3.png
Use git diff --cached --diff-filter=M --name-only to obtain these file names. Add -- '*.png' if needed to keep the list filtered to just *.png files—the command will list any to be committed file whose status is M (modified).
Things to know to keep this from just being a "use this magic command" answer
In text, you first called these modified in the current branch. This phrase doesn't mean any one specific thing. Fortunately you then went on to show git status output, where they were listed under Changes to be committed.
Git doesn't store diffs at all. Git stores snapshots—whole files, intact, inside the main unit of storage, which is the commit. That means that in order to see a change, you have to pick two commits: $old and $new. Git will extract both, then compare them. Whatever is different between commit $old and commit $new, Git will tell you about that. The actual change can be any of a number of change-status-es:
A means Added: the file is not in $old and is in $new.
M means Modified: the file is different between $old and $new. The difference could just be the mode of the file: executable, or not.
D means Deleted: the file is in $old, but not in $new.
R, C, T, and some other rare cases can also occur, though some of them may require extra flags to git diff: you won't see an R status unless you enable rename-detection, for instance. (Rename detection defaults to on in the most modern Git versions, but off in older Git versions.)
Using --name-status, git diff will show you the file names and status letters, instead of showing an actual diff. (Try this out to see.) The --diff-filter argument lets you tell Git: only tell me about files whose status meets the letters I pick.
Note, by the way, that the special name HEAD always means the current commit. It does not matter how you made this commit become the current commit, though one typical way is by using git checkout: you git checkout a commit by its hash ID, for instance, and that commit is now checked out and is the current commit. Or, you git checkout a branch name, and the tip commit of that branch is now out and is the current commit. There is always1 a current commit, and you can name it by writing the name HEAD in all uppercase.2
All of the above talks about comparing commits, but there are two other places that files can exist, that are not commits. Note that both of these places are temporary: they get wiped out by various operations, and once wiped out, cannot be recovered in Git: you have to copy from these temporary places, into actual commits, to make the files permanent. Once the files are in commits, they're frozen for all time, and can be restored to useful form in the future for as long as the commit itself exists (which tends to be "forever", or as long as the repository exists).
These two places are:
the index, which Git also calls the staging area or (rarely) the cache, and
the work-tree or working tree or any of several variants on this name.
Files that are in the index right now are ready to be committed. Every file that will be committed is in the index right now, even if the index copy matches the current (HEAD) commit copy.
You can, at any time, compare the HEAD commit to whatever is in the index right now. One command that does this is git diff --cached. For every file in HEAD and/or in the index, Git compares the two copies of the file. If they are different, the file is modified. If the index file exists but there is no such file in HEAD, the file is added. If the file exists in HEAD but not in the index, the file is deleted.
You can also, at any time, compare HEAD to the work-tree, or the index to the work-tree. The commands that do this are git diff HEAD and git diff (with no name). Again, for every file on the left-hand side (HEAD or the index), and every file on the right-hand side (in the work-tree), Git compares the two copies of the file.
Last, note that git status runs two git diffs. It does a quick git diff --cached to compare HEAD vs index. Whatever is different here, git status lists that file as to be committed. It also does a quick git diff (with no extra arguments except for --name-only) to compare index vs work-tree. Whatever is different here, git status lists that file as changes not staged for commit.
You wanted to compare HEAD vs index, so you want git diff --cached. You then wanted to list only those files that are Modified, so you can add --diff-filter=M. You didn't want to see the actual differences—nor even the status letters; file names only please!—so you can add --name-only. You also wanted only to list files whose name matches *.png, so add -- '*.png'—the quotes protect the * from the shell; we want Git to see the * so that Git can treat it as a pathspec—to get just those.
1Actually, this is really almost always. There's a special state in which HEAD exists and contains a branch name, but the branch name itself doesn't exist. This state mostly occurs when you create a new, totally-empty repository. Git requires a branch name like master to identify some existing, valid commit hash ID. There are no commits, so there are no valid hash IDs, so master itself is not allowed to exist. Nonetheless, HEAD holds the name master, so that Git will create the master branch when you make the first commit.
2On Windows and MacOS, you can sometimes get away with using head (lowercase) instead of HEAD (all-uppercase). This misbehaves if you start using git worktree add, so it's a bad habit to get into. If you don't like typing HEAD in all capitals, consider using the symbol #, which is a synonym for HEAD.

Git how to get hashid of last GitHub release tag

Every 10-20 commits I have a commit of the form "version uped to x.y.z" that is also marked a tag, since its a GitHub release point. Example below. I need to get hashid of last such commit so I can use it in a script like "git rebase -i $(hashid)", which is a point of freezing, where existing commits should not changed. There are 2 possible means to get it: search for last commit with message starting with "version uped" or search for last commit being a tag. I am not skilled with bash, so please assist.
dfd48cd (HEAD -> master, origin/master, origin/HEAD) Operator [:] for GreedyRange removed
b610256 Array GreedyRange docs updated
e6a1446 Embedded docstring updated
825bf83 moved gallery and deprecated_gallery to new folders
9414a55 Kaitai comparison schemas moved to a folder
61e9ccb Padded fixed, negative length check and docstring
ad6148c FixedSized updated, changed build semantics
979538d FixedSized NullTerminated NullStripped fixed, _parsereport and docstrings
4719d67 lib/py3compat updated, supportsintflag supportsintflag more accurate
9c164d4 makefile added xfails profile
672fefa (tag: v2.9.40) version uped to 2.9.40
In this example, output would be: 672fefa52b537c17f5ede90996b9156eb0e040ac
Here is one way:
git tag --sort -v:refname | head -1 | xargs git rev-list -n 1
Explained:
Get the list of all tags, sorted descending by version number
Pick the first one (the one with the highest version number)
Pass it to git rev-list to find the commit hash it references

How to get changed lines only in diff in TortoiseGit or with Windows?

I have two (big) files and what I need is to extract changed/added lines only. Is a plain text file (CSV).
Simply return and save a third file with these lines..
UPDATE
I resolved with DiffMerge using the "Show Differences Only" function built in described here.
By the way, I still require a solution that "programmatically" do the same thing but I will create another Question maybe because I need it in a Linux environment.
UPDATE 2
Resolved also with TortoiseGit, see below.
Select two files, and TortoiseGit -> Diff
Create Patch file in TortoiseGitMerge
the unified diff file
UPDATE
For viewing diff only, using "Collapse".
UPDATE 2
If you don't need the context, just set the "Context lines for patches" to zero.
diff --changed-group-format='%<' --unchanged-group-format='' file1 file2
What you are looking for could be as simple as the default unified diff format given by git diff but with context lines stripped. You will still get the location information before every change.
git diff --unified=0 <commit1>:<path/to/file1> <commit2>:<path/to/file2> > <output file>
The files do not have to be versioned under Git, you can just omit the commit reference in that case (or when comparing separate files in same version). For different versions of the same file
git diff --unified=0 <commit1> <commit2> -- <file> > <output file>

How to ignore Icon? in git

While trying to setup a dropbox folder with git, I saw a "Icon\r" file which is not created by me. I try to ignore it in the ~/.gitignore file. But adding Icon\r Icon\r\r Icon? won't work at all.
You can use vim as well.
vim .gitignore
in a new line write Icon, then
press ctrl+v and then press Enter
repeat step 3
save and exit (shortcut: ZZ)
Now you should have Icon^M^M and it's done :)
For a smarter use you could add it to your gitignore global config file in ~/.gitignore_global.
(This improves on the original answer, following a suggestion by robotspacer, according to hidn's explanation.)
The Icon? is the file of OS X folder icon. The "?" is a special character for double carriage return (\r\r).
To tell git to ignore it, open a terminal and navigate to your repository folder. Then type:
printf "Icon\r\r" >> .gitignore
If the file does not exist, it will be created and Icon\r\r will be its one line. If the file does exist, the line Icon\r\r will be appended to it.
"Icon[\r]" is probably a better alternative.
In vim, you just put Icon[^M], which is Icon[ followed by CtrlV, Enter then ].
The problem with "Icon\r\r" is EOL conversion.
The whole line is actually "Icon\r\r\n", counting line ending. Based on your setup, CRLF may be converted to LF on commit, so your repo will actually have "Icon\r\n". Say you sync the changes to another repo. You will get "Icon\r\n" in that working directory, which ignores Icon but not Icon^M. If you further edit .gitignore and commit it there, you will end up with "Icon\n" - completely losing \r.
I encountered this in a project where some develop on OS X while some on Windows. By using brackets to separate \r and the line ending, I don't have to repeat \r twice and I don't worry about EOL conversion.
The best place for this is in your global gitignore configuration file. You can create this file, access it, and then edit per the following steps:
>> git config --global core.excludesfile ~/.gitignore_global
>> vim ~/.gitignore_global
press i to enter insert mode
type Icon on a new line
while on the same line, ctrl + v, enter, ctrl + v, enter
press esc, then shift + ; then type wq then hit enter
Regarding Naming (and Quoting) Things: First, more people would benefit by knowing that ANSI-C Quoting can be used to unambiguously match the macOS icon file. Both Icon$'\r' or $'Icon\r' and work in Bash and Zsh and most other modern shells, I hope, such as Fish.
Keep Your .gitignore Editable: While I'm impressed by the byte-level manipulation offered by other answers here, these methods are brittle in practice. Simply put, programmers tend to use text editors, and many of these editors are configured to alter line endings when saving a file. (For example, see this VS Code discussion about line ending normalization.)
Do you want your careful byte editing undone by your editor? Of course not. So perhaps you find it practical and convenient to configure your editor so that it doesn't affect line endings. You might look into (a) editor-specific configuration settings; or (b) cross-editor configuration (i.e. EditorConfig).
But this gets complex and messy. If want a simpler, more flexible way, use this in your .gitignore file:
# .gitignore
Icon?
![iI]con[_a-zA-Z0-9]
Explanation for the patterns:
Use Icon? because the gitignore format does not support \r as an escape code.
Use [iI] because Git can be case sensitive.
Use [_a-zA-Z0-9] to catch many common ASCII characters; you may want to broaden this.
You can test that your gitignore patterns are working as expected with:
git check-ignore -v *
For example, for testing, with these files in a directory:
-rw-r--r--# Icon?
-rw-r--r-- icon8
drwxr-xr-x icons
-rw-r--r-- iconography
... the result of git check-ignore -v * is:
/Users/abc/.gitignore:3:Icon? "Icon\r"
/Users/abc/.gitignore:4:![iI]con[_a-zA-Z0-9] icon_
/Users/abc/.gitignore:4:![iI]con[_a-zA-Z0-9] icons
This is what you want.
Long Term Recommendation This problem would be trivial to fix if Git supported the \r escape in .gitconfig files. One could simply write:
# .gitignore
Icon[\r]
So I suggest we engage with the Git community and try to make this happen.
(If you do want to wade in and suggest a patch to Git, be sure to read first.)
References
From the gitignore documentation:
Otherwise, Git treats the pattern as a shell glob: "*" matches anything except "/", "?" matches any one character except "/" and "[]" matches one character in a selected range. See fnmatch(3) and the FNM_PATHNAME flag for a more detailed description.
Please see This linuxize.com article for good examples of the square bracket syntax and negation syntax in .gitignore files.
For those that want to dig deep and see how pattern matching has changed over time in the Git source code, you can run this search for uses of fnmatch in the git repository on GitHub.
The Icon? is the file of OSX folder icon. It turn out that \r is actually CRLF. So I use ruby to add the line to .gitignore file. Open terminal and navigate to home folder, then:
> irb
>> f = File.open(".gitignore", "a+") #<File:.gitignore>
>> f.write("Icon\r\r") # output a integer
>> f.close
>> exit
For me this worked in TextMate: Icon<CR><CR>. The <CR> is a carriage return character, which is at ctrl-alt-return on the keyboard. You can also find it in the standard Character Viewer app searching for cr. Please note that the <CR> is an invisible character, so it's only visible if the editor is set up to show them.
I'm posting just an update answer because the one above didn't work for me but actually simply adding Icon? in my .gitignore worked. If you look at your name file on your Finder, it is actually how it is displayed.
Icon[\r] did not work for me. I had to use the following in .gitignore...
Icon*
I also added Icon* to my Settings > Core > Ignored Names in Atom...
.git, .hg, .svn, .DS_Store, ._*, Thumbs.db, desktop.inis, Icon*
Add Icon? to your .gitignore file and save it. It should do the job.
Icon?
To avoid wasting time on such trivial issues, I recommend using gibo.
gibo dump macOS >> .gitignore
The result:
### Generated by gibo (https://github.com/simonwhitaker/gibo)
### https://raw.github.com/github/gitignore/e5323759e387ba347a9d50f8b0ddd16502eb71d4/Global/macOS.gitignore
# General
.DS_Store
.AppleDouble
.LSOverride
# Icon must end with two \r
Icon
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

Resources