Version Control
Version control
- Use version control software on your own code to keep track of changes that you make over time (initializing and adding changes to a repository).
- Use version control software on your own code to make changes and quickly be able to revert to a working state.
-
Identify files that should not be version controlled and make sure they
aren’t included in a repository’s history
(
.gitignore
).
Our bigger topic this week is looking at version control software.
Version control is something that’s been around for a long time and
different people have different opinions about how to do version
control, and which software should be used for version control, but this
week we’re going to focus on a specific version control tool: git
.
Version control software helps you keep track of the changes that you’ve made to your own code or any kind of file over time. Version control isn’t entirely automatic, you have to tell the software which files to track and when it should track changes.
Initializing and adding changes to a repository
We have to tell our version control software where the files are that we want it to help us manage. Most of the time this is a single folder where you keep your source code for a specific assignment or project. We’re going to call this location where files are a “repository”.
We have to “initialize” the repository. Initializing the repository has the version control software create some new files and folders that it uses to help keep track of the changes we make to our files.
Let’s start by making a new directory (somewhere appropriate in your folder structure).
mkdir my-project
cd my-project
Once in the directory that contains the files (or will
contain the files) that we want to track, we can ask our version control
software to initialize the folder as a repository. With Git, this is a
special command called init
.
git init -b main ##### NOTE: -b only works in git 2.28 or newer
Git should then tell you that it’s “Initialized an empty Git repository” in this directory!
When you initialized the repository, Git told you that it initialized
the repository in your directory and then there was a folder at the end
named .git/
.
Try running ls
in your directory. Can you see that
folder?
…
No. You can’t see that folder. Where is it?
You can see that folder if you ask ls
to list
all files:
[you@bird my-project]> ls -a
./ ../ .git/
And look at that, there are those two special directories that we’ve
seen before: .
the current directory and ..
the parent directory.
Now that we’ve got an empty repository, we can start adding files to it. Version control software generally requires that you tell it specifically which files you want it to keep track of. That means that Git isn’t going to keep track of files that just happen to be in the same directory as the repository, you have to tell Git to actually keep track of those files.
Create some new files in the repository:
Make a new plain text file with some text in it using a command-line text editor like
vim
. Name the fileREADME.md
.Transfer a file from your computer to Aviary into this directory.
Copy the
hello
folder from wherever it is to this directory.You can copy files from one place to another on the same computer using the
cp
command. You can copy entire folders from one place to another using the-r
option for recursive (just likescp
!).To copy the
hello
folder here, you can usecp
from this directory:cp -r ../hello . # if hello is in the parent directory
Even though we put these files into this directory with the repository, Git still doesn’t know anything about them. We have to add these files to the repository in two steps:
- Staging the changes by adding them with the
add
subcommand, and - Committing the changes using the
commit
subcommand.
Adding files to the repository
Adding files to the repository is straightforward: you use
git add
and then tell Git which files should be added to
the repository.
Files that you add with git add
are “staged” — Git knows
that the files exist and that you want it to track changes to these
files, but Git isn’t actually ready to track the changes.
You can add files and changes to the repository one at a time using
git add
and including the name of the file that you want to
add:
[you@bird my-project]> git add README.md
You can also add entire folders to the repository:
[you@bird my-project]> git add hello
You can also add everything in the current working directory to the repository:
[you@bird my-project]> git add .
Git doesn’t tell you anything when you add files or changes to be
staged, but you can ask Git to tell you about the current state it’s in
by using the status
subcommand.
You can find out which files or changes are staged to be committed
using git status
:
[you@bird my-project]> git status
On branch main
No commits yet
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: README.md
Git helpfully tells you how to unstage a file if you accidentally added a file or change you didn’t want to add to the repository.
Committing changes to the repository
Even though Git knows about the files or changes that you’ve staged, you haven’t told git that you really want it to track changes to those files.
A “commit” is what Git really manages: what a specific set of files looked like at a specific point in time.
When you make a commit to a repository, you’re telling your version control software that the changes that you’ve made to these files are important and it should keep track of that. Some important times where you would want to keep track of things:
- The first time you create a repository and you’re adding some files to it.
- When the thing you’re working on works.
- When you’ve finished working on a small part of a bigger project.
When you make a commit to a repository, you have to include a “commit message”: a brief, human-readable message describing what you’re committing to the repository.
We can commit staged changes to our repository using the
commit
subcommand.
Unfortunately, we’ve got to do some one-time set up before we can make our first commit. It’s OK If you skip this step (you can fix it later), but things will flow more smoothly if you do it now.
Before we can make a commit, we need to tell git
who we
are. We can configure git
and tell it who we are with the
git config
command:
git config --global user.name "Your name"
git config --global user.email you@myumanitoba.ca # you should use your own
# e-mail address here.
You only need to do this once per physical computer that you’re using
git
on. In other words: if you’re using two computers,
you’ll need to run this set up step on each computer, but you only need
to do it once.
We can commit staged changes to a repository using
git commit
and either writing a commit message in the
command itself, or writing a longer commit message in your text
editor.
[you@bird my-project]> git commit -m "Initial commit" # commit with a message inline
[main (root-commit) acff39c] Initial commit
1 file changed, 10 insertions(+), 0 deletions(-)
create mode 100644 README.md
Your commit is going to look different from mine because you added more files than me to your commit and you used a different commit message.
You can write a longer commit message by leaving out the
-m
part, and this will ask Git to open up your text editor
where you can write a longer message.
[you@bird my-project]> git commit # opens your text editor to write a message
After you commit your changes to the repository, everything still looks pretty normal. Your files are all still there. Great 🎉!
You can ask Git to tell you what its status is again with
git status
, but this time it’s just going to say that there
is nothing to commit
[you@bird my-project]> git status
On branch main
nothing to commit, working tree clean
Git commit messages are important metadata (data about your data) that help you (or others) better understand the changes that you’ve made to your files over time. They’re a lot like writing comments in code in the sense that they shouldn’t describe the files that you changed or even describe how you changed the files, but should describe why you changed the files and what the ultimate side-effect is (“I fixed this bug!”).
Lots of people have opinions on Git commit messages:
- Tim Pope writes about them in A Note About Git Commit Messages.
- Chris Beams writes about them in How to Write a Git Commit Message.
- Randall Munroe visualizes them in Git Commit.
Adding changes and committing them to your repository is something that you should be doing not just when you initialize the repository, but frequently as you’re doing your work. You should add any changes to your repository each time you accomplish something (a new method is working, a bug has been fixed, a feature is complete).
Reverting changes
Initializing our repository and adding and committing files and changes to our repository is great, but just adding stuff to the repository isn’t exactly useful.
One important use for version control is being able to go back in time to changes that were previously committed to the repository.
Let’s make some changes to our file. We don’t actually care what the changes are because we’re making these changes to be able to see how to undo changes.
We can revert uncommitted changes to a file using the
restore
subcommand in Git.
Mess up your README.md
. Go ahead. Have fun!
Here are some things you can do:
Open it in your text editor and start typing.
“Accidentally” copy a file and replace
README.md
with that filecp hello/compiled/hello.c README.md
Remove your
README.md
[you@bird my-project]> rm README.md rm: remove regular file 'README.md'? y # LIVE DANGEROUSLY [you@bird my-project]>
Once you’ve made changes to your file, you can ask Git to tell you
about the status of the file (with git status
). Depending
on what you did, Git will tell you different things, but it will
importantly notice that something has changed. Git will also
helpfully give you a hint about how to get your files back to the state
they were in before.
Now revert the changes you made to your README.md
with
the restore
subcommand
git restore README.md
Now open up your README.md
again, and it’s back to the
way it was before you made the changes!
Files that shouldn’t be version controlled
Being able to keep track of changes to our important files is great, but there are some files that will have changes that don’t care about.
Some examples of files that will change as you change your own files:
- The
.class
files that get generated when you compile Java programs. - The
.docx
and.pdf
files that you generate when convering from Markdown to those formats. - Output files that get generated when you run certain programs.
The common identifying property of all of these files is that they
are files that get generated or created by something
else from the file that you want to keep track of (.class
files are generated from the .java
files you care about;
.docx
in this case is generated from the .md
files that you care about).
We don’t usually want to include these files in a version control repository for a couple of reasons:
- We can really quickly re-generate them from the files that we’re
working from (it’s pretty easy to run
pandoc
again!). - Sometimes these files can be really big in terms of file size.
- They are extra changes in our version control history that we don’t care about.
We can tell Git to ignore certain files when they change by created a
special file named .gitignore
in
our repository.
Use your text editor to create a new file named
.gitignore
in the same folder as your repository.
Each line in a .gitignore
file is either a comment
(lines beginning with #
) or describes specific filenames or
“patterns” of filenames to be ignored.
Here’s an example of what a .gitignore
file might look
like:
# Ignore README.docx (a specific filename)
README.docx
# Ignore all .class files (a "pattern", any file that ends with .class)
*.class
# Ignore the entire build directory
build/
The .gitignore
file is a file you want Git to
keep track of, so you need to add this file to your repository (use
git add
).
Just like the .git
folder, the .gitignore
file is “hidden”, it doesn’t show up when you try to look at the
contents of a directory using commands like ls
. This is
true in general: any file that begins with a period (.
) is
considered to be “hidden” on Linux and macOS, but you can see those
files by telling a command like ls
to list everything
(ls -a
).
Further reading
This document gets you started with version control and Git, but you’re definitely going to run into situations where you want to do things that are more complex than what we’ve seen here, or you’re going to make a mistake and need to fix it.
You can find lots of good information online about Git and version control, but here are some resources that are particularly good or helpful:
Roger Dudler’s git - the simple guide does a great job of quickly summarizing the tasks you might need to do when doing version control with Git.
Seth Robertson’s On undoing, fixing, or removing commits in git is a “choose your own adventure”-style guide on … well, undoing, fixing, or removing commits in Git! Maybe you accidentally committed a file that you don’t want to be in your repository, or maybe you accidentally forgot to include a file in a commit that should have been included in that commit. This will guide you through getting into the state that you want to be in.
If you’re OK with some profane language in English, Katie Sylor-Miller’s Oh Sh*t, git!?! is also an excellent source of solutions to “Please help me!” moments that you will definitely encounter when you’re using
git
.Yeah. I know you can swear on the internet. But I have children. And while one of my children can read, I don’t think that he’ll be able to figure out what sh*t means, and I’m OK with that.