Use Case
I love Git. It can sometimes suck the life out of you, though. That may not be the case for everyone. How much time you spend banging your fingers on the command-line kind of depends on your workflow. If you're a front-end dev, for example, and you spend a lot of time tweaking CSS (or SASS, or whatever), a good way to minimize your use of Git is to simply leverage your browser's Developer Tools more efficiently. Another good strategy is to focus on your localhost--reducing the number of pushes you need to make. In my experience, before you go looking for shortcuts, the best way to cope with command-line stress is to think first about optimizing overall workflow. Optimization aside... there are always exceptions to the rules.
Personally, I don't always want to work on my localhost. I often work with DEV, STAGE and PROD environments and sometimes it makes sense to push my code changes straight to DEV (in most cases, I'm the only maintainer). At other times, I work with simple sites (like this blog) and I know that the odd CSS change isn't something that will bring the site down. In these cases, adding, committing and pushing for each and every little tweak can be both redundant and exhausting. Git can be extremely pedantic at times.
Hence, this script...
The Problem: Count the Keystrokes
In a typical Git workflow, you'll smack your keyboard a minimum of about 50 times in between adding unstaged files and a push. That seems like overkill to me--especially if you're continually pushing changes and you've got deadlines to meet; the word 'karoshi' (過労死) comes to mind--a Japanese word for when you basically work so hard your mortality in this world comes to a sudden but depressingly anti-climactic stop. You all know what it looks like:
$ git add -A
$ git commit -a -m 'some descriptive message'
$ git push origin master
There has to be a better way...
NOTE: Yes... you could alias each of these commands (i.e., "ga" for "git add"; "gc" for "git commit", etc.), but even then, your focus game needs to be on point. Letting a script handle all of this is like hopping into the back seat of a cab. Sometimes it's nice to let someone else do the driving; just provide a little direction.
The Solution: Bash Scripting
There is a better way, and it's called Bash. If you're new to it, here's a good introduction to Bash and Bash Scripting. Otherwise, let's jump straight to the meat and potatoes. By moving these Git commands to a Bash script, we should be able to reduce the number of keystrokes from staging to push by up to 80%. I'll walk you through the script, but let's first have a look at the result.
Here's what's going on:
- The script automatically does a git add with the -A option.
- I'm gonna call the script uaing an alias, gacp (shorthand for Git-Add-Commit-Push), with an inline comment for the commit.
- The script asks where to push to, but checks which branch you're on and prefills that as an answer.
- I get asked whether I really want to go forward with the push; again, the answer 'y' is prefilled.
That's it. Inline comment text aside, this should be a lot faster than traditional keyboard cruising. Let's look at the script. Before we break it down, here it is as a whole:
#! /bin/bash
function addcommitpush () {
current=$(git branch | grep "*" | cut -b 3-)
message=\'"$@"\'
git add -A && git commit -a -m "$message"
echo "Where to push?"
read -i "$current" -e branch
echo "You sure you wanna push? (y/n)"
read -i "y" -e yn
if [ "$yn" = y ]; then
git push origin "$branch"
else
echo "Have a nice day!"
fi
}
addcommitpush $1
Let's break this down...
Step 1: Function Setup
The first thing you'll want to do is create a file with Nano (or whatever your favorite editor is). I keep my scripts in a directory in my $HOME path. Within that directory I've created a file called "gacp.sh". Don't forget to start your file with a hash-bang (#! /bin/bash). Once that's done you can start defining a function. To start with, we'll get the function in place and begin filling it out in the next step. I've chosen to call my function addcommitpush.
#! /bin/bash
# declare a function called addcommitpush
function addcommitpush () {
# we'll start filling this out in Step 2...
}
Step 2: Determine Current Branch
The inspiration for doing this comes from Frank Rietta's article on getting and comparing the current Git branch in Bash. Frank uses a combination of git branch and sed to establish the current branch. Personally, I have a regex allergy, though--so, my preference is for a simpler method. git branch will list all of your local branches and put an asterisk next to whichever one you happen to have checked out. We can isolate the checked out branch name by running grep and searching for the one character that makes it unique: the asterisk. If I ran git branch | grep "*" the output would look something like this: * currentbranchname. We can then use cut to remove the first two bytes of that output (the asterisk and the space before the name). I can take that current branch name and then store it in a variable to use later in my script--more on this later. For now, here's what it looks like:
function addcommitpush () {
# find current branch and store it in a var called current
current=$(git branch | grep "*" | cut -b 3-)
# Continued in Step 3...
Step 3: Git Add and Git Commit
Before we get too far ahead of ourselves, let's talk briefly about how we'll run the function. We'll create an alias for the script and then run it from the command-line with an inline variable. That inline variable is our commit message; from the command line it would look like this $ gacp "some commit message". We'll need to take that variable (the message) and pass it to the function. Since we'll piggy back our commit on having completed a git add, that's just something we need to be aware of.
The first thing I want to do in my function, then, is take the value of the inline variable (the commit message) and store it in a new variable. After that, I can write out a command to add unstaged files and make a commit. It'll look like this:
# ... Continued from Step 2
# wrap inline var in single quotes and store in another var called message
message=\'"$@"\'
# git add and pass message var to git commit
git add -A && git commit -a -m "$message"
# Continued in Step 4...
The message variable looks like it has some intimidating syntax, but it's simple enough. Since an inline commit message is always made inside single quotes, I want to do the same for whatever my message is. I can wrap the message in single quotes within my message variable, but I need to escape the quotes so Bash interprets them as literal: i.e., \'. The inline variable (the actual commit message) is stored in $@. However, I wrap this variable in double quotes to allow for spaces in whatever that inline variable is--hence, "$@". You'll note that I also use the double quotes to to pass the message variable into git commit: i.e., git commit -a -m "$message".
A final note: I use the same double quotes when running the function from the command line--$ gacp "some commit message"--again, I need to do this since the commit message contains spaces. If I had a one word commit message, I wouldn't need the quotes: i.e., $ gacp message.
Step 4: Where to Push to?
If you're working adding/committing on a given branch, you probably want to push to the same branch. Do you really need to ask which branch to push to?--no. Nonetheless, I think it's useful to make sure the user knows what's going on. I'm prefilling the answer to the question here, so all the user needs to do is hit return. It's an extra keystroke--yes--but it'll help me stay sane when I use the script. In my version, I'm also allowing for the user to bail: pushing is a big deal, so a way to opt-out right before the push is built into the script. Here's how it all goes:
# ... continued from Step 3...
echo "Where to push?"
# prefill read prompt with $current var and create a new var called branch
read -i "$current" -e branch
# last chance to opt out before push
echo "You sure you wanna push?"
# read and create yes/no var
read yn if [ "$yn" = y ]; then git push origin "$branch" else echo "Have a nice day!" fi }
Pretty straight forward. If you don't want to bother with an opt-out, or you don't want to confirm the current branch, you can shorten this quite a bit:
# ... continued from Step 3...
# PUSH
git push origin "$current"
}
Lastly, if you need a little excitement in your day, feel free to pipe all of these echo commands with lolcat: echo "You sure you wanna push?" | lolcat... or maybe replace echo with cowsay.
Step 4: Call the Function!
Finalize your script by calling the function and passing the inline variable (i.e., commit message) to it. We can do that like so:
#! /bin/bash
function addcommitpush () {
# ... lot's of cool commands and Bash variables and stuff...
} addcommitpush $1
Step 5: Make Executable and Alias
This is the end; very last thing to do is make your script executable and then alias it: run $ chmod u+x gacp.sh and then edit your .bash_aliases with the following: alias gacp='~/pathto/gacp.sh'. Do a quick reload from your $HOME path on the command line with $ source .bashrc and you should be good to go. Having the alias setup like this is what allows us to run the script like this: $ gacp "some commit message".
That's about it...
Have fun!