Git – Rebasing to a specific commit

I was working on homework that challenged us to create tests to build a rails app from the ground up. I finished the homework on a master branch and decided I wanted to do it again for practice. So I created a topic branch off of an early commit (e3f8704) to do the work from scratch.

$ git checkout -b homework e3f8704

Later I realized I made a mistake and wanted to use interactive rebase to fix a commit on the topic branch. To do so, I just passed in the commit I used to branch as an option. This tells Git that you want to rebase from that commit.

$ git checkout homework
$ git rebase -i e3f8704

Resource: https://git-scm.com/docs/git-rebase

Advertisements

Cleaner Git logs and Git aliases

If you spend any time during your day reading Git logs, you probably hate having to page through the log to see a specific commit. Here’s a couple of nice git log options to help you quickly glance at your commit history.

--abbrev-commit abbreviates the commit SHA to just 6 or 7 unique characters.
--pretty=oneline prints only one line of your commit message. This is especially useful if you follow the convention of writing descriptive subject lines for your commits. This lets you page through many lines of commits and know exactly how your project progressed. Here is a nice guide for writing good commit messages.


Git aliases

If you find yourself using these two git log options frequently, you might want to consider aliasing them. A Git alias allows you to create a ‘shortcut’ command. The code below creates a permanent global change to my Git configuration. So that I can write git cleanlog instead of git log --abbrev-commit --pretty=oneline.

git config --global alias.cleanlog 'log --abbrev-commit --pretty=oneline'

Another Git command that I aliased was the reset command to remove files from the index. To me, writing git unstage seemed to be a sensical command to type when I want to unstage files.

git config --global alias.unstage 'reset HEAD -- '

If you feel like aliasing is the way to go, start thinking about the Git commands you use on a daily basis and start creating your own aliases. You might want to start simple, like alias.co checkout and then add more options as you find yourself using more complex flow control commands.

EDIT: Mar 10, 2016

I find myself using a few more really simple, but useful Git aliases nowadays.

git config --global alias.commit 'commit -v'
git config --global alias.s 'status -s'

Git – Merge vs. Rebase

Version control systems are an essential tool in software development and branches make it easier to manage code and organize projects. So being able to keep track of feature branches goes a long way in moving projects along efficiently.  I’ll outline what I’ve learned to be good practices for managing code on your local repository as well as your remote(s). And then I’ll cover the basics of merging vs. rebasing and how both methods can be used to re-integrate feature branches back into master.


Managing Branches

Rule #1 – Preserve the master branch

Your master branch should contain all of latest and greatest code that is worthy of your final product. Ideally, all of the work you do should be completed in feature branches and then merged back into master when it’s been tested and approved. If you’re having conflicts when you pull or push on your master branch, something may have gone terribly wrong and it’s definitely worth taking the time to figure out what happened and fixing the problem.

Rule #2 – Be clear

Visualize a desktop screen on a computer that is littered with over 100 files. You decide you want to organize them so you group them into directories, each with a unique name. But you get careless and name one directory ‘important’, another ‘essential’, and yet another ‘files to keep’. What’s the difference between these directories? Can you tell me their contents by just glancing at them?

Branches are ways to work on groups of files that are all related to one topic. If you’re creating a way for people to purchase things on your website, maybe you’ll call the branch ‘purchase’. Or maybe you can be even more specific and have a ‘shopping_cart’ branch as well as a ‘credit_card_authentication’ branch. The point is that being specific is important to maintaining your sanity while working on multiple projects at once. Name your branch something so specific that you can identify it’s purpose and contents with a quick glance.

Rule #3 – Be consistent

This rule goes hand in hand with being clear about your work. In short, try to follow conventions for naming branches locally as well as remotely so that you don’t confuse yourself. Specifically, any major remote project repository should be called upstream and your own forked copy on GitHub should be called origin. In addition, if you have a project directory called ‘shopping_cart’, your local and remote branches should also be called ‘shopping_cart.’

Rule #4  – Commit often and commit well

If you’re working on a feature branch, you could theoretically have one commit for that branch. Just save your work as you go and then make one commit when you’re done coding. WRONG! Commits keep track of the work you’ve done and create a history that reflects the actual changes you made to a file. Good commit messages are important because they summarize what changes you made and, more important, why you made them. Do you press the save key incessantly while writing word documents? Commits are like the save key…it’s hard to overuse them.


How to integrate into master

There are two main methods used for integrating code into the master branch and the first is git merge. Anytime you need to bring code into a branch (even if it’s not master), you need to use the merge command to take the commits from one branch and merge them into your target branch. You can add complexity and power to merging code back into the master branch comes from another re-integration method called git rebase. I’ll go into detail about rebasing in a bit, but basically it’s away of updating a feature branch so that it accurately reflects the most recent changes made on master. It’s sort of like updating your phone – your phone’s contents are still unique to you, but it’s running on the most recent software updates.

As with many things in life, people are often split on whether using merge vs. rebase is the best way to re-integrate code into the master branch. It ultimately boils down to personal preference as both methods are legitimate ways of managing branches and merging into master. I’ll go over the details of what each method does and then you can decided which you prefer.


Merge

Merging is a fairly simple process by which you can integrate code from a topic branch into the master branch. Let’s imagine that you branch off of master to do some work on a small code fix. You make just two commits and are ready to bring those changes into master. In the meantime, someone else made another commit that got integrated into the master branch. This isn’t necessarily a problem because Git will figure out how to merge the two branches together in the best way possible.

Essentially, Git takes a look at both branches and finds the commit that’s a common ancestor to both branches. In the image below, that’s the first commit represented by a blue circle. It then compares all the commits made on master and on the topic branch, decides if they can be combined without conflicts, and then creates a new merge commit.

Merge commit

This last step is important. Anytime that you merge two branches, Git creates a special commit called a merge commit. Unlike all other commits, which have one ‘parent’, merge commits have two parent. It points at the two branches that it merged together so that you can trace back into the two branches that resulted in the merge.

Sometimes, you might find that you modified the same file in two branches. If there are any conflicts because that file exists as two different versions, you will have to resolve them manually. The built in mergetool is a good tool for those who are just starting to find merge conflicts that they need to resolve. Basically, you will have to choose which version of the file you want to keep, line by line.

For example, let’s say you have a file called ‘foo.txt’ in both branches. In the master branch, line 1 says ‘hello’ and in your feature branch, it says ‘hi’. This is a conflict that Git cannot resolve so you have to use mergetool and decide, do I want line 1 to say ‘hello’, ‘hi’, or both?! If you choose both lines, line 1 will stay as ‘hello’ and your line, ‘hi’ will get input as line 2. The important thing to remember is that you probably DON’T ever want to replace the version on master with the version on your feature branch. If it exists on the master branch, it’s probably not code that you want to be changing.


Rebase

Rebasing is a powerful Git tool that can modify the history of commits made on a branch. Now, you might be thinking that Git is supposed to preserve history, that the whole point of using this amazing VCS was so that you could remember exactly all of the changes that were made at some point in time. Well you’re right. Git won’t ERASE any history and it won’t change the contents of any commit. What we’re talking about is rebasing (updating) your topic branch to reflect the most recent changes in master.

Rebase 1

If you make a topic branch and do some work, you could merge those changes back into master with a merge commit. But maybe you want to make sure that your changes don’t conflict with any commits on master BEFORE you merge. You know…because your colleagues might appreciate it if you fix all merge conflicts before they try to merge your code into the master branch? At this point, you would run rebase on the topic branch and it changes its history. Look at the image below. Notice that it rebases the topic branch so that its ‘base’ is built off the last commit on master.

Rebase 2

This is extremely useful because it let’s you see how your changes in the topic branch stack up to the changes made on master. Think about this for a second. Remember that when you merge a branch onto master, Git will notify you if you have any merge conflicts. Rebase will do the same exact thing, but right when you rebase it. So basically, merge and rebase will both help you merge commits onto the master branch, but the difference lies in when you get notified of merge conflicts.

Merge warns you of conflicts while you are trying to merge. Rebase makes you address those conflicts right when you rebase so you can clean them up BEFORE you merge.

Once you try to merge those topic branch commits, your master branch will end up looking like a straight line as if the topic branch commits were just pasted in front of the master commits. This is because of the way Git interprets your branch architecture. If your topic branch just has two different commits compared to your master branch, all Git does is add those to master.

Rebase 3

This is different from a normal merge where Git notices that there are commits that are not shared between your master branch and your topic branch so it creates that special merge commit. In a rebase, you will eventually have to use git merge and create a merge commit. However, the history of your commits will look cleaner as rebasing simply builds your branch commits on the most recent master commits.


An important note about rebasing

Rebasing is extremely useful when you’re managing a large project with a lot of topic branches. It allows developers to compare their code to the recent changes being merged into the master branch so that they can check that their code won’t cause merge conflicts. But there’s a situation when you should NEVER rebase.

If you rebase your topic branch, it will change all of the SHA keys for the commits on that branch. Git does this automatically because you’re effectively telling Git to change the base of your branch from an old commit to the most recent commit on master. Since the ‘history’ of the branch is being changes, the commit SHAs do as well.

So if you ever push your commits onto a public repository, one that other developers are actively pulling from, you should NEVER rebase that branch. Think about it. If your friend pulls all of your commits from a branch and then you rebase that branch, you’ve just changed ALL of the commit SHA keys for that branch. And then next time they pull from that public repository, Git will tell them that they have some serious problems because the commits on the public repository don’t match the commits on their local one…uh oh. Hopefully, you guys catch the problem and can remedy it, but wouldn’t you just like to avoid the problem altogether?


If you want to read more about Git, checkout the Git documentation online. It’s an AMAZING resource, the inspiration for some of my images in this article, and just plain fun reading…if you’re a Git nerd.

Git – Amending multiple commit messages

Home_Alone_Boy1

There may come a time when you’ve really botched your commit messages and you need to amend a few of them all at once. You could amend all of them one at a time and then push them to remote. Or you could just amend them in a large batch and save yourself some time and probably a bit of anxiety.

Remember, by amending commit messages, the metadata of the commits change so it will have a new SHA key – even if you didn’t change any code at all. Obviously, this will create conflicts with your remote, which has all of the old commit SHA keys. So once you’re done with your amendments, be sure to force push your changes to remote. NOTE: Please don’t ever force push to a repo/branch that someone else is also working on.


 Checking for commit errors

In class, we use Pivotal Tracker (PT) to keep track of stories we are required to complete for homework. It’s just one of many project management tools out there based on Agile. In addition, we’re using a PT service on GitHub that requires us to add our PT story ID onto each commit message we write on a topic branch. I had forgotten about this requirement and needed to add the story ID to all of my commits.

To check how badly I messed up, I went back to the branch I needed to amend, chapter1. Then, I ran git log to see how many amendments I needed to make.

$ git branch
  chapter2
* chapter1
  master
$ git log
commit d9f5b78f5e4701f0d5052827ea5ec8badc01a3cf
Author: Tim <timsjpark@gmail.com>
Date: Tue Sep 29 23:40:47 2015 -0600

 Add ex1.rb to run chapter exercises

commit d8ece159bc942fde9bf0b538c72b2c2df705acd4
Author: Tim <timsjpark@gmail.com>
Date: Tue Sep 29 23:40:00 2015 -0600

 Added Readme

commit 873e4bb55164372f2acec114f4d3fdc1a4dcf895
Author: Tim <timsjpark@gmail.com>
Date: Tue Sep 29 18:39:30 2015 -0600

 Create chapter_1 directory, and Readme and ex1.rb

 [#104389600]

commit 237eda11a586239ff8b67e06e406485f4b76acfd
Author: Tim <timsjpark@gmail.com>
Date: Tue Sep 29 18:58:27 2015 -0600

 Add file to have rubocop ignore Guardfile

commit e1a26f79b86ed9d5b27edf5e930289e4afdc2f78
Author: Tim <timsjpark@gmail.com>
Date: Mon Sep 28 20:20:08 2015 -0600

 Initial commit

As you can see, going back in time, I have two commit messages that need the PT ID added to the end of the message. The one before correctly shows the ID in brackets preceded by a hash (#). You’ll also notice that the oldest two commit messages don’t have an ID. That’s because those commits were made in the master branch and weren’t related to any story.


Preparing to make amendments

Now that we’ve spotted our problems, we need to start making amendments. We can accomplish this with

$ git rebase master -i

If you read the documentation for git rebase --help , you would see that the -i option allows you to “make a list of the commits which are about to be rebased. Let the user edit that list before rebasing.”


Amending multiple commits

Once you type in git rebase master -i you’ll be placed into vim. There, you want to look at the top, which lists your commits in chronological order.

NOTE: This is the opposite order of the git log output.

Since I wanted to amend the last two commits I made, I’ll replace the word pick with reword.

reword

 


Editing your commits

Now, all I have to do is add my Pivotal Tracker ID to each commit message.

Once you make your amendments, you’ll pop out of vim and back into your terminal. Notice that your have an output saying you successfully rebased.

$ git rebase origin/master -i
[detached HEAD 4da44d3] Added Readme
 Date: Tue Sep 29 23:40:00 2015 -0600
 1 file changed, 86 insertions(+)
[detached HEAD 58e1c94] Add ex1.rb to run chapter exercises
 Date: Tue Sep 29 23:40:47 2015 -0600
 1 file changed, 9 insertions(+)
Successfully rebased and updated refs/heads/104389600_chapter_1_master.

But wait…there’s more!

One last step. When I check git status , you’ll notice that my branch and origin “have 2 and 2 different commits each.” Well remember that rebasing and then amending your commits changed their SHA keys. Since I amended two commits, my local branch has two new commits that origin doesn’t have and origin has the two old commits that my local branch doesn’t have.

Well I want my origin to reflect the changes I made with adding the Pivotal Tracker ID, which I know are correct. So I force push my changes onto origin with the -f option. Run git status one more time and it looks like you’re all good…

$ git status
On branch chapter1
Your branch and 'origin/chapter1' have diverged,
and have 2 and 2 different commits each, respectively.
  (use "git pull" to merge the remote branch into yours)
nothing to commit, working directory clean
$ git push -f origin chapter1
Counting objects: 8, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (8/8), done.
Writing objects: 100% (8/8), 2.36 KiB | 0 bytes/s, done.
Total 8 (delta 2), reused 0 (delta 0)
To git@github.com:timsjpark/lrthw_exercises.git
 + d9f5b78...58e1c94 chapter1 -> chapter1 (forced update)
Branch chapter1 set up to track remote branch chapter1 from origin.
$ git status
On branch chapter1
Your branch is up-to-date with 'origin/chapter1'.
nothing to commit, working directory clean

Thanks to Jason Noble for showing me this nice Git feature.