Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 10 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • 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

isayakmondal commented Jan 14, 2022

Hey, I've tried this but getting an error.
Couldn't really make sense of it.
Could you help?

$ git commit-status
mktemp: too few X's in template ‘git-commit-status-message’TMPFILE=$(mktemp -t git-commit-status-message);         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'         | sed -re 's/^[[:space:]]*//g' > $TMPFILE;         git commit -F $TMPFILE;         rm -f $TMPFILE: $TMPFILE: ambiguous redirecterror: switch `F' requires a value

@erikw
Copy link
Author

erikw commented Jan 14, 2022

@isayakmondal I was using BSD mktemp.

I've updated the command to work with both BSD and GNU coreutils (which I assume you are using) version of mktemp.

Give it a try again now and let me know if it works :)

@isayakmondal
Copy link

Okay, it works but facing two issues.

  • Why does it have A in front of the commit message?
  • In github web it shows like this and not the full message.

image
image

@erikw
Copy link
Author

erikw commented Jan 14, 2022

Why does it have A in front of the commit message?

That is a good question! I've used this alias daily for soon 2 months without seeing this happening. Does it always happen? Is there only one file modified in the staged area, or what is the minimal setup for reproducing this? What OS/toolchain are you using?
Try the updated version above!

In github web it shows like this and not the full message.

It's because each change got it's own line. I updated the alias with the version that I'm acutally using myself which puts everything on one line. Test the updated version!

@isayakmondal
Copy link

isayakmondal commented Jan 14, 2022

That is a good question! I've used this alias daily for soon 2 months without seeing this happening. Does it always happen? Is there only one file modified in the staged area, or what is the minimal setup for reproducing this? What OS/toolchain are you using?
Try the updated version above!

I'm using -

  • git version 2.30.0.windows.2 with git bash
  • Win 11
  • VS Code(latest stable)
  • Used git add . followed by git commit-status and there was only one file that was added and modified before the commit.

Just tried with one file as of now. Will let you know later.
**Update - Both of the bugs seem to be gone for now after using the updated version. :)

@luis-fss
Copy link

Is there any way to print or check the message before commit?

@erikw
Copy link
Author

erikw commented Jan 14, 2022

@isayakmondal great!

@Lukkian Do you mean for debugging? Yes, it's easy. Just modify by removing the last parts so it becomes:

	cis = !"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' \
		"

Now it will just print what the commit message would have been.

@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