Sort files in a directory by modification time in Git
Recently, I had a sort of unusual requirement to get the last modified file (tracked by git
) in a certain directory for a specific branch. I tried to look for any existing porcelain or plumbing command in git
that can be used to advantage, but couldn't find anything, so had to stitch up something combining several utilities.
Here's how it looks eventually:
git ls-tree -r -z --name-only <branch> <directory>/ | \ xargs -r -0 -I filename git log -z -1 --format='%at filename' filename | \ sort -nrz -k 1.1,1.10 | \ awk 'BEGIN {RS="\0"; ORS="\n"; OFS=""} {$1=""; print $0}'
Assuming the branch is <branch>
and the directory to look into is <directory>/
.
One-liner:
git ls-tree -r -z --name-only <branch> <directory>/ | xargs -r -0 -I filename git log -z -1 --format='%at filename' filename | sort -nrz -k 1.1,1.10 | awk 'BEGIN {RS="\0"; ORS="\n"; OFS=""} {$1=""; print $0}'
As all of the pipe-d commands uses ASCII NUL (\0
) as the line separator instead of the usual newline (\n
), the above should handle any unusual filename as well (e.g. filename with a newline in it).
Breakdown:
I'm going to give an overview of each command without delving into the individual arguments used in each. The man
pages of the respective commands can be consulted to get a more detailed idea.
-
git ls-tree -r -z --name-only <branch> <directory>/
: Gets the files (recursively) in the<directory>/
(the trailing/
is important to makegit ls-tree
treating it as a directory) tracked in branch<branch>
-
xargs -r -0 -I filename git log -z -1 --format='%at filename' filename
: Takes the filenames fromgit ls-tree
and callsgit log
on them with the output for each being in the format<UNIX epoch time><space><filename>
-
sort -nrz -k 1.1,1.10
: Numericallysort
s in descending order the output fromgit log
on the space-separated first field only (UNIX epoch) -
awk 'BEGIN {RS="\0"; ORS="\n"; OFS=""} {$1=""; print $0}'
: Prints the filenames only (omitting the UNIX epoch time). Here, I've outputted the filenames separated by newline, but if someone still wants to output them as NUL-separated, theORS
(Output Record Separator) in thisawk
command can be changed to\0
i.e.ORS="\0"
If any filename does not contain any unusual characters, the above can be simplified a bit (essentially we can drop the the ASCII NUL terminations from all commands):
git ls-tree -r --name-only <branch> <directory>/ | \ xargs -r -I filename git log -1 --format='%at filename' filename | \ sort -nr -k 1.1,1.10 | \ awk 'BEGIN {OFS=""} {$1=""; print $0}'
Now, we can take this one step further by making this reusable with a shell function that takes the branch name and directory as arguments. I've created the following function to take the directory as the first argument and branch name as the (optional) second argument (if the branch name isn't passed, the current branch is used):
git_dir_sort_by_mod_time () { directory="$1" if [[ -z $directory ]]; then printf 'Directory must be provided.\n' return 1 fi if ! [[ $directory =~ "/$" ]]; then directory="${directory}/" fi branch="$2" if [[ -z $branch ]]; then branch=$(git branch --show-current) fi git ls-tree -r -z --name-only "$branch" "$directory" | \ xargs -r -0 -I filename git log -z -1 --format='%at filename' filename | \ sort -nrz -k 1.1,1.10 | \ awk 'BEGIN {RS="\0"; ORS="\n"; OFS=""} {$1=""; print $0}' }
The above function is tested on bash
and zsh
.
As an example, using the above function, to get the files in the directory foobar
descendingly sorted based on the last modification date in git
, for branch development
:
git_dir_sort_by_mod_time foobar development
To find the same for the current branch:
git_dir_sort_by_mod_time foobar
Important note: I use a GNU system, so the xargs
, sort
, and awk
commands used above are the GNU versions of them. Not all arguments/options used are POSIX compatible.
References:
Comments
Comments powered by Disqus