Preliminaries
Understanding how to use Git properly is a key development skill, both in this class and in
industry. Atlassian has an in-depth tutorial
that serves as a quick reference for many common tasks. Here are some very useful
topics that align with a lot of what you'll need in this class. To avoid frustration
and save yourself headaches, you should read all modules listed here.
git clone - necessary for basics of setting up your submission directories
git add and
git commit -
necessary for the basic use of git and understanding revision control
git remote,
git push, and
git pull -
necessary for understanding how you actually submit your code or share with a project partner
git branch and
its sub-topics - proper use of branches and merging will significantly reduce your
frustration when collaborating
git log -
very useful tool for understanding the state of your code and how versions have changed
- Feature Branch Workflow -
useful summary of how branches, remotes, and push/pull fit together
This document illustrates the standard workflow of commands that you will need in this
class. In all cases in the examples on this page, we will use the username lennonjw.
You should substitute your username as appropriate. Similarly, these examples are based on
setting up Lab 1 using the name lab1-ptrs. Other
labs and projects will use a different name.
Recommendation: Throughout this document, there are
references to using SSH to connect to student.cs.jmu.edu (known as stu
for short). For this class, we recommend that you use the
VS Code Remote SSH extension. After you
set it up once, you will automatically connect to the stu command line whenever you
pull up the terminal.
Account configuration
Before you can use Git, you must set up a configuration file in your account on
stu.cs.jmu.edu. To do this, use ssh (see CS
361 Development Basics) to log into stu and run the following commands:
$ git config --global user.name "John W. Lennon"
$ git config --global user.email "lennonjw@dukes.jmu.edu"
$ git config --global receive.denycurrentbranch updateInstead
Note: You may be familiar with
GitHub, a popular web-based interface for publishing
code. Git and GitHub are not the same thing. One of the goals of the
CS 261-361 Computer Systems sequence is to give you the opportunity to explore how
systems like GitHub work. As such, we will be using Git specifically on the command-line,
which will give you important experience with understanding how to use this tool more
effectively.
Repository Setup
WARNING: If you have not set up your
configuration file as described above, you will NOT be be able to submit because you will
not be able to push. Make sure that you have done everything there.
All labs and projects must adhere exactly to these instructions. Submissions
that do not adhere to these instructions will not be graded.
As a first step, you must create a repository that is a fork of the distribution.
Each lab or project will have its own distribution repository, so you will need to do
this for each lab or project. It is very important that you set up your repository
correctly. If you set up your repository incorrectly, you may find yourself dealing with
some bizarre errors with incredibly unhelpful messages. Or you may just get a 0 because
your submission could not be retrieved and graded.
To set up your repository, you must ssh to stu.cs.jmu.edu
and execute the following commands. Remember that you do not type the $
(which is used to distinguish command prompt lines from sample output) and you use
your e-ID instead of lennonjw. Note that other labs will have a
different name instead of lab1-ptrs.git. Similarly, projects will be in
projects/lennonjw instead of labs/lennonjw.
$ cd /cs/students/cs361/s26/kirkpams/labs/lennonjw
$ git clone --bare /cs/students/cs361/s26/kirkpams/src/lab1-ptrs.git
Cloning into bare repository 'lab1-ptrs.git'...
done.
$ cd lab1-ptrs.git
$ ls
branches config description HEAD hooks info objects packed-refs refs
$ git log --graph --oneline
* 1ea3136 (HEAD -> main, origin/main, origin/HEAD) Set up Spring 2024 distro
WARNING: When you run git clone
you may see a message like this:
fatal: detected dubious ownership in repository at '/cs/students/cs361/s26/kirkpams/src/lab1-ptrs.git'
To add an exception for this directory, call:
git config --global --add safe.directory /cs/students/cs361/s26/kirkpams/src/lab1-ptrs.git
fatal: Could not read from remote repository.
This message is a warning that you are copying someone else's code and you
need to tell Git that you trust the author (me). All you have to do is copy
and paste the git config line exactly as is.
The ls and git log lines are just for reference so that
you can determine it was set up correctly. Note that the 1ea3136
value is based on a SHA-1 hash value. If you are working on a different lab
or the sample distribution used here has been updated, you will see a
different value there.
WARNING: The example above
shows the directory structure for labs, not projects. If you
are setting up a project repository, you must replace /labs/ with
/projects/ in the directory name.
This directory is now set up as a bare repository. You are not
intended to work in this directory and you should not modify any files.
The purpose of a bare repository is to create a central place that you will submit
your code (using git push) and we will retrieve your submissions (using
git pull).
If you are working with a partner on the projects, only one of you needs to
set up this shared repository. Your directories are set up so that both e-IDs
point to the same location.
Normal Git Workflow
Assuming your repository is set up as described above, you can set up a
clone to use as a working directory. If you are working with a partner on
a project, you will both create your own clones so that you have distinct
workspaces. This will allow you to work on parts of the code independently
without interfering with each other. However, it also means you need to learn
to use git properly so that you don't find yourself frustrated
with merge conflicts.
Creating a local clone
When working on any lab or project, you'll want to set up an individual
workspace (called a clone in your account on stu. To
keep all of your work organized, we recommend that you create a
cs361 directory in your account and create all of your lab/project
clones there. You can do this using the following steps, substituting your
e-ID for lennonjw:
$ cd $HOME
$ mkdir cs361
$ cd cs361
$ git clone /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
Cloning into 'lab1-ptrs'...
done.
These steps will create a new subdirectory as $HOME/cs361/lab1-ptrs
(note this directory name does not end with ".git"). You can then cd
into this directory and begin working. If you then run git log --graph --oneline
in that directory, you should see the same line of output as above that begins
with * 1ea3136.
You will need to create a clone for each lab or project, just as you will
create a separate bare repository for each. Creating the separate repositories
and clones allows you to keep each assignment separate so nothing interferes
with each other. Also note that you will only need to create the
$HOME/cs361 directory (using mkdir above) once.
Implementing a feature as a branch
At this point, you can begin editing and submitting the code in your clone,
using git commit and git push as appropriate.
However, that's not the proper way to use Git. We strongly recommend
that you get in the habit of implementing features as distinct branches. Doing
so will really help you to organize your work and avoid nasty headaches caused
by merge conflicts.
To start working on code in a branch, you'll need to both create and
checkout the branch. (The git checkout command is used to switch
between branches by their names.) You can do both steps in a single command:
$ git checkout -b fix-struct
Switched to a new branch 'fix-struct'
$ git branch
* fix-struct
main
The second command here is used to show all branches (fix-struct and main)
and to indicate which branch you are using with the *. You do
not have to do git branch here, but you can use it to double-check
that you are on the right branch.
At this point, you can start implementing the feature that you are working
on, compiling and testing the code as you go. The idea is that this feature
should be an independent piece of code that can be developed without
interfering with other features. Once you've been working on this feature and
you want to save your work, you can confirm what files you've modified:
$ git status
On branch fix-struct
Changes not staged for commit:
(use "git add <file>..." to update what will be committed)
(use "git restore <file>..." to discard changes in working directory)
modified: movies.h
no changes added to commit (use "git add" and/or "git commit -a")
These messages indicate that you've changed movies.h but you
haven't indicate that you want Git to save them. You do this by committing
the changes with the following commands (the exact output will vary based on
what you've changed). In this example, we've only changed one file, so we
mention it by name on the git add line. If you've changed
multiple files, you can use git add . to refer to anything that
has changed in the current directory or any of its subdirectories.
$ git add movies.h
$ git commit -m "Fixed the struct header"
[fix-struct d740d1f] Fixed the struct header
1 file changed, 3 insertions(+)
$ git log --graph --oneline
* d740d1f (HEAD -> fix-struct) Fixed the struct header
* 1ea3136 (origin/main, origin/HEAD, main) Set up Spring 2024 distro
Notice there is a new log entry (d740d1f). Whatever you put in
quotes on the git commit line becomes the log message.
You should use meaningful log messages that describe the work you've done.
For many features, you may go through several steps, creating separate
commits for each step. The purpose of a commit is to give you a checkpoint
that you can revert to if you mess up your code. Once you've committed
everything for this particular feature and you are done with it, you'll
switch back to the main branch, merge the fix-struct
branch into it, and delete the branch since you no longer need it:
$ git checkout main
Switched to branch 'main'
Your branch is up to date with 'origin/main'.
$ git merge fix-struct
Updating 1ea3136..d740d1f
Fast-forward
movies.h | 3 +++
1 file changed, 3 insertions(+)
$ git log --graph --oneline
* d740d1f (HEAD -> main, fix_struct) Fixed the struct header
* 1ea3136 (origin/main, origin/HEAD) Set up Spring 2024 distro
$ git branch -d fix-struct
Deleted branch fix-struct (was d740d1f).
Pushing your code for sharing and submission
Now that you've created a branch and used it to implement a feature, you
will need to push these updates back to your repository. If you do
not push your code, it cannot submitted and will not be graded.
You can make sure you are ready to push your code with the following command:
$ git status
On branch main
Your branch is ahead of 'origin/main' by 1 commit.
(use "git push" to publish your local commits)
nothing to commit, working tree clean
WARNING: Observe the messages
above carefully and notice that it specifically mentions branch main
and origin/main. If any other branch is mentioned (such as the
branch fix-struct above), it means you have not merged your
feature branch into main. You MUST merge your
feature into main before pushing.
The second line of output indicates that you have "1 commit" that has not
yet been pushed. That means you have not submitted your code for grading.
To submit your code, run the following command:
$ git push
Enumerating objects: 5, done.
Counting objects: 100% (5/5), done.
Delta compression using up to 112 threads
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 342 bytes | 171.00 KiB/s, done.
Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
To /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
1ea3136..d740d1f main -> main
There are two common error messages that you might see at this point. The
first includes the following line:
remote: error: You can set 'receive.denyCurrentBranch' configuration variable to
If you see this message, then you did not set up your account
configuration as described at the top of
this document. Follow those instructions and try again. In particular, check
for common typos (e.g., misspelling "recieve" instead of "receive").
Another common error starts with a line that says ! [rejected].
There are several things that could cause this, but the most common reason is
that your repository contains additional commits (probably pushed by a
partner) that you need to merge. Read on to the next section.
Code Submission Procedures
For both labs and projects, most of the work for submitting your code is
already done based on the instructions above. That is, submitting your
code is mostly based on using Git as described in the instructions
above. Most of the problems that students have had with submitting
their code is a failure to follow the above instructions. Assuming you have
correctly cloned, committed, and pushed your code, this section describes the
extra steps required for getting credit.
Submitting labs
For labs, all you have to do to submit your code for grading is to push
your code according to the instructions above. To grade your labs, I will
pull whatever you have pushed and run make test on that. I often
get asked how you can check if your code was submitted. If you want to confirm
that your code has been successfully submitted, create a new clone using
git clone. The code that you see there will be exactly what I
get when I go to grade your submission. You do not need me to check.
Submitting projects
For projects, the submission procedures are different. You will
need to push your code as described above, but there are additional steps that
you need to take. If you have not already, you should make yourself familiar
with the Test Case Procedures, including the
descriptions of the infrastructure and the procedures for running tests.
Once you have tested your code and are ready to submit, you can do so by
running make submit, which should produce output similar to the
following:
$ make submit positive
[possible Makefile messages here...omitted for brevity]
========================================
CODING STYLE CHECK
----------------------------------------
add.c pass
add.h pass
main.c pass
----------------------------------------
Code correctly adheres to required style.
========================================
GRADING SUBMISSION
----------------------------------------
evens pass
odds pass
----------------------------------------
SUCCESS: All tests passed
----------------------------------------
Updated project feature status:
BASIC-positive DONE
MIN-negative incomplete
INTER-mixed_sign incomplete
ADV-errors incomplete
----------------------------------------
Current grade: D
[possible Makefile messages here...omitted for brevity]
While the lines here might be intuitive in what they are reporting, there
are several steps happening behind the scenes that are not immediately
obvious. The steps involved are as follows:
- Coding style check:
Code that does not adhere to the required course
coding standards will not be
submitted. You must fix any errors before the submission can proceed.
These checks are listed in the
CODING STYLE CHECK
section of the output.
- Repository cloning:
The first step is to create a temporary directory and clone your
repository into it. If your repository is not set up correctly or you
have not pushed your code, this step will NOT work
correctly.
- Feature testing:
The
GRADING SUBMISSION section shows the results of checking
that the submission is correct. This part of the script will first check
that you are not re-submitting code that already passes (once a feature
is passed, you do not need to resubmit it) before checking for any
prerequisite features. For instance, you cannot submit a MIN
feature for grading until you have already submitted the prerequisite
BASIC feature. The script will then check that your pushed
code passes all of the tests for that feature.
- Recording submission time:
If your code passes all tests for this feature (as shown above), the
submission script will create and push a new branch to document the
submission time. Recall that you can only get credit for (at most)
two features per week. (You can later confirm this branch was
recorded by running
make status, which will list the features
that have been recorded as passing.)
Note that the line that describes a "Current grade"
is an estimate for just this project. As described in the
syllabus, the project grade component is actually
based on both projects together.
Code submission demo
Once you've been able to submit your code, you must see me in
office hours to demo your work. At each visit, you may demo up to
two features at a time (with the exception that you can demo all three
MIN features at the same meeting). To reduce your waiting time,
you are encouraged to make an appointment.
In these demos, you should be prepared to run your code interactively
without using make. That is, you will need to show that
you understand how the testing infrastructure relates to command-line
processing. You will also need to show how your code handles input data
similar (but not identical) to the commands that are provided in the test
cases, as well as bad input that should be handled adequately.
These demos also provide you an opportunity to request clarification or
guidance on moving on to the next set of requirements.
Merging Without Panicking
Assume that you are working with a partner (starkeri) who also
tried to implement the same feature you did. They followed the same instructions
for creating a branch, committing, etc. After deleting the branch, they try to push
their code and see the following:
$ git log --graph --oneline
* a566075 (HEAD -> main) Added the year
* 1ea3136 (origin/main, origin/HEAD) Set up Spring 2024 distro
$ git push
To /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to '/cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
hint: See the 'Note about fast-forwards' in 'git push --help' for details.
This means there is code in the shared repository that starkeri
did not have in their clone before they started working on their branch. To
fix this, starkeri will need to retrieve the missing code
(git pull). There are two possibilities that can happen at this
point. In the simpler case, starkeri would see messages like
the following:
$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 322 bytes | 24.00 KiB/s, done.
From /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
1ea3136..d740d1f main -> origin/main
Merge made by the 'recursive' strategy.
This message means that nothing conflicted between what was in the
repository and what starkeri had implemented in their branch.
That is, Git confirmed that the changes from starkeri and
lennonjw affected different parts of the file (or different
files). When this happens, Git automatically opens a text editor asking for
a commit message:
Merge branch 'main' of /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git into main
# Please enter a commit message to explain why this merge is necessary,
# especially if it merges an updated upstream into a topic branch.
#
# Lines starting with '#' will be ignored, and an empty message aborts
# the commit.
Saving this file and exiting the text editor will add and commit the
merged files. Make sure to push after this merge. Once
starkeri pushes the merged code, lennonjw
can pull the merged version and everyone is looking at the same code
version again.
The more complicated case occurs when lennonjw and
starkeri changed the same lines of code and Git cannot figure
out which is the correct version to keep. In this case, you would see a
message like the following:
$ git pull
remote: Enumerating objects: 5, done.
remote: Counting objects: 100% (5/5), done.
remote: Compressing objects: 100% (3/3), done.
remote: Total 3 (delta 2), reused 0 (delta 0), pack-reused 0
Unpacking objects: 100% (3/3), 322 bytes | 24.00 KiB/s, done.
From /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
1ea3136..d740d1f main -> origin/main
Auto-merging movies.h
CONFLICT (content): Merge conflict in movies.h
Automatic merge failed; fix conflicts and then commit the result.
Note: Instead of those
last three lines, you may see a number of hints:
hint: You have divergent branches and need to specify how to reconcile them.
hint: You can do so by running one of the following commands sometime before
hint: your next pull:
hint:
hint: git config pull.rebase false # merge
hint: git config pull.rebase true # rebase
hint: git config pull.ff only # fast-forward only
hint:
hint: You can replace "git config" with "git config --global" to set a default
hint: preference for all repositories. You can also pass --rebase, --no-rebase,
hint: or --ff-only on the command line to override the configured default per
hint: invocation.
fatal: Need to specify how to reconcile divergent branches.
The Atlassian tutorial explains
merging vs. rebasing.
For our purposes in this class, we recommend you configure your code to merge:
$ git config pull.rebase false
Assuming that starkeri sees this message, they should edit and
fix the files mentioned on the CONFLICT line. Opening
movies.h in a text editor, they might see something like this
in part of the code:
<<<<<<< HEAD
// A change from starkeri
=======
// A change from lennonjw
>>>>>>> d740d1f2baadf6f36315ad2683b1c37bee0176de
The changes made by starkeri (committed locally) are in between
the <<<<<<< HEAD and =======
lines. Then the changes made by lennonjw (pulled from the shared
repository) are between that and the >>>>>>> d740...
line. At this point, starkeri should decide what
to keep (and what to throw away) then delete the special Git format lines
(the <<<<<<< HEAD, =======,
and >>>>>>> d740... lines).
After doing this, the previous commit from lennonjw
is discarded and lennonjw must make a new commit:
$ git status
On branch main
Your branch and 'origin/main' have diverged,
and have 1 and 1 different commits each, respectively.
(use "git pull" to merge the remote branch into yours)
You have unmerged paths.
(fix conflicts and run "git commit")
(use "git merge --abort" to abort the merge)
Unmerged paths:
(use "git add ..." to mark resolution)
both modified: main.c
no changes added to commit (use "git add" and/or "git commit -a")
$ git add movies.h
$ git commit -m "Merged"
[main 501b8f7] Merged
$ git push
Enumerating objects: 10, done.
Counting objects: 100% (10/10), done.
Delta compression using up to 112 threads
Compressing objects: 100% (6/6), done.
Writing objects: 100% (6/6), 625 bytes | 156.00 KiB/s, done.
Total 6 (delta 4), reused 0 (delta 0), pack-reused 0
To /cs/students/cs361/s26/kirkpams/labs/lennonjw/lab1-ptrs.git
d740d1f..501b8f7 main -> main
The merged code has now been pushed to the main repository. At this point,
lennonjw needs to do git pull to get the merged
code. To confirm that everyone is up-to-date, use git log to
check that you both see the same history:
$ git log --graph --oneline
* 501b8f7 (HEAD -> main, origin/main, origin/HEAD) Merged
|\
| * d740d1f Fixed the struct header
* | a566075 Added the year
|/
* 1ea3136 Set up Spring 2024 distro
Final suggestions
These instructions are just a basic introduction to Git. If you need
more help, please work through the tutorials mentioned at the top of this
page. You can also find others by searching for "git tutorial" in your
favorite Internet search engine.