-
Notifications
You must be signed in to change notification settings - Fork 2
Git tutorial
This page will walk you through how to use git. The goal is to teach you everything you need to contribute to our team and follow are workflow. Nothing more, nothing less. I will be using git on the command line, as it is the most stable and feature complete way to use git, and you can copy and paste the commands to your shell :wink: . If you want to you can try using a graphical front end to git, but keep in mind that we have had issues with the one from Github in the past, and not all of git's features will be accessible.
Windows users can install git here: https://git-scm.com/download/win. This comes with both a gui program as well as a "git-bash" program. Use the git-bash program for this tutorial, but the gui program that comes with this is one of the most reliable, so if you feel more comfortable using the gui version, feel free to use it after this tutorial.
Mac and Linux users should have git already installed, but if not Linux users can use their package manager to install it, Mac users can download it here, or use homebrew.
More detailed documentation for git can be found at git-scm. If you feel inspired, please read through any chapters that seem relevant to our work flow. It is also recommended that you create your own repository for testing and practicing git and Github. This tutorial will explain only what you need to know for working with the USST, and is the only required reading (along with the page on our git workflow). The link above is totally optional, but encouraged.
Git is a version control system (VCS) that keeps track of the changes made to software. There are three key features about git that make it different from other VCS programs:
- Repositories are distributed
- Branching is easy
- Git tracks changes to your code, not files.
I'll explain what both these points mean soon. Basically, git is like an advanced backup system, but instead of just keeping a database of previous versions of your files, you manually take snapshots of your code each time you make changes (this can be as often as you need), you write descriptions of what each change was and why it was made, and multiple people can do this without messing up eachother's work. Unfortunately, if you don't know how to use git, it is easy to accidentally mess up your work, or even other people's work, hence why the lengthy tutorial. Stick with me, it's not that hard once you learn it 😄. Also, while git may have a bit of a reputation for being hard to learn, the command line version does frequently tell you what to do in many situations, so be sure to read everything it prints to the console.
For this tutorial, you will create your own repository. In the upper right hand corner, there is a '+' menu; Click it and select "New Repository". On the homepage for a newly created repository, Github gives instruction on how to work with your new repository. You can follow the instructions under the "...or create a new repository on the command line" heading, but that skips through half this tutorial, so I will explain the process in more detail. Open a command line prompt and navigate to a directory (folder) that you want to work from. Then enter:
git clone <url>
and replace with the URL of the repo that is under the "Quic setup" heading (it's not the same as the url of the website). Use HTTPS for now, but in the future SSH is more convenient. This will create a local version of the repo on your computer. Changes made on your computer will not be seen by other people until you "push " them to Github (more on that later).
When you run the git clone command, the output should be something like this:
Cloning into 'git-practice'...
warning: You appear to have cloned an empty repository.
After you have the repo cloned, move into that directory/folder by typing:
cd <name-of-repo>
This is the basic process of how changes are made with git:
- Make changes with text editor/IDE.
- Add changes or new files to git.
- Commit them to git.
- Repeat
The next few steps will explain these in detail
Before we make changes to the code, enter git status.
The shell should respond with the following:
On branch master
Initial commit
nothing to commit (create/copy files and use "git add" to track)
We'll talk about branches later, but note what the third line says. Lets create a new file. With the text editor of your choice, make a new file and write something in it.
Now enter git status. You should get something like:
On branch master
Initial commit
Untracked files:
(use "git add <file>..." to include in what will be committed)
test.txt
nothing added to commit but untracked files present (use "git add" to track)
git realises that you made a new file, but have not added it to be tracked by
git. As the git output states, we can use "git add" to tell git to track it.
Enter git add <name of file> replacing <name of file> with the name of the
file you created in the previous step.
Entering git status should now output:
On branch master
Initial commit
Changes to be committed:
(use "git rm --cached <file>..." to unstage)
new file: test.txt
Now git has your change in the "Staging area". As you work on changes, you use
the add comand to mark the changes you want in the next commit. I tend to
use a shortcut to combine the adding and commiting stages, but everytime you add
a new file, you must use git add. Read chapter 1.3 in the book linked at
the begining of the tutorial for a more accurate description of what is going on
here.
Now that you have a change added to the staging area, it is time to commit this change to git. Commiting is like taking a snapshot of the code in its current state. One important feature of commits are commit messages. When you commit your changes, you will attach a message telling everyone what the change was. We don't want to have to read every line of your code to find out what you did, so make sure you give a brief, high level summary of what you did and why. Perform a commit by entering the following:
git commit -m "Added <name of your file>"
As always, replace with the name of the file you created earlier. Git will print out something like:
[master (root-commit) aa0cf4c] Added test.txt
1 file changed, 1 insertion(+)
create mode 100644 test.txt
The first line states what branch the commit was on (master), and the hexidecimal number is the first bit of the hash code of the commit. Every commit has a unique hash code, and this is how we can identify commits when we want to roll back to them later on.
The second line tells us how many changes were made, as well as the type of changes.
I forget what the third line means, so it's not important. Read the official documentation if you're currious 😉. I think it's something to do with file permissions.
It is important to note that the -m option allows you to specify a message to you commit. Commit messages are important as they summarize the changes you made, letting other developers know what is going on without having to read the code line by line. If you forget the -m option, git will open up a text editor called vim for you to write a commit message. If you've never used vim, look up a basic tutorial (maybe I'll write one later if people want), and just know that you:
- Enter Insert mode by pressing the "i" key.
- Type your commit message.
- Exit Insert mode by pressing the "Escape" key.
- Type
:wqand hit Enter/return.
Enter git status again and you should get:
On branch master
Your branch is based on 'origin/master', but the upstream is gone.
(use "git branch --unset-upstream" to fixup)
nothing to commit, working tree clean
This looks a bit wierd, and to be honest I've never seen this until I worked on
revising this tutorial. Luckily, git tells us what to do: git branch --unset-upstream.
You can do this, or wait until we do a push, I found this warning doesn't prevent
things from working.
Make a modification to the file we made. It doesn't matter what it is.
When you're done, the command git diff
displays the changes that have been made since the last commit. For me it looks
like:
diff --git a/test.txt b/test.txt
index 9daeafb..f1cdc59 100644
--- a/test.txt
+++ b/test.txt
@@ -1 +1,2 @@
-test
+test all the things
+more test
The output is a bit cryptic, but basically the lines beggining with '-' are
deletions, and lines beggining with '+' are additions. I added more text to
first line, and the line "more test" was a whole new line that I added.
Running git status produces:
On branch master
Your branch is up-to-date with 'origin/master'.
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git checkout -- <file>..." to discard changes in working directory)
modified: test.txt
no changes added to commit (use "git add" and/or "git commit -a")
Git knows about the file "test.txt" already, but it noticed that it was
changed since the last commit, and gives us some options of what to do. We've
seen git add already, but the other useful command is git checkout -- <file>. As git describes, this will discard the changes made to since the last
commit. This is useful if you've messed things up and want to throw it away and start over.
You can also use git checkout . to throw away everything since the last commit.
Don't do this unless you really want to lose everything since the last commit, as there is no undo.
We could use git add to stage this for the next commit, but instead I will
use a shortcut. The command git commit -a adds all the changes (provided
the files containing them have previously been added to git), and performs a
commit. I will run the command git commit -am "Add more text."
to commit this change.
Now that we have made some changes, lets get this on Github. This step is easy.
Simply run git push. This will push the branch you're currently in
(master) to the remote server named "origin". Knowing about how remote repositories
work in git is not strictly necesarry for the day to day usage that you will be doing, so
I'll leave that to research on your own if you're interested. Here is my
output for git push:
Username for 'https://github.com': ottopasuuna
Password for 'https://ottopasuuna@github.com':
Counting objects: 3, done.
Writing objects: 100% (3/3), 275 bytes | 0 bytes/s, done.
Total 3 (delta 0), reused 0 (delta 0)
To github.com:ottopasuuna/git-practice.git
3289a61..d680205 master -> master
If you cloned the repo with HTTPS (which is the default), it will prompt you for your github username and password. You can look up how to configure git to remember your credentials for a period of time, or set up and use SSH keys to bypass passwords altogether. I use the latter method, but it's beyond the scope of this tutorial. Talk to me (Carl) if you want to do this and need help.
NOTE: I usually pull first before I push. I used the opposite order for this tutorial to make it flow better.
As other people push changes to the master branch on Github, you will have to manually
update your own local copy. This is also simple by running git pull.
This is actually performing two actions: git fetch and git merge orign/master. You've heard me mention branches several times in the
tutorial, and don't worry; The next section finally explains what they are and
how to use them. git fetch downloads the latest version of the remote
branch, and the git merge command merges the changes of the remote branch into
your local branch. This is explained below, so just ignore it for now. I bring
this up because sometimes you will update your local repo with git pull,
and have to deal with a "merge conflict". This happens when someone else changes
the same things that you did, but changed it differently. This is where git gets messy and hard
to use. If you thought the process of committing was annoying, merge conflicts
are worse! Luckily, we have a git workflow as described in this wiki page.
The goal of a workflow is to eliminate merge conflicts for the average developer,
and prevent them for project managers. In our workflow, you will be working and
pushing code from a different branch than the one you pull from, so merge
conflicts from git pull should be minimal, and you shouldn't need to worry
about it's dual action nature.
I included that wall of text to lead into branches, because while you can technically use git without using multiple branches, they are so useful that you'd be crazy not to.
If commits are snapshots representing different iterations of your code, branches are groups of commits that represent different version of your code base. Branches allow you to develop multiple versions of a software project in parallel. We can visulaize code as a stream of commits in a timeline:
A--B--C--D--E--> master
One timeline equals one branch. If we add another branch our code could look like this:
A--B--C--------G--> master
\
---D--E--F--> feature-one
In the above tree, the branch "feature-one" branched off of the master branch at commit B. "feature-one" has commits A and B, and adds D,E,and F, which are exclusive to "feature-one", and are not present in "master". Development continued in "master" with commits C and G, which are exclusive to "master", and are not present in "feature-one". We now have two versions of the software being developed in parallel. This is useful because while we are working on our feature branch, our code isn't being messed up by other peoples work. Once the developer of "feature-one" feels their work is ready to be included in master, they can merge "feature-one" into "master":
A--B--C--------G--H--> master
\ /
---D--E--F---
Here, commit H is a "merge commit". It doesn't represent new code changes, but represents the changes from "feature-one" being merged into the "master" branch. While conflicts can occur at the merge stage, this typically only happens once at the end of the feature development so we can deal with any issues all at once. If we used one branch that we frequently updated, we could run into conflicts multiple times during development which would distract us from our work and make the commit history messy. Further more, having multiple branches means that we can keep the master branch in a state that is always working, and only completed (hopefully) bug free code is added.
Lets create a branch: git checkout -b newbranch
git checkout moves you to another branch, and the -b flag creates the branch
if it doesn't already exist. Replace "newbranch" with what ever you want.
Git will output:
Switched to a new branch 'newbranch'
Now create a new file, add some text to it, and commit it. Make any other changes you feel like. Here is a slick command to view the history of the git repo along with it's output:
git log --oneline --decorate=short --graph --all
* 8ba2536 (HEAD -> newbranch) Added a period
* a1bae58 Added branch.txt
* d680205 (origin/master, master) Add more text
* 3289a61 Added test.txt
HEAD is where you currently are in the line of commits. This lists commits from most recent to least recent. As you can see the branch I created "newbranch" has two commits, and then comes the master branch. The master branch is two commits behind newbranch.
Lets go back to the master branch and make changes there: git checkout master.
Using git checkout without the -b flag switches you to an already existing
branch. You will notice that the file you created in the other branch is no
longer there. Make a modification to the file you made at the beginning of the
tutorial, and commit it. Lets use the same git log command used above:
git log --oneline --decorate=short --graph --all
* a10c75b (HEAD -> master) More text for branch testing
| * 8ba2536 (newbranch) Added a period
| * a1bae58 Added branch.txt
|/
* d680205 (origin/master) Add more text
* 3289a61 Added test.txt
This time, we can see how commits a1bae58 and 8ba2536 branched off
of master, while master kept going with a10c75b .
Before we go to the next section, push you changes in master to Github.
Lets push this branch to Github. Checkout the branch you made with git checkout newbranch, and run git push -u origin newbranch.
Username for 'https://github.com': ottopasuuna
Password for 'https://ottopasuuna@github.com':
Counting objects: 6, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (4/4), done.
Writing objects: 100% (6/6), 558 bytes | 0 bytes/s, done.
Total 6 (delta 0), reused 0 (delta 0)
To github.com:ottopasuuna/git-practice.git
* [new branch] newbranch -> newbranch
Branch newbranch set up to track remote branch newbranch from origin.
This pushes your branch to github, and sets your local branch to "track" the
remote one. From now on, if you want to push more updates to your branch you can simply run
git push from your branch.
If you go to the github page for your repo, you will see a yellow bar with the branch you just pushed. Press the "Compare and pull request" button. This will bring you to a page to create a pull request. There should be two drop down menus: base and compare. For "compare", select the branch you made, and for "base" select master (this should be the default). Base is the branch you want your changes to be merged into. Add a title describing what your branch does, and add more descriptions in the comment (These can be bogus for this tutorial repo). Click the button to submit the pull request.
There is not much point to submitting pull requests on your own repositories where the only contributer is you, but pull requests are useful for team settings. After submitting a pull request, your code can be reviewed by other members of the team, just in case you forgot something. When the rest of the team feels your branch is ready, one of the senior members will merge and close your pull request. We hope that this code review process provides a valuable learning experience for all of us, and increase the stability of our software significantly. Our more important repos have settings enforcing that branches are reviewed before they can be merged so you won't be able to force your changes into the codebase.
For this testing repo however, I will show you how to do your own merges.
Github has a single-button solution to merge pull requests, but we'll do this from the
command line to get a better understanding of what's going on.
Checkout the master branch and run git merge --no-ff newbranch.
This will take the changes from your
branch, and merge them into the master branch. The --no-ff option tells git to
not use a "fast-forward" strategy. You can read up on fast-forwarding in the
official documentation, but for now just know that without fast forwarding, git
will create another commit to mark the point at which code from the newbranch
branch got merged into the master branch. We want this because it gives valuable
history as to how code was added to the master branch. Here is the output I got:
Merge made by the 'recursive' strategy.
branch.txt | 1 +
1 file changed, 1 insertion(+)
create mode 100644 branch.txt
It also threw me in vim first, so if that happens to you, type either ZZ
or :wq to save and quit vim.
Here is the git log result:
* 3549da0 (HEAD -> master) Merge branch 'newbranch'
|\ 3 minutes ago Carl Hofmeister
| * 8ba2536 (origin/newbranch, newbranch) Added a period
| | 2 days ago Carl Hofmeister
| * a1bae58 Added branch.txt
| | 2 days ago Carl Hofmeister
* | a10c75b (origin/master) More text for branch testing
|/ 2 days ago Carl Hofmeister
* d680205 Add more text
| 2 days ago Carl Hofmeister
* 3289a61 Added test.txt
2 days ago Carl Hofmeister
This graph shows commits starting in master, branching off into newbranch, new changes made in master followed by new changes in newbranch, and finallly the changes in newbranch are merged into master. Explaining this in words is confusing, sorry. Push your local branches (master and newbranch in my case).
On the Github page, go to the "Insights" tab and select graphs. Then go to the "Network" tab. You should see your branch merging at the right-most end. The pull request you made earlier should also be closed now. Thats it, you've successfully created a branch and merged it in git! One common practice in git is to merge master into your feature branch before you submit a pull request. This updates your branch with the latest changes master, so if there are changes in master that break your branch or vice versa, you can fix these issues in you branch before they are merged into master.
Again, we did this manual merging process to learn more about branches, normally on the team you will not have to deal with merging yourself.
Read the official documentation to learn how to delete the branch you created, as it's safely merged with master and is not needed anymore. Now that you know how to use git, read our USST workflow.