Lab 2: Version Control with Git
Table of contents
- Lab Participation
- Prerequisite: GitHub account
- Setting up SSH keys
- Collaborative GitHub Exercise
- Optional Feedback Form
Lab Participation
For this lab (and for this lab only unless otherwise stated in future labs), instead of having explicit instructions to checkoff your progress with a tutor, we will be conducting checkoffs at certain times during the lab session. This means that you don’t need to worry about calling over a tutor when you reach a checkpoint. Also do not worry about having made “enough” progress by the time we come over to conduct checkoffs—lab sessions are intended for you to work at your own pace!
In the best interest of your eyes (and our eyes), please change the font size in your terminal to something you’re comfortable with. Try
Ctrl
+Shift
+=
to increase andCtrl
+-
to decrease the font size (orCmd
+=
andCmd
+-
respectively for Macs). The exact shortcut may be different for your machine.My insurance does not cover another pair of glasses this year!
Prerequisite: GitHub account
Before this lab, you should have already created a GitHub account with your school email address. This is pretty necessary to do anything with GitHub, and will be essential in future classes. If you haven’t created one, please do so before proceeding! Some instructions to help you create and set up an account are available here (only those up to and including “Configuring two-factor authentication” are relevant).
Setting up SSH keys
Just like you “SSH” into our ieng6 account, you can also use SSH to connect to your GitHub account. SSH is a method of connecting and authenticating (checking that you’re the right person) to a remote server, which is generally considered to be more secure than password-based methods. Using SSH ensures that no one else should be able to access your GitHub account and… do your PAs for you?
How SSH keys work is beyond the scope of this class, but you may learn how they work eventually in CSE 107/127. For now, we just need to understand that SSH keys come in pairs: a public key and a private key. You can think of the private key as a physical key, and the public key is actually better thought of as a physical padlock. The key (private key) and padlock (public key) are created together, and one opens the other. To secure your account, you:
- create a public/private key pair,
- keep the private key in your ieng6 account,
- and give the public key to GitHub.
GitHub will only allow the holder of the private key that matches the public key to access your account.
The following setup process is a bit of a necessary detour from the main focus of this lab, but if all goes well you will never need to do this setup again for this class. Please feel free to ask tutors for help with this process!
Creating your SSH keys
Make sure to ssh
into your ieng6 account! We’ll be using ieng6 for this and future labs, unless otherwise stated.
Within your ieng6 account, use the following command to generate a new key pair, replacing your_email@ucsd.edu
with your GitHub email address:
$ ssh-keygen -t rsa -b 4096 -C "your_email@ucsd.edu"
You’ll be prompted to “Enter a file in which to save the key”. Press Enter to accept the default location. You’ll then be prompted to enter a passphrase, which isn’t really necessary. Press Enter twice to continue without setting a passphrase. Though if you really want to set a passphrase, refer to GitHub docs on passphrases.
Adding your SSH key to GitHub
By default, the public SSH key is saved to a file at ~/.ssh/id_rsa.pub
.
Instead of typing out the whole filename, you can type out some prefix of the name (e.g.
~/.ssh/id
), and press Tab to autocomplete the name. In this case, tab complete won’t complete the full filename, since the private key happens to be namedid_rsa
.Please be too lazy to type out entire filenames and use tab complete instead!
View the contents of this file (using vim or cat
), then copy the contents of the public key file to your clipboard (or if you can’t copy, you can figure out how to scp
the file to your local machine).
On the GitHub website, click your profile picture in the top right to open a menu, and click on “Settings”. On the left, open “SSH and GPG keys”, then click on “New SSH key”. Populate the fields as follows:
- Title: “ieng6”
- The title doesn’t affect the functionality of the key, it’s just a note for you that this key is tied to your ieng6 account.
- Key type: “Authentication key”
- Key: Paste the contents of the public key file here.
Click “Add SSH key”. You may need to confirm access to your account on GitHub at this point.
Testing your SSH key
Finally, test your connection to GitHub with the command:
$ ssh -T git@github.com
If this is your first time connecting to GitHub, you might get a warning about the “authenticity of host can’t be established”. This is a warning for you to make sure that you’re connecting to the right thing. For the purposes of this lab, we assume that GitHub didn’t suddenly get hacked, so you can safely respond with “yes”. But if you’re really paranoid, you can check GitHub’s public key fingerprint here.
After a successful connection, it should output Hi <your-username>! You've successfully authenticated, but GitHub does not provide shell access
.
That should conclude the GitHub SSH key setup process. If the others in your group are struggling with this, please help them! The next step will require everyone in your group to be able to successfully connect to GitHub.
Collaborative GitHub Exercise
Before this lab even starts, you should have been put into pairs. (If not, please remind the TA to do so!)
Have one member of the group follow this link (https://classroom.github.com/a/KxifxlLC) to accept the assignment for this lab on GitHub Classroom. We’ll be using GitHub Classroom for submitting PAs as well. Create a team for your group (give it any name you want!), then the other group members should add themselves to the team by following the link themselves.
Before you start using git hands-on, we should establish some vocabulary and concepts about git and GitHub.
- git is a command line program that enables version control, i.e. the ability to switch between different versions of your code, including switching to past versions.
- a repository (or repo) is a folder, usually containing code, which is configured to work with git to manage different versions. When you accepted the assignment on GitHub Classroom, it should have automatically created an empty repo for your group.
- GitHub is a website which hosts repos, allowing you to save your work on a server, and can allow people to contribute to others’ repos.
If there is any software which most (if not all) programmers use, it’s git and its integration with GitHub. That’s why knowing how to use git and having a GitHub account are essential for programmers.
git organizes your code files in a particular way to give you flexibility in how you manage different versions of your repo, which we’ll visualize below. Note that git does not actually create distinct “areas” in your repo directory and move files between them, but we may refer to them analogously as if they were physical areas that files move in and out of.
Source: https://dev.to/mollynem/git-github–workflow-fundamentals-5496
- We refer to files stored on your device as local, and files stored on GitHub (in a server far away) as remote.
- technically, files in your ieng6 account are remote relative to your machine, but we will consider them local relative to the GitHub servers.
- The working directory refers to all of the files and folders in a specific version of your repo.
- The local repo contains a history of committed files (or just commits).
- You can think of a commit as a set of modified files which are “packaged” together and given a name. This method lets us easily group and track the history of changes for our files.
- The staging area temporarily contains files which will go into the next commit.
- This exists to let you neatly organize what should be committed. For example, if your workflow involves making changes to lots of files, but you want to separate them into different commmits, you can separately stage and commit groups of changes.
- The remote repo is a repo that is hosted on GitHub. After commits are made to the local repo, you can push them to the remote repo to save your work on a server.
git contains many commands which let you manipulate and move files within this system of organization. We’ll practice some essential commands in the upcoming exercises.
Clone the repo
First, find the SSH url for your group’s repo. When you are on the page of an empty repo, it should be displayed on this page. It starts with “git@github.com” and ends in “.git”. Make sure you get the SSH url and not the HTTPS url! Each group member should clone the repo into their respective cse29
folder, with the following command while in the cse29
folder, replacing <ssh-url>
(including the angle brackets <
and >
) with the actual SSH url:
$ git clone <ssh-url>
This means that a new folder will be created in cse29
with the remote repo name and set up as a local repo. Change into this repo directory.
Did you change into the repo directory? Are you sure? Are you really sure?
If you accidentally cloned the repo into the wrong place, you can move the repo directory to the right place with the mv
command:
$ mv <repo-name> ~/cse29/<repo-name>
where <repo-name>
is the name of your repo. This command is also used to move and rename files.
Introduce yourselves
In this activity, we’ll guide you through some exercises to practice using git commands. Designate one of you to be student A and the other to be student B. This naming just helps us give detailed instructions to each of you below.
First, student A will create a text file called intro.txt
using vim. Then in this file, write 1-2 sentences introducing yourself: your name, pronouns, year, major, and college. After writing the file, you can use git status
to see the state of the local repo with a new file. git status
is a generally useful command to use to look at the state of the repo at any time; expect to be using this often! Its output will look something like this:
On branch main
No commits yet
Untracked files:
(use "git add <file>..." to include in what will be committed)
intro.txt
nothing added to commit but untracked files present (use "git add" to track)
“Untracked” is a status for files that have not been committed to the local repo previously, indicating that git is not tracking the history of changes in this file. After a file becomes tracked, and further changes are made to the file, the file is referred to as “modified”. git add
is used to both track untracked files and stage modified files:
$ git add intro.txt
This puts the intro.txt
file into the staging area, indicating to git that it will go into the next commit. Check using git status
to see that the file is now staged. Next, use
$ git commit -m "insert-commit-message"
to commit all files in the staging area to the local repo. Replace insert-commit-message
with a brief description of what changed in this commit.
The -m
option allows the user to directly write a commit message as a string in the command. If you were to run git commit
without this option, it will open vim and instruct you to write a commit message there. Generally, commit messages are concise enough that using a text editor to write them out is unnecessary.
Writing a meaningful commit message is quite useful, and usually expected in a professional setting. The commit message serves as a way for others (and yourself, in the future, looking back) to easily understand what changes were made in a commit without having to read through the changes manually.
Besides -m
, there are other common options that programmers use with git commit
:
git commit -a
: automatically stages all modified files in the working directory, but not any untracked files.git commit -am
: has the behavior of both-a
and-m
options.
Again, you can use git status
to check that intro.txt
is no longer staged after being committed. Finally, push these changes from the local repo to the remote repo so that your partner can access your changes:
$ git push
This will probably produce an error from git, telling you to use a different command that looks like:
$ git push -u origin main
This happens the first time you try to push from a local repo, because git needs to know exactly where the changes should go in GitHub. For this first push, run git push -u origin main
to set this up. origin
refers to the remote repo that this local repo was cloned from, and main
is the default version of the repo. In subsequent pushes in this repo, you can just use git push
.
Next, student B will pull these changes with
git pull
This, as the name suggests, does the opposite of a push: instead of sending files from the local repo to the remote repo, it brings files from the remote repo to the local repo. When you pull, the changes are automatically reflected in the working directory.
Then, in the same file, student B writes 1-2 sentences to introduce themselves. Push these changes to GitHub.
It’s implied here that to “push these changes” must involve staging and committing the changes before pushing. We omit saying these steps to be concise. Future instructions in this lab, in this class, and maybe in other programming resources may make this same implication.
We’ll repeat this cycle just one more time. One after the other, each of you should pull changes, write a bit more about yourself, then push changes. For example, you could write about your hobbies, what other classes you’re taking, etc.
You’ll be using the same or similar commands repeatedly in this activity. Instead of retyping the whole command, Unix allows you to recall previously used commands using the up and down arrow keys. Press up to browse through earlier commands, and press down to browse through more recent commands.
By now you’ll have made at least four commits in this repo, two commits per person. To view the history of commits in this repo, you can use git log
, which also outputs some other information about each commit: their identifier, who made the commit, when it was committed, etc. Use the arrow keys to scroll up and down the log, and press q
to exit.
If you have a lot of commits, the output from git log
can take up the entire terminal screen, and it doesn’t go away after you exit. Use the clear
command to clear up your terminal and make it nicer to look at. Sometimes clear
doesn’t really delete the terminal history. It clears your terminal much like how I clear my room: hiding things out of sight. If you scroll up, the terminal history may still be there!
Make sure the both of you have the same changes in your local repos before continuing!
A bit of parallel development
While it’s a funny idea, GitHub is not in fact used by programmers to talk to other programmers via a really awkward and slow “messaging” system. Instead, GitHub can be used to distribute and combine programming work.
First, student B creates a file called triangle.c
, which contains:
#include <stdio.h>
void upright_triangle(int num) {
}
void inverted_triangle(int num) {
}
int main() {
upright_triangle(3);
inverted_triangle(3);
return 0;
}
This program is supposed to output an upright triangle and an inverted triangle, both made of asterisks (*), but it’s not fully implemented yet. You can try compiling and running this program to confirm that it does, in fact, not do anything. Push this file to the repo, and student A pulls these changes.
Next, each of you will “implement” these functions independently. (By “implement”, we mean “for the sake of time and focus, just type out the given implementations below in the right places pls ty ty”). Student A should “implement” upright_triangle
in their local repo, and student B should “implement” inverted_triangle
in theirs. This means that you’ll be making different changes to the same file at the same time! Try compiling and running this program with just your local changes to see that your changes work, but the other function still doesn’t work.
void upright_triangle(int num) {
int i, j;
for (i = 1; i <= num; i++) {
for (j = 1; j <= i; j++) {
printf("* ");
}
printf("\n");
}
}
void inverted_triangle(int num) {
int i, j;
for (i = num; i >= 1; i--) {
for (j = 1; j <= i; j++) {
printf("* ");
}
printf("\n");
}
}
Once you’ve each “implemented” these functions, we must push to and pull from the remote repo to get the other’s implementation. First, each of you commit your changes to your local repo. GitHub does not allow you to pull changes if your local repo has uncommitted changes. Then, either one of you can push their changes first; this will go through without issue. When the other student tries to push (and here, actually try to push to see what happens!), you’ll get some kind of error, saying that you have to pull the new changes from the remote first.
Before you try pulling though, we just need to quickly specify to git how to combine these changes. Run
$ git config pull.rebase false
This tells git to use a strategy that allows us to merge changes in a way that works for our case, where we’ve made changes to two different functions separately. Running git pull
at this point will open vim for you to edit the commit message of a new commit which combines these two changes. Feel free to leave the commit message as is by exiting vim.
After pulling your partner’s changes, git should automatically be able to merge the changes in the same file. This is not always the case, but here we have structured the activity such that it won’t happen (hopefully).
Push the merged file back to GitHub, so that your partner can pull your changes into their local repo. Compile and run this program to see that both parts work!
Rewinding Time
So far, we’ve covered commands you can use to do things with git: staging changes, committing changes, and pushing changes. But inevitably, you will make a mistake somewhere, and you’ll need to know how to undo those changes. Each of you can follow the instructions below in your local repos independently, up until the part about pushing.
First, start by making a mistake. Go into triangle.c
and just completely mess it up.
If you were to “accidentally” use git add
to stage these changes to triangle.c
, you unstage these changes with the command:
git restore --staged triangle.c
Check using git status
to confirm that the changes are properly unstaged! Notice that this doesn’t actually remove the changes in triangle.c
; it just removes the file from the staging area. You could use this if you want to make a commit containing changes in another file, but accidentally staged an unrelated, modified file.
To actually restore the state of the file to what it was before this change (i.e. to its state in the last commit), we also use the git restore
command, but we omit the --staged
option:
git restore triangle.c
This will remove all changes made to the file since the last commit.
Go back into triangle.c
and make some more mistakes, then “accidentally” commit these changes. To revert this commit, you can use:
git revert HEAD
Instead of deleting the previous commit, this command actually creates a new commit which does the opposite of the previous commit. Check with git log
to see that this happened.
Since git revert
creates a new commit, you can also revert changes on the remote repo by pushing this new commit. You can try this out with one person’s local repo: push a mistake to GitHub, git revert
in your local repo, and then push the reverting commit to GitHub. Then when your partner pulls these changes, they will get two commits that cancel out, leaving the repo in the same state.
Optional Feedback Form
If you’d like to give feedback on how labs are conducted and how they can be improved, please feel free to submit any comments in this anonymous form.