Try the following command, which I have tested:
$ cp -pv --parents $(git diff --name-only) DESTINATION-DIRECTORY
Answer from York on Stack OverflowTry the following command, which I have tested:
$ cp -pv --parents $(git diff --name-only) DESTINATION-DIRECTORY
The following should work fine:
git diff -z --name-only commit1 commit2 | xargs -0 -IREPLACE rsync -aR REPLACE /home/changes/protected/
To explain further:
The
-zto withgit diff --name-onlymeans to output the list of files separated with NUL bytes instead of newlines, just in case your filenames have unusual characters in them.The
-0toxargssays to interpret standard input as a NUL-separated list of parameters.The
-IREPLACEis needed since by defaultxargswould append the parameters to the end of thersynccommand. Instead, that says to put them where the laterREPLACEis. (That's a nice tip from this Server Fault answer.)The
-aparameter torsyncmeans to preserve permissions, ownership, etc. if possible. The-Rmeans to use the full relative path when creating the files in the destination.
Update: if you have an old version of xargs, you'll need to use the -i option instead of -I. (The former is deprecated in later versions of findutils.)
Your question conceals a fundamental issue. Let's take a look at a typical commit graph fragment:
...--E--F--G--H--I--J <-- master
You pick two commits, such as E and I, and run git diff --name-only (or git diff --name-status) to compare them:
$ git diff --name-only <hash-E> <hash-I> myfolder/a.txt code/snippet.c test.txt
but then say:
... the result should be files introduced with the corresponding SHA.
The fact that these file names come out means that all three files are present in commits E and/or I, but if you had commit E in your work-tree and wanted to modify it to get commit I, those three files would need some kind of change: create, modify, or even delete. Using --name-status will give you the kind of change as well: A for "the file is to be newly created", M for "the file is to be modified", and D for "the file is to be deleted". (There are, of course, probably dozens or thousands of files in both E and I that are the same and hence are not printed here.)
But now you ask for the corresponding hash where this change is introduced. There may not be a the hash. There might be more than one. (There must be at least one of course). For instance, test.txt might be removed entirely in F by mistake (instead of being corrected), put back intact-but-wrong in H, and then corrected in I. Meanwhile code/snippet.c might be modified in both G and I.
Which commit hash(es) would you like for each file? The answer to that determines how to find them. (Of course, if there is only one such hash, the problem vanishes.)
xxfelixxx's answer gives a (slighlty faulty but easily fixed and improved) method for obtaining one commit—the first one git log prints. To fix the one bug and improve it a bit, replace the do sequence with:
do echo -n "$file "; git rev-list <starthash>..<endhash> -- "$file" | head -1
That is, we want to find one of the commit hashes printed by git rev-list when run only over the specified start/stop points and looking for changes to the one file. We need <starthash>..<endhash> to do the initial commit-limiting, and the -- $file to select only commits that add, modify, or remove that one path. Note that if there are spaces in the file name you will need to quote it (so I did), although then reading the output of git diff itself gets tricky too.
Using head -1 gets you the most recent commit that touched the file, e.g., with our example where code/snippet.c in both G and I, you get the hash for commit I. This is because Git works backwards, from newer commits to older ones. If you want the first one, use tail -1, and if you want all of them, you will need a fancier format. :-)
(There is another subtle difference here, between git log and git rev-list, involving merge commits, but that probably won't affect you.)
Not sure if there is an internal git command to do this, but you could always just loop over your files and grab the first commit:
for file in `git diff [start-sha] [end-sha] --name-only`;
do echo -n "$file "; git log --pretty=short [start-sha] [end-sha] -- $file | /bin/grep commit | cut -b8- | head -n 1;
done
According to GitConfig there is no such option among configuration variables, so you can only achieve this using Git Aliases
git config --global alias.diffn 'diff --name-only'
Or setting it manually in .gitconfig under [alias] section:
diffn = diff --name-only
I would also recommend looking at dotfiles repos for more inspiration on aliases and .gitconfig
You can use bash alias
echo "alias gitdiffname='git diff --name-only'">>~/.profile
source ~/.profile
gitdiffname
Building on torek's answer, I was able to achieve this in one line using the --line-prefix flag:
git diff --name-only --line-prefix=`git rev-parse --show-toplevel`/ HEAD release
Just add the top level yourself:
git rev-parse --show-toplevel
prints the path to the Git repository.
Feed the diff --name-only output through, e.g., sed -e "s,^,$path," (assuming no commas in your path name):
git-diff-with-abs-path() {
local path
path=$(git rev-parse --show-toplevel) &&
git diff --name-only "$@" | sed "s,^,$path/,"
}
Edit #2: for --name-status, the name comes after a literal tab, so:
git-diff-with-abs-path() {
local path tab=$'\t'
path=$(git rev-parse --show-toplevel) &&
git diff --name-status "$@" | sed "s,$tab,$tab$path/,"
}
Fancy this up a bit and you can make it pick --name-only or --name-status out of the $@ part and compute the correct sed expression, rather than hardcoding --name-only or --name-status.
Besides the --name-flag that displays only the file name, the git diff command (without other arguments) will display the difference between your index and your working directory (as explained in the diagaram here).
Whereas the git status command will display the whole current status of your working tree (files that are staged, modified, deleted, untracked etc.)
However, by considering the -s flag that shorten the output of the status command (which typically display only names), we can have some situations where these two commands will give you "almost" the exact same output.. It all depends of what you already did in your working directory.
For example, if you do git status -s, you will get all the differences from your working directory against your HEAD and your index including untracked files.. (But say you don't have untracked files..)
On the other hands, and regardless of what you already staged or not, if you do git diff --name-only HEAD (see the above-mentioned diagram), there is a chance that you get almost the same output.
Also, the results are almost the same (not exactly the same) because git status -s shows pretty useful additional information:
- a letter at the beginning of the line that summarize the status (M for modified, D for deleted, A for added, ? for untracked files)
- a color code that shows what are already staged (green for staged, red for unstaged).
So, we can consider that diff is more suited to view differences in actual file contents. That is why not displaying untracked files by diff makes sense.. Comparing differences starting from only something we have (the tracked files)..
All that said, if you want some concise output about your actual changes (after all this is the rational for using --name-only) you should really consider git status -s.
On one hand, the git diff --name-only returns the name of the files which are ready to be staged on the next commit. As following :
git diff --name-only
/src/main/java/com/core/First.java
/src/main/java/com/core/Second.java
On the other hand, git status provides you not only the details of the files to be staged in the current working repository, but also the comparison with the origin of your branch.
git status
On branch myBranch
Your branch is up-to-date with 'origin/myBranch'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: /src/main/java/com/core/First.java
modified: /src/main/java/com/core/Second.java
no changes added to commit (use "git add" and/or "git commit -a")
#! /bin/sh
# See the discussion at https://stackoverflow.com/q/67724347/7976758
if [ -z "$1" ]; then
echo "Usage $0 from_commit [to_commit]"
exit 1
fi
if [ -z "$prog_dir" -o -z "$prog_name" ]; then
start_dir="`pwd`"
prog_dir="`dirname \"$0\"`"
prog_name="`basename \"$0\"`"
cd "$prog_dir"
# Get full path
prog_dir="`pwd`"
cd "$start_dir"
export prog_dir prog_name
fi
from_commit="$1"
to_commit="${2:-HEAD}"
export from_commit to_commit
# In the superproject
git --no-pager diff --name-only "$from_commit" "$to_commit"
git submodule foreach '
# In submodule "$name"
prev_commit=`(git -C "$toplevel" ls-tree "$from_commit" "$sm_path" | awk "{print \\$3}")`
curr_commit=`(git -C "$toplevel" ls-tree "$to_commit" "$sm_path" | awk "{print \\$3}")`
"$prog_dir/$prog_name" $prev_commit $curr_commit
'
recursivenameonlysubmodulediff() {
git update-ref refs/scratch/old $1
git update-ref refs/scratch/new $2
git diff --name-only scratch/old scratch/new
export realtop=$PWD
git submodule foreach -q --recursive '
git update-ref refs/scratch/old `git -C .. rev-parse scratch/old:$sm_path`
git update-ref refs/scratch/new `git -C .. rev-parse scratch/new:$sm_path`
prefix=$toplevel/$sm_path/
git diff --name-only scratch/old scratch/new | sed s,^,${prefix#$realtop/},
'
}
will handle the russian-dolls-submodules case, using the scratch refs to avoid them getting inadvertently pushed or fetched.
You can use this command to see the changed file names, but not with line numbers:
git diff --name-only
Go forth and diff!
Line numbers as in number of changed lines or the actual line numbers containing the changes? If you want the number of changed lines, use git diff --stat. This gives you a display like this:
[me@somehost:~/newsite:master]> git diff --stat
whatever/views/gallery.py | 8 ++++++++
1 files changed, 8 insertions(+), 0 deletions(-)
There is no option to get the line numbers of the changes themselves.
It should just be:
git diff --cached
--cached means show the changes in the cache/index (i.e. staged changes) against the current HEAD. --staged is a synonym for --cached.
--staged and --cached does not point to HEAD, just difference with respect to HEAD. If you cherry pick what to commit using git add --patch (or git add -p), --staged will return what is staged.
A simple graphic makes this clearer:

git diff
Shows the changes between the working directory and the index. This shows what has been changed, but is not staged for a commit.
git diff --cached
Shows the changes between the index and the HEAD (which is the last commit on this branch). This shows what has been added to the index and staged for a commit.
git diff HEAD
Shows all the changes between the working directory and HEAD (which includes changes in the index). This shows all the changes since the last commit, whether or not they have been staged for commit or not.
Also:
There is a bit more detail on 365Git.