This howto demonstrates how to set up a repository consisting of several, possibly related, examples for distribution to students. Each example is on its own branch, and there is typically an ancestor relationship between two related example branches.
When teaching programming courses one often gives small codes snippets in class for the students to play with and, possibly, use in their own programs. Sometimes one example builds on a previous example. Other times an example is unrelated to previous examples. Git branches can be used to show the relationships among examples, make it easy to switch from one example to another, and to experiment with examples while still allowing new examples to be added easily.
To simplify the discussion, assume we are teaching an introductory C programming course and would like to give some simple sample programs to give the students something to build on. These programs are deliberately minimal as the goal is to demonstrate git usage.
The gits addexample course repo command can be used to create an examples repository for a course. Example:
$ gits addexample csci100 Examples
Students in CSCI 100 will be allowed to clone the Examples repository locally, but not fork their own server-side repository. This can be changed later if need be.
Now the instructor can clone this repository into a local directory:
$ git clone csci:csci100/Examples
which creates a new directory called Examples in the current working directory. Change directory to it now:
$ cd Examples
The approach taken here is to use the "master" branch as the starting point for all the examples, but does not contain an example itself. To start a "new" example, unrelated to previous examples, checkout out a new branch with master as the starting point.
For the running example we want to make sure we never commit any binary files. The default GIT Submit repository is already configured to ignore .o files, but there is no easy way to preconfigure a generic list of binary executable filenames. The approach taken here is to name all source programs t.c and the corresponding executable t. Thus, we need to add t to the list of files we want git to ignore:
$ echo "t" >> .gitignore $ git add .gitignore $ git commit -m "Ignore files named 't' - our default executable." [master 983451d] Ignore files named 't' - our default executable. 1 file changed, 1 insertion(+) $
Finally, we push this branch to the server:
$ git push Counting objects: 5, done. Delta compression using up to 2 threads. Compressing objects: 100% (2/2), done. Writing objects: 100% (3/3), 339 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To csci:csci100/Examples fb8c6c0..983451d master -> master $
Now we can create our examples. The basic idea is to create a new branch for each example, add (and test!) the code for the example, and then push it to the server.
The first example, obviously, is "hello world." First, make sure we are on the master branch (if you are following this howto in sequence, you will be):
$ git branch * master $
An example below shows how to get back to the master branch when required.
Now create and checkout a branch called HelloWorld:
$ git checkout -b HelloWorld Switched to a new branch 'HelloWorld' $
Now we can write our "hello world" example program, either using your favourite editor, or perhaps like this (^D means type Control-D):
$ cat > t.c #include <stdio.h> int main() { printf("Hello world\n"); return 0; } ^D
At this point compile and run the program to make sure it works:
$ make t $ ./t Hello world $
Make sure the program works correctly before continuing.
Now we want to commit this example and push ("publish") it to the server. First, see what git thinks is going on:
$ git status # On branch HelloWorld # Untracked files: # (use "git add..." to include in what will be committed) # # t.c nothing added to commit but untracked files present (use "git add" to track) $
As expected, git tells us that t.c is a new file; t is ignored because that is what we told git to do above. We now add t.c and commit the change:
$ git add t.c $ git commit -m "Initial 'hello world' program." [HelloWorld 3520a25] Initial 'hello world' program. 1 file changed, 6 insertions(+) create mode 100644 t.c $
Finally, we push to the server so that students can get the example:
$ git push -u origin HelloWorld Counting objects: 4, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 386 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To csci:csci100/Examples * [new branch] HelloWorld -> HelloWorld Branch HelloWorld set up to track remote branch HelloWorld from origin. $
Let's recap before going forward: we have created a repository for examples, configured it to ignore our executables, written our first example, and published it. The next stage is to extend the example to print another message.
Now we will extend our current example to say "good-bye." First we need to make sure our directory is clean, which it should be if you are following along:
$ git status # On branch HelloWorld nothing to commit (working directory clean) $
Now we want to create a new branch, GoodbyeWorld with HelloWorld as the starting point - we can see from the previous command's output that we are currently on this branch, so we just create and checkout a new branch:
$ git checkout -b GoodbyeWorld Switched to a new branch 'GoodbyeWorld' $
Now edit t.c to print "Goodbye world":
#include <stdio.h> int main() { printf("Hello world\n"); printf("Goodbye world\n"); return 0; }
As above, compile and run the program to make sure it works.
Once you are satisfied the new program is complete, you need to add, commit and push the changes:
$ git add t.c $ git commit -m "Example to say goodbye, too." [GoodbyeWorld a1da4a4] Example to say goodbye, too. 1 file changed, 1 insertion(+) $ git push -u origin GoodbyeWorld Counting objects: 5, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 344 bytes, done. Total 3 (delta 1), reused 0 (delta 0) To csci:csci100/Examples * [new branch] GoodbyeWorld -> GoodbyeWorld Branch GoodbyeWorld set up to track remote branch GoodbyeWorld from origin. $
At this point we have created two branches, HelloWorld and GoodbyeWorld, each containing a C program called t.c. The second is a continuation of the first and this is documented in the branching structure. This can be seen by running the command:
$ gitk --all
which shows the commit sequence graphically.
Suppose at this stage you want to write a demonstration of a simple loop, but not use either of the current versions of t.c as the starting point. This can be accomplished by checking out the original master branch and then create a new Loop branch for this example. Again, we make sure we have no unsaved changes:
$ git status # On branch GoodbyeWorld nothing to commit (working directory clean) $
And then checkout master - this effectively "rewinds" the current directory back to how it looked when the master branch was last committed (ZZZ):
$ git checkout master Switched to branch 'master' $
Finally, create the new branch:
$ git checkout -b Loop Switched to a new branch 'Loop' $
Now we can write and test a new t.c - my version looks like this:
#include <stdio.h> #define MAX 10 int main() { int i; for (i = 0; i < MAX; i++) { printf("%d\n", i); } return 0; }
Once you are satisfied with the program, add, commit and push it:
$ git status # On branch Loop # Untracked files: # (use "git add..." to include in what will be committed) # # t.c nothing added to commit but untracked files present (use "git add" to track) $ git add t.c $ git commit -m "Example of a simple for loop." [Loop 80a95e2] Example of a simple for loop. 1 file changed, 10 insertions(+) create mode 100644 t.c $ git push -u origin Loop Counting objects: 4, done. Delta compression using up to 2 threads. Compressing objects: 100% (3/3), done. Writing objects: 100% (3/3), 426 bytes, done. Total 3 (delta 0), reused 0 (delta 0) To csci:csci100/Examples * [new branch] Loop -> Loop Branch Loop set up to track remote branch Loop from origin. $