Reducing the complexity of software configuration

3 downloads 0 Views 268KB Size Report
des exécutables, selon le case, des modules composants. Un module Marmoset est réalisé comme un directory unix contenant un nombre de fi- chiers, certains ..... the user to write using Roman letters and have them translated into Cyrillic.
Reducing the complexity of software configuration∗ John Plaice William W. Wadge D´epartement d’Informatique Dept of Computer Science Universit´e Laval University of Victoria Qu´ebec, Qu´ebec P.O. Box 3055, Victoria, B.C. Canada G1K 7P4 Canada V8W 3P6 e-mail: [email protected] e-mail: [email protected]

Abstract Marmoset is a simple unix-based tool for creating and maintaining large systems built from reusable “modules”. Marmoset provides unix commands for creating and editing modules, and for configuring modules so that they produce object code, libraries, or executables, as the case may be, from the component modules. A Marmoset module is implemented as a unix directory containing a number of files, some of which are supplied by the user, and others of which are generated automatically. For a given language, a user will supply a number of component files, including an “import” list of other modules which are required for this module to be configurable. The generated files might contain, among other things, external declarations and compiled code. The Marmoset link command automatically identifies all those modules imported directly or indirectly by the root module, and ensures (by reconfiguring if necessary) that all the output files are up to date. Should a particular version of a module be looked for, then the most relevant versions of the imported modules will be sought, and the most relevant versions of component files will be used. Marmoset significantly reduces the cost of software configuration, and thereby increases software reliability. Marmoset est un outil unix simple pour la cr´eation et la gestion de grands syst`emes construits avec des modules r´eutilisables. Marmoset fournit des commandes unix pour la cr´eation et l’´edition de modules. et aussi pour configurer des modules afin qu’ils puissent g´en´erer du code object, des biblioth`eques ou des ex´ecutables, selon le case, des modules composants. Un module Marmoset est r´ealis´e comme un directory unix contenant un nombre de fichiers, certains fournis par l’utilisateur et d’autres g´en´er´es automatiquement. Pour un langage donn´e, un utilisateur fournira un certain nombre de fichiers composants, incluant une liste d’importation d’autres modules qui sont requis afin que ce module soit configurable. Les fichiers g´en´er´es pourraient contenir, entre autres, des d´eclarations externes et du code compil´e. La commande de configuration Marmoset identifie automatiquement toutes les modules qui sont import´es directement ou indirectement par le module racine, et s’assure, en reconfigurant si n´ecessaire, que tout est a ` jour. Si une version particulaire d’un module est recherch´e, alors les versions les plus appropri´ees des modules import´es sont recherch´ees, et les versions les plus appropri´ees des fichiers composants sont utilis´ees. Marmosetr´eduit de mani`ere significative le cout de la configuration de logiciels, et donc augmente la fiabilit´e des logiciels. ∗ Presented to the 1st African Conference on Research in Computer Science, October 14–20, 1992, Yaound´ e, Cameroon.

1

1

Introduction

It is clear that it is very difficult to develop reliable software. A typical computer system can have several thousand modules, be developed by several hundred programmers, and evolve significantly over time. How is it possible to develop a reliable system in such a context? The first obvious answer is to ensure that the system is modular. During the design of the system, the problems are decomposed into a set of relatively independent problems, with interfaces between these problems. Each of these problems can then be decomposed, and the process continues until the tasks at hand are directly programmable. The basic idea is that if components do not know about the actual implementation of other components, then the risks of error are reduced. Similarly, one only attempts to tackle problems which are of a manageable size. Finally, not every programmer has to know what every other one is doing. However, a highly modular system could contain an unbelievable number of modules. The bookkeeping necessary for good software configuration becomes itself a daunting prospect. As components are fixed, as specifications are changed, how are the changes recorded in the system? And how much bookkeeping has to be done when the change is made? To alleviate the problems created by the first solution, another proposal is to encourage software reusability. Should several components of a system or several systems share certain components, then software becomes cheaper, the total number of modules to maintain is reduced and reliability is increased, since code that is used more often tends to be better field-tested. But for software to be truly reusable, then the different components or systems must be using exactly the same code, because if they are not, sooner or later, someone is going to modify one of the copies, and the result will be many subtle clones, all similar, yet each different. To avoid this problem requires yet more reliable bookkeeping work. To deal with the problem of evolution, version control systems such as sccs [22] and rcs [28, 29] have been developed for files. These systems allow for keeping track of modifications made to files, and allow one to keep track of the entire history of a file. But what sort of bookkeeping facilities are available? Essentially, software configuration systems based on make [11] or similar systems. Most of these systems deal with modules and the files which make up the modules. Should a module use other modules, through some sort of “importing” process, then that has to be written explicitly. When versions and variants of the components of the module are written, they must be incorporated as well. And the result is lots of tedious work, very error-prone, which discourages the use of variants, modularity and reusability, and therefore reduces the reliability of software. Some configuration management systems, such as System Modeller [14, 23], GANDALF [7, 19, 26, 27], Adele [1, 2, 9, 10], DSEE [15], Jasmine [18], shape [16, 17] and Odin [5], allow for variants of implementations of modules, and so in configuring a system, one can “pick and choose”, having version V of module M “require” version V 0 of module M 0 . The bookkeeping problems become even more daunting! But, software is more than just a collection of modules. It is the configuration of those modules, the interconnections, that make it software. In this paper, we present a generalization of the make program, Marmoset, which can be used to build complete systems, which includes a sophisticated system of version control, for complete systems as well as for individual components, and in which the overhead for software configuration is minimal. It is a system which encourages software reusability, as well as a high degree of modularity, since, even for configuration management, a module need only refer to the modules that it directly needs; adding a module requires little new bookkeepping. The underlying concept of Marmoset is that it treats, as much as possible, all levels of a system in an orthogonal manner. One can refer to the version of a complete system, of a library of libraries, of a library, of a module or of an individual file. Marmoset will go and find the most relevant components and rebuild the required subsystem. Similarly, a subsystem that “imports” certain other subsystems need only refer to those subsystems which are directly needed. Marmoset will automatically find all the components which are needed by the “imported” subsystems and will ensure that everything is properly configured.

The result is that in a system using Marmoset, even with very high modularity and very complicated sets of versions and variants, the equivalent to a makefile is incredibly simple. Marmoset generalizes Sloth [20], a programming environment for “C modules”, as well as Lemur [21], Sloth with versions. Unlike the other two systems, Marmoset allows modules for different languages, and even allows them to be mixed. The addition of versions to Marmoset in no way affects the way Marmoset is run. Therefore, section 2 presents Marmoset without versions: when modules “import” each other, Marmoset computes the transitive closure of the induced dependency graph, and configures each module in turn. Section 3 presents the version language, as well as how versions are used. Section 4 concludes.

2

Making structured systems

As soon as a software system is composed of more than about three files, and often before, it becomes useful to have a system whereby compilations, and creations of libraries and executables, are done automatically as modifications are made to the source files. When the system has just one module, the task at hand is straightforward with a system such as make [11]. However, when other modules are to be imported, then the makefile must be imported. Every time that the list of modules to be imported changes, the makefile has to be changed. But it is easy to forget such a task, and it is possible that an error is made. Furthermore, when one is dealing with a programming language such as C, where no modular facilities are offered in the language itself, significant amounts of bookkeeping have to be done. C files are generally split into two, a .h file and a .c file, where the former contains type and interface declarations so that the routines defined in the .c file can be used in other files. However, the declarations in the .h file might require the declarations in other .h files. It is a non-trivial task to ensure that all of the .h files which need to be #include’d are, in the right order and only once. Similarly, many of the routines and data structures in each .c file require some sort of initialization. So, once again, one must ensure that the initialization routines for all of the .c files are called, and in the right order. There is a related general problem. Given an import relation, how does one avoid the, for example, error module from being asked to recompile itself one hundred times, just because it is in the import-list of one hundred other modules? Attempts to reduce the bookkeeping necessary to handle these situations have been quite limited. Most work attempting to make the C programming language more modular works at the language level. Modular C [3, 4] introduces a number of C macros to give the impression of import lists and export lists, among other things. Dutta [8] does the same thing to create public and private objects. Iannello [13] shows how abstract data types can be programmed in C. And, of course, C++ [24, 25] adds classes to C, thereby allowing for inheritance and improved data encapsulation. Gorlen [12] shows how C++ can be used to create a Smalltalk-like hierarchy of classes. However, there seems to be little work on how to arrange modules in such a way that includes are not a headache. Coggins and Bollella [6] touch the issue, but their solution requires a global include file which knows about all of the modules that can be used. In our system, everything is built automatically through the automatic creation of a uselist. The authors developed a C programming environment, Sloth [20], in which users can write “C modules”. Each directory is reserved for one C module, or .c file. For each module, the user can provide, a define.i file with the type declarations and the #define’s particular to that module, a var.i file for declarations of those variables which are visible outside the .c file, a proc.i file to put all the function declarations local to the module, and a body.i file for the initialisation routines. The user also provides a import file to declare what other modules are needed for this one to run. Sloth then automatically builds up the dependency graph between the modules, using the import lists, and creates, for each module, a uselist file. It then puts the .i files together to

form a prog.c file, which is then compiled. The right define.i files are included, in the right order, by the right prog.c files, and the initialisation routines are called in the right order. One never has to write a makefile for Sloth. See [20] for more details. Since there is little bookkeeping cost, there is very little cost to making a system more modular. Marmoset takes the ideas of Sloth and generalizes them so that they can be used for any programming language. As for Sloth, each module is associated with one unix directory, and the import file states which modules are needed for this module to be able to work. However, a configure file must be added, at least conceptually, to each module. The configure file, which replaces the makefile, states how the objects within a particular module are related, and how one is produced from the other. However, unlike for a makefile, the import’ed modules are themselves automatically configured, and the objects within an import’ed module can be referred to in a configure file. For example, here is the configure file for a typical Sloth module: prog.o: prog.c $(CC) $(CFLAGS) -c prog.c prog.c: define.i var.i proc.i body.i @uselist/define.i @uselist/extern.i sed -f $(SLOTH_EXTERN) var.i >extern.i sed -f $(SLOTH_INCLUDES) uselist >prog.c uselist: ^import The last line tells Marmoset to compute the transitive closure of all import lists, putting the result in file uselist. @uselist/define.i expands into dir 1 /define.i · · · dir n /define.i, where the uselist file contains dir 1 · · · dir n . The main module would have a slightly more complicated configure file: main: main.o prog.o @uselist/prog.o $(LD) $(LDFLAGS) main.o prog.o @uselist/prog.o main.o: main.c $(CC) $(CFLAGS) -c prog.c main.c: uselist sed -f $(SLOTH_MAIN) uselist >main.c prog.o: prog.c ... Of course, there is no reason to repeat the same configure file over and over again. So, a configure file with just the word Sloth or just the word SlothMain would automatically refer to the above configure files, and once again the user would have very little overhead, thereby increasing the reliability of the software. A small example may help clarify the notion of a module import relation. Suppose that we have a desk calculator program in the calc module. Our calculator manipulates expressions, uses a stack, and performs various mathematical operations. Suppose further that these functions are not implemented in calc, but that instead calc imports procedures, types, constants and so on found in modules expr, stack, and math. In our terminology we say that calc imports the modules expr, stack and math; that these three constitute the import list of the calc module. Next, suppose that both expressions and stacks are implemented using lists, as provided by a list module; and that the list module in turn imports a heap module.

Finally, suppose that we have a separate line editor application in a module ed; that the editor uses stacks and arrays; that arrays are provided by a module arr which also imports heap; and that the editor shares the stack module with the desk calculator. If there are no other imports we can describe the import relation between these eight modules with the following diagram:  heap 

 SS S     arr math list 



 SS  SS  S   S   expr stack 



SS  SS  S   S  calc ed 







Notice that the calculator and editor share only the stack module directly. One can imagine having several different kinds of import list, depending on the kinds of module being imported (C modules, libraries, etc.). Suppose for example that we wanted to have C libraries as well as C modules. Then the “main” module could have a libimport file, and a libuselist file which would compute the transitive closure of the dependencies among libraries. Each library could, on the other hand, use the ordinary import file for including all the C modules that make it up. Marmoset therefore permits, in an elegant manner, the configuration of systems using several different languages. If a discipline is well-defined, only one configuration file would have to be written for any given language.

3

Versions

Software systems, like most other things, evolve. Bugs must be fixed, specifications change, hardware platforms change, optimisations are made, and different related systems may all be simultaneously maintained. To handle this evolution requires a version control system. A version control system uses a language in which to express the different possible versions that a file, a module or a system may take. sccs [22] and rcs [28, 29], for example, use strings of numbers, such as 1.2.1.1, to describe the possible versions that a file may take. Many configuration tools attempt to combine the automatic regeneration capacities of make with version control. Adele [1, 2, 9, 10], for example, allows identifiers to be used for the description of variant implementations, and uses an rcs-like system for the versions of a given variant, and a system can be automatically reconfigured. In most of these systems, the possible versions that a system can have form a tree structure. Yet, one of the most important aspects of version control is to be able to merge the work of several people. For example, one person might have worked on the French variant, and someone else on the graphics variant. To get a version with French and graphics, the work would have to be merged. Regardless of how the merge took place, the tree structure of typical version languages cannot model the fact that a merge took place. And so, comments must be inserted manually to describe the procedure, hardly encouraging reliable software. In addition, the concept of variant is not fully developed. In the discussion on variants in [30], no one could give a definition of variant. In [17], we read, “We suspect that it is still an unsolved problem of software engineering to produce portable software designs in the sense of predicting

and planning the possibility that certain modules of a system sprout variant branches. It is still a fact that variants happen.” Perhaps the problem is that variants must be planned, instead of being allowed to happen. Furthermore, one should be able to refer to the version of a complete system, in the same language as one does for the versions of components. Furthermore, the possible versions for one component of a system rarely have any relationship with the possible versions for another component. For example, should there be a fast version for each of modules A, B and C, there is no reason to believe that they have anything to do with one another. Once again, the fact that there is no relationship between a component and the larger entity of which it is a part means that very complex bookkeeping must be done to ensure that a particular version of a complete system is properly configured. In Marmoset, as in its predecessor, Lemur [21] (Sloth with versions—it only worked for C), there is a language to describe the versions of a system. The same language is applicable at every level of the system, unless local versions are used. The scope of a version identifier is the entire system, unless a local identifier is used to mask it. When a particular version V of a complete system is desired, Marmoset will use the “variant substructure principle”, which states that the most relevant (to V ) versions of components will be selected to form that system. The concept of “most relevant” is formally defined below. For more discussion about the version language, see [21]. To describe the possible versions that a system may take, the following language is used: V

::=  | N | x | V %V | V &V | V + V

N

::= n | N.n

Upon this language is defined a well-order, written v, with minimal element , along with an equality relation ≡. “Most relevant” refers to the order v. As for any well-order, it satisfies the following properties: vV V v V 0 V 0 v V 00 V v V 00 The N in the version space refers to numeric versions, which would normally be used to record the incremental development of a system. The refinement order is the dictionary order: n≤m nvm N v N.n N v M N 6≡ M N.n v M So, for example, 1.2.3.4 v 1.3 v 2.4.5. The % operator is used for subversions. V %V 0 refers to the V 0 subversion of version V . The refinement relation is as follows: V v V %V 0 In addition, the % operator is considered to have  as identity and to be associative:

0

%V

≡ V

V %

≡ V

(V %V )%V

00

≡ V %(V 0 %V 00 )

The meet operator, &, defines the greatest lower bound of its two arguments: V &V 0 v V It is idempotent, commutative and associative, and left-distributes %: V &V



V

0



V 0 &V

(V &V 0 )&V 00



V &(V 0 &V 00 )

V %V 0 &V %V 00



V %(V 0 &V 00 )

V &V

i.e., V &V 0 is the greatest version which is less than or equal to each of V and V 0 . The join operator, +, defines the least upper bound of its two arguments: V vV +V0 V1 v V10 V2 v V20 V1 + V2 v V10 + V20 It is idempotent, commutative and associative, and left-distributes %: V +V

≡ V

V +V0

≡ V0+V

(V + V 0 ) + V 00

≡ V + (V 0 + V 00 )

V %V 0 + V %V 00

≡ V %(V 0 + V 00 )

i.e., V + V 0 is the least version which is greater than or equal to each of V and V 0 . Suppose that some software has been written with an English-language interface. Someone wishes to have a bilingual Bulgarian+Russian interface. Now, for some files, there would have to be a Russian version, for others a Bulgarian version, and for many a Bulgarian+Russian version. Along with the standard translation, a transliteration routine needs to be added, to allow the user to write using Roman letters and have them translated into Cyrillic. For example, by writing “Dzhon” (John), the result would be “Äæîí”. This file should also be usable should a unilingual Bulgarian or Russian version be needed. In this case, then the transliteration scheme should have version Bulgarian&Russian. Suppose then that someone wishes to modify the original English interface so that it uses graphics. So then a graphics version is written, and some of the files will then have graphics versions. And then someone says that graphics are nice, but they aren’t much use if there is no mouse. So the graphics%mouse version is developed. Finally, the Russian and Bulgarian users insist on having graphics. So a Bulgarian+Russian+graphics%mouse system is developed. Marmoset allows any file to have versions, including the configure, import and (described below) version files. For example, version fast of file proc.i becomes file proc fast .i and version 1.3 of file configure becomes file configure 1.3 . In the presence of multiple versions, the configuration of a system proceeds exactly as described in the previous section, with the added stipulation that the most relevant components will be sought. Those components automatically generated by Marmoset will have as version, not the version that was looked for, but rather the most general version corresponding to the components making it up. More formally, suppose that one asks for version V of system S, which has components C1 , . . . , Cn . For each of the components Ci , version Vi = max{W ; W is a version of Ci and W v V } is chosen. Should any of these maxima not exist, then the version cannot be generated. Otherwise, the version of S generated will be maxi Vi .

For example, suppose directory D contains files A French and B graphics and that a user asks to build file exec (which uses A and B) with version French+graphics%mouse. Then file exec French+graphics will be generated. Modules are used to hide information, such as internal data structures. The information about the successive versions of a module being developed should also be hidden. In Marmoset, this is done using the version file. In this file, the user defines versions which are local to the module, as well as to all of the modules which it imports. The version file consists of a series of inequalities between version names. For example, each module could have an inequality of the kind: release >= 1.2.3.4 to state exactly which version should be used to produce a release. Inequalities can also be used to write down information about versions, without necessarily writing out huge expressions involving %, & and +. For example, CIS >= Russian CIS >= Byelorussian CIS >= Ukrainian CIS >= trilingual Russian >= Cyrillic Byelorussian >= Cyrillic Ukrainian >= Cyrillic would give some information about the versions, and one could write CIS instead of Russian+Byelorussian+Ukrainian+trilingual+Cyrillic Versions are therefore added in an elegant manner to Marmoset, and literally no changes have to be made to configure files. Everything is done automatically. It is this sort of facility that improves software reliability.

4

Discussion

Marmoset is a configuration tool which significantly reduces overhead, to the point where it is an insignificant factor. The relationship between modules is automatically looked after, and versions can be used with impunity without the creation of monster makefiles. The experience of the authors with Sloth and Lemur is that these tools significantly aid in the modularity and reuse of software, and that the concept of version is very useful. Several small compilers have been written, sharing many modules, in fact sharing exactly the same code, and nothing special has to be done! However, there are some things that Marmoset does not do. All of them would be remedied if there were a software database, as exists in many configuration systems. First, Marmoset does not store files in a compressed format, for example, using deltas for text files and compress for object files. Second, Marmoset is, like make, insensitive to time. A user cannot say, at least at the moment, “give me version French+graphics as of July 14, 1989,” for we do not currently record the changes that have been made to a file over time. Third, Marmoset does not offer any special support for multi-programmer environments. One can define certain version names so that they correspond to the people working on them, but only discipline prevents different users from destroying each other’s work. Finally, both Sloth and Lemur have routines to view (vm) and modify (mm) components of a module, so that the user never need be worried about how exactly the files are arranged. Marmoset lacks equivalent routines. It would have to provide a parametrisable vm and mm, and we have no elegant solution at the moment.

References [1] N. Belkhatir and J. Estublier. Experience with a database of programs. In ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, Palo Alto (CA), USA, 1986. In SIGPLAN 22(1):84-91, Jan. 1987. [2] N. Belkhatir and J. Estublier. Protection and cooperation in a software engineering environment. In Advanced Programming Environments, pages 221–229, Trondheim, Norway, June 1986. LNCS 244. [3] Stowe Boyd. Modular C. ACM SIGPLAN Notices, 18(4):45–54, April 1983. [4] Stowe Boyd. Free and bound generics: two techniques for abstract data types in Modular C. ACM SIGPLAN Notices, 19(3):12–20, March 1984. [5] Geoffrey M. Clemm. The Odin System: An Object Manager for Extensible Software Environments. PhD thesis, University of Colorado, Boulder (CO), USA, 1986. [6] J. M. Coggins and G. Bollella. Managing C++ libraries. ACM SIGPLAN Notices, 24(6):37– 48, June 1989. [7] Lee W. Cooprider. The Representation of Families of Software Systems. PhD thesis, CarnegieMellon University, Pittsburgh (PA), USA, 1978. [8] Kalyan Dutta. Modular programming in C: an approach and an example. ACM SIGPLAN Notices, 20(3):9–15, March 1985. [9] J. Estublier. A configuration manager: the Adele data base of programs. In Workshop on Software Engineering Environments for Programming-in-the-large, pages 140–147, Harpwichport (MA), USA, June 1985. [10] J. Estublier and J.-M. Favre. Structuring large versioned software products. In 30th Annual International Conference on Computer Software and Applications (COMPSAC ’89), Orlando (FL), USA, Sept. 1989. [11] S. I. Feldman. Make—a program for maintaining software. Software — Practice and Experience, 9(3):255–265, 1979. [12] Keith E. Gorlen. An object-oriented class library for C++ programs. Software — Practice and Experience, 17(12):899–922, Dec. 1987. [13] Giulio Iannello. Programming abstract data types, iterators and generic modules in C. Software — Practice and Experience, 20(3):243–260, March 1990. [14] Butler W. Lampson and Eric E. Schmidt. Organizing software in a distributed environment. In SIGPLAN ’83 Symposium on Programming Language Issues in Software Systems, San Francisco (CA), USA, 1983. In SIGPLAN 18(6):1–13, June 1983. [15] David B. Leblang and Robert P. Chase, Jr. Computer-aided software engineering in a distributed workstation environment. In ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, Pittsburgh (PA), USA, 1984. In SIGPLAN 19(5):104–112, May 1984. [16] Axel Mahler and Andreas Lampen. An integrated toolset for engineering software configurations. In ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, Boston (MA), USA, 1988. In SIGPLAN 24(2):191–200, Feb. 1989.

[17] Axel Mahler and Andreas Lampen. shape — A software configuration management tool. In International Workshop on Software Version and Configuration Control. (Stuttgart: TeubnerVerlag), Grassau, Germany, Jan. 1988. [18] Keith Marzullo and Douglas Wiebe. Jasmine: A software system modelling facility. In ACM SIGSOFT/SIGPLAN Software Engineering Symposium on Practical Software Development Environments, Palo Alto (CA), USA, 1986. In SIGPLAN 22(1):121–130, Jan. 1987. [19] David Notkin. The GANDALF project. Journal of Systems and Software, 5(2):91–106, May 1985. [20] J. A. Plaice and W. W. Wadge. A UNIX tool for managing reusable software components. Technical Report TR-91-13, University of Ottawa Department of Computer Science, Ottawa, Canada, 1991. Submitted for publication. [21] J. A. Plaice and W. W. Wadge. A new approach to version control. IEEE Transactions on Software Engineering, 1992. In press. [22] Marc F. Rochkind. The source code control system. IEEE Transactions on Software Engineering, SE-1(4):364–370, Dec. 1975. [23] Eric Emerson Schmidt. Controlling Large Software Development in a Distributed Environment. PhD thesis, University of California, Berkeley (CA), USA, 1982. [24] Bjarne Stroustrup. Classes: An abstract data type facility for the C language. ACM SIGPLAN Notices, 17(1):42–52, Jan. 1982. [25] Bjarne Stroustrup. The C++ Programming Language. Addison-Wesley, 1986. [26] Walter F. Tichy. Software development based on module interconnection. In 4th International Conference on Software Engineering, pages 29–41, Munich, Germany, Sept. 1979. [27] Walter F. Tichy. Software Development Control based on System Structure Description. PhD thesis, Carnegie-Mellon University, Pittsburgh (PA), USA, 1979. [28] Walter F. Tichy. Design, implementation, and evaluation of a revision control system. In 6th International Conference on Software Engineering, pages 58–67, Tokyo, Japan, Sept. 1982. [29] Walter F. Tichy. RCS—A system for version control. Software — Practice and Experience, 15(7):637–654, 1985. [30] J¨ urgen F. H. Winkler. Report on the first international workshop on software version and configuration control. SIGSOFT, 13(4):61–73, Oct. 1988.