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 commit
s (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.