Working with git file names modified in the workspace or most recent commit


I frequently find myself wanting to perform an operation on all the files modified in the workspace or staging error. For example, run edit all the files or run them through a tool like clang-format or oclint. If there are no uncommitted changes I want to work with all the files in the most recent commit in the branch. To do this I wrote a gitfiles fish shell function (transforming this to bash should be trivial):

function gitfiles \
    --description 'Enumerate files in git workspace or head commit matching 0+ globs.'
    # -c: files with a C/C++ extension
    # -p: files with a .py extension
    argparse -n gitfiles c p -- $argv
    or return

    set -l patterns $argv
    if set -q _flag_c
        set -a patterns '*.c' '*.cpp' '*.h'
    end
    if set -q _flag_p
        set -a patterns '*.py'
    end

    # The `sed` below could be replaced with `string replace -r '^ *[^ ]* *' ''`.
    # However, doing so is unlikely to be measurably faster let alone noticed by a user.
    # It's also more likely to be misunderstood.
    set -l files (
        git status --porcelain --short --untracked-files=all | sed -e 's/^ *[^ ]* *//')
    if not set -q files[1]
        set files (git show --word-diff=porcelain --name-only --pretty=oneline)[2..-1]
    end

    if set -q patterns[1]
        for pattern in $patterns
            string match $pattern $files
        end | sort -u
    else
        printf '%s\n' $files
    end
end

Note that it accepts globs to limit the files to those matching one or more patterns. If no globs are specified then all modified files are listed. It supports the -c flag to match C/C++ file names and -p to match python file names since those are the languages I work with the most often.

This makes it easy to type vim (gitfiles) (which I actually wrap in a gitvim fish function) to edit all the modified files.