The goal of this lab is to practice object-oriented design on a problem that will help you review the object-oriented features of Java and prepare for an exploration of object-oriented design techniques. This lab will also introduce the use of Mercurial, a revision control system.
A Revision control system or version control system is a system for tracking changes to files and is an indespensible tool for working with a team on a shared software project.
Suppose two users are editing the same file A. This might be a source file for a software project, but it could be a word processing document, a spreadsheet, and image, or anything.
User 1, after editing the file, saves it and thereby overwrites A to produce A1. Suppose then user 2, who is still looking at and editing the original version of A saves a new version (A2), wiping out user 1's changes.
To prevent something like this, we could implement some sort of locking mechanism, for example, user 1 locks file A until he is finished. However, then so work could be done in parallel, and in deed some parallel work on the same file is harmless and necessary. Also, a locking mechanism could easily be misused: what if user 1 locked a file and forgot to unlock it before going on vacation?
Moreover, some conflicts aren't merely at the file level. Suppose files A and B depend on each other—for example, suppose they are classes that call each other's methods. If they are edited separately but at the same time, the code would break because each depends on the other's old version.
Finally, in addition to coordinating changes made by various team members, there is sometimes a need to bring back an old version of a file or group of files, or to branch the project into concurrent versions (for example, to experiment with two approaches for implementing a feature) and later to merge them together.
The solution to scenarios like this is a revision control system, a software package that tracks versions of shared files, keeping files and a log of changes in a repository.
Using a revision control system, user 1 and user 2 can each check out their own local copy of the file and edit it, commit their changes to the repository, and update their version from the repository.
In this lab you will implement a simple revision control system as an exercise to practice writing classes and linked structures. You will also have an opportunity to use Mercurial, a real revision control system, as you work on your program.
Object-oriented design is concerned with the relationship among classes and other types—which classes implement which interfaces, which classes serve as components to other classes (by means of instance variables), and (as we will learn about soon) which classes share code by inheritance or composition.
You will practice doing object-oriented design (and think about how revision control works) by designing your own simple revision control system. You will write a program that presents the user with a simple text area to edit. The user can "commit" changes made to the text area; these changes are saved in memory and assigned a version number; and the user can then go back to them later.
The user interface is a window that looks like this:
The "current" and "max" indicator at the top should display the version number of the currently displayed version (besides any changes that have been made since the last commit or recall) and the highest version number that has been assigned, respectively. When the program starts up, these should read "Current: 0" and "Max: 0", not "Current" and "Max" as the image shows.
When the user enters some text and then presses the "commit" button, the current and max counters should increment. Suppose the user then enters more text and presses "commit" again. If the user then presses the "<" button, the text should then revert to its state when the "commit" button was pressed the first time. Pressing the ">" button will change the text to what it was when "commit" was pressed the second time. In this way the user can shuffle though versions. Pressing "<" when the current indicator is 0 does nothing, and pressing ">" when the current indicator is equal to the max does nothing.
That specification would be adequate (and the problem very easy) if all the revisions were made in a linear fashion, and the user commited changes made only on the latest version—in which case we would have an organization like
But what should happen if the user flips back to an earlier version, makes changes, and commits? Should the new version simply be put at the end of the chain, even though it does not descend from the latest version? Should the new version replace the version that used to come after the version that was edited, and the rest of the chain be lost?
Instead of the two options mentioned, your program should consider the series of versions to branch at this point. The new version will receive the next verions number (ie, the max version plus one), but instead of conceptually coming after the most recent version, it will be considered to branch off from the version it was derived from.
Suppose the user takes the collection of versions illustrated above, and then navigates back to version 3, makes a change, and commits (creating version 6). Then he or she makes another change (to the new version 6) and commits it as version 7. Then he or she navigates to 4, changes and commits, navigates back to 6 and makes to changes in a row, and navigates to version 5 to make and commit a final change. That would result in the following tree of versions.
At this point, your version navigation feature should work as follows: When at version 6, pressing the "<" button will bring up version 3, the parent. Pressing ">" (at version 6) would bring up verion 9, the most recent descendant. To move among "sibling" versions, the user would use the "-" button; so, pressing the "-" button at version 6 would bring up version 4, and pressing "-" again would bring back version 6. If there are more than two siblings, the "-" should move from the most recent to the oldest, in a circular manner.
Mercurial (program hg
) is one of many revision
control tools that are readily
available. We use Mercurial in this class because it is easy to start
out with, and the ideas you learn using Mercurial will carry over if
you later have a reason to use a more sophisticated (and complicated)
tool, such as git
.
To work with Mercurial, you need to understand some basic vocabulary. A place that stores the full history of your collection of files is called a repository. The repository efficiently stores multiple revisions of the whole collection, so that you can check out any revision that you want. Checking out a revision produces a working copy that you can modify. After you have made changes to the files in your working copy, you can commit your changes to create a new revision within the repository. You can also discard changes in your working copy by reverting to one of the stored revisions.
Mercurial hides a copy of a repository inside your working copy; in this class, you will often get your starting point—a working copy with its own repository—by cloning an existing repository. So the simplest work pattern is something like:
If at some point while you are editing you discover that you have made a mess, you can revert individual files or the entire working copy to some committed revision. Mercurial also provides ways to ask what files the working copy have been changed and how.
For more complicated scenarios, such as when you are collaborating with others, Mercurial provides efficient ways to communicate revisions between your copy of the repository and those of your partners. It also provides mechanisms for merging changes that have been made separately to form a single new revision. But we don't need to worry about that right now.
The lab will walk you through the simple work pattern.