May 2020
A Beginner’s Git reference
Reading technical documentation is sometimes boring, but reading Git documentation is always boring.
With that said, here’s a post about Git.
Big Picture
$ git status
Unlike much of Git, this command is crystal clear . We’ll see if any files have been updated, added, deleted, staged, committed, etc. This is a good place to start a work session.
$ git branch -a
Show me all the branches that exist for the project, both local and remote. Also, show me what branch I’m on! Ommitting the -a
flag will only show local branches.
These first couple commands only provide information — they don’t affect your repo in any way — this is a relief! You can run these commands 500 times without a worry.
First complication
It turns out git branch -a
is sometimes outdated, so we need to prune things every now and again. This command removes all outdated remote branches.
$ git fetch --prune
Our view of the remote branches becomes outdated when a remote branch is deleted — Git won’t automatically update your local reflection of deleted remote branches.
Depending on your git flow, remote branches may get deleted quite often, especially when closing Pull Requests on Github, so this command may feel a bit tedious.
The good news is that we can change our local config to auto-prune on every pull.
$ git config --global fetch.prune true
Deleting Branches
Remote
$ git push —d origin remote_branch_to_delete
That’s how we’d do it from our terminal (there are other variations), but as mentioned earlier, it’s likely that we’ll be deleting remote branches via Github’s Pull Request GUI on their website.
Once we delete remote branches, it probably makes sense to delete their associated local branches. Deleting fully merged local branches (or remote) is okay because Git has a long, long memory, and we can google how to revert if need be.
Local
$ git branch -d local_branch_name
The above command works if local_branch_name
has been merged somewhere, but if it hasn’t, Git will complain, afraid that you might lose commits.
Perhaps this was just a test branch, and you don’t mind losing commits. The big -D
flag should take care of that.
$ git branch -D local_branch_name
Basics
Let’s get back to the basics — 98% of Git involves simple commands like staging, committing, pushing, and traversing branches.
We made some changes to our code and we’re thinking it might be time to add some files into the staging area. Before we stage our files, it can be helpful to see what’s changed since our last commit. (Note that this is totally optional.)
$ git diff
This will likely fill our terminal window with text, some lines preceded with +
and others with -
. I think we can guess which lines have been added and which removed.
LESS
There’s often so much text that the diff
wants to print that it will overflow the terminal window viewable height. Just to complicate matters, our Terminal will display the results using something called LESS
which is what’s a terminal pager program.
If you haven’t seen this before, you might feel stuck. Scrolling with your mouse or touchpad has suddenly stopped working. There is nowhere to type our next command.
Luckily, typing q
will restore sanity — which I’m guessing is short for Quit.
So what is LESS? It’s basically a way to turn a long block of text into a PowerPoint, in your terminal window. Text is suddenly only viewable by page. Pressing the spacebar
will move you to the next page. Using the down/up arrows will just move us 1 line at a time.
Of course there are a million commands, but all we have to remember is q
and spacebar
.
Alternatively, if we want to avoid this situation, we can run
$ git --no-pager diff
which will print the log without LESS — meaning no pagination.
Branch Check
Ok so we’re happy with what we saw on our git diff
. Before staging, let’s double check that we’re on the correct branch.
git branch
will tell us, or perhaps we set up a persistent branch label in our terminal (skip ahead to Terminal Settings at the end for more info).
If we’re on the wrong branch, we can still checkout the correct branch without much trouble. Our unstaged changes will be carried with us.
If we proceed with staging to the wrong branch, we’ll have to mess around with un-doing things, which can get complicated.
Staging
Now we want to stage
these files, which is just a temporary step to collect ourselves before we commit these changes to the record.
There are a couple flags worth remembering:
Stage all files that have been added or updated:
$ git add .
All files that have been added, updated, or deleted:
$ git add -A
An individual file:
$ git add filename
though I don’t stage individual files often, so I’m not sure why I’m mentioning it. To that point, here are the official git docs if you want serious, trustworthy Git information.
Committing
Our files are staged, and we think we’re ready to commit them. This is probably the most important command in Git!
This is the proverbial snapshot of our files, at this moment in time. We can come back and visit whenever, forever.
One quick side note before committing — perhaps we forgot to run a git diff
before staging. If we want to check the diff
at this point, we have to say git diff --staged
because we want to see the difference between our staged files and the previous commit.
Ok, now we’re ready to commit:
$ git commit -m 'my commit message here'
Messages are mandatory for commits, and sometimes this interaction is a pain - especially if you want to write a longer commit message.
One solution is to omit the -m ‘my message’
flag, and your terminal will open its default text editor - which in my case was Vim, inside of the terminal.
Personally, I don’t enjoy feeling trapped in a text editor, so here’s how you can change your terminal’s default text editor:
$ git config --global core.editor "atom --wait"
Now, every time you git commit
(without the -m
flag), Atom will open up a new text file in Atom, giving you a bit more breathing room. Saving and closing the file completes the commit.
The —-wait
flag is important, or else the commit will fail before you get a chance to write your message.
Logs
We’ve made some commits, and we’re thinking about pushing to a remote repository. Maybe it’s been a few hours, or even a few days. Might make sense to take a brief look back at the commit history.
$ git log -1
shows us our last commit.
$ git log -5
shows us our last 5 commits.
$ git log
shows us way too many commits, and we’re back in that LESS
paradise we talked about earlier.
We can also specify a date range, if that’s easier, but I’ve found just asking for the last X commits to be simplest.
Sharing
We’ve collected some local commits, and we’re to share our changes with the bigger project. There are a variety of ways that happens — we’ll just talk about 2: merging
and pushing
.
Merging
Merging is good if we just want to share our updates locally, without worrying about what’s happening on remote yet.
First we want to check out our destination branch (where we want to merge into).
$ git checkout master
Then we can merge into master with the branch we were working on.
$ git merge branch_we_were_working_on
Pushing
We’re ready to share our local code with remote.
And we’ve already connected our local repo to the remote repo on Github (if we haven’t Github will explain how to do this).
$ git push
If it’s the first attempt at pushing to a remote, you’ll get an error saying that the upstream branch hasn’t been set. And your terminal will provide you the command you need.
$ git push --set-upstream origin my_branch_name
From then on, simple git push
should do the trick.
Remote Comparisons
Now that our code’s sitting in a remote branch somewhere, we can employ new methods of comparing our code.
Instead of comparing our code to previous commits like we did with git diff
, we’ll probably want to compare our code with other remote branches, as a merge is likely in our future.
Github’s web interface has a couple of nice options. If we aren’t ready to create a Pull Request, we can Compare our remote branch to another branch. If we’re ready for the Pull Request, there’s a useful interface to see how things compare, and if there are conflicts.
Pulling
We’re probably a bit late in mentioning this, but the ying to the push
yang is pull
.
When a collaborator pushes something to a remote branch, we’ll need to pull those changes to see them locally.
$ git pull
The above will work for individual local branches that track remote branches. In other words, make sure you have the my_feature
local branch checked out if you want to pull from the my_feature
remote.
One caveat is that we want to make sure that we’re fully committed before doing this, as the inner-workings of a git pull
involve a merge
, since the new remote code is merging
with our local stuff.
A nice side effect of git pull
is that we’ll see any new remote branches that have been created, although their contents won’t be automatically pulled to our local system.
If we just want to pull the contents of one of these new remote branches, we just need to check out the local version, and then run a git pull.
Branching
So far we’ve talked about moving around branches, but haven’t looked at how to switch to a different local branch:
$ git checkout existing_branch_name
If we want to create a branch, and immediately switch to it:
$ git checkout -b new_branch_name
This will copy the code from the originating branch.
Totally Optional Stuff:
Aliases
For repeatable tasks, it’s useful to shorten them as much as possible. Git is chock-full of repeatable tasks, so we can set up something called Git Aliases.
Now, if this at all introduces confusion, we shouldn’t do it, because clarity is far more important than brevity.
With that said, let’s set up some aliases. Instead of typing git commit
over and over again, let’s just type git c
$ git config --global alias.c commit
And a few others we can shorten as well:
$ git config --global alias.st status
$ git config --global alias.b branch
$ git config --global alias.ch checkout
To see all the configs that have been set:
git config --list
Or we can peak in our .gitconfig
file that lives in our home user directory (just need to show hidden files).
Terminal settings
It would be nice if we didn’t have to git branch
, or with our alias git b
, every time we need to be reminded of which branch we’re on. The good news is that there is a way. The bad news is that it requires tracking down your system’s .bash_profile
file and editing it.
You can do this in the terminal, or you can update your Mac to show hidden files (files that start with .
), and look in your root directory in finder. Opening .bash_profile
is just like opening a text file, and we want to add this code in there:
parse_git_branch() {
git branch 2> /dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/ (\1)/'
}
export PS1="\u@\h \W\[\033[32m\]\$(parse_git_branch)\[\033[00m\] $ "
This little hack came from here
Update December 2021:
To get this to work on new versions of MacOS, add this to your .zshrc
file:
function parse_git_branch() {
git branch 2> /dev/null | sed -n -e 's/^\* \(.*\)/[\1]/p'
}
setopt PROMPT_SUBST
export PROMPT='%F{grey}%n%f %F{cyan}%~%f %F{green}$(parse_git_branch)%f %F{normal}$%f '
From here
Conclusion
This is the end for now. Hope it wasn’t too boring 🙃