Run Commands on Files Based on Git Status¶
Open files touched by last commit in vim¶
$ git log -1 --name-only --pretty=format:
src/main.c
src/utils.c
src/utils.h
The output is each file on its own line. That can be passed to vim:
$ vim $(git log -1 --name-only --pretty=format:)
13 files to edit
Just before vim actually opens the file, it displays a message of how many files it (vim) is going to edit. After you close the editor, the message is still on the terminal.
And it works for more than one commit too:
$ vim $(git log -15 --name-only --pretty=format: HEAD~1)
81 files to edit
In any case, we used --name-only
in conjunction with --pretty=format:
with no format specifier after the colon (:
) precisely because we want to output the filenames and nothing else.
The output may contain some empty lines, but those will simply be ignored by git, so we don’t need to clean them up manually, but we can anyway, just for kicks. Let’s see this example from vim repository itself:
~/work/src/projects/others/vim [master|u=]
$ git log -5 --name-only --pretty='format:'
runtime/autoload/gzip.vim
runtime/ftplugin/perl.vim
runtime/ftplugin/ruby.vim
src/ex_docmd.c
src/list.c
src/macros.h
src/testdir/test_crash.vim
src/testdir/test_usercommands.vim
src/userfunc.c
src/version.c
src/testdir/test_vim9_disassemble.vim
src/version.c
src/testdir/test_recover.vim
src/version.c
Then we can use sed (or tr) to exclude the empty lines from the output:
$ git log -5 --name-only --pretty='format:' | sed '/^$/d'
runtime/autoload/gzip.vim
runtime/ftplugin/perl.vim
runtime/ftplugin/ruby.vim
Filelist
src/ex_docmd.c
src/list.c
src/macros.h
src/testdir/test_crash.vim
src/testdir/test_usercommands.vim
src/userfunc.c
src/version.c
src/testdir/test_vim9_disassemble.vim
src/version.c
src/testdir/test_recover.vim
src/version.c
And pass the result to vim, just like earlier:
$ vim $(git log -5 --name-only --pretty='format:' | sed '/^$/d')
17 files to edit
Credits and references¶
Got the gist of the idea from this blog post by Luis Osa. I just added more details to the explanation as they made sense to me.
Optimize images¶
Run git status
and we see a .png
image.
We want to optimize it before uploading somewhere or committing it to the repository.
$ git status --short
M git/run-commands-on-selected-files.adoc
?? __assets/bash-help-null-command-2023-09-06T11-28-27-441Z.png
?? cmdline/null-command.adoc
It doesn’t show on the text above, but those “??” characters are printed with some colors, which means git introduced shell color escape codes on that output.
We had better use --porcelain
to get a more “easy-to-parce format for scripts”:
See git status --help
then search for --porcelain
:
--porcelain[=<version>]
Give the output in an easy-to-parse format for scripts. This is
similar to the short output, but will remain stable across Git
versions and regardless of user configuration. See below for details.
The version parameter is used to specify the format version. This is
optional and defaults to the original version v1 format.
$ git status --short --porcelain
M git/run-commands-on-selected-files.adoc
?? __assets/bash-help-null-command-2023-09-06T11-28-27-441Z.png
?? cmdline/null-command.adoc
No colors now! Time to parse it.
First, we filter only files ending with .png
using grep
:
$ git status --short --porcelain | grep '\.png$'
?? __assets/bash-help-null-command-2023-09-06T11-28-27-441Z.png
And then we use cut
to get the second field delimited by a space:
$ git status --short --porcelain | grep '\.png$' | cut -d ' ' -f 2
__assets/bash-help-null-command-2023-09-06T11-28-27-441Z.png
We can finally optimize that image with a tool like optipng
:
$ optipng -o7 $(git status --short --porcelain | grep '\.png$' | cut -d ' ' -f 2)
** Processing: __assets/bash-help-null-command-2023-09-06T11-28-27-441Z.png
451x185 pixels, 4x8 bits/pixel, RGB+alpha
Reducing image to 8 bits/pixel, 158 colors in palette
Input IDAT size = 12297 bytes
Input file size = 13018 bytes
Trying:
zc = 9 zm = 9 zs = 0 f = 0 IDAT size = 3011
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 3011
zc = 9 zm = 9 zs = 1 f = 0 IDAT size = 2990
zc = 9 zm = 8 zs = 1 f = 0 IDAT size = 2990
Selecting parameters:
zc = 9 zm = 8 zs = 1 f = 0 IDAT size = 2990
Output IDAT size = 2990 bytes (9307 bytes decrease)
Output file size = 4197 bytes (8821 bytes = 67.76% decrease)
In the previous example, we had a single .png
file to optimize, but a tool like optipng
is able to handle multiple image file parameters at once:
excerpt from optipng –help
$ optipng --help
Synopsis:
optipng [options] files ...
So, even if we have multiple files, the above command, unmodified, just works as expected:
$ git status --short --porcelain | grep '\.png$' | cut -d ' ' -f 2
__assets/bash-null-built-in-command-2023-09-06T11-50-04-415Z.png
__assets/bash-test-built-in-command-2023-09-06T11-49-25-911Z.png
$ optipng -o7 $(git status --short --porcelain | grep '\.png$' | cut -d ' ' -f 2)
** Processing: __assets/bash-null-built-in-command-2023-09-06T11-50-04-415Z.png
441x178 pixels, 4x8 bits/pixel, RGB+alpha
Reducing image to 8 bits/pixel, 158 colors in palette
Input IDAT size = 12152 bytes
Input file size = 12873 bytes
Trying:
zc = 9 zm = 9 zs = 0 f = 0 IDAT size = 2988
zc = 9 zm = 8 zs = 0 f = 0 IDAT size = 2988
zc = 9 zm = 9 zs = 1 f = 0 IDAT size = 2967
zc = 9 zm = 8 zs = 1 f = 0 IDAT size = 2967
Selecting parameters:
zc = 9 zm = 8 zs = 1 f = 0 IDAT size = 2967
Output IDAT size = 2967 bytes (9185 bytes decrease)
Output file size = 4174 bytes (8699 bytes = 67.58% decrease)
** Processing: __assets/bash-test-built-in-command-2023-09-06T11-49-25-911Z.png
806x142 pixels, 4x8 bits/pixel, RGB+alpha
Reducing image to 8 bits/pixel, 158 colors in palette
Input IDAT size = 16237 bytes
Input file size = 16958 bytes
Trying:
zc = 9 zm = 9 zs = 0 f = 0 IDAT size = 4793
zc = 8 zm = 9 zs = 0 f = 0 IDAT size = 4789
zc = 8 zm = 8 zs = 0 f = 0 IDAT size = 4789
zc = 9 zm = 9 zs = 1 f = 0 IDAT size = 4738
zc = 8 zm = 9 zs = 1 f = 0 IDAT size = 4738
zc = 8 zm = 8 zs = 1 f = 0 IDAT size = 4738
Selecting parameters:
zc = 8 zm = 8 zs = 1 f = 0 IDAT size = 4738
Output IDAT size = 4738 bytes (11499 bytes decrease)
Output file size = 5945 bytes (11013 bytes = 64.94% decrease)
But if a given image optimization tool you are using takes only a single image at a time, we can do a shell loop.
Let’s see with a simple printf
first:
for img in $(git status --short --porcelain | grep '\.png$' | cut -d ' ' -f 2)
do
printf '%s\n' "$img"
done
__assets/bash-null-built-in-command-2023-09-06T11-50-04-415Z.png
__assets/bash-test-built-in-command-2023-09-06T11-49-25-911Z.png
If we are satisfied with the result, we can replace printf
with optipng
(or whatever other tool).
Let’s save it as run_optipng.sh
:
run_optipng.sh
#!/usr/bin/env bash
imgs=(\
$(git status --short --porcelain \
| grep '\.png$' \
| cut -d ' ' -f 2 \
) \
)
for img in "${imgs[@]}"
do
optipng -o7 "$img"
done