In today's lab:
Version control is also critical when multiple developers are working together on a single project. In particular, version control makes it easier for two people to make edits to the same set of files without overwriting each other's changes.
Today you will learn the basics of SVN, an open source version control tool that is available on machines in the CS department network.
Create a repository in your home directory with the command svnadmin create:
Here /home/username/svn is the pathname of the new repository. Option --fs-type specifies the type of file system to be used by SVN; you must use the value fsfs to save a repository on a shared network, as is the case with department home directories.$ mkdir /home/username/svn $ svnadmin create --fs-type fsfs /home/username/svn
You can see that your new repository directory already contains files and subdirectories:
You should not directly modify any of these files or subdirectories; they are only to be used by SVN.$ ls /home/username/svn README.txt conf dav db format hooks locks
Create the directory /tmp/lab8_username on your machine:
Also create these subdirectories:$ mkdir /tmp/lab8_username
trunk is the directory that you will use to store and edit files that you will upload to the repository. We will explain directories branches and tags later on.$ mkdir /tmp/lab8_username/trunk $ mkdir /tmp/lab8_username/branches $ mkdir /tmp/lab8_username/tags
In this lab we will start by making a repository from the unmodified mypoint files from the last lab. Copy these files into the trunk directory:
Next you will add the source files into your SVN repository. (We only want to import source code files, so if you have compiled the above program then remove the binary files now.)$ cd /tmp/lab8_username/trunk $ cp /stage/classes/archive/2011/summer/50101-1/lab/lab7/src/mypoint/* . $ ls distance.c mypoint.c mypoint.h simple.c
Call svn import to add lab8 and all its contents to the repository. (Here we refer to import as a subcommand of svn):
Let's look at the arguments in this call to svn import:$ svn import /tmp/lab8_username file:///home/username/svn/cspp50101/lab8 -m"Initial import" Adding /tmp/lab8_wax/trunk Adding /tmp/lab8_wax/trunk/mypoint.c Adding /tmp/lab8_wax/trunk/mypoint.h Adding /tmp/lab8_wax/trunk/simple.c Adding /tmp/lab8_wax/trunk/distance.c Adding /tmp/lab8_wax/branches Adding /tmp/lab8_wax/tags Committed revision 1.
SVN stores multiple revisions of your repository, where each revision is a snapshot of the repository at a certain point in time. A new, empty repository is at revision 0, and as the above output shows, revision 1 is created when you first import data.
View the contents of your repository by calling svn ls:
$ svn ls file:///home/username/svn/cspp50101/lab8 branches/ tags/ trunk/ $ svn ls file:///home/username/svn/cspp50101/lab8/trunk distance.c mypoint.c mypoint.h simple.c
At this point you can remove the directory /tmp/lab8_username because its contents are now stored in your SVN repository.
$cd $rm -rf /tmp/lab8_username
You can see that all the files for the Lab 8 project are now found in directory /home/username/cspp50101/lab8/trunk:$ cd /home/username/cspp50101 $ svn checkout file:///home/username/svn/cspp50101/lab8 A lab8/trunk A lab8/trunk/distance.c A lab8/trunk/mypoint.c A lab8/trunk/mypoint.h A lab8/trunk/simple.c A lab8/branches A lab8/tags Checked out revision 1.
$ cd lab8/trunk $ ls distance.c mypoint.c mypoint.h simple.c
After you have distance.c and mypoint.c, you can use the svn status command to compare the current directory with the repository:
The '?' tells us that, as we expect, files Makefile and distance are not under version control. On the second line, 'M' tells us that source code files mypoint.c, mypoint.h, and distance.c have been modified since you checked it out from the repository. File simple.c is not listed since it has not yet been updated.$ svn status ? distance ? Makefile M mypoint.c M mypoint.h M distance.c
In addition to '?' and 'M', SVN uses many more status codes that are not covered in this lab; you can review the full list in the online manual.
You can use the command svn diff to see how mypoint.c differs from the repository version. (The exact output of course depends on how you completed Lab 7.):
Here the lines marked with '+' mark which lines are found in the updated file, and '-' shows the same line in the repository version.$ svn diff mypoint.h Index: mypoint.h =================================================================== --- mypoint.h (revision 1) +++ mypoint.h (working copy) @@ -8,6 +8,11 @@ #define MYPOINT_H /* *** Add structure declaration here *** */ +struct mypoint +{ + double x; + double y; +}; /* Returns the distance between points (x1,y1) and (x2,y2) */ double get_distance( struct mypoint * p_p1, struct mypoint * p_p2);
The last line tells us that we are at revision 2 of this repository. You can also type svn info to see revision number information:$ svn commit -m"Adding changes for lab 7" Sending trunk/distance.c Sending trunk/mypoint.c Sending trunk/mypoint.h Transmitting file data . Committed revision 2.
$ svn info Repository Root: file:///home/username/cspp50101/svn Repository UUID: 1d5eca6d-b426-4f00-922e-c42863db29fe Revision: 1 Node Kind: directory Schedule: normal Last Changed Author: username Last Changed Rev: 2 Last Changed Date: 2009-03-07 12:16:30 -0600 (Fri, 07 Mar 2009)
After committing your changes, re-check the status of the current directory:
Note that source code files no longer shows up as modified, i.e., with the 'M' status code, since the repository and working copies are now identical.$ svn status ? distance ? Makefile
It is possible to recover previous revisions of a file that is under version control: You can use the command svn update to restore your working copy to the latest version in the repository:
You can also checkout earlier revisions of a file with the -r option:$ rm mypoint.c ## delete source file $ svn update Restored 'mypoint.c' At revision 2. $ ls *.c mypoint.c distance.c simple.c
Here the 'U' indicates that your current version of the file was updated to match the revision that you requested.$ rm mypoint.c ## delete source file $ svn update mypoint.c -r 1 U mypoint.c Updated to revision 1.
You can also call svn update without a file name to update an entire directory:
$ rm *.c $ svn update Restored 'mypoint.c' Restored 'distance.c' Updated to revision 2.
You should have already added a Makefile to your lab8 directory. Add this Makefile to the repository by calling svn add:
Here the status code 'A' indicates that Makefile will be added on the next commit.$ svn add Makefile A Makefile
You can now call svn update and svn ls to see that your changes are in the repository:$ svn commit -m"Adding Makefile" Adding trunk/Makefile Transmitting file data . Committed revision 3.
$ svn update At revision 3. $ svn ls Makefile distance.c mypoint.c mypoint.h simple.c
Next you will tag the current version of your project, which now contains a Makefile. To do this you will make a virtual copy of your latest revision, using the command svn copy:
Next commit your changes. Note that you should pass the argument tags to the svn commit command, to indicate that you only want to commit changes in this directory:$ cd .. $ pwd /home/username/cspp50101/lab8 $ ls branches tags trunk $ svn copy trunk tags/uses-make A tags/uses-make
While it appears that we have copied all the files in the trunk directory, in reality SVN has only tagged a particular revision in its internal database. At any time you can check out a copy of this new directory:$ svn commit tags -m "Tagging version where Makefile added" Adding tags/uses-make Committed revision 4. $ svn ls tags/uses-make Makefile distance.c mypoint.c mypoint.h simple.c
The branch subdirectory is used when you want to make two separate versions, or "branches" of your program, and would like to continue to work on both branches independent of each other. We will not cover branches in this lab.$ cd /tmp $ svn checkout file:///home/username/svn/cspp50101/lab8/tags/uses-make A tags/uses-make/Makefile A tags/uses-make/mypoint.c A tags/uses-make/mypoint.h A tags/uses-make/distance.c A tags/uses-make/simple.c $ ls uses-make Makefile mypoint.c mypoint.h distance.c simple.c
One alternative is to place the repository on another machine and access it remotely. This can be done by using the svn+ssh schema with SVN commands, as shown below (You do not need to run this step yourself):
Remote access also allows users on different machines to access the same SVN repository and work together on a single project.$ svn ls svn+ssh://someone@hostname.cs.uchicago.edu/data/svn/trunk usernames@hostname.cs.uchicago.edu's password: foo.c foo.h Makefile
If you would like to set up a repository for collaborative work on future projects, then you should email techstaff@cs.uchicago.edu for assistance.
Your work for exercises 1 and 2 should be completed in directory /home/username/cspp50101/lab8/trunk/ where you initially added your updates for the mypoint module.
In file mypoint.h we created a new compound data type called mypoint, representing a 2-dimensional point.
In our code we refer to this type as struct mypoint.
We declare variables of this new type as:
Alternately, we can rename this new data type using typedef:
typedef and type constructors
struct mypoint p1, p2;
typedef struct mypoint {
double x;
double y;
} MYPOINT;
MYPOINT is a new type name that is equivalent to struct mypoint. A declaration of two type point variables becomes:
MYPOINT p1, p2;
You can read more about typedef in Chapter 14 of Prata.
Then, update mypoint.h, mypoint.c, and distance.c so that all functions use this new type name instead of struct mypoint. (So the keyword struct should not appear in your .c files.) You will need to update local variable declarations and function arguments.
The output of program distance should not change:
$./distance 1 5 4 9 Distance between (1.00,5.00) and (4.00,9.00): 5.00
Use this function prototype:
Next update distance.c to call get_cityblock_distance() instead of get_distance(). Your output should look like this:
As an example, consider the function show_file() from Lab 6:
We can also use typedef to declare a new type name for functions with the same type as show_file(). Here, the type name SHOW_FP is created for such functions:
Copy this file into your lab8/trunk directory:
/stage/classes/archive/2011/summer/50101-1/lab/lab8/src/path.c
You can see that this program includes a function called get_path_distance() that computes the total length of a path of multiple points.
For example, the distance traveled with path (p1,p2,p3) can be computed as the distance from p1 to p2 plus the distance from p2 to p3.
Next, add a typedef statement to mypoint.h to define DIST_FUNC, a new function pointer data type.
Functions get_distance() and get_cityblock_distance() are both valid values for a DIST_FUNC variable. You can see in main() that both are passed as input to get_path_distance():
After you complete mypoint.h and compile path.c, your output should look like this:
Exercise 2
Add a function to your mypoint called get_cityblock_distance().
This function computes the "city-block" distance between two points.
This distance is the sum of the distances in the x and y dimensions:
double get_cityblock_distance(MYPOINT * p_p1, MYPOINT * p_p2);
You can use the function fabs() in math.h to compute the absolute value of a floating point number. Type man 3 fabs at the command line for more info.
$ ./distance 1 5 4 9
Distance between (1.00,5.00) and (4.00,9.00): 7.00
Function pointers
You have already seen how pointers can be used to store the memory addresses of basic types (ex: int, char) and complex types such as structures.
It is also possible to declare a function pointer, or a pointer to the start of a function's machine instruction loaded in memory.
int show_file(char * filename);
The type of show_file() is "function that takes char* as input and returns int". We can declare a pointer to functions with this type as:
int (*fp)(char *);
Here fp is a pointer to a function that returns int and takes one char* as input. We can set the value of fp to show_file() with this assignment statement:
fp = show_file;
We can then call show_file using variable fp with this function call:
(*fp)("in1.txt");
or more simply:
fp("in1.txt");
typedef int (*SHOW_FP)(char *);
Function pointer variable fp is declared more simply as:
SHOW_FP fp;
You can read more about function pointers in Chapter 14 of Prata.
Exercise 3
Function pointers are useful when you want the same block of code to call different functions of the same type.
For example, you may decide at runtime whether you will compute Euclidean or city-block distance between two points.
double get_path_distance(DIST_FUNC fp, MYPOINT * p_arr, unsigned int len)
{
double dist = 0;
int i;
for(i=0;i<len-1;i++)
{
MYPOINT * p_a = p_arr+i;
MYPOINT * p_b = p_arr+i+1;
/* compute distance between points i and (i+1) */
dist+=(*fp)(p_a,p_b);
}
return dist;
}
Note that the above function takes a variable fp of type DIST_FUNC. fp is the called in a for-loop to compute the length of sequential points in path p_arr.
double d1=get_path_distance(get_distance,point_arr,3);
...
double d2=get_path_distance(get_cityblock_distance,point_arr,3);
$ ./path 0 0 3 4 7 1
Path distance (Euclidean): 8.66
Path distance (City-block): 11.00
You do not need to make any other updates besides adding this new function pointer type to mypoint.h. Note that path.c will not compile until you add this typedef.