pete > courses > CS 315 Fall 26 > lab 01: make and git


Lab 01: make and git

Goals


recall the compile command we used this morning:

$ gcc -Wall -pedantic -o copy-file copy-file.c

this is annoying to type every time you want to compile

the make program allows us to codify the commands used to compile programs in a single configuration file and then just run make to do everything necessary

this configuration file must be in the current directory, named Makefile (Linux is case sensitive, meaning that Makefile is different from makefile, and while both work, convention dictates the former)

here is the Makefile for today’s program:

copy-file: copy-file.c
    gcc -Wall -pedantic -o copy-file copy-file.c

.PHONY: clean
clean:
    rm -f copy-file

let’s walk through it step by step


a Makefile is composed of recipes

for now, let’s look only at the first one:

copy-file: copy-file.c
    gcc -Wall -pedantic -o copy-file copy-file.c

each recipe has three parts: a target (the thing to be built), a list of prerequisites (the things required to build the target), and the commands that do the job

the first thing in every rule is the target: it’s the thing that comes before the colon—in this case, copy-file

this should be the name of the file that gets produced

a recipe’s prerequisites are listed after the colon

thus, the copy-file recipe has a single prerequisite: a file named copy-file.c

finally, on the following lines, indented, are the command(s) that build the target

in this case, to build copy-file, we use the gcc command from above


we’ll get back to other recipe in a moment, but first let’s test this one

back in the shell:

$ make
make: 'copy-file' is up to date.

(if you run make all alone, it will attempt to build only the first target in the file: in this case, copy-file)

what has happened here is that make has examined the modification times of the prerequisite (copy-file.c) and the target (copy-file) and has deduced that the former has not changed since the latter was built, and therefore there’s no point in compiling again

this isn’t so useful in an example with a single source file, but if we’re building a project with hundreds or thousands of sources files, being able to detect and skip unnecessary compilation can save us a bunch of time

we can use the touch command to update the modification time of the source file to the current time:

$ touch copy-file.c

and then use ls to observe that it is now more recent than the program:

$ ls -ltr
-rw-r--r-- 1 pete pete  6039 Sep 11 22:10 notes.md
-rw-r--r-- 1 pete pete   219 Sep 12 08:47 copy-file-design.c
-rw-r--r-- 1 pete pete   109 Sep 12 08:48 Makefile
-rwxr-xr-x 1 pete pete 16984 Sep 12 09:08 copy-file
-rw-r--r-- 1 pete pete  1215 Sep 12 09:51 copy-file.c

and now running make will actually cause work to get done:

$ make
gcc -Wall -pedantic -o copy-file copy-file.c

(note that make prints out the command that it runs, and also re-note that gcc produces no output when it works)


check out the other recipe:

.PHONY: clean
clean:
    rm -f copy-file

by convention, Makefiles include a rule to clean up the workspace: to delete (remove) all files that can be produced from other files

in this case, it means the program, which can be built from the underlying source file

(more on this later when we get to git)

for now, though, note two things about this rule:

to recognize this fact, we tell make that this is a phony rule—that is, that it does not actually produce anything

the concrete effect of doing this is that make will still run the rule even if a file named clean exists

even if you don’t imagine such a file ever creeping into your source directory, it’s still a good idea to mark the rule phony to be safe


recall from before that, by default make only builds the first target in the file

if we want it to "build" the clean target, we give that as a parameter on the command-line:

$ make clean
rm -f copy-file

again note that it prints out the command it executes

and also note that the compiled program has disappeared:

$ ls
copy-file.c  copy-file-design.c  Makefile  notes.md

that’s enough about make for now; we’ll add more complexity next week


now, git

git is a version control system (VCS) or source-code management (SCM)

it performs as advertised: it helps manage your source code, it helps control the versions of your source code

the basic idea is that is tracks checkpoints in your development process

add a feature, make a checkpoint

fix a bug, make a checkpoint

then git will allow you to examine these various checkpoints, compare them, etc

this makes it easy to roll back to a previous version if, for example, you discover a bug

all git commands follow the pattern

$ git command command-arguments

in git parlance, a repository is a single collection of source code

in this class, you will create one repository per assignment, and that repo will contain all source files necessary to build and run it

the init command creates a repo

"." refers to the current directory, thus the following sequence of commands creates a new directory called sample-repo, moves into it, and creates a new repo inside

$ mkdir sample-repo
$ cd sample-repo
$ git init .
Initialized empty Git repository in /home/pete/tmp/sample-repo/.git/

ALTERNATIVELY, git can run mkdir for you:

$ git init sample-repo
Initialized empty Git repository in /home/pete/tmp/sample-repo/.git/
$ cd sample-repo

(do NOT run both sets of commands in this section, as they do the equivalent thing!)


when you create your first repo, you will be met with a message like this:

hint: Using 'master' as the name for the initial branch. This default branch name
hint: is subject to change. To configure the initial branch name to use in all
hint: of your new repositories, which will suppress this warning, call:
hint:
hint:   git config --global init.defaultBranch <name>
hint:
hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and
hint: 'development'. The just-created branch can be renamed via this command:
hint:
hint:   git branch -m <name>

I’m not going to inflict the notion of branches on you (yet) but know that you can pursue different development ideas within the same repository and keep them separate by putting those different ideas in different branches

historically, the primary branch of development has been called "master"

this message tells you how to change the name of the default branch for any repositories you create in the future

to change that default name (for example, to "main"), use this command:

$ git config --global init.defaultBranch main

(if you don’t pick a default using the above command, git will show you this warning every time you create a new repository with git init)


now we can create some files:

$ ls
copy-file.c  Makefile

and ask git what it thinks of its current situation:

$ git status
On branch master

No commits yet

Untracked files:
  (use "git add <file>..." to include in what will be committed)

    Makefile
    copy-file.c

nothing added to commit but untracked files present (use "git add" to track)

this tells us that it found two files that it doesn’t know anything about: they are untracked


to ask git to begin to track changes to these files, we must "add" them to the repo

$ git add copy-file.c Makefile

and then we can check status again:

$ git status
On branch master

No commits yet

Changes to be committed:
  (use "git rm --cached <file>..." to unstage)

    new file:   Makefile
    new file:   copy-file.c

and now they are ready to be "committed", which means that git will save a new revision in its history that contains the exact contents of these files at this instant, so we can roll back to them if necessary


to commit these files, we use the "commit" command, which requires a message describing the purpose of the commit (choosing a good commit message will make it easier to dig through your revision history later on):

$ git commit -m 'initial commit'

but it doesn’t like it:

Author identity unknown

*** Please tell me who you are.

Run

  git config --global user.email "you@example.com"
  git config --global user.name "Your Name"

to set your account's default identity.
Omit --global to set the identity only in this repository.

fatal: empty ident name (for <user@host>) not allowed

this is because git needs to record who is responsible for these changes

we can make git happy by running the commands it tells us to run:

$ git config --global user.email "pete@middlebury.edu"
$ git config --global user.name "Peter Johnson"

(You only need to do this once ever: because it uses --global, future commits and future repositories will use these configuration parameters)


now we run the same command again and it works:

$ git commit -m 'initial commit'
[master (root-commit) 7ed4973] initial commit
 2 files changed, 67 insertions(+)
 create mode 100644 Makefile
 create mode 100644 copy-file.c

now let’s make a change: I’m going to change the value of BUFFER_SIZE to 20000

first, "git status" will show us that git has noticed we’ve changed a file:

$ git status
On branch 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:   copy-file.c

no changes added to commit (use "git add" and/or "git commit -a")

and we can then ask git to show us the precise differences between the "working copy" (that is, the current uncommitted version) and the most recent committed version using the "diff" command:

$ git diff
diff --git a/copy-file.c b/copy-file.c
index 95c63d3..89863d6 100644
--- a/copy-file.c
+++ b/copy-file.c
@@ -7,7 +7,7 @@
 #include <stdio.h>
 #include <stdlib.h>

-#define BUFFER_SIZE 10240
+#define BUFFER_SIZE 20000

 int
 main(int argc, char *argv[])

lines preceded by a "+" indicate code that has been added

lines preceded by a "-" indicate code that has been removed


to commit these changes, we first need to "add" them to the list of files to be committed:

$ git add copy-file.c

which is corroborated by "git status"

$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

    modified:   copy-file.c

and now we can commit:

$ git commit -m 'copy more bytes at a time'
[master 9b6ecc4] copy more bytes at a time
 1 file changed, 1 insertion(+), 1 deletion(-)

finally, we can ask git about the version history using the "log" command:

$ git log
commit 9b6ecc46160c89840a09b7c74c53dfbd56e83db5 (HEAD -> master)
Author: Peter Johnson <pete@middlebury.edu>
Date:   Wed Sep 11 19:31:54 2019 -0400

    copy more bytes at a time

commit 7ed4973818150ebcca8cec7223975b002cf717f3
Author: Peter Johnson <pete@middlebury.edu>
Date:   Wed Sep 11 19:26:37 2019 -0400

    initial commit

your basic (working alone) workflow for git should be


this is helpful when working in isolation, but tons of code development these days happens in teams, sometimes distributed around the world

we’re not going to worry about such large-scale organization (though supporting that kind of scale is exactly why git was invented); we’re going to focus on sharing code between partners

the first step is that each partner is going to have a git repository on their own computer, in which they do development

the complication then arises when one partner makes a commit or three and wants to send those commits to the other partner

the good news is that git includes facilities to send all commits from one repository to another

the problem is: how do we identify where to send those commits?

laptops (upon which much—and perhaps even most—development happens) can move around the Internet a lot, which makes it difficult to identify where to send those commits

the solution is to have a stable, easily-findable computer sitting on the Internet somewhere

when Partner A has commits they want to share, they push the code from their laptop to this stable machine on the Internet

Partner A tells Partner B that there is new code available

Partner B pulls the new commits from the stable machine

many people doing development in the real world use github.com as the stable machine on the Internet; in this course, we will use a Middlebury-managed machine named weathertop


therefore, between you and your partner, for each assignment, you will have four repositores:

one remote repository for you, to which you will push and your partner will pull

one remote repository for your partner, to which they will push and you will pull

one local repository for you, in which you will edit code, add, and commit, and push to your remote repository

one local repository for your partner, in which they will edit code, add, and commit, and push to their remote repository


so how do we set all this up?

it will happen in a bit of a weird order (though this weird order ends up being more concise)

each partner will:

because you’ll use this process several times throughout the semester, I’ve put all the instruction on a separate page, a link to which you can find in the Resources section of the main course website

also here: git


as with make, we’ll add more complex commands as the semester proceeds, and introduce some shortcuts, but for now that should do you

Last modified: