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's an "imaginary diff option", used to indicate to the reader that it's not just the output of running the diff command. For example, in git's own git repo:
$ git diff HEAD~1..HEAD | head
diff --git Documentation/git.txt Documentation/git.txt
index bd659c4..7913fc2 100644
--- Documentation/git.txt
+++ Documentation/git.txt
@@ -43,6 +43,11 @@ unreleased) version of Git, that is available from the 'master'
branch of the `git.git` repository.
Documentation for older releases are available here:
+* link:v2.10.0/git.html[documentation for release 2.10]
+
$
The diff command itself, if you invoked it with the same file name twice, would show no differences. git presumably creates temporary files corresponding to two different versions of Documentation/git.txt and feeds them to diff -- but the names of those temporary files would not be useful. I think git diff massages the output of diff to make it more meaningful to the reader. (This speculation was not entirely correct. See below.)
Diving into the git source code, diff.c has the string "diff --git" hardwired as a string literal:
strbuf_addf(&header, "%s%sdiff --git %s %s%s\n", line_prefix, meta, a_one, b_two, reset);
And looking into the history of diff.c for the earliest version that contains that string:
$ git log -n 1 b58f23b3
commit b58f23b38a9a9f28d751311353819d3cdf6a86da
Author: Junio C Hamano <[email protected]>
Date: 2005-05-18 09:10:47 -0700
[PATCH] Fix diff output take #4.
This implements the output format suggested by Linus in
<[email protected]>, except the
imaginary diff option is spelled "diff --git" with double dashes as
suggested by Matthias Urlichs.
Signed-off-by: Junio C Hamano <[email protected]>
Signed-off-by: Linus Torvalds <[email protected]>
$
Presumably <Pine.LNX...> is the message-id of some email message in a mailing list somewhere. In any case, this commit message makes it clear that diff --git is an "imaginary diff option".
This email message, cited by nos in a comment, appears to be part of the discussion that led to this. Update: That link is now dead, and I don't know whether the archive is available elsewhere.
UPDATE: I speculated above that git diff massages the output of diff, adding this information. I just tried running git diff under strace. It doesn't actually invoke the diff command, or any other command. Rather, all the output is printed by the git process itself, and apparently it computes the differences internally. This behavior might also depend on the version of git (I'm using 2.23.0), the diff command(s) available, and the arguments used.
I'll note that GNU diff has a --label=LABEL option that could be used for this kind of thing:
'-L LABEL'
'--label=LABEL'
Use LABEL instead of the file name in the context format (*note
Context Format::) and unified format (*note Unified Format::)
headers. *Note RCS::.
but git diff doesn't use it, at least in the one case I tried, and I don't see a reference to it in the git sources.
The --git is to mean that diff is in the "git" diff format. It doesn't refers to and option of the /usr/bin/diff command You can find the list of diff format documentation. Other formats are:
diff --combineddiff --ccdiff --summary