Skip to main content

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 make git 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 from git ls-tree and calls git log on them with the output for each being in the format <UNIX epoch time><space><filename>

  • sort -nrz -k 1.1,1.10: Numerically sorts in descending order the output from git 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, the ORS (Output Record Separator) in this awk 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:

  1. man git-ls-tree
  2. man xargs or info xargs
  3. man git-log
  4. man sort or info sort
  5. man gawk or info awk

Comments

Comments powered by Disqus