Reading 29: Team Version Control
Software in 6.031
Objectives
Git workflow
You’ve been using Git for problem sets and in-class exercises for a while now. Most of the time, you haven’t had to coordinate with other people pushing and pulling to and from the same repository as you at the same time. For the group projects, that will change.
In this reading, prepare for some in-class Git exercises by reviewing what you know and brushing up on some commands. Now that you’re more comfortable with Git basics, it’s a good time to go back and review some of the resources from the beginning of the semester.
Review Inventing version control: one developer, multiple developers, and branches.
If you need to, review Learn the Git workflow from the Getting Started page.
Merging, merge conflicts, and pull before you start working will be important during the final project.
Viewing commit history
Review 2.3 Viewing the Commit History from Pro Git.
You don’t need to remember all the different command-line options presented in the book! Instead, learn what’s possible so you know what to search for when you need it.
Clone the example repo from Version Control:
https://github.com/6031/ex05-hello-git.git
Use log
commands to make sure you understand the history of the repo.
Graph of commits
Recall that the history recorded in a Git repository is a directed acyclic graph.
The history of any particular branch in the repo (such as the default main
branch) starts at some initial commit, and then its history may split apart and come back together, if multiple developers made changes in parallel (or if a single developer worked on two different machines without committing-pushing-pulling before the switch).
Here’s the output of git lol
for the example repository, which shows an ASCII-art graph:
* b0b54b3 (HEAD, origin/main, origin/HEAD, main) Greeting in Java * 3e62e60 Merge |\ | * 6400936 Greeting in Scheme * | 82e049e Greeting in Ruby |/ * 1255f4e Change the greeting * 41c4b8f Initial commit
And here is a diagram of the DAG:
In the ex05-hello-git
example repo, make sure you can explain where the history of main
splits apart, and where it comes back together.
Review Merging from the Version Control reading.
You should understand every step of the process, and how it relates to the result in the example repo.
Review the Getting Started section on merges, including merging and merge conflicts.
reading exercises
Alice and Bob both start with the same TypeScript file, hello.ts
:
export default function greet(name: string): void {
console.log(greeting() + ", " + name);
}
function greeting(): string {
return "Hello";
}
Alice changes
|
|
(missing explanation)
Same starting program:
export default function greet(name: string): void {
console.log(greeting() + ", " + name);
}
function greeting(): string {
return "Hello";
}
(missing explanation)
Same starting program:
export default function greet(name: string): void {
console.log(greeting() + ", " + name);
}
function greeting(): string {
return "Hello";
}
Alice changes greet(..)
to return instead of print:
export default function greet(name: string): string {
return greeting() + ", " + name;
}
Bob creates a new file, main.ts
:
import greet from './hello';
greet("Eve");
(missing explanation)
Disaster recovery
As recommended in Version Control, a GUI program can make it easier to navigate your repo and find the data you need to recover from a mistake. The built-in version control UI in VS Code is also useful for this (for example, the TIMELINE view in the Explorer pane allows you to quickly see the history of a file), but we continue to recommend the command line for running Git commands.
In 6.031, you can use the web interface to review commits that were pushed to github.mit.edu.
On the summary page for a repo you see the files and folders in the root of the project on the current
main
branch.Click on a commit message or SHA to see the changes introduced by that commit.
Then click the Browse Files button on the far right to browse the entire project at that old version.
While you browse, notice the Tree: …SHA… button on the left, which reminds you that you are looking at an old version of your repo. Normally, when you are browsing the latest version of your repo, this button says Branch: main instead.
git clone
Rather than having to recover from a catastrophic loss of work, it would be much better to prevent catastrophe.
If you add, commit, and push your work regularly, that work is safely in the remote repo and you can retrieve it with git clone
into a fresh folder.
The next section has advice about pushing commits when you work on a team.
git revert
If you want to undo an entire commit: find its ID and use git revert
.
For example, to revert the addition of a Ruby greeting program in ex05-hello-git
repo:
- Use
git lol
to see the current commit graph. - And use
ls
to see the current files. - Revert the “Greeting in Ruby” commit…
- … and Git asks for a commit message, providing a reasonable default.
- Save the commit message, and Git adds a new commit to the project history.
This new commit undoes the change made by the old commit.
It does not remove the old commit from the history of the project. - The new commit is now the
HEAD
, andls
no longer showshello.rb
.
$ git lol * b0b54b3 (HEAD, origin/main, origin/HEAD, main) Greeting in Java * 3e62e60 Merge |\ | * 6400936 Greeting in Scheme * | 82e049e Greeting in Ruby |/ * 1255f4e Change the greeting * 41c4b8f Initial commit
git revert
doesn’t rewind the whole repo back to an old version, it reverses the effect of an old commit by creating a new commit at the HEAD
.
git show and git checkout
Most of the time when we make a mistake, we don’t want to revert an entire commit: for example, if the commit improved three different functions, and only one of those improvements was misguided, we’d like to undo just that one.
Often, the easiest way to find the working code you need is to ask Git to show you an old version. Hover or tap on each step in these examples:
git show
revision
will give you the diff for a commit.
Removed text is in red, added is in green.- If you
git show
a commit with a diff that doesn’t fit on one screen, it will be displayed one screenful at a time (originally using themore
command, but now more likely its successorless
). You’ll know this is happening if you see a:
prompt at the bottom of the terminal… - … which means you can press
space
to page down the diff, or use the arrow keys to go line-by-line. (You can pressh
for more help with usingless
.) Pressq
to quitless
and get back to the command prompt.
$ git show 1255f4e commit 1255f4e4a5836501c022deb337fda3f8800b02e4 Author: Max Goldman <maxg@mit.edu> Date: Mon Sep 14 14:58:40 2015 -0400 Change the greeting diff --git a/hello.txt b/hello.txt index c1106ab..3462165 100644 --- a/hello.txt +++ b/hello.txt @@ -1 +1 @@ -Hello, version control! +Hello again, version control!
git checkout
revision
--
path/to/file
will replace a file in your working directory with an old version. Both the--
(with a space after it) and the path in that command are important, don’t forget them!- This command does not make a commit yet, so our history has not changed…
- … but it does stage the changes for commit!
- We can look at the staged changes: we’re reverting
hello.txt
back to its old contents by removing “again”.
$ git checkout 41c4b8f -- hello.txt
Unlike git revert
, the revision in this commit is the specific revision we want: the file will look as it did including the changes in that revision, and none afterwards.
You do not want to use the command git checkout
revision
without a --
path/to/file
.
If you do, your HEAD will now point to that old commit, and you will no longer be on branch main
.
This is called “detached HEAD,” and nobody wants that.
If a command warns you that your HEAD is detached, seek assistance before you continue, to avoid losing work. If you have been working in a detached HEAD situation without realizing it, the work you did can almost always be recovered, but it has to be done carefully. Get help!
Using version control in a team
Every team develops its own standards for version control, and the size of the team and the project they’re working on is a major factor. Here are some guidelines for a small-scope team project of the kind you will undertake in 6.031:
Communicate. Tell your teammates what you’re going to work on. Tell them that you’re working on it. And tell them that you worked on it. Communication is the best way to avoid wasted time and effort cleaning up broken code.
Write specs. Necessary for the things we care about in 6.031, and part of good communication.
Write tests. Don’t wait for a giant pile of code to accumulate before you try to test it. Avoid having one person write tests while another person writes implementation (unless the implementation is a prototype you plan to throw away). Write tests first to make sure you agree on the specs. Everyone should take responsibility for the correctness of their code.
Run the tests. Tests can’t help you if you don’t run them. Run them before you start working, run them again before you commit.
Automate. You’ve already automated your tests with a tool like Mocha, but now you want to automate running those tests whenever the project changes. For 6.031 group projects, we provide Didit as a way to automatically run your tests every time a team member pushes to github.mit.edu. This also removes “it worked on my machine” from the equation: either it works in the automated build, or it needs to be fixed.
Review what you commit. Use
git diff --staged
or a GUI program to see what you’re about to commit. Run the tests. Don’t usecommit -a
, that’s a great way to fill your repo with debugging print statements and other stuff you didn’t mean to commit. Don’t annoy your teammates by committing code that doesn’t compile, spews debug output, isn’t actually used, etc.Pull before you start working. Otherwise, you probably don’t have the latest version as your starting point — you’re editing an old version of the code! You’re guaranteed to have to merge your changes later, and you’re in danger of having to waste time resolving a merge conflict.
Sync up. At the end of a day or at the end of a work session, make sure everyone has pushed and pulled all the changes, you’re all at the same commit, and everyone is satisfied with the state of the project.
We don’t recommend using features like branching or rebasing for 6.031-sized projects. Working on separate branches is extremely important when the size of the project, the length of time, or the number of people is much larger than the small, 1–2-week, 3-person final project in this class. For 6.031, focus on clear communication and frequent syncing-up of the whole team.