CVS is a simple version control system that is widely used in industry. It can be used in Unix or Windows. It is normally used in a client/server configuration, but in this exercise you will mainly use it in a stand-alone mode on just one computer.
To explore how CVS is used by teams of developers, you will pretend to be two people: a junior developer and a senior developer.
For this lab, you may use either command-line CVS in Unix, or WinCVS for Windows. There are many other CVS clients (including clients built-in to Eclipse and other IDEs), but these are the easiest to start learning. You are encouraged to work through this exercise with both clients.
Download, install and get ready to run a CVS client.
The first time that you run WinCVS, it will ask you for your home directory. Make sure you choose a directory that only you can access, not a directory on the local hard disk of a lab machine.
The most common way to use CVS is to use a client to access an existing source code repository. You will practice that at the end of this exercise. For now, you will create your own repository to experiment with.
Go in and look around at the contents of the repo directory now. There will be a subdirectory named CVSROOT with several configuration files that control the behavior of the CVS server. Do not edit these files now. Keep in mind that the repo would normally exist on a server machine and you would not have direct access to it.
Create a new directory named 'src' anywhere on your computer, outside of the CVS repo directory. Create the following file named Fib.java in directory src.
/** Simple program to print table of Fibonacci numbers. */ public class Fib { /** Print a table of the first 10 Fibonacci numbers. */ public static final void main(String args[]) { for (int i = 0; i < 10; i++) { System.out.println(i + ": " + fib(i)); } } /** Use recursion to compute the Nth Fibonacci number */ public static int fib(int n) { if (n == 0 || n == 1) { return 1; } else { return fib(n - 1) + fib(n - 2); } } }
Use the following commands to import the 'src' directory and all its contents into the CVS repository.
Take a look in the 'repo' directory now. You will see that there is a 'src' directory there with a file 'Fib.java,v'. The files that end in ',v' are version control files. Each ',v' file contains the entire history of an actual source code file. You may look at the contents of the ',v' files, but do not edit them yourself.
Now you will pretend to be two different developers working together to enhance this program. Even though both developers are working together on the same general goal, they work fairly independently and only integrate changes into the CVS repository when they have something working.
Set up a working copy for a junior developer:
Take a look inside the 'junior' directory now. It contains a sub-directory named 'CVS' with files that are used by the CVS tool to keep track of where in the repository this working copy came from, and which revisions were checked out. You may look at these files, but do not edit them. Also, note that the name 'CVS' (and 'cvs' on MS Windows) are reserved for use by the CVS tool itself: you should never try to name your own files 'CVS' or 'cvs'.
Set up a working copy for a senior developer in a new directory named 'senior'. Otherwise, the steps are the same as above.
Edit the file senior/src/Fib.java to use this code for the main() function instead.
/** Print a table of requested Fibonacci numbers. */ public static final void main(String args[]) { for (int i = 0; i < args.length; i++) { int n = Integer.parseInt(args[i]); int fibN = fib(n); System.out.println(n + ": " + fibN); } }
At this time, the senior developer would compile and test this program until he/she was satisfied that the change was correct. Developers always compile and test code before putting it into the repository.
Commit this change to the repository:
Look inside the repo/src directory and you will see that the modification date of the 'Fib.java,v' file has changed. If you view (but do not edit!) 'Fib.java,v', you will see how CVS stores the history of changes in a compressed format called "reverse deltas".
At this time, the senior developer has made a change in his/her working copy and has committed that change to the repository. However, the junior developer's working copy still contains the original revision of the source file.
View the file junior/src/Fib.java. Note that it has the original contents from revision 1.1.
It is important that each developer's working copy DOES NOT automatically update to the latest version. That kind of constant change would make it very hard for a developer to code and debug their own work. In this case, let us assume that the junior developer has not done any work yet, and is happy to upgrade to the latest version.
Update the junior developer's working copy:
The junior developer runs the program and notices that it works great for small numbers, but that calculating the 30th, 40th, or higher Fibonacci numbers takes a long time. He/she decides to add timing statement to the program to understand exactly how long it is really taking.
Edit junior/src/Fib.java to use the following code for the main() function instead.
/** Print a table of requested Fibonacci numbers. */ public static final void main(String args[]) { for (int i = 0; i < args.length; i++) { int n = Integer.parseInt(args[i]); long before = java.util.Calendar.getInstance().getTimeInMillis(); int fibN = fib(n); long after = java.util.Calendar.getInstance().getTimeInMillis(); System.out.println(n + ": " + fibN); System.out.println((after - before) + " milliseconds"); } }
The junior developer finds this interesting, and enters an issue about it in the team issue tracking database. However, the junior developer cannot do anything about it yet, so he/she reverts to the most recent revision from the repository.
Now it is time for the junior developer to do some work on formatting the output of the program.
Edit the file junior/src/Fib.java to replace main() with the following code.
/** Print a nicely formatted table of requested Fibonacci numbers. */ public static final void main(String args[]) { System.out.println("N \t fib(N)"); System.out.println("---- \t --------"); for (int i = 0; i < args.length; i++) { int n = Integer.parseInt(args[i]); int fibN = fib(n); System.out.println(n + " \t " + fibN); } }
At this time, the junior developer would compile, run, test, and debug this change. When he/she is satisfied with the change, it can be committed to the CVS repository.
There how now been two changes to Fib.java by two people. Imagine that there had been many more changes made over a longer period of time by more than two people. It is important to be able to see the changes that have been made in the past. The differences between two revisions of a file are called a 'diff'.
View the diff between Fib.java revision 1.1 and the junior developer's working copy (which happens to still be the same as revision 1.3):
View the diff between Fib.java revision 1.1 and revision 1.2 in the repository:
View the log of commit comments for Fib.java:
Now pretend that you are the senior developer again. The senior developer's working copy contains revision 1.2 of Fib.java. He/she could update to the latest version, however often developers prefer to stay with a revision that is known to work so that he/she may focus on making a new change.
In this case, the senior developer has reviewed the issue about poor performance entered by the junior developer. The senior developer realizes that the current double recursion implementation will take nearly O(2^n) time or O(n) memory space on the stack to calculate fib(n). He/she decides to replace the recursive implementation of fib() with a more efficient algorithm based on the dynamic programming approach. This new implementation should take only O(n) time to calculate fib(n) while still only needing O(n) memory space.
Edit senior/src/Fib.java to replace fib() with the following code:
/** D.P. Table of all Fibonacci numbers that we have computed so far */ static long knownAnswers[] = new long[1000]; /** Indicates how much of the knownAnswers table has been filled in */ static int highestKnownAnswer = 1; /** Compute the Nth Fibonacci number */ public static long fib(int n) { if (!isKnown(n)) { fillTableUpTo(n); } return knownAnswers[n]; } private static boolean isKnown(int n) { return n <= highestKnownAnswer; } private static void fillTableUpTo(int n) { knownAnswers[0] = 1; // TODO: this really should only be done once knownAnswers[1] = 1; for (int i = highestKnownAnswer + 1; i <= n; i++) { knownAnswers[i] = knownAnswers[i-1] + knownAnswers[i-2]; } }
Also, edit senior/src/Fib.java to change the type of variable fibN from int to long. After this one-word change, the resulting main() should look like:
/** Print a table of requested Fibonacci numbers. */ public static final void main(String args[]) { for (int i = 0; i < args.length; i++) { int n = Integer.parseInt(args[i]); long fibN = fib(n); System.out.println(n + " \t " + fibN); } }
At this time, the senior developer would compile, run, test, and debug this change. He/she finds that it works much much faster and can easily calculate the 50th, 60th, or even 999th number in the Fibonacci sequence. When he/she is satisfied with the change, it can be committed to the CVS repository.
This attempt to commit will fail and the repository is not changed at all. The reason is that the senior developer's working copy does not have the most recent changes from the junior developer. CVS requires that each developer merge recent changes into their working copy before they can commit a new revision.
The senior developer must now update his/her working copy.
CVS how now merged the changes from the junior developer with the changes from the senior developer. The result is in the senior developer's working copy. View (but do not edit yet) the file senior/src/Fib.java. You will see both the dynamic programming code, and the table formatting code.
CVS is a special purpose tool for software development that knows how to merge changes in source code files line-by-line. However, there are two situations that CVS is not smart enough to handle:
If there were any syntactic conflicts and conflict markers, edit senior/src/Fib.java to remove them.
At this time, the senior developer would compile, run, test, and debug this change. When he/she is satisfied with the change, it can be committed to the CVS repository.
This time, the commit succeeds and a new revision is entered into the repository.
Now, switch back to the junior developer. Update his/her working copy to have the new algorithm.
Change the source code to have more elaborate command-line processing, including on-line help:
/** Print a nicely formatted table of requested Fibonacci numbers. */ public static final void main(String args[]) { System.out.println("N \t fib(N)"); System.out.println("---- \t --------"); for (int i = 0; i < args.length; i++) { if ("-h".equals(args[i])) { System.out.println("Usage: java Fib n..."); } else { int n = Integer.parseInt(args[i]); long fibN = fib(n); System.out.println(n + " \t " + fibN); } } }
That commit should succeed, because it was based on an up-to-date revision of Fib.java.
Now assume that the junior developer decides that handling the -h command-line option is not a good idea after all. He/she can merge a change into his/her working copy that will reverse the change just made.
First, view the diff between the two revisions:
Now, view the diff between the two revisions, in the opposite order:
Note that the order of the lines in the diff output is reversed.
CVS can take a set of differences between any two revisions, and apply those differences to a working copy. Consider the following (note that the command-line option is -j, not -r):
The operation above actually has no effect. That is because the changes made between revisions 1.4 and 1.5 are already in the working copy of the junior developer. CVS will not normally apply the same changes twice. However, reversing the order of the revision numbers asks CVS to merge in a diff that undoes the changes between 1.4 and 1.5.
Now view junior/src/Fib.java. The code to handle the -h option has been removed. This is one way to reverse a change in CVS. Here one developer has reversed his/her own change. Of course, a developer could manually edit a file to undo changes that they made recently. The power of a version control system like CVS is that large and complex changes can be reverse, changes from any point in history can be reversed (with the possibility of conflicts that would have to be resolved), and one developer can reverse or repeat the changes made by another developer.
So far, the change has only been reversed in the junior developer's working copy. The repository is never updated unless a commit command is done (one exception to that rule is when directories are added). At this time, the junior developer would compile, run, test, and debug this change. When he/she is satisfied with the change, it can be committed to the CVS repository.
This change makes a new revision number 1.6 in the repository. Revision is the same as revision 1.4, you can verify that with a diff command. Revision 1.5 has not been forgotten: everything is kept in the history.
Now the junior developer would like to produce javadoc API documentation for the Fib.java class and check it into CVS. He/she does that by running the javadoc command. The result is a new directory named junior/src/api/Fib.html.
For the purpose of this exercise, you do not need to actually run javadoc. Just create a new directory junior/src/api and create a file named Fib.html inside that directory. The contents of Fib.html do no matter for this exercise.
The junior developer now adds the directory 'api' and the file 'Fib.html' to the CVS repository.
Note that this immediately adds the directory to the CVS repository: there is no need to do a CVS commit. If you look inside the 'repo/src' directory, you will see an 'api' directory. This is an exception to the normal rule that it takes a commit to modify the repository.
This operation does follow the rule that only a commit changes the repository. The Fib.html file is only marked as added, it is not actually added into the repository until a commit is done.
Now the file Fib.html is actually added to the repository with revision 1.1. If you look in the directory 'repo/src/api' you will see a history file named 'Fib.html,v'.
Note, this was an example of adding a text file. CVS treats text files differently than binary files (like images or MS Word documents). When adding binary files, use the '-kb' command-line option or "Add binary" menu item.
At this point, the junior developer feels that the team has produced a good first release of the Fibonacci number application. He/she would like to mark this as the first milestone in the development process. This will be done by creating a CVS symbolic tag.
The tagging process immediately updates the CVS repository, there is no need to commit. The output of the cvs tag command lists the names of the files that were tagged, each line starts with a "T" for "tagged". Directories themselves are never tagged: only the files in each directory are tagged.
The junior developer sends out an email announcing that the first milestone has been reached, and that the team should celebrate.
The senior developer sees the email announcement and decides to update his/her working copy to have exactly the set of files that were tagged as milestone_1.
Now the senior developer's working copy has exactly the revisions of the files that were tagged, even if later revisions (e.g., that add a few feature for a future milestone) had been added to the repository after the tag was made.
The senior developer reviews these revisions of all the files and notices a mistake: the Fib.html file should not be stored under version control. As a rule, the output of a program (such as javadoc or javac) are never put into CVS. Instead, only the input to that program (e.g., the Fib.java source code) is placed in CVS and the entire development team agrees to regenerate the output from the source code (e.g., by running javadoc themselves). This is a good idea because it prevents the possibility that two files in the repository will be out of sync with each other. If the process of running javadoc is complex, it can be written into a build file (this will be covered in a later exercise).
The senior developer asks the junior developer to remove the Fib.html file from CVS.
The junior developer removes the file from his/her working copy:
Just as with adding a file, removing a file does not update the repository until a commit is done.
This creates a new revision of the Fib.html file numbered 1.2. This revision is marked as "dead" meaning that the file has been marked a removed. If you look in directory 'repo/src/api' you will see that file 'Fib.html,v' has been moved to a new subdirectory named 'Attic'. BTW, 'Attic' is also a reserved name in CVS, so you should never name a file or directory 'Attic' (or 'ATTIC' or 'attic' if anyone on the development team is using MS Windows).
There are two things to note here:
So far in this exercise you have used a directory on your local machine as the repository. A more common way to use CVS with a team of developers is to set up a CVS server machine with a repository that is accessed over a LAN or the Internet.
The senior developer checks out the source code of an open source project from a remote CVS server:
Now the senior developer has a working copy of the latest source code for the jdom project on his/her local hard disk. He/she can also view logs and diffs, but the anonymous account is not allowed to commit changes to the repository.
sample use case templateexample test plan templateProject plan template