Git: Time Travel for Fun And Profit

Video

Motivation

Git was created as a result of some convoluted, confusing, and petty drama between Linus Torvalds and the other Linux core developers. After getting kicked off the (then closed-source) corporate source-code sharing service, they started to roll their own code sharing systems, and everything was a mess for Linux for most of 2005.

Out of that mess, the clear winner was the new Linus-backed Git, which he named after the fact that he is a massive jerk. (this is not a joke - this is real history.) Hitting version 1.0 in January, the highly distributed Linux core team quickly spread it around the world where it took over development projects large and small - to the point where many people have never heard of other solutions like Subversion, CVS, BitBucket or Mercurial.

What Git Can Do For You

Git uses the same tools to allow for three completely different (on the face of it) benefits.

Collaboration

First, it is a streamlined way to share your files and their content with other people in a way that you can all edit them - even edit different parts of the same files - and have it automatically merge your changes.

Versioning

Git allows you to do what I like to refer to as time travel. With a bit of proficiency, we will be able to go back to past versions of our files - and more potent, steal things to bring to the present.

It can even store metadata about when a file or a line in a file was last changed - and, if you give it a bit of help, you can explain to your future self - or collaborators - why a change was made.

Branching

A third surprisingly useful trick is that git allows you to work on separate features in isolation, so that changes you make in one direction don't affect development in other directions. You can then merge (or, we will learn, rebase) the branches back together again.

Shaking Things Up

Before we get into the tech of it, let's go through the first-time setup, and the day to day, of a Git environment and project.

Telling it who you are

Git, like most other software we have discussed, has a lot of customization options stored in files scattered about your operating system.

To edit them, we use the shell command:

git config

Remember you can read the documentation on this with:

git config --help

You can find existing git config files and edit them, but there is a shorthand for setting config levels:

git config --global user.name "Your Name Here"

This will tag all your edits in all your gits with your name, so that you - or collaborators - know who to ask about what changes.

The system was meant for old-school distributed computing, so it also wants to tag everything with an email:

git config --global user.email "youremail@youremailhost.com"

Be warned that if you make your git public (like on GitHub), that makes this email public as well.

The last option is personal. Git - at times - asks you for your input. For more complex input, it likes to use an editor. We can tell it what editor we want it to use with:

git config --global core.editor "atom --wait"

The --wait option for atom tells it to emulate old-style behavior - only continuing when the window is closed.

To practice that, and check our config, run:

git config --global -e

All of these config options can be set at various levels - mainly, global and local specific - if you want to use different names, emails, or editors for different projects.

Creating a Git Project

Create a new directory. I'm going to call mine "GitPractice".

Initializing

Now that you have a directory, let's get set up! Open your directory in your terminal, and run:

git init

If you type ls, it doesn't look like much has changed. However, adding the -a flag - for ls -a - you can see that there is a new hidden directory, .git.

Congratulations! We are now in a git repository. You are ready to use git.

First, let's make an example file containing:

example.txt

Some Text

Once we have made a change we are happy with, let's save this point in time with a commit.

First, add the changes to the commit:

git add example.txt

Often, you want to add all your changes - you can do that from your root directory with

git add .

To create the commit, with a nice helpful message so we know what this point in time represents, run:

git commit -m "Edited A File For The First Time."

Projects may have different message styles, but the first line is always a brief summary of why you did what you did.

Now this is a point in time we can always go back to (unless we delete it) - a fixed point. It is a good idea to create a commit whenever you make a little thing and it is working - if you have ever felt like your changes were mistakes, it is good to be able to quickly go back in time. (This goes hand-in-hand with testing; if your commits pass your tests, and you write tests first, your commits are probably pretty good! Or you need more tests.)

We'll see how to travel the commits later in the class, but first, we have to move time forward.

Atom Git Integration

Atom was published by GitHub, and comes out of the box with git integration.

Open your example folder in Atom as a project directory, and hit ctrl-shift-9 (or in the menu, Packages->GitHub->Toggle Git Tab).

This is the git tab. It allows us to quickly do the steps we just did - adding ("staging") files, writing the commit message, and sending it.

Go back to your example.txt file, and add a new line with some text. Save the file and you'll see it show up as an Unstaged Change on the Git panel.

Click it to see a brief summary of the change, and if it looks good, double-click it to add it to the commit. You can double click it in Staged Changes to remove it from the commit.

Add in an example message. This interface makes it much easier to put in a long commit message - so make this commit message a few lines long.

When you are satisfied, Click Commit to master.

Viewing the Commit History

We can see the Commit History from within the project with:

git log

There are a ton of options for git log. We're going to cover them as they become useful, but I encourage you to read the --help if you want different information or for it to display differently!

For now, we can limit ourselves to our one-line summaries with:

git log --oneline

For a really quick overview.

We can filter the commits in any number of ways - or multiple at once. We can do it by number:

git log -n 1

By commit hash:

git log c2648c1 -n 1

By filename:

git log example.txt

By author:

git log --author="Clinton Bradford"

By date:

git log --after="2020-7-1" --before="2020-7-30"

Or by searching the commit message with a grep:

git log --grep="First"

But perhaps my favorite is searching by changes:

git log -S "Some Text"

To see the actual changes for a commit, use the -p patch flag.

Traversing The Commit History

Let's say you want to go back. Back to the past.

For now, you just want to grab something quick - something you changed that you need in the present.

We can take a quick, non-destructive trip to the past - just to observe - with:

git checkout c2648c1

Remember the rules for simple time rules - No changing the past, it might corrupt the present.

You can hop around times, but when you want to return to the main timeline in the present,

git checkout master

We'll see in a bit how you can make a branching timeline off of this point.

To rewind actions in the timeline, create anti-commits with:

git revert c2648c1

Or to do it relative to your current position,

git revert master~1

or a range of commits:

git revert master~5..master~2

If you are really, truly committed to destroying the timeline past this point:

git reset --hard c2648c1

Be warned - this is one of few ways to lose your committed data.

Assigning Blame

One of the things that will happen to you, and may already have, if you work on a project with others - or on a project over months - is confusion. Looking at a piece of code - a change or a function - you will find yourself asking:

Who did this? And why?

With git log -S you can search for specific text changes, but often you want to investigate a whole function or a whole file.

To see a summary of who changed what lines most recently in a file, try:

git blame example.txt

git blame gives us a line-by-line breakdown of the most recent change. It tells you (in order) the Commit ID, Author Name, Commit Timestamp, and then the line.

We can ask about specific line ranges:

git blame example.txt -L 0,1

OK, so now we can see the who and when.

If you are working on a project by yourself, then there is nobody to blame but yourself. If you just want the commit id and time, use the -s flag:

git blame example.txt -s

Now that you know what line (and commit hash), we can get the whole commit info with git log.

Branching Timelines

Quick Stashing

Say you have made some changes, and the code doesn't work. You need a working version now. However, you have done some progress - and you want to go back to this. What you want to do is stash this code somewhere for later use.

For that, you can use:

git stash

You can see what you stashed with:

git stash list

This can be unhelpful, so to see what we did in stash 0:

git stash show 0

And resume with:

git stash apply 0

To make it more helpful, let's leave a message when we stash:

git stash save "Practicing Stashing"

We can clear our stash with:

git stash clear

The stash is an easy-come, easy-go cubbyhole for code you are actively working on. As a general rule, never leave code in there long enough for you to come close to forgetting it - an hour is pushing it.

Proper Branching

So say we want to store our code somewhere other than our main line, but we want to keep track of it with our git tools - and not have it disappear next time we git stash clear.

Branches are splits in the timeline - you can think of them as the decision moment that creates parallel timelines - such as one in which you developed a new feature, and one in which you kept your code stable.

We can switch to another branch with:

git checkout -b branching_practice

If the branch name exists, it switches over to it - however you left it before. (you can even create divergent timelines in the past by passing it a commit hash.)

If not, it creates a new one at the point of the last commit.

If you want to create a new branch with work in progress, it's easy - just git stash the changes, and git apply them in the new branch!

You can switch back and forth between your branches with checkout whenever you are between commits (and when you're not, a quick stash is your friend.) The original branch is called master - after the "master record" in copymaking - so a quick git checkout master can take you right back.

We can see the branches - and which one we're on - with:

git branch -a

Merging Branches

Say you finish your feature - it's passing all tests - and you want it in your main branch.

Return to the target branch - in this case, master:

git checkout master

Now we can merge in some data.

git merge branching_practice

This (by default) creates an automatically-formatted commit, with a commit message "Merge branch 'branching_practice'".

If nobody has changed the overall file structure too much, or you have all worked on separate files, git is almost always going to figure out the correct merge for your files. If you edit the same line in multiple branches and then try a merge, this is likely.

In that case, it opens your editor - set in core.editor - to a file where you can resolve the conflicts.

Many modern editors - especially atom - have special behaviors to make resolving these conflicts reasonably easy. Atom will list the conflicts in the git pane, and allow you to select with a click or a keystroke which to choose.

When you have resolved the conflicts, then you can create a new merged commit!

Viewing Merges

It can be helpful to view graphs of the changes across branches.

This is possible with:

git log --oneline --graph --all

The --all flag allows us to see all the branches, which can help us see if it is safe to merge.

GitIgnores

Operating systems and programs often create a lot of temporary files. These files can change frequently, but aren't particularly useful - are often regenerated.

In addition, sometimes you are sharing a repository which contains some information you don't want to share.

We can hide things from git with a .gitignore file.

You can make one yourself, but I find that the automatically constructed ones from gitignore.io work well. You just select the things you use - Windows, LaTeX, and Python, for example - and it constructs you a file which will keep git away from things that don't matter.

Homework

There's no graded worksheet for today. However, for next week, please:

  • Make a GitHub account
  • Start talking with your peers about forming groups of 2-4 for a group git project next Tuesday, so we can learn git collaboration features.
Department of Mathematics, Purdue University
150 N. University Street, West Lafayette, IN 47907-2067
Phone: (765) 494-1901 - FAX: (765) 494-0548
Contact the Webmaster for technical and content concerns about this webpage.
Copyright© 2018, Purdue University, all rights reserved.
West Lafayette, IN 47907 USA, 765-494-4600
An equal access/equal opportunity university
Accessibility issues? Contact the Web Editor (webeditor@math.purdue.edu).