The author works as a computer and electronics engineer with the U.S. Department of Defense. He has a PhD in Electrical and Computer Engineering from Louisiana State University. His interests include computer vision, artificial intelligence, software engineering, and programming languages.
Have you ever wanted to go back to a version of a program you wrote six months ago? You've changed the program, thrown out your printouts, written over your backup floppies, and you have no hope of retrieving the version you want. A revision control system can solve this problem for you. A revision control system (RCS) allows you to save source code as you develop it and retrieve any past version later.
This article discusses and implements a simple revision control system for MS-DOS. It shows how an RCS can help you in your work, demonstrates the basic commands, and looks at the source code for the RCS. The RCS written by Walter Tichy (Sept. 1982, July 1985) for UNIX systems inspired this work.
Benefits of an RCS
Programmers and authors need a revision control system because source code and documentation change constantly. We are always adding or removing a feature from a program, then often change our minds about a revision. We sometimes need to go back to a feature we had months ago, but have since deleted. Unfortunately, that feature is gone forever and will take a week to recreate.Revisions cannot be tracked by regular backups. Backups use the same name for the copy and the original, but you need different identifiers for different versions. Backups do not help if your source changed several times between backups. You lose the intermediate versions. Backups do not explain why and how your source changed. And the greatest deficiency, backups lose the versions that are older than your oldest backup.
An RCS is a simple answer to these problems. An RCS keeps all of the old versions of your source, and records when and why you made the changes. It keeps these in a separate RCS file in a separate directory out of harm's way. You can retrieve any previous version of your source and use it. When you do your weekly backup, backup the current source code files and the RCS files.
Processing a File
The RCS presented here has six separate programs. They are:
I will process an example, a_file (Figure 1) , to illustrate how each command works and helps keep track of revisions.
- checkin
- checkout
- revlog
- revdiff
- revup
- prunerev
You begin by checking a_file into the RCS. Type:
checkin a_filecheckin prompts you to enter comments or header lines and then puts the first version of a_file into its own RCS file. The RCS file is in an RCS directory below the current directory as shown in Figure 2. checkin inserts the letter v before the filename and uses va_file as the name for a_file's RCS file.checkin removed a_file from the current directory (moved it out of harm's way), so before revising, you must check it back out. Check out a_file by typing:
checkout a_filecheckout reads the RCS file for a_file and brings back the original text. You can edit a_file (such as the small change shown in Figure 3) , then check the changed a_file back in by typing:
checkin a_filecheckin prompts for header lines and stores the new version of a_file in its RCS file. checkin retains the prior versions and header lines for a_file.The revlog command displays the header lines and dates of all revisions for a file. Figure 4 shows the output of typing revlog a_file. It lists the information for three versions of a_file.
The revdiff command displays the difference between the current copy of a file and any version that you checked into the RCS file. Figure 5 shows the difference between the current file and the latest version in the RCS file after typing revdiff a_file.
The next two commands revup and prunerev help you maintain your RCS files. revup allows you to move up the latest version to any number you desire. If you use a_file with several other files and you wanted them all to be at version 10, you would type revup a_file 10. Figure 6 shows how the revision log would appear after using revup. This permits you to keep related files coordinated at a desired version number.
The prunerev command deletes unwanted versions from the RCS file. The RCS file could grow too large over time. prunerev prunes or reduces the RCS file by deleting versions you select. Figure 7 shows the revision log for a_file after pruning the initial version with prunerev a_file 1. Use prunerev with caution. Once you prune a version from the RCS file it is gone and you cannot retrieve it.
Implementation
Figure 8 shows the format of the RCS files. The first line is a FIRST_LINE constant. The next lines hold the version number and the date you checked the version into the RCS system. The next section holds the header lines you enter when you call checkin. The header lines section ends with a line containing a DELlMETER constant. The next part contains the complete text of the source code or document and ends with another line containing the DELIMETER constant. The next version begins with the FIRST_LINE constant and continues like the first.Figure 9 shows the RCS file for the a_file example given earlier. It holds three versions of a_file. You can see the FIRST_LINE constant, version number, date, header lines, DELIMETER constant, and so on for each of the three versions.
Note, this RCS only works for text files because it reads and writes lines of text. It will not work with executables or data files in its present form.
I wrote the programs in C using Microsoft's version 6.0 compiler. The code does not contain any compiler dependent statements so it should port to other C compilers easily. The include file (Listing 1) includes the needed .h files and defines the FIRST_LINE and DELIMETER constants. The FIRST_LINE and DELIMETER constants would probably not appear in any normal text.
Listing 2 contains the checkin program. The command line is checkin [-l] source-file. checkin asks you for header lines, then writes these and the text of the source file into the RCS file. If you enter checkin a_file, checkin checks in a_file and removes it from the current directory (moves it out of harm's way). If you enter checkin -l a_file, checkin checks in a_file and leaves it for further editing.
The first section of checkin interprets the command line. The call to create_rcs_file_name (Listing 8) constructs the name of the RCS file. (rcsutil.c in Listing 8 holds functions common to several of the RCS programs.) If the RCS file does not exist (result == -1), checkin creates it, gets the header lines from the user, and copies the input file to the RCS file. If the RCS file does exist (result == 0), checkin reads the latest version number from the RCS file, increments the version number, and copies the input file to the RCS file. checkin copies via a temporary file. checkin copies the input file to the temporary file by calling copy_source_to_rcs, and then the RCS file to the end of the temporary file. checkin closes the input, RCS, and temporary files, uses DOS to copy the temporary file to the RCS file, and then deletes the temporary file. If desired (leave_source == 0), checkin removes the input file.
The other functions in Listing 2 are short utilities used by checkin. copy_source_to_rcs copies lines of text from one file to another. get_latest_ revision_number reads two lines of text and converts the second from a string to a number.
Listing 3 shows the checkout program. The command line is checkout [-r#] source-file [output-file]. checkout will pull any version already in the RCS file and place it in either the source file or an output file of a different name. For example, checkout -r3 a_file b_file pulls the third version of a_file from the RCS file and places it in b_file. If you do not specify -r#, checkout pulls the newest version from the RCS file. If you do not specify an output file, checkout puts the version in the source file.
The first section of checkout interprets the command line to determine which version to check out and which destination to use. After this, checkout calls create_rcs_file_name to construct the name of the RCS file and then opens the necessary files.
The next section of checkout copies the desired version from the RCS file to a destination file. If the user specified an output file (extra_file == 1), checkout copies the version to the output file. If the user did not specify a particular version (rev == 0), checkout copies the latest version by calling copy_latest_rcs_to_source (see Listing 8) . If the user specified a particular version, checkout finds that version by calling go_to_correct_rev (see Listing 8) and then copies it by calling copy_rcs_to_source (also in Listing 8) . If the user did not specify an output file (else use source_file), checkout performs the same steps with the source file as the destination.
Listing 4 shows the revlog program. The command line is revlog sourcefile.revlog reads through the entire RCS file and prints out all the version numbers, dates, and header lines. The bulk of revlog reads through the RCS file looking for header lines. A FIRST_LINE constant indicates the following lines down to a DELIMETER constant should be printed. revlog prints these lines and continues reading until the end of the file.
Listing 5 shows the revdiff program. The command line is revdiff [-r#] [-r#) source-file. revdiff uses the DOS utility fc to show the difference between the current source file and any previous version or between any two previous versions. revdiff a_file will show the difference between the current version of a_file and the last version checked in. revdiff -r2 a_file will show the difference between the current version of a_file and the second version checked in. revdiff -r2 -r4 a_file will show the difference between the second and fourth versions of a_file that were checked in.
The first section of revdiff interprets the command line to determine which versions of the source file to compare. revdiff then acquires the name of the RCS file and opens it. revdiff compares versions of the source file by using temporary files. It opens one or two temporary files (output_file and output2_file) and copies versions from the RCS file to these for comparison. If the user wants to compare the source file with one version from the RCS file (extra_file == 0), revdiff finds and copies the desired version from the RCS file to a temporary file output_file). Otherwise, revdiff finds and copies two versions from the RCS file to two temporary files. revdiff closes all the files and compares them by calling the DOS fc utility.
Listing 6 shows the revup program. The command line is revup sourcefile #.revup obtains header lines from the user and then pushes up the latest version in the RCS file to the version number entered by the user. If the user enters revup a_file 10, revup obtains header lines and copies the latest version from the RCS file up to version 10.
Just like the other programs, revup interprets the command line, obtains the name of the RCS file name, and opens it. revup uses a temporary file to copy up the latest version of the RCS file. revup calls get_header_lines (Listing 8) to obtain header lines and puts them into the temporary file. revup then calls copy_latest_rcs_to_source to move the latest version from the RCS file to the temporary file. Next, revup seeks back to the beginning of the RCS file and copies all of it to the end of the temporary file. The final section uses DOS to copy the temporary file back to the RCS file and delete the temporary file.
Listing 7 shows the prunerev program. The command line is prunerev source-file rev-number. prunerev deletes a version from the RCS file. You must use it with caution because once you delete a version you cannot recover it. Typing the command prunerev a_file 1 removes version 1 from the RCS file.
prunerev interprets the command line, gets the RCS file name, opens the RCS file, and opens a temporary file. prunerev well copy all but the unwanted version from the RCS file to a temporary file and then copy back to the RCS file. The bulk of the program is in the function copy_wanted_revs__to_temp_file. It has a nest of whiles, ifs, and elses that read through the RCS file and copy selected lines to the temporary file. The logic is to read and copy until you hit a FIRST_LINE constant. If the version number is the one you want to prune, then read through it without copying. If the version number is not the one you want to prune, then read and copy it. After this finishes, prunerev closes the files, uses DOS to copy the temporary file to the RCS file, and deletes the temporary file.
Listing 8 shows the file rcsutil.c. This file contains functions used by several of the RCS programs. create_rcs_file_name constructs the name of the RCS file by inserting rcs/v into the proper place. It turns a_file into rcs/va_file and c:/text/a_file into c:/text/rcs/va_file. rev_number takes in a string and returns the integer value of the number. If the input is the string 12 the output is the number 12. The function replace_slash replaces slashes (/) with back slashes (\). The RCS programs need this because the DOS utilities they use interpret all slashes as options.
go_to_correct_rev searches through an RCS file and stops when it finds the desired version. It is similar to copy_wanted_revs_to_temp_file in how it looks for the FIRST_LINE constants and compares the version numbers. go_to_correct_rev reads past the version number, date, and header lines and quits when it points to the start of the text for the desired version.
copy_rcs_to_source and copy_latest_rcs_to_source are very similar. copy_rcs_to_source copies text from an RCS file to another file. It assumes it begins reading at the desired text and stops copying when it hits a DELlMETER constant. copy_latest_rcs_to_text a assumes it begins reading at the start of an RCS file. It reads down until it hits the first DELIMETER. The line after this first DELIMETER is the first line of text in the latest version. The function copies until it hit the next DELIMETER and then quits.
get_header_lines obtains header lines from the user and writes them to the RCS file. It reads from the keyboard until it hits a line whose first character is a period.
Conclusions
Programmers and authors need a revision control system to help manage the changing nature of their work. The simple RCS presented here works effectively for basic text files and enables you to save and retrieve versions of your work. Since writing it, I have used it for several months on various projects. It has paid for itself in time and peace of mind.
References
Tichy, Walter F. September 1982. "Design, Implementation, and Evaluation of a Revision Control System," Proceedings of the Sixth International Conference on Software Engineering. IEEE Computer Society Press, pp. 58-67.Tichy, Walter F. July 1985. "RCS A System for Revision Control," Software Practice and Experience, Vol. 15, No. 7, pp. 637-654.