Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Save erikw/654386d35ecfdb0354cd2b71763f19ae to your computer and use it in GitHub Desktop.
Save erikw/654386d35ecfdb0354cd2b71763f19ae to your computer and use it in GitHub Desktop.
Generate git commit message from git-status. Will generate a commit message like "Added: file1.py file2.py file3.py Modified: file4.py file5.py Deleted: README.md Renamed: test.txt-> test2.txt". Put this in your .gitconfig.

git commit-status alias

An alias that will generate a git commit message staged changes as shown in git-status. Put this alias (section below) in your .gitconfig.

The message generated will be in the format of:

$ git status --porcelain
A file1.py
A file2.py
A file3.py
M file4.py
M file5.py
D README.md
R test.txt-> test2.txt
$ git commit-status
$ git log --no-decorate -n 1
bee4f8e Added: file1.py file2.py file3.py Modified: file4.py file5.py Deleted: README.md Renamed: test.txt-> test2.txt

Shared on Stack Overflow.

[alias]
# commit-status: generate a commit with message from git-status (staged changes).
# Source: https://gist.github.com/erikw/654386d35ecfdb0354cd2b71763f19ae
# Explanation:
# - Get only staged changes
# - Ignore changes in working area (2nd letter, the Y in XY as explained in $(git help status))
# - + split label and file path to separate lines so we can process the labels separately
# - Keep only the first label using awk
# - Add newline before each label section so we later can truncate \n to put everything on one line
# - Make labels human readable e.g. M -> Modified
# - Put everything on one line and trim leading & trailing whitespaces
commit-status = !" \
TMPFILE=$(mktemp /tmp/git-commit-status-message.XXX); \
git status --porcelain \
| grep '^[MARCDT]' \
| sort \
| sed -re 's/^([[:upper:]])[[:upper:]]?[[:space:]]+/\\1:\\n/' \
| awk '!x[$0]++' \
| sed -re 's/^([[:upper:]]:)$/\\n\\1/' \
| sed -re 's/^M:$/Modified: /' \
| sed -re 's/^A:$/Added: /' \
| sed -re 's/^R:$/Renamed: /' \
| sed -re 's/^C:$/Copied: /' \
| sed -re 's/^D:$/Deleted: /' \
| sed -re 's/^T:$/File Type Changed: /' \
| tr '\n' ' ' | xargs \
> $TMPFILE; \
git commit -F $TMPFILE; \
rm -f $TMPFILE \
"
# If you want to have a [Yn] prompt showing the commit message before making the commit,
# then simply inject the following lines below in to the alias above just before the
# "git commit -F $TMPFILE; \" line:
cat $TMPFILE; \
commit=''; \
while :; do \
echo '> Commit with this message? [Yn]: '; \
read commit; \
([ -z \"$commit\" ] || [ \"$commit\" = y ] || [ \"$commit\" = Y ] || [ \"$commit\" = n ]) && break; \
done; \
test \"$commit\" != n || exit; \
@isayakmondal
Copy link

@erikw Btw just wondering is this bash script?

@erikw
Copy link
Author

erikw commented Jan 14, 2022

@Lukkian It's easy to add an interactive [Yn] prompt as well. Check this:

	commit-status = !"TMPFILE=$(mktemp /tmp/git-commit-status-message.XXX); \
		git status --porcelain \
		| sort \
		| sed -re '/^\\?\\?[[:space:]]/d' -e '/^[[:space:]]+[[:upper:]]/d' \
		| sed -re 's/^([[:upper:]]+)[[:space:]]+/\\1:\\n/g' \
		| awk '!x[$0]++' \
		| sed -re 's/^([[:upper:]]+:)[[:space:]]+/\\1 /g' \
		| sed -re 's/^([[:upper:]]*)M[[:upper:]]*:$/\\1 Modified: /g' \
		| sed -re 's/^([[:upper:]]*)R[[:upper:]]*:$/\\1 Renamed: /g' \
		| sed -re 's/^([[:upper:]]*)A[[:upper:]]*:$/\\1 Added: /g' \
		| sed -re 's/^([[:upper:]]*)D[[:upper:]]*:$/\\1 Deleted: /g' \
		| sed -re 's/^([[:upper:]]*)T[[:upper:]]*:$/\\1 File Type Changed: /g' \
		| tr '\n' ' ' \
		| sed -re 's/(^|[[:alpha:]]+:[[:space:]])[[:space:]]*/\\1/g' > $TMPFILE; \
	        cat $TMPFILE; \
	        echo; \
	        commit=''; \
	        while :; do \
			echo '> Commit with this message? [Yn]: '; \
			read commit; \
			([ -z \"$commit\" ] || [ \"$commit\" = y ] || [ \"$commit\" = Y ] || [ \"$commit\" = n ]) && break; \
	        done; \
		if [ \"$commit\" != n ]; then \
			git commit -F $TMPFILE; \
		fi; \
		rm -f $TMPFILE"

It will look like

$ git commit-status
Added: file_a  Modified: file_b file_c
> Commit with this message? [Yn]:

@isayakmondal Yes it's pure bash baked in to a string that git execute. The ! in the beginning tells git it's shell script to execute in the string. It sure would make sense to put this in a commit-status.sh script and just call that. The reason why I made it like this is that I wanted to provide a copy-and-pase:able solution that one can put directly in a .git-config. But if you want to modify and extend this solution, you should really put it to a bash script yes :).

@luis-fss
Copy link

@erikw wow nice! thank you so much.

@isayakmondal
Copy link

Cool, I just know the basics of shell programming but never really got deep into it. This now makes me wanna learn it deeply. Btw is this same thing possible with other languages like python or c?

@erikw
Copy link
Author

erikw commented Jan 14, 2022

@isayakmondal Cool, go for it! You can use any language to execute the commands needed to get the input for the program, meaning that a Python/C/whatever can execute $ git status --porcelain, get the output of this command, then do the logic of formatting the message, and execute git commit with this message.

I would recommend looking in to Python; it's easiest to pick up and yet powerful :)

This solution was made in bash to not require dependencie e.g. Python, so that it can be used directly by anyone. But if you want to make something proper, bash is likely not the right answer for many cases.

@isayakmondal
Copy link

Thank you so much for this. @erikw
I swear just one last question. Is the formatting of the message done using REGEX? I've tried to learn it many times but it's a lot of syntaxes to memorize.

@erikw
Copy link
Author

erikw commented Jan 14, 2022

@isayakmondal Yes that's correct. For example in

sed -re 's/^([[:upper:]]+:)[[:space:]]+/\\1 /g'

this part is a regex

s/^([[:upper:]]+:)[[:space:]]+/\\1 /g

Tip: learn regex interactivly at https://regex101.com/
The O'Reilly book "Mastering Regular Expressions" is really good!

@isayakmondal
Copy link

isayakmondal commented Jan 16, 2022

@erikw Hey, hope you're doing well. I just found a new bug.
You can clearly see that two files have been modified but the commit message has only 1.

$ git status --porcelain
M  Equal_Coins.cpp
 M Task.cpp

$ git commit-status
Modified: Equal_Coins.cpp 
> Commit with this message? [Y/n]:
n

Looks like there's a space before M Task.cpp that might be causing the issue, but why is there a space in the first place?
Please have a look.

  • Update - Sorry my bad, I forgot to do git add . lol.

@erikw
Copy link
Author

erikw commented Jan 16, 2022

@isayakmondal Hello!

Yes I was just about to write that, that a space before means that the M is is for the file in the working area, not in the staged area (after git add).

Nevertheless, I started to look at this alias and felt a need to simplify it a bit. I worked out an updated version. As you are an active user of this one, would you mind trying if it works for you as well? It works when I've tested it, but it would be great to hear if it works also for at least another person!

	# commit-status: generate a commit with message from git-status (staged changes).
	# Source: https://gist.github.com/erikw/654386d35ecfdb0354cd2b71763f19ae
	# Explanation:
	# - Get only staged changes
	# - Ignore changes in working area (2nd letter, the Y in XY as explained in $(git help status)) + split X from the filename
	# - Keep only the first label using awk
	# - Add newline before each label section
	# - Make labels human readable e.g. M -> Modified
	# - Put everything on one line and trim leading & trailing whitespaces
	commit-status = !" \
	        TMPFILE=$(mktemp /tmp/git-commit-status-message.XXX); \
		git status --porcelain \
		  | grep '^[MARCDT]' \
		  | sort \
		  | sed -re 's/^([[:upper:]])[[:upper:]]?[[:space:]]+/\\1:\\n/' \
		  | awk '!x[$0]++' \
		  | sed -re 's/^([[:upper:]]:)$/\\n\\1/' \
		  | sed -re 's/^M:$/Modified: /' \
		  | sed -re 's/^A:$/Added: /' \
		  | sed -re 's/^R:$/Renamed: /' \
		  | sed -re 's/^C:$/Copied: /' \
		  | sed -re 's/^D:$/Deleted: /' \
		  | sed -re 's/^T:$/File Type Changed: /' \
		  | tr '\n' ' ' | xargs \
		  > $TMPFILE; \
		git commit -F $TMPFILE; \
		rm -f $TMPFILE \
		"

@isayakmondal
Copy link

Sorry for the late reply, this seems to work fine till now but the version with [Y/n] prompt is better.

@erikw
Copy link
Author

erikw commented Jan 17, 2022

@isayakmondal Thank you for testing. I added then [Yn] prompt to the Gist as well :)

@tomeo
Copy link

tomeo commented Feb 1, 2022

If you want to add this alias as a oneliner from Powershell:
git config --global alias.cs "! TMPFILE=`$(mktemp /tmp/git-commit-status-message.XXX); git status --porcelain | grep '^[MARCDT]' | sort | sed -re 's/^([[:upper:]])[[:upper:]]?[[:space:]]+/\1:\n/' | awk '!x[`$0]++' | sed -re 's/^([[:upper:]]:)`$/\n\1/' | sed -re 's/^M:`$/Modified: /' | sed -re 's/^A:`$/Added: /' | sed -re 's/^R:`$/Renamed: /' | sed -re 's/^C:`$/Copied: /' | sed -re 's/^D:`$/Deleted: /' | sed -re 's/^T:`$/File Type Changed: /' | xargs > `$TMPFILE; git commit -F `$TMPFILE; rm -f `$TMPFILE"

@erikw
Copy link
Author

erikw commented Feb 1, 2022

Thanks at @tomeo. That's a pretty massive one-liner there 😄

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment