.\"
.\" aegis - project change supervisor
.\" Copyright (C) 1991-1994, 1999, 2002, 2004-2008, 2010, 2012 Peter Miller
.\"
.\" This program is free software; you can redistribute it and/or modify
.\" it under the terms of the GNU General Public License as published by
.\" the Free Software Foundation; either version 3 of the License, or
.\" (at your option) any later version.
.\"
.\" This program is distributed in the hope that it will be useful,
.\" but WITHOUT ANY WARRANTY; without even the implied warranty of
.\" MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
.\" GNU General Public License for more details.
.\"
.\" You should have received a copy of the GNU General Public License
.\" along with this program. If not, see
.\" .
.\"
.nh 2 "The Developer"
.LP
The developer role is the coal face\**.
.FS
I thought this expression was fairly common English usage, until I had
a query. "The Coal Face" is an expression meaning "where the \fIreal\fP
work is done" in reference to old\[hy]style coal mining which was hard,
tiring, hot, very dangerous, and bad for your health even if you were lucky
enough not to be killed. It was a 14\[hy]hour per day job, and you walked
to and from work in the dark, even in summer. Unlike the mine owners,
who rode expensive horses and saw sunlight most days of the week.
.FE
This is where new software is written,
and bugs are fixed.
This example shows only the addition of new functionality,
but usually a change will include modifications of existing code,
similar to bug\[hy]fixing activity.
.nh 3 "Before You Start"
.LP
Have you configured your account to use Aegis? See the \fIUser Setup\fP
section of the \fITips and Traps\fP chapter for how to do this.
.nh 3 "The First Change"
.LP
While the units of change, unoriginally, are called "changes",
this also applies to the start of a project \- a change to nothing, if you like.
The developer of this first change will be Pat.
.LP
First, Pat has been told by the project administrator
that the change has been created.
How Alex created this change will be detailed in the "Administrator" section,
later in this chapter.
Pat then acquires the change and starts work.
.E(
pat% \f(CBaedb \-l \-p example.1.0\fP
Project "example.1.0"
List of Changes
.E)
.E(
Change State Description
\-\-\-\-\-\-\- \-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-
10 awaiting_ Create initial skeleton.
development
pat% \f(CBaedb example.1.0 10\fP
aegis: project "example.1.0": change 10: development directory "/u/pat/
example.1.0.C010"
aegis: project "example.1.0": change 10: user "pat" has begun development
pat% \f(CBaecd\fP
aegis: project "example.1.0": change 10: /u/pat/example.1.0.C010
pat%
.E)
.LP
At this point Aegis has created a development directory for the change
and Pat has changed directory to the development directory\**.
.FS
The default directory in which to place new development directories
is configurable for each user.
.FE
.LP
Five files will be created by this change.
.E(
pat% \f(CBaenf aegis.conf Howto.cook gram.y lex.l main.c\fP
aegis: project "example.1.0": change 10: file "Howto.cook" added
aegis: project "example.1.0": change 10: file "aegis.conf" added
aegis: project "example.1.0": change 10: file "gram.y" added
aegis: project "example.1.0": change 10: file "lex.l" added
aegis: project "example.1.0": change 10: file "main.c" added
pat%
.fi
.E)
.LP
The contents of the
.I aegis.conf
file will not be described in this section,
mostly because it is a rather complex subject;
so complex it requires four chapters to describe:
the
.I "History Tool"
chapter, the
.I "Dependency Maintenance Tool"
chapter, the
.I "Difference Tools"
chapter and the
.I "Project Attributes"
chapter.
The contents of the
.I Howto.cook
file will not be described in this section,
as it is covered in the
.I "Dependency Maintenance Tool"
chapter.
.LP
The file
.I main.c
will have been created by Aegis
as an empty file.
Pat edits it to look like this
.E(
#include
.E)
.E(
static void
usage()
{
fprintf(stderr, "usage: example\en");
exit(1);
}
.E)
.E(
void
main(argc, argv)
int argc;
char **argv;
{
if (argc != 1)
usage();
yyparse();
exit(0);
}
.E)
.LP
The file
.I gram.y
describes the grammar accepted by the calculator.
This file was also created empty by Aegis,
and Pat edits it to look like this:
.E(
%token DOUBLE
%token NAME
.E)
.E(
%union
{
int lv_int;
double lv_double;
}
.E)
.E(
%type DOUBLE expr
%type NAME
.E)
.E(
%left '+' '\-'
%left '*' '/'
%right UNARY
.E)
.E(
%%
.E)
.E(
example
: /* empty */
| example command '\en'
{ yyerrflag = 0; fflush(stderr); fflush(stdout); }
command
: expr
{ printf("%g\en", $1); }
| error
expr
: DOUBLE
{ $$ = $1; }
| '(' expr ')'
{ $$ = $2; }
| '\-' expr
%prec UNARY
{ $$ = \-$2; }
| expr '*' expr
{ $$ = $1 * $3; }
| expr '/' expr
{ $$ = $1 / $3; }
| expr '+' expr
{ $$ = $1 + $3; }
| expr '\-' expr
{ $$ = $1 \- $3; }
.E)
.LP
The file
.I lex.l
describes a simple lexical analyzer.
It will be processed by \fIlex\fP(1) to produce C code
implementing the lexical analyzer.
This kind of simple lexer is usually hand crafted,
but using lex allows the example to be far smaller.
Pat edits the file to look like this:
.E(
%{
#include
#include
%}
%%
[ \et]+ ;
[0\-9]+(\e.[0\-9]*)?([eE][+\-]?[0\-9]+)? {
yylval.lv_double = atof(yytext);
return DOUBLE;
}
[a\-z] {
yylval.lv_int = yytext[0] \- 'a';
return NAME;
}
\en |
\&. return yytext[0];
.E)
.LP
Note how the
.I gram.h
file is included using the
\fC#include <\f(CIfilename\fC>\fR
form.
This is very important for builds in later changes,
and is discussed more fully in the
.I "Using Cook"
section of the
.I "Dependency Maintenance Tool"
chapter.
.LP
The files are processed, compiled and linked together using the
.I aeb
command;
this is known as
.I building
a change.
This is done through Aegis
so that Aegis can know the success or failure of the build.
(Build success is a precondition for a change to leave
the
.I "being developed"
state.)
The build command is in the
.I aegis.conf
file so vaguely described earlier.
In this example it will use the \fIcook\fP(1) command
which in turn will use the
.I Howto.cook
file,
also alluded to earlier.
This file describes the commands and dependencies for the various
processing, compiling and linking.
.E(
pat% \f(CBaeb\fP
aegis: project "example.1.0": change 10: development build started
aegis: cook \-b Howto.cook project=example.1.0 change=10
version=1.0.C010 \-nl
cook: yacc \-d gram.y
cook: mv y.tab.c gram.c
cook: mv y.tab.h gram.h
cook: cc \-I. \-I/projects/example/branch.1./branch0/baseline \-O \-c gram.c
cook: lex lex.l
cook: mv lex.yy.c lex.c
cook: cc \-I. \-I/projects/example/branch.1/branch.0/baseline \-O \-c lex.c
cook: cc \-I. \-I/projects/example/baseline \-O \-c main.c
cook: cc \-o example gram.o lex.o main.o \-ll \-ly
aegis: project "example.1.0": change 10: development build complete
pat%
.E)
.LP
The example program is built, and Pat could even try it out:
.E(
pat% \f(CBexample\fP
\f(CB1 + 2\fP
3
\f(CB^D\fP
pat%
.E)
.LP
At this point the change is apparently finished.
The command to tell Aegis this is the
.I "develop end"
command:
.E(
pat% \f(CBaede\fP
aegis: project "example.1.0": change 10: no current 'aegis \-DIFFerence'
registration
pat%
.E)
.LP
It didn't work, because Aegis thinks you have missed the difference step.
.LP
The difference step is used to produce files useful for reviewing changes,
mostly in the form of context difference files between the project baseline
and the development directory.
Context differences allow reviewers to see exactly what has changed,
and not have to try to track them down and inevitably miss obscure
but important edits to large or complex files.
.E(
pat% \f(CBaed\fP
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/Howto.cook >
/u/pat/example.1.0.C010/Howto.cook,D; test $? \-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/aegis.conf >
/u/pat/example.1.0.C010/aegis.conf,D; test $? \-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/gram.y >
/u/pat/example.1.0.C010/gram.y,D; test $? \-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/lex.l >
/u/pat/example.1.0.C010/lex.l,D; test $? \-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/main.c >
/u/pat/example.1.0.C010/main.c,D; test $? \-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 10: difference complete
pat%
.E)
.LP
Doing a difference for a new file may appear a little pedantic,
but when a change consists of tens of files,
so modifications of existing files and some new,
there is a temptation for reviewers to use "more *,D"
and thus completely miss the new files if it were not for this pedanticism\**.
.FS
This is especially true when you use a tool
such as \fIfcomp\fP(1)
which gives a complete file listing with the
inserts and deletes marked in the margin.
This tool is also available from the author of Aegis.
.FE
.LP
So that reviewers,
and conscientious developers,
may locate and view all of these difference files,
the command
.E(
pat% \f(CBmore `find . \-name "*,D" \-print | sort`\fP
\fI\&.\&.\&.examines each file.\&.\&.\fP
pat%
.E)
could be used,
however this is a little too long winded for most users,
and so the
.I aedmore
alias does exactly this.
There is a similar
.I aedless
alias for those who prefer the
.I less (1)
command.
.LP
So now Pat is done, let's try to sign off again:
.E(
pat% \f(CBaede\fP
aegis: project "example.1.0": change 10: no current 'aegis \-Test'
registration
pat%
.E)
.LP
It didn't work, again.
This time Aegis is reminding Pat that
every change must be accompanied by at least one test.
This is so that the project team can be confident at all times
that a project works\**.
.FS
As discussed in the
.I "How Aegis Works"
chapter,
aegis has the objective of ensuring that projects always work,
where "works" is defined as passing all tests in the project's baseline.
A change "works" if it passes all of its accompanying tests.
.FE
Making this a precondition to leave the
.I "being developed"
state means that
a reviewer can be sure that a change builds and passes its tests
before it can ever be reviewed.
Pat adds the truant test:
.E(
pat% \f(CBaent\fP
aegis: project "example.1.0": change 10: file "test/00/t0001a.sh" new
test
pat%
.E)
.LP
The test file is in a weird place, eh?
This is because many flavors of
.UX
are slow at searching
directories, and so Aegis limits itself to 100 tests per directory.
Whatever the name, Pat edits the test file to look like this:
.E(
#!/bin/sh
#
# test simple arithmetic
#
tmp=/tmp/$$
here=`pwd`
if [ $? \-ne 0 ]; then exit 1; fi
.E)
.E(
fail()
{
echo FAILED 1>&2
cd $here
rm \-rf $tmp
exit 1
}
.E)
.E(
pass()
{
cd $here
rm \-rf $tmp
exit 0
}
trap "fail" 1 2 3 15
.E)
.E(
mkdir $tmp
if [ $? \-ne 0 ]; then exit 1; fi
cd $tmp
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# with input like this
#
cat > test.in << 'foobar'
1
(24 \- 22)
\-(4 \- 7)
2 * 2
10 / 2
4 + 2
10 \- 3
foobar
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# the output should look like this
#
cat > test.ok << 'foobar'
1
2
3
4
5
6
7
foobar
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# run the calculator
# and see if the results match
#
$here/example < test.in > test.out
if [ $? \-ne 0 ]; then fail; fi
diff test.ok test.out
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# this much worked
#
pass
.E)
.LP
There are several things to notice about this test file:
.XP
\(bu It is a Bourne shell script.
All test files are Bourne shell scripts because they are the most portable.\**
.FS
Portable for Aegis' point of view:
Bourne shell is the most widely available shell.
Of course,
if you are writing code to publish on USENET or for FTP,
portability
of the tests will be important from the developer's point of view also.
.FE
(Actually, Aegis likes test files not to be executable,
it passes them to the Bourne shell explicitly when running them.)
.XP
\(bu It makes the assumption that the current directory is
either the development directory or the baseline.
This is valid,
aegis always runs tests this way;
if you run one manually, you must take care of this yourself.
.XP
\(bu It checks the exit status of each and every command.
It is essential that even unexpected and impossible failures are handled.
.XP
\(bu A temporary directory is created for temporary files.
It cannot be assumed that a test will be run from a directory
which is writable;
it is also easier to clean up after strange errors,
since you need only throw the directory away,
rather than track down individual temporary files.
It mostly protects against rogue programs scrambling files
in the current directory, too.
.XP
\(bu Every test is self\[hy]contained.
The test uses auxiliary files,
but they are not separate source files
(figuring where they are when some are in a change and some are in the baseline
can be a nightmare).
If a test wants an auxiliary file,
it must construct the file itself,
in a temporary directory.
.XP
\(bu Two functions have been defined, one for success and one for failure.
Both forms remove the temporary directory.
A test is defined as passing if it returns a 0 exit status,
and failing if it returns anything else.
.XP
\(bu
Tests are treated just like any other source file,
and are subject to the same process.
They may be altered in another change,
or even deleted later if they are no longer useful.
.LP
The most important feature to note about this test,
after ignoring all of the trappings,
is that it doesn't do much you wouldn't do manually!
To test this program manually you would
fire it up,
just as the test does,
you would give it some input,
just as the test does,
and you would compare the output against your expectations
of what it will do,
just as the test does.
.LP
The difference with using this test script and doing it manually
is that most development contains many iterations of
the "build, test, \fIthink\fP, edit, build, test.\&.\&." cycle.
After a couple of iterations,
the manual testing,
the constant re\[hy]typing,
becomes obviously unergonomic.
Using a shell script is more efficient,
doesn't forget to test things later,
and is preserved for posterity (i.e. adds to the regression test suite).
.LP
This efficiency is especially evident when using commands\**
.FS
This is a \fIcsh\fP specific example, unlike most others.
.FE
such as
.E(
pat% \f(CBaeb && aet ; vi aegis.log\fP
\&.\&.\&.
pat% \f(CB!aeb\fP
\&.\&.\&.
pat%
.E)
.LP
It is possible to talk to the shell extremely rarely,
and then only to re\[hy]issue the same command,
using a work pattern such as this.
.LP
As you have already guessed,
Pat now runs the test like this:
.E(
pat% \f(CBaet\fP
aegis: sh /u/pat/example.1.0.C010/test/00/t0001a.sh
aegis: project "example.1.0": change 10: test "test/00/t0001a.sh"
passed
aegis: project "example.1.0": change 10: passed 1 test
pat%
.E)
.LP
Finally,
Pat has built the change,
prepared it for review
and tested it.
It is now ready for sign off.
.E(
pat% \f(CBaede\fP
aegis: project "example.1.0": change 10: no current 'aegis \-Build'
registration
pat%
.E)
.LP
Say what?
The problem is that the use of \fIaent\fP canceled the
previous build registration.
This was because Aegis is decoupled from the dependency maintenance tool
(\fIcook\fP in this case),
and thus has no way of knowing whether or not the new file in the
change would affect the success or failure of a build\**.
.FS
Example:
in addition to the executable file "example" shown here,
the build may also produce an archive file
of the project's source for export.
The addition of one more file may push the size of this archive beyond
a size limit;
the build would thus fail because of the addition of a test.
.FE
All that is required is to re\[hy]build,
re\[hy]test,
re\[hy]difference (yes, the test gets differenced, too)
and sign off.
.E(
pat% \f(CBaeb\fP
aegis: logging to "/u/pat/example.1.0.C010/aegis.log"
aegis: project "example.1.0": change 10: development build started
aegis: cook \-b Howto.cook project=example.1.0 change=10
version=1.0.C001 \-nl
cook: "all" is up\-to\-date
aegis: project "example.1.0": change 10: development build complete
pat% \f(CBaet\fP
aegis: logging to "/u/pat/example.1.0.C010/aegis.log"
aegis: sh /u/pat/example..1.0.C010/test/00/t0001a.sh
aegis: project "example.1.0": change 10: test "test/00/t0001a.sh"
passed
aegis: project "example.1.0": change 10: passed 1 test
pat% \f(CBaed\fP
aegis: logging to "/u/pat/example.1.0.C010/aegis.log"
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C010/test/00/
t0001a.sh > /u/pat/example.1.0.C010/test/00/t0001a.sh,D; test
$? \-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 10: difference complete
pat% \f(CBaede\fP
aegis: sh \*(L)/de.sh example.1.0 10 pat
aegis: project "example.1.0": change 10: development completed
pat%
.E)
.LP
The change is now ready to be reviewed.
This section is about developers,
so we will have to leave this change at this point in its history.
Some time in the next day or so
Pat receives electronic mail that this change has passed review,
and another later to say that it passed integration.
Pat is now free to develop another change,
possibly for a different project.
.nh 3 "The Second Change"
.LP
The second change was created because someone wanted to
name input and output files on the command line,
and called the absence of this feature a bug.
When Jan arrived for work,
and lists the changes awaiting development,
the following list appeared:
.E(
jan% \f(CBaedb \-l \-p example.1.0\fP
Project "example.1.0"
List of Changes
.E)
.E(
Change State Description
\-\-\-\-\-\- \-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-
11 awaiting_ Add input and output file names to the
development command line.
12 awaiting_ add variables
development
13 awaiting_ add powers
development
jan%
.E)
.LP
The first on the list is chosen.
.E(
jan% \f(CBaedb \-c 11 \-p example.1.0\fP
aegis: project "example.1.0": change 11: development directory "/u/
jan/example.1.0.C011"
aegis: project "example.1.0": change 11: user "jan" has begun
development
jan% \f(CBaecd\fP
aegis: project "example.1.0": change 11: /u/jan/example.002
jan%
.E)
.LP
The best way to get details about a change is to used the "change details"
listing.
.E(
jan% \f(CBael cd\fP
Project "example.1.0", Change 11
Change Details
.E)
.E(
NAME
Project "example.1.0", Change 11.
.E)
.E(
SUMMARY
file names on command line
.E)
.E(
DESCRIPTION
Optional input and output files may be specified on the
command line.
.E)
.E(
CAUSE
This change was caused by internal_bug.
.E)
.E(
STATE
This change is in 'being_developed' state.
.E)
.E(
FILES
Change has no files.
.E)
.E(
HISTORY
What When Who Comment
\-\-\-\-\-\- \-\-\-\-\-\- \-\-\-\-\- \-\-\-\-\-\-\-
new_change Fri Dec 11 alex
14:55:06 1992
develop_begin Mon Dec 14 jan
09:07:08 1992
jan%
.E)
.LP
Through one process or another,
Jan determines that the
.I main.c
file is the one to be modified.
This file is copied into the change:
.E(
jan% \f(CBaecp main.c\fP
aegis: project "example.1.0": change 11: file "main.c" copied
jan%
.E)
.LP
This file is now extended to look like this:
.E(
#include
.E)
.E(
static void
usage()
{
fprintf(stderr, "usage: example [ [ ]]\en");
exit(1);
}
.E)
.E(
void
main(argc, argv)
int argc;
char **argv;
{
char *in = 0;
char *out = 0;
int j;
.E)
.E(
for (j = 1; j < argc; ++j)
{
char *arg = argv[j];
if (arg[0] == '\-')
usage();
if (!in)
in = arg;
else if (!out)
out = arg;
else
usage();
}
.E)
.E(
if (in && !freopen(in, "r", stdin))
{
perror(in);
exit(1);
}
if (out && !freopen(out, "w", stdout))
{
perror(out);
exit(1);
}
.E)
.E(
yyparse();
exit(0);
}
.E)
.LP
A new test is also required,
.E(
jan% \f(CBaent\fP
aegis: project "example.1.0": change 11: file "test/00/t0002a.sh" new
test
jan%
.E)
which is edited to look like this:
.E(
#!/bin/sh
#
# test command line arguments
#
tmp=/tmp/$$
here=`pwd`
if [ $? \-ne 0 ]; then exit 1; fi
.E)
.E(
fail()
{
echo FAILED 1>&2
cd $here
rm \-rf $tmp
exit 1
}
.E)
.E(
pass()
{
cd $here
rm \-rf $tmp
exit 0
}
trap "fail" 1 2 3 15
.E)
.E(
mkdir $tmp
if [ $? \-ne 0 ]; then exit 1; fi
cd $tmp
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# with input like this
#
cat > test.in << 'foobar'
1
(24 \- 22)
\-(4 \- 7)
2 * 2
10 / 2
4 + 2
10 \- 3
foobar
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# the output should look like this
#
cat > test.ok << 'foobar'
1
2
3
4
5
6
7
foobar
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# run the calculator
# and see if the results match
#
# (Use /dev/null for input in case input redirect fails;
# don't want the test to hang!)
#
$here/example test.in test.out < /dev/null
if [ $? \-ne 0 ]; then fail; fi
diff test.ok test.out
if [ $? \-ne 0 ]; then fail; fi
$here/example test.in < /dev/null > test.out.2
if [ $? \-ne 0 ]; then fail; fi
diff test.ok test.out.2
if [ $? \-ne 0 ]; then fail; fi
.E)
.E(
#
# make sure complains about rubbish
# on the command line
#
$here/example \-trash < test.in > test.out
if [ $? \-ne 1 ]; then fail; fi
.E)
.E(
#
# this much worked
#
pass
.E)
.LP
Now it is time for Jan to build and test the change.
Through the magic of static documentation,
this works first time,
and here is how it goes:
.E(
jan% \f(CBaeb\fP
aegis: logging to "/u/pat/example.1.0.C011/aegis.log"
aegis: project "example.1.0": change 11: development build started
aegis: cook \-b /projects/example/baseline/Howto.cook
project=example.1.0 change=11 version=1.0.C011 \-nl
cook: cc \-I. \-I/projects/example/baseline \-O \-c main.c
cook: cc \-o example main.o /projects/example/baseline/gram.o
/projects/example/baseline/lex.o \-ll \-ly
aegis: project "example.1.0": change 11: development build complete
jan% \f(CBaet\fP
aegis: logging to "/u/pat/example.1.0.C011/aegis.log"
aegis: sh /u/jan/example.1.0.C011/test/00/t0002a.sh
aegis: project "example.1.0e": change 11: test "test/00/t0002a.sh"
passed
aegis: project "example.1.0": change 11: passed 1 test
jan%
.E)
.LP
All that remains if to difference the change and sign off.
.E(
jan% \f(CBaed\fP
aegis: logging to "/u/pat/example.1.0.C011/aegis.log"
aegis: set +e; diff \-c /projects/example/main.c /u/jan/
example.1.0.C011/main.c > /u/jan/example.1.0.C011/main.c,D; test $?
\-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 11: difference complete
jan% \f(CBaedmore\fP
\fI\&.\&.\&.examines the file.\&.\&.\fP
jan%
.E)
Note how the context difference shows exactly what has changed.
And now the sign\[hy]off:
.E(
jan% \f(CBaede\fP
aegis: project "example.1.0": change 11: no current 'aegis \-Test
\-BaseLine' registration
jan%
.E)
.LP
No,
it wasn't enough.
Tests must not only pass against a new change,
but must fail against the project baseline.
This is to establish,
in the case of bug fixes,
that the bug has been isolated
.I and
fixed.
New functionality will usually fail against the baseline,
because the baseline can't do it
(if it could, you wouldn't be adding it!).
So,
Jan needs to use a variant of the
.I aet
command.
.E(
jan% \f(CBaet \-bl\fP
aegis: sh /u/jan/example.1.0.C011/test/00/t0002a.sh
usage: example
FAILED
aegis: project "example.1.0": change 11: test "test/00/t0002a.sh" on
baseline failed (as it should)
aegis: project "example.1.0": change 11: passed 1 test
jan%
.E)
Running the regression tests is also a good idea
.E(
jan% \f(CBaet \-reg\fP
aegis: logging to "/u/pat/example.1.0.C011/aegis.log"
aegis: sh /projects/example/baseline/test/00/t0001a.sh
aegis: project "example.1.0": change 11: test "test/00/t0001a.sh"
passed
aegis: project "example.1.0": change 11: passed 1 test
jan%
.E)
.LP
Now Aegis will be satisfied
.E(
jan% \f(CBaede\fP
aegis: sh \*(L)/aegis/de.sh example.1.0 11 jan
aegis: project "example.1.0": change 11: development completed
jan%
.E)
.LP
Like Pat in the change before,
Jan will receive email that this change passed review,
and later that it passed integration.
.nh 3 "The Third and Fourth Changes"
.LP
This section will show two people performing two changes,
one each.
The twist is that they have a file in common.
.LP
First Sam looks for a change to work on and starts,
like this:
.E(
sam% \f(CBaedb \-l\fP
Project "example.1.0"
List of Changes
Change State Description
\-\-\-\-\-\-\- \-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-
12 awaiting_ add powers
development
13 awaiting_ add variables
development
sam% \f(CBaedb 12\fP
aegis: project "example.1.0": change 12: development directory "/u/
sam/example.1.0.C012"
aegis: project "example.1.0": change 12: user "sam" has begun
development
sam% \f(CBaecd\fP
aegis: project "example.1.0": change 12: /u/sam/example.1.0.C012
sam%
.E)
.LP
A little sniffing around reveals that only the
.I gram.y
grammar file needs to be altered,
so it is copied into the change.
.E(
sam% \f(CBaecp gram.y\fP
aegis: project "example.1.0": change 12: file "gram.y" copied
sam%
.E)
.LP
The grammar file is changed to look like this:
.E(
%token DOUBLE
%token NAME
%union
{
double lv_double;
int lv_int;
};
.E)
.E(
%type DOUBLE expr
%type NAME
%left '+' '\-'
%left '*' '/'
%right '^'
%right UNARY
.E)
.E(
%%
example
: /* empty */
| example command '\en'
{ yyerrflag = 0; fflush(stderr); fflush(stdout); }
;
.E)
.E(
command
: expr
{ printf("%g\en", $1); }
| error
;
.E)
.E(
expr
: DOUBLE
| '(' expr ')'
{ $$ = $2; }
| '\-' expr
%prec UNARY
{ $$ = \-$2; }
| expr '^' expr
{ $$ = pow($1, $3); }
| expr '*' expr
{ $$ = $1 * $3; }
| expr '/' expr
{ $$ = $1 / $3; }
| expr '+' expr
{ $$ = $1 + $3; }
| expr '\-' expr
{ $$ = $1 \- $3; }
;
.E)
.LP
The changes are very small.
Sam checks to make sure using the difference command:
.E(
sam% \f(CBaed\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: set +e; diff \-c /projects/example/baseline/gram.y /u/sam/
example.1.0.C012/gram.y > /u/sam/example.1.0.C012/gram.y,D; test $?
\-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 12: difference complete
sam% \f(CBaedmore\fP
\fI\&.\&.\&.examines the file.\&.\&.\fP
sam%
.E)
The difference file looks like this
.E(
*** /projects/example/baseline/gram.y
\-\-\- /u/sam/example.1.0.C012/gram.y
***************
*** 1,5 ****
\-\-\- 1,6 \-\-\-\-
%{
#include
+ #include
%}
%token DOUBLE
%token NAME
.E)
.E(
***************
*** 13,18 ****
\-\-\- 14,20 \-\-\-\-
%type NAME
%left '+' '\-'
%left '*' '/'
+ %right '^'
%right UNARY
%%
example
.E)
.E(
***************
*** 32,37 ****
\-\-\- 34,41 \-\-\-\-
| '\-' expr
%prec UNARY
{ $$ = \-$2; }
+ | expr '^' expr
+ { $$ = pow($1, $3); }
| expr '*' expr
{ $$ = $1 * $3; }
| expr '/' expr
.E)
.LP
These are the differences Sam expected to see.
.LP
At this point Sam creates a test.
All good software developers create the tests first,
don't they?
.E(
sam% \f(CBaent\fP
aegis: project "example.1.0": change 12: file "test/00/t0003a.sh" new
test
sam%
.E)
.LP
The test is created empty,
and Sam edit it to look like this:
.E(
:
here=`pwd`
if test $? \-ne 0 ; then exit 1; fi
tmp=/tmp/$$
mkdir $tmp
if test $? \-ne 0 ; then exit 1; fi
cd $tmp
if test $? \-ne 0 ; then exit 1; fi
.E)
.E(
fail()
{
echo FAILED 1>&2
cd $here
chmod u+w `find $tmp \-type d \-print`
rm \-rf $tmp
exit 1
}
.E)
.E(
pass()
{
cd $here
chmod u+w `find $tmp \-type d \-print`
rm \-rf $tmp
exit 0
}
trap "fail" 1 2 3 15
.E)
.E(
cat > test.in << 'end'
5.3 ^ 0
4 ^ 0.5
27 ^ (1/3)
end
if test $? \-ne 0 ; then fail; fi
.E)
.E(
cat > test.ok << 'end'
1
2
3
end
if test $? \-ne 0 ; then fail; fi
.E)
.E(
$here/example test.in < /dev/null > test.out 2>&1
if test $? \-ne 0 ; then fail; fi
.E)
.E(
diff test.ok test.out
if test $? \-ne 0 ; then fail; fi
.E)
.E(
$here/example test.in test.out.2 < /dev/null
if test $? \-ne 0 ; then fail; fi
.E)
.E(
diff test.ok test.out.2
if test $? \-ne 0 ; then fail; fi
.E)
.E(
# it probably worked
pass
.E)
.LP
Everything is ready.
Now the change can be built and tested,
just like the earlier changes.
.E(
sam% \f(CBaeb\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: project "example1.0": change 12: development build started
aegis: cook \-b /projects/example/baseline/Howto.cook
project=example.1.0 change=12 version=1.0.C012 \-nl
cook: yacc \-d gram.y
cook: mv y.tab.c gram.c
cook: mv y.tab.h gram.h
cook: cc \-I. \-I/projects/example/baseline \-O \-c gram.c
cook: cc \-I. \-I/projects/example/baseline \-O \-c /projects/
example/baseline/lex.c
cook: cc \-o example gram.o lex.o /projects/example/baseline/
main.o \-ll \-ly \-lm
aegis: project "example": change 3: development build complete
sam%
.E)
.LP
Notice how the yacc run produces a
.I gram.h
which logically invalidates the
.I lex.o
in the baseline,
and so the
.I lex.c
file in the baseline is recompiled,
using the
.I gram.h
include file from the development directory,
leaving a new
.I lex.o
in the development directory.
This is the reason for the use of
.E(
#include <\f(CIfilename\fP>
.E)
directives, rather then the double quote form.
.PP
Now the change is tested.
.E(
sam% \f(CBaet\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: sh /u/sam/example.1.0.C012/test/00/t0003a.sh
aegis: project "example.1.0": change 12: test "test/00/t0003a.sh"
passed
aegis: project "example.1.0": change 12: passed 1 test
sam%
.E)
.LP
The change must also be tested against the baseline, and fail.
Sam knows this, and does it here.
.E(
sam% \f(CBaet \-bl\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: sh /u/sam/example.1.0.C012/test/00/t0003a.sh
1,3c1,6
< 1
< 2
< 3
\-\-\-
> syntax error
> 5.3
> syntax error
> 4
> syntax error
> 27
FAILED
aegis: project "example.1.0": change 12: test "test/00/t0003a.sh" on
baseline failed (as it should)
aegis: project "example.1.0": change 12: passed 1 test
sam%
.E)
.LP
Running the regression tests
is also a good idea.
.E(
sam% \f(CBaet \-reg\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: sh /projects/example/baseline/test/00/t0001a.sh
aegis: project "example.1.0": change 12: test "test/00/t0001a.sh"
passed
aegis: sh /projects/example/baseline/test/00/t0002a.sh
aegis: project "example.1.0": change 12: test "test/00/t0002a.sh"
passed
aegis: project "example.1.0": change 12: passed 2 tests
sam%
.E)
.LP
A this point Sam has just enough time to get to the lunchtime aerobics
class in the staff common room.
.LP
Earlier the same day,
Pat arrived for work a little after Sam,
and also looked for a change to work on.
.E(
pat% \f(CBaedb \-l\fP
Project "example.1.0"
List of Changes
Change State Description
\-\-\-\-\-\-\- \-\-\-\-\-\-\- \-\-\-\-\-\-\-\-\-\-\-\-\-
13 awaiting_ add variables
development
pat%
.E)
.LP
With such a wide choice,
Pat selected change 13.
.E(
pat% \f(CBaedb 13\fP
aegis: project "example.1.0": change 13: development directory "/u/
pat/example.1.0.C013"
aegis: project "example.1.0": change 13: user "pat" has begun
development
pat% \f(CBaecd\fP
aegis: project "example.1.0": change 13: /u/pat/example.1.0.C013
pat%
.E)
.LP
To get more information about the change,
Pat then uses the "change details" listing:
.E(
pat% \f(CBael cd\fP
Project "example.1.0", Change 13
Change Details
.E)
.E(
NAME
Project "example.1.0", Change 13.
.E)
.E(
SUMMARY
add variables
.E)
.E(
DESCRIPTION
Enhance the grammar to allow variables. Only single
letter variable names are required.
.E)
.E(
CAUSE
This change was caused by internal_enhancement.
.E)
.E(
STATE
This change is in 'being_developed' state.
.E)
.E(
FILES
This change has no files.
.E)
.E(
HISTORY
What When Who Comment
\-\-\-\-\-\- \-\-\-\-\-\- \-\-\-\-\- \-\-\-\-\-\-\-
new_change Mon Dec 14 alex
13:08:52 1992
develop_begin Tue Dec 15 pat
13:38:26 1992
pat%
.E)
.LP
To add the variables the grammar needs to be extended to understand them,
and a new file for remembering and recalling the values of the
variables needs to be added.
.E(
pat% \f(CBaecp gram.y\fP
aegis: project "example.1.0": change 13: file "gram.y" copied
pat% \f(CBaenf var.c\fP
aegis: project "example.1.0": change 13: file "var.c" added
pat%
.E)
.LP
Notice how Aegis raises no objection to both Sam and Pat
having a copy of the
.I gram.y
file.
Resolving this contention is the subject of this section.
.LP
Pat now edits the grammar file.
.E(
pat% \f(CBvi gram.y\fP
\fI\&.\&.\&.edit the file.\&.\&.\fP
pat% \f(CBaed\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: set +e; diff \-c /projects/example/baseline/gram.y /u/pat/
example.1.0.C013/gram.y > /u/pat/example.1.0.C013/gram.y,D; test $?
\-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 13: difference complete
pat%
.E)
.LP
The difference file looks like this
.E(
\&...\fIhey, someone fill me in!...\fP
.E)
.LP
The new
.I var.c
file was created empty by Aegis,
and Pat edits it to look like this:
.E(
static double memory[26];
.E)
.E(
void
assign(name, value)
int name;
double value;
{
memory[name] = value;
}
.E)
.E(
double
recall(name)
int name;
{
return memory[name];
}
.E)
.LP
Little remains except to build the change.
.E(
pat% \f(CBaeb\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: cook \-b /example.proj/baseline/Howto.cook
project=example.1.0 change=13 version=1.0.C013 \-nl
cook: yacc \-d gram.y
cook: mv y.tab.c gram.c
cook: mv y.tab.h gram.h
cook: cc \-I. \-I/projects/example/baseline \-O \-c gram.c
cook: cc \-I. \-I/projects/example/baseline \-O \-c /projects/
example/baseline/lex.c
cook: cc \-I. \-I/projects/example/baseline \-O \-c var.c
cook: cc \-o example gram.o lex.o /projects/example/baseline/
main.o var.o \-ll \-ly \-lm
aegis: project "example.1.0": change 13: development build complete
pat%
.E)
.LP
A new test for the new functionality is required and
Pat creates one like this.
.E(
:
here=`pwd`
if test $? \-ne 0 ; then exit 1; fi
tmp=/tmp/$$
mkdir $tmp
if test $? \-ne 0 ; then exit 1; fi
cd $tmp
if test $? \-ne 0 ; then exit 1; fi
.E)
.E(
fail()
{
echo FAILED 1>&2
cd $here
chmod u+w `find $tmp \-type d \-print`
rm \-rf $tmp
exit 1
}
pass()
{
cd $here
chmod u+w `find $tmp \-type d \-print`
rm \-rf $tmp
exit 0
}
trap "fail" 1 2 3 15
.E)
.E(
cat > test.in << 'end'
a = 1
a + 1
c = a * 40 + 5
c / (a + 4)
end
if test $? \-ne 0 ; then fail; fi
.E)
.E(
cat > test.ok << 'end'
2
9
end
if test $? \-ne 0 ; then fail; fi
.E)
.E(
$here/example test.in < /dev/null > test.out 2>&1
if test $? \-ne 0 ; then fail; fi
.E)
.E(
diff test.ok test.out
if test $? \-ne 0 ; then fail; fi
.E)
.E(
$here/example test.in test.out.2 < /dev/null
if test $? \-ne 0 ; then fail; fi
.E)
.E(
diff test.ok test.out.2
if test $? \-ne 0 ; then fail; fi
.E)
.E(
# it probably worked
pass
.E)
.LP
The new files are then differenced:
.E(
pat% \f(CBaed\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: set +e; diff \-c /projects/example/baseline/gram.y /u/pat/
example.1.0.C013/gram.y > /u/pat/example.1.0.C013/gram.y,D; test $?
\-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C013/test/00/
t0004a.sh > /u/pat/example.1.0.C013/test/00/t0004a.sh,D; test
$? \-eq 0 \-o $? \-eq 1
aegis: set +e; diff \-c /dev/null /u/pat/example.1.0.C013/var.c > /u/
pat/example.1.0.C013/var.c,D; test $? \-eq 0 \-o $? \-eq 1
aegis: project "example.1.0": change 13: difference complete
pat%
.E)
.LP
Notice how the difference for the
.I gram.y
file is still current,
and so is not run again.
.LP
The change is tested.
.E(
pat% \f(CBaet\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: sh /u/pat/example.1.0.C013/test/00/t0001a.sh
aegis: project "example.1.0": change 13: test "test/00/t0004a.sh"
passed
aegis: project "example.1.0": change 13: passed 2 tests
pat%
.E)
.LP
The change is tested against the baseline.
.E(
pat% \f(CBaet \-bl\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: sh /u/pat/example.1.0.C013/test/00/t0001a.sh
1,2c1,4
< 2
< 9
\-\-\-
> syntax error
> syntax error
> syntax error
> syntax error
FAILED
aegis: project "example.1.0": change 13: test "test/00/t0004a.sh" on
baseline failed (as it should)
pat%
.E)
.LP
And the regression tests
.E(
pat% \f(CBaet \-reg\fP
aegis: logging to "/u/pat/example.1.0.C013/aegis.log"
aegis: sh /projects/example/baseline/test/00/t0001a.sh
aegis: project "example.1.0": change 13: test "test/00/t0001a.sh"
passed
aegis: sh /projects/example/baseline/test/00/t0002a.sh
aegis: project "example.1.0": change 13: test "test/00/t0002a.sh"
passed
aegis: project "example.1.0": change 13: passed 2 tests
pat%
.E)
.LP
Note how test 3 has not been run,
in any form of testing.
This is because test 3 is part of another change,
and is not yet integrated with the baseline.
.LP
All is finished for this change,
.E(
pat% \f(CBaede\fP
aegis: sh \*(L)/de.sh example.1.0 13 pat
aegis: project "example.1.0": change 13: development completed
pat%
.E)
.LP
Anxious to get this change into the baseline,
Pat now wanders down the hall in search of a reviewer,
but more of that in the next section.
.LP
Some time later,
San returns from aerobics feeling much improved.
All that is required for change 12 is
to do develop end, or is it?
.E(
sam% \f(CBaede\fP
aegis: project "example.1.0": change 12: file "gram.y" in baseline
has changed since last 'aegis \-DIFFerence' command
sam%
.E)
.LP
A little sleuthing on Sam's part with the Aegis list command
will reveal how this came about.
The way to resolve this problem is with the difference command,
but the merge variant \- this will merge the new baseline version, and
Sam's edit together.
.E(
sam% \f(CBaem\fP
aegis: logging to "/u/pat/example.1.0.C012/aegis.log"
aegis: co \-u'1.1' \-p /projects/example/history/gram.y,v > /tmp/
aegis.14594
/projects/example/history/gram.y,v \-\-> stdout revision 1.1 (unlocked)
aegis: (diff3 \-e /projects/example/baseline/gram.y /tmp/
aegis.14594 /u/sam/example.003/gram.y | sed \-e '/^w$/d'
\-e '/^q$/d'; \techo '1,$p' ) | ed \- /projects/example/
baseline/gram.y,B > /u/sam/example.003/gram.y
aegis: project "example.1.0": change 12: merge complete
aegis: project "example.1.0": change 12: file "gram.y" was out of
date and has been merged, see "gram.y,B" for original source
aegis: new 'aegis \-Build' required
sam%
.E)
.LP
This was caused by the conflict between change 13,
which is now integrated,
and change 12;
both of which are editing the
.I gram.y
file.
Sam examines the
.I gram.y
file,
and is satisfied that it contains an accurate merge of
the edit done by change 13 and the edits for this change.
The merged source file looks like this:
.E(
%{
#include
#include
%}
%token DOUBLE
%token NAME
%union
{
double lv_double;
int lv_int;
};
.E)
.E(
%type DOUBLE expr
%type NAME
%left '+' '\-'
%left '*' '/'
%right '^'
%right UNARY
.E)
.E(
%%
example
: /* empty */
| example command '\en'
{ yyerrflag = 0; fflush(stderr); fflush(stdout); }
;
.E)
.E(
command
: expr
{ printf("%g\en", $1); }
| NAME '=' expr
{ assign($1, $3); }
| error
;
.E)
.E(
expr
: DOUBLE
| NAME
{ extern double recall(); $$ = recall($1); }
| '(' expr ')'
{ $$ = $2; }
| '\-' expr
%prec UNARY
{ $$ = \-$2; }
| expr '^' expr
{ $$ = pow($1, $3); }
| expr '*' expr
{ $$ = $1 * $3; }
| expr '/' expr
{ $$ = $1 / $3; }
| expr '+' expr
{ $$ = $1 + $3; }
| expr '\-' expr
{ $$ = $1 \- $3; }
;
.E)
The automatic merge worked
because most such conflicts are actually working
on logically separate portions of the file.
Two different areas of the grammar in this case.
In practice,
there is rarely a real conflict,
and it is usually small enough to detect fairly quickly.
.LP
Sam now rebuilds:
.E(
sam% \f(CBaeb\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: project "example.1.0": change 12: development build started
aegis: cook \-b /projects/example/baseline/Howto.cook
project=example.1.0 change=12 version=1.0.C012 \-nl
cook: rm gram.c
cook: rm gram.h
cook: yacc \-d gram.y
cook: mv y.tab.c gram.c
cook: mv y.tab.h gram.h
cook: rm gram.o
cook: cc \-I. \-I/projects/example/baseline \-O \-c gram.c
cook: rm lex.o
cook: cc \-I. \-I/projects/example/baseline \-O \-c /projects/
example/baseline/lex.c
cook: rm example
cook: cc \-o example gram.o lex.o /projects/example/baseline/
main.o /projects/example/baseline/var.o \-ll \-ly \-lm
aegis: project "example.1.0": change 12: development build complete
sam%
.E)
.LP
Notice how the list of object files linked has also adapted to the
addition of another file in the baseline,
without any extra work by Sam.
.LP
All that remains is to test the change again.
.E(
sam% \f(CBaet\fP
aegis: /bin/sh /u/sam/example.1.0.C012/test/00/t0003a.sh
aegis: project "example.1.0": change 12: test "test/00/t0003a.sh"
passed
aegis: project "example.1.0": change 12: passed 1 test
sam%
.E)
And test against the baseline,
.E(
sam% \f(CBaet \-bl\fP
aegis: /bin/sh /u/sam/example.1.0.C012/test/00/t0003a.sh
1,3c1,6
< 1
< 2
< 3
\-\-\-
> syntax error
> 5.3
> syntax error
> 4
> syntax error
> 27
FAILED
aegis: project "example.1.0": change 12: test "test/00/t0003a.sh" on
baseline failed (as it should)
aegis: project "example.1.0": change 12: passed 1 test
sam%
.E)
.LP
Perform the regression tests, too.
This is important for a merged change,
to make sure you didn't break the functionality
of the code
you merged with.
.E(
sam% \f(CBaet \-reg\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: /bin/sh /projects/example/baseline/test/00/
t0001a.sh
aegis: project "example.1.0": change 12: test "test/00/t0001a.sh"
passed
aegis: /bin/sh /projects/example/baseline/test/00/
t0002a.sh
aegis: project "example.1.0": change 12: test "test/00/t0002a.sh"
passed
aegis: /bin/sh /projects/example/baseline/test/00/
t0004a.sh
aegis: project "example.1.0": change 12: test "test/00/t0004a.sh"
passed
aegis: project "example.1.0": change 12: passed 3 tests
sam%
.E)
All done, or are we?
.E(
sam% \f(CBaede\fP
aegis: project "example.1.0": change 12: no current 'aegis \-Diff'
registration
sam%
.E)
The difference we did earlier,
which revealed that we were out of date,
does not show the differences since the two changes were
merged, and possibly further edited.
.E(
sam% \f(CBaed\fP
aegis: logging to "/u/sam/example.1.0.C012/aegis.log"
aegis: set +e; diff /projects/example/baseline/gram.y /u/pat/
example.1.0.C012/gram.y > /u/pat/example.1.0.C012/gram.y,D;
test $? \-le 1
aegis: project "example.1.0": change 12: difference complete
sam%
.E)
This time everything will run smoothly,
.E(
sam% \f(CBaede\fP
aegis: project "example.1.0": change 12: development completed
sam%
.E)
Some time soon
Sam will receive email that this change passed review,
and later that it passed integration.
.LP
Within the scope of a limited example,
you have seen most of what Aegis can do.
To get a true feeling for the program
you need to try it in a similarly simple case.
You could even try doing this example manually.
.bp
.nh 3 "Developer Command Summary"
.LP
Only a few of the Aegis commands available to developers
have been used in the example.
The following table (very tersely) describes the Aegis commands most
useful to developers.
.TS
center,tab(;);
l l.
Command;Description
_
aeb;Build
aeca;edit Change Attributes
aecd;Change Directory
aeclean;Clean a development directory
aeclone;copy a whole change
aecp;Copy File
aecpu;Copy File Undo
aed;Difference
aedb;Develop Begin
aedbu;Develop Begin Undo
aede;Develop End
aedeu;Develop End Undo
ael;List Stuff
aenf;New File
aenfu;New File Undo
aent;New Test
aentu;New Test Undo
aerm;Remove File
aermu;Remove File Undo
aet;Test
.TE
.LP
You will want to read the manual entries for all of these commands.
Note that all Aegis commands have a
.I \-Help
option,
which will give a result very similar to the
corresponding
.I man (1)
output.
Most Aegis commands also have a
.I \-List
option,
which usually lists interesting context sensitive information.
/* vim: set ts=8 sw=4 et : */