A Guide to Single Branch Development with Git
Addendum: I’ve recently compiled this post and the previous post, with a lot of extra content, into a book which can be bought here on my website. If you get any value from this post and would like to learn more about git, please check it out! They are being sold as DRM-free PDFs over Gumroad. You can check out the first chapter here.
Also, be sure to spread the love and share on social media! It would mean the world to me :)
The Who
This post is aimed at people who have read my last one (which can be viewed here) and all those who want to learn more about how to incorporate git into their solo developer workflow.
If you have any questions, don’t hesitate to ask! You can comment here or reach me at my email.
The What
In the last chapter, we explored creating our first git repo and hosting it on Github. We made our first commits, staged them, and pushed onto an external repo.
Now that we have the basics down on git repos, let’s do some housekeeping. In this post you’ll learn how to do workflows as a single user such as setup basic documentation on our repos in the form of markdown files and how to pull down changes from our remote Github repository. You’ll also learn how to do git checkouts basic branching and what to do when things go wrong, an example of which would be if a wrong code commit got made, causing our codebase to fail.
The How
‘README.md’: Letting people know what’s in a Git Repo
Git has grown to be the dominant choice for code projects in open source. As such, many hosting services such as Github have grown to have conventions around this. Use of these conventions is integral to any project and not having them will cause many people to get lost when looking at your code. They can be a bit of a pain to write up at first, but they’ll save you a lot of pain later on from having to explain to every user who comes to your repo what this particular git repo exists for.
Let’s walk through an example to show you what I mean. Imagine you stumble across this cool new open source project called wxyz. You have no idea what it is, but it did come up in your internet search for javascript libraries to handle parallax scrolling [alternatively insert your coding problem here]. You click on the link to that git repository: what would you need to know?
You’d want to know some basic info on what it is you’re looking at. This would go into the README.md
file. This file gets displayed if you access the home page of your git project. A bare minimum "README.md" file should contain the name of the project, the author, and a few sentences about what the project is. The extension .md
stands for markdown, which is the text file format a git hosting service will use to generate html. A good README will also include compiling instructions as well as information as to how to contribute to the project, the latter of which is especially important in the open source world.
So let’s go back to our git repo and start making a readme file and add some text to it so we know whats in our repo. We’ll be adding it to the root of our repository.
If we look at our blank repository on github, we can see that Github is actually recommending we put a readme file here. You can create this file on your local computer or you can use Github’s built in text editor to add the file. We’ll go ahead and do the latter for now.
As you can see, Github autopopulates the header name (marked by a #
) to a README.md
file.
Markdown is a mark up language similar to HTML. It actually compiles to HTML in the background. Markdown is simpler to write than HTML, with its annoying nested tabs and many many tags, and has the benefit of being readable without being rendered as a web page. You can read through it on your terminal if you clone the repository.
If we click “Preview”, we can actually pull up what github is showing and get a sense as to how our markdown text will look.
So let’s start adding some text! A good README.md will have the following items at minimum:
- The name of your project
- The principal author or maintainer of the project
- What the project is in a few words
- How to Compile
- How to Contribute (sometimes its own file calle “CONTRIBUTING.md”)
In our case we have nothing to compile and no need to tell others how to contribute (yet), so we’ll be skipping those two. Not all projects need all the bullet points here, but its a good idea to think about these when writing your README.md
file.
You can access a more comprehensive list of all the different things that markdown can do at the end. Keep in mind that every hosting service uses a slightly different flavor of Markdown. For now, I’ll touch on some key tags.
First is that headers guide your reader as he looks through the documents. They use “#
" characters and usually compile into h1
tags in HTML. If we use two "#
" characters ("##
"), we'll be typing out an h2
tag, and so on. We'll use those to stub out the big topics we want in our README.md file.
Next is that we can bold or italicize words by using underscores (“_
") or asterixes ("*
"). I personally prefer underscores, so I'll use those here, but both are acceptable to use. If we use _hello_
, we'll italicize and if we use __hello__
we'll bold. General markdown does not allow for both bolding and italicizing, though Github's h2
tags have both by default, so check with your git repo hosting company.
Lastly, we can use lists by simply adding in bullet points like “*
" or "-
". Markdown is smart enough to know when a point is added, so feel free to write shorter lines to keep the text readable. I'll be doing that in the below snippet.
# My First Repo!Git is really cool! I especially like how Github helps keep my
repository hosted and makes life easier for writing code by using
snapshots and the like.
## Authors- Vincent Z.
# How to Compile _To-Do_
# How to contribute.__Simply clone and add!__I don't know how to branch yet, but I hear you can easily do it and
then I'll pull from you or accept your patches.
Neat huh? As a bonus, we can pull it up in a local text editor and see that it still looks fine. In fact, let’s do that right now. We’ll just scroll down and click the Submit
button and commit this new text file to our repo.
Git Fetch & Pull
If we go to our local git repo, we’ll see that our local git repo thinks we’re “synced up” with our remote repo. However, we know that’s not quite true as we just added a text file to the repo. To bring our local repo up to speed, we’ll run git fetch
. This command will tell git to go to our remote repository (it assumes to use the remote repo called origin
unless we specify otherwise) and pull down any changes it doesn't already have locally.
Now we have the commit sitting on our local repo. However, we’re still “behind” so we don’t see the file. Git hasn’t yet incorporated this change so fetch
only grabbed the new code. We can see this when we run git status
.
To “fast-forward” our local git repo, we’ll need to run git pull
.
Now we can see all the new information available. Git knew that the most recent commit was in the repository and not in the local repo, so it overroad all the changes we had.
Now let’s take a look at our commit with our favorite text editor now that the file exists in our local repo.
Awesome! Now we’ve learn how to create readmes and pull changes from our repo. As an exercise, let’s commit a few changes to the README and then push these to the server. First, we’ll open up the readme in the text editor and add a new line in.
We’ll exit out, stage the new change, commit with a one liner (git commit -m "message"
), and push to the server.
Now we can go back to our Github page and see the change.
"git checkout"
Let’s now suppose that we don’t really like this line, that we’d prefer to go back to the “old” read me we had before. To do so, we’ll be taking advantage of a command called git checkout
.
git checkout
allows us to checkout
a particular commit. This will be local to our repo and not touch our remote repository on github.
We can see the full history of commits we’ve made over time here, as was mentioned in the previous post. We want to pay attention to the commit hashes, which are highlighted in brown in my terminal (Note: this may be different for your terminal). So let’s find the hash of the commit we want to go back to and see what happens. We’ll run the following command:
# git checkout <commithash>
git checkout d52738f0f
Note that we don’t need to include all of the commit hash.
When we run git status
, git tells us we're in a "detached" state. This means any commits we create will not go into our git branch without some painful merging (note git merge
will get covered in a later post so we won't go into it here, but we still demonstrate how branching works).
Let’s start by adding some extra text to first_file.txt
and then running git status.
So what happens if we try to commit this? Well…
Hmm… Whats going on here? We’ll start with a diagram of our git repo before we got detached.
The ‘X’ here represents where we were in the code base at that point in time. As can be seen clearly, we were at the very front of the commits. If more commits got made, we’d simply move forward. What happened when we ran checkout
?
We simply moved back one! Now when we made that change however, we started going into a “pseudo” state where we disconnected from the main tree here. Our new commit sits somewhere detached from this “master” branch, veering off to the side. To correct ourselves, we’ll need to create a new “branch” and work our commits there. Once we do so, it’ll become more clear where our commit went.
We’ll run the following to do this.
# git checkout -b <name_of_new_branch>
git checkout -b new-branch
This command will cause all of our changes to move into that new branch. If we didn’t include the -b
flag, then git would assume that the branch was already created and return an error if it didn't see a branch called new-branch
.
We can also run git branch
to see what branches are available. It will highlight the one we're using with an asterix and, in my terminal, green text.
Git now registers two branches in our local repo. Note that we’re still in the local repository: we’ve yet to commit any of these changes to our Github repository yet.
If we want, we can now switch back to our previous branch by running git checkout master
. The -b
flag before was to tell git to create a new branch if one did not yet exist.
We can now see that the git checkout did not touch our master branch. Note that lack of newline at the end of the text file caused a strange run-on at the end. This is not part of the file.
That’s part of the power of git. We can make changes, quickly branch off with some new feature or idea and then merge back in again. We’ll go over git merge
in more detail later on, but know that methods to take two branchs of development and put them back together is forth coming.
If we go back to the other branch and look at the text file, we’ll see that it now has our changes.
So what’s going on here? Git is creating a new branch that begins with the commit hash “d52738…”. This means that this commit will be the last common ancestor of the master branch and the new-branch. Confused? Let me pull up a diagram to better illustrate what I mean.
With this we can see that our new branch of development is completely separate from our master after commit “d52738f0f”. This commit, which is where we branched off, is called the common ancestor of the the branches. Its the last time the two branches had the same code base. After this, the commits in master may look different from the commits in first-branch. In our case the differences are small, but they can be substantial in larger projects.
If we want to push this change, we’ll need to push this new branch. Github, along with most remote hosting providers, is smart enough to create the new branch on their side so we don’t have to worry about that.
If we look on Github, we can see this new branch come up.
We can click on that branch and see that new source code come up too. I’ve highlighted some extra info that lets us know where we are in relation to the master branch. This can be important at times, especially in open source.
We can also pull up the network and see how the new commit looks there.
"git reset”
& "git revert"
Now you may be wondering what that extra commit in master is on the github network. That’s a new commit made that broke our application! In this hypothetical situation, we now want to not just branch but instead move back entirely to a prior comit. This is called git revert
and is a powerful feature of git, enabling us to move back to a prior commit. This manuever will register in git as a commit itself.
So let’s see what this randomness is…
Oof! Our project is small enough that we can just manually re-edit the files but sometimes it can be a nightmare to hand edit files. Luckily, git provides two good methods to deal with this.
The first is git reset
. This command tells git to put the local code base back to a particular commit hash. Its similar to checkout
in this regard.
To do this git reset
, we'll be running the following command:
# git reset --hard <commit_hash>
git reset --hard 2506b97
What’s with the --hard
flag? That's to let git know we don't care at all about commits made after this particular hash nor do we care about any changes we may have not yet committed but still had sitting on our local repository. We go back just to the way those files were.
Note that this affects just the local repo. The remote repository is still unaffected. To make this a change that affects the remote repository, we need to use git revert
.
git revert
will undo one or a series of commit hashes. In our case, we just want to undo the one commit hash. Therefore, we'll run the following command
# git revert <commit_hash>
git revert 49fc46eb
This will pop open a new commit message. We’re keeping this commit in. It will look like a commit message you’ve done before and its generally auto populated. Fill in what you want and then save the message.
Once we do that, we’ll see that our commit is again one ahead of master. That’s because we haven’t pushed yet.
We can see through our logs that this gets put in as a commit and our files are now back to where they need to be. We’ll push this to the repo and take a look at github there.
Now you can see the edits that were reverted above. They are marked with minus (“-
") signs. You can also take a look at the network to see how this all worked, complete with our new branch.
This looks just fine: the git revert
got put in as a commit. That means it has its own hash and we can still retrieve the broken commit if we really want to. By keeping it this way, Git allows us to never lose code! That's important: git acts as a vault that stays "read-only" but never "rewrite". Our code is almost immutable and our timeline of changes can help with larger teams, as we'll see in later posts.
The Why
Git is supposed to be a never ending timeline of code changes. As projects become complex and change over time, we don’t want to be manually keeping track of all those changes. This is commonly known as “flat files” or “tarballs and patches” and is a pain to manage. Its also easy to fall into disrepair; there is little in the way of guard rails forcing you to adhere to one style or another. If people come and go from a project, a common occurrence, then they will carry with them secret knowledge about how that particular system of tarballs and patches worked. Not good.
Better is where you just keep all changes in your one git repo. By doing so, no work is ever lost. If you break things, you may still want to recover that code. For instance, imagine your production server goes down due to a bug introduced in a change. In the moment, you want to correct the problem on your production servers as fast as possible. But in the future, you may still want to grab that code and explore that bug later on. Git allows you to do that.
Git’s branching system, introduced here, is enormously powerful yet still simple. We used it in a very simple way here and will be moving on to more complex projects in later sections. For a single developer, the benefits are enormous and for vast teams of developers, the benefits are even bigger. If you think git is too simple for a large project, just look at the Linux kernel, which git was invented to help. They have thousands of branches! They still manage to pretty painlessly manuever them as well. [1]
Git is extraordinarily powerful and is worth using even if you are a single developer. By taking the time to properly document your code with readme files and use commits and reverts, you will get dividends in the form of increased productivity when things go wrong — and they always do in coding!
FAQs
Q. Whats the “head commit”?
A. This is simply the more forward commit in Git where our code base defaults to using. Its the newest commit and generally the one everybody ends up using, where everybody can be machines running code.
Links to Git Documentation
Footnotes
[1]: To be fair, most companies will have many git repos. You can watch this talk if you’re curious about how Uber handled have a lot of repos in their application development.