vcheckout Dissection

Introduction

This is an explanation of the implementation of vcheckout.  The indended audience is anyone interested in the workings of the repository user-interface tools, and the Vesta repository APIs in general.  This document assumes that you're already fairly familiar with how vcheckout works, as well as various Vesta concepts such as mutable attributes, replication, and mastership transfer.  No time will be spent here explaining those concepts.

vcheckout is one of the most complicated repository tools.  It may not be the best place to start learning about the Vesta APIs, but it does illustrate more precisely because it is complicated.

The code listings in this document are taken from /vesta/vestasys.org/vesta/repos_ui/22/src/vcheckout.C.  At several points, APIs from other packages are mentioned.  The specifiec versions this document is written relative to are:

The Code

[Note: not every line of code is covered here.  In some cases, enclosing ifs are not shown, so be sure to follow along in the complete source.  The point of this document is to illustrate how vcheckout uses the Vesta APIs, not to go over every single line of code.]

29
30
31
32
33
34
35
36
37
#include <Basics.H>
#include <Text.H>
#include <VestaConfig.H>
#include <VestaSource.H>
#include <VDirSurrogate.H>
#include <VestaSourceAtomic.H>
#include <Replicator.H>
#include <signal.h>
#include "ReposUI.H"

Inclusion of header files mostly from Vesta:

72
73
74
75
76
77
    Text defpkgpar, defworkpar, timefmt;
    defpkgpar = VestaConfig::get_Text("UserInterface",
                                      "DefaultPackageParent");
    defworkpar = VestaConfig::get_Text("UserInterface",
                                       "DefaultWorkParent");
    timefmt = VestaConfig::get_Text("UserInterface", "TimeFormat");

Read settings from the Vesta configuration file using VestaConfig::get_Text into Text variables of   See The vcheckout man page for how these are used.

79
80
81
    time_t now = time(NULL);
    char timebuf[256];
    strftime(timebuf, sizeof(timebuf), timefmt.cchars(), localtime(&now));

Format the current time into a string.  Used for setting "checkout-time" attribute.  See the strftime(3) man page.

82
83
84
85
    Text cuser(AccessControl::self()->user());
    int atpos = cuser.FindChar('@');
    Text user(cuser.Sub(0, atpos));
    Text uuser(user + "_" + cuser.Sub(atpos+1)); // cuser with @ replaced by _

Get the user's global identifier (username@realm).  (AccessControl::self returns the user's identity object, and its user member function returns the global username as a text string.)  From that, construct a string that can be used in generating a unique session directory name for non-exclusive checkouts by replacing "@" with "_".

86
87
88
89
    char mode[8];
    mode_t oumask;
    umask(oumask = umask(0));
    sprintf(mode, "%03o", 0777 & ~oumask);

Generate a text string representing the umask.  Used in setting "#mode" attribute on created objects.  See the umask(2) man page.

90
91
    Text lhost(VDirSurrogate::defaultHost());
    Text lport(VDirSurrogate::defaultPort());

Get the default repository host and port as text strings using the static member functions VDirSurrogate::defaultHost and VDirSurrogate::defaultHost.

93
94
95
    const Replicator::Flags rflags = (Replicator::Flags) 
      (Replicator::attrNew | Replicator::attrOld | Replicator::attrAccess |
       Replicator::revive | Replicator::inclStubs | Replicator::latest);

Initialize a Replicator::Flags. These replicator options are used when replicating the old version and the created new version and session directory to the repository where the checkout is performed.

97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
    // 
    // Parse command line
    //
    Text oldver, newver, sessdir, workdir, pkg, hints, repos;
    bool default_old = true, default_new = true, default_sess = true;
    bool uniquify_nondefault_sess = false;
    bool default_work = true, default_repos = true;
    bool omit_old = false, omit_new = false, omit_sess = false;
    bool omit_work = false;
    bool quiet = false, query = false, force = false;
    opterr = 0;
    for (;;) {
      char* slash;
      int c = getopt(argc, argv, "qQfo:On:Ns:Suw:Wh:R:");
      if (c == EOF) break;
      switch (c) {
      case 'q':
        quiet = true;
        break;
      case 'Q':
        query = true;
        break;
      case 'f':
        force = true;
        break;
      case 'o':
        oldver = optarg;
        default_old = false;
        break;
      case 'O':
        omit_old = true;
        oldver = "0";
        default_old = false;
        break;
      case 'n':
        newver = optarg;
        default_new = false;
        break;
      case 'N':
        omit_new = true;
        newver = "1";
        default_new = false;
        break;
      case 's':
        sessdir = optarg;
        default_sess = false;
        break;
      case 'S':
        omit_sess = true;
        break;
      case 'u':
        uniquify_nondefault_sess = true;
        break;
      case 'w':
        workdir = optarg;
        default_work = false;
        break;
      case 'W':
        omit_work = true;
        break;
      case 'h':
        hints = optarg;
        break;
      case 'R':
        repos = optarg;
        default_repos = false;
        break;
      case '?':
      default:
        usage();
        /* not reached */
      }
    }

Command line parsing. See getopt(3) man page. See The vcheckout man page for the meaning of the different options.

171
172
173
174
175
176
177
178
179
180
181
    switch (argc - optind) {
    case 1:
      pkg = argv[optind];
      break;
    case 0:
      pkg = ".";
      break;
    default:
      usage();
      /* not reached */
    }

Determine the package to check out.  If there's a command-line argument left, use that.  Otherwise default to the current working directory.

183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
    //
    // Use a nondefault repository if specified, and automatically
    // add it to the hints if not already present.
    //
    if (!default_repos) {
      int colon = repos.FindCharR(':');
      if (colon == -1) {
        lhost = repos;
        repos = repos + ":" + lport;
      } else {
        lhost = repos.Sub(0, colon);
        lport = repos.Sub(colon+1);
      }
      hints = hints + " " + repos;
    }

If a non-default repository was specified as local, parse it into a hostname and port.  The port is optional, and if not supplied the default will be used.

A non-default repository is also added to the "hints" list, which is used when searching for the master repository of the package and its checkout subdirectory, as well as when searching for a copy of the old version.  (Normally only the default repository and any repositories mentioned in "master-repository" attributes will be searched, which is why it's necessary to add a non-default repository to the hints.)

199
200
201
202
    //
    // Canonicalize pkg
    //
    Text cpkg = ReposUI::canonicalize(pkg, defpkgpar);

Convert the package path to a canonical form (a fully-qualified absolute pathname that starts with "/vesta/") using ReposUI::canonicalize.  A relative pathname that does not start with "." or ".." will be interpreted relative to [UserInterface]DefaultPackageParent.

210
211
      VestaSource* vs_mpkg = ReposUI::filenameToMasterVS(cpkg, hints);
      long high = ReposUI::highver(vs_mpkg);

Locate the master copy of the package with ReposUI::filenameToMasterVS and find the highest version that exists there with ReposUI::highver.  Consulting the master copy is necessary for determining the new version to reserve and/or the basis version of the checkout.  Only the master repository is guaranteed to know all names that exist in the package, so looking at a non-master repository might overlook versions not replicated there.

212
213
214
215
216
217
218
219
220
221
222
      if (default_old) {
        if (high == -1) {
          // No existing versions, pretend old was version 0
          omit_old = true;
          oldver = "0";
        } else {
          char valbuf[64];
          sprintf(valbuf, "%ld", high);
          oldver = valbuf;
        }
      }

If the old version should be determined automatically (the default), do so based on the highest version in the package.  If no versions exist in the package, remember that we will have no old version.

223
224
225
226
227
228
229
      if (default_new) {
        // Set newver to next version after highest in use,
        // minimum 1.
        char valbuf[64];
        sprintf(valbuf, "%ld", (high < 0) ? 1 : high + 1);
        newver = valbuf;
      }

If the new version should be determined automatically (the default), do so based on the highest version in the package.  If no versions exist in the package, the new version will be version 1.

232
233
234
235
236
237
238
239
240
241
242
    //
    // Canonicalize oldver and newver
    //
    Text coldver, coldverpar, oldverarc;
    Text cnewver, cnewverpar, newverarc;
    coldver = ReposUI::canonicalize(oldver, cpkg);
    ReposUI::split(coldver, coldverpar, oldverarc, "old-version");
    if (!omit_new) {
      cnewver = ReposUI::canonicalize(newver, cpkg);
      ReposUI::split(cnewver, cnewverpar, newverarc, "new-version");
    }

Convert the old and new versions to fully-qualified pathnames that start with "/vesta/" using ReposUI::canonicalize.  If either are relative paths that don't start with "." or "..", they will interpreted relative to the package to be checked out.  After this conversion, split them into their parent directory and last pathname component with ReposUI::splt.  (It may seem like ReposUI::splt is being used to perform the reverse of ReposUI::canonicalize here, but if the new or old versions are supplied explicitly on the command-line, both steps must be performed as the arguments could be either absolute or relative paths.)

244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
    //
    // Compute default for sessdir
    //
    if (default_sess) {
      if (!omit_new) {
        // Set sessdir to newver with "checkout/" inserted before
        // last component.
        sessdir = cnewverpar + "/checkout/" + newverarc;
      } else {
        // Need to make up a unique name, since newver is
        // meaningless.  First, test whether oldver comes from a
        // checkout session.
        if (coldver.FindText("/checkout/") >= 0) {
          // Oldver is from a checkout session, so set sessdir to
          // oldver with the last "/" changed to a ".", and "." +
          // uuser appended.  Later we will further append a
          // uniquifying integer ".u" (starting at 1) to this.
          sessdir = coldverpar + "." + oldverarc + "." + uuser;
        } else {
          // Oldver is not from a checkout session, so set sessdir
          // to oldver with "checkout/" inserted before the last
          // component and "." + uuser appended.  Later we will
          // further append a uniquifying integer ".u" (starting
          // at 1) to this.
          sessdir = coldverpar + "/checkout/" + oldverarc + "." + uuser;
        }
      }
    }

Determine the session directory name to create.

The simplest case (handled on line 251) is when we are reserving a new version, in which case the session directory is just:

package/checkout/newverarc

In the case of non-exclusive checkouts, if the old version is itself a snapshot in a checkout session, the name of the new session is based on the old version, in an attempt to make the naming show the derivation. uuser (set on line 85) is appended to the session directory to form a more unique name. For example, if the old-version is:

package/checkout/1/2

The value of sessdir will be:

package/checkout/1.2.uuser

This is handled on line 261.

For a non-exclusive checkout where the old-version is not itself in a checkout session (its path doesn't contain "/checkout/"), the value of sessdir will be:

package/checkout/oldverarc.uuser

This is handled on line 268.

279
280
      csessdir = ReposUI::canonicalize(sessdir, cpkg);
      ReposUI::split(csessdir, csessdirpar, sessdirarc, "session-dir");

Convert the session directory to a fully-qualified absolute pathname that starts with "/vesta/" (with ReposUI::canonicalize), and split it into its parent and parent directory and last pathname arc (with ReposUI::split).  If sessdir is a relative path that doesn't start with "." or "..", it will interpreted relative to the package to be checked out.

283
284
285
286
287
288
289
      if ((default_sess && omit_new) ||
          (!default_sess && uniquify_nondefault_sess)) {
        // Need to uniquify the session dir
        vs_msessdirpar = ReposUI::filenameToMasterVS(csessdirpar, hints);
        sessdirarc = ReposUI::uniquify(vs_msessdirpar, sessdirarc);
        csessdir = csessdirpar + "/" + sessdirarc;
      }

There are two cases where ".N" (for some small integer N) is appended to make a session directory unique:

In order to determine a unique name to use, the master copy of the session directory parent must be consulted (as only the master copy of an appendable directory knows all the names that exist in it).  On line 286, ReposUI::filenameToMasterVS is used to find the master copy.

On line 287, ReposUI::uniquify is used to generate a unique session directory name by appending ".N".

292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
    //
    // Compute default for workdir
    //
    if (default_work) {
      // Get last arc of absolute package name that does not
      // begin with a digit
      int i, j;
      j = cpkg.Length();
      for (;;) {
        i = cpkg.FindCharR('/', j-1);
        if (i == -1) {
          // Should not happen
          cerr << program_name
               << ": can't find base package name in " << cpkg << endl;
          exit(2);
        }
        if (!isdigit(cpkg[i+1])) {
          workdir = cpkg.Sub(i+1, j-i-1);
          break;
        }
        j = i;
      }
    }

Determine the name of the working directory.  This is a simple text search upward through directory path components starting with the package name for an arc that starts with a non-digit.  (I believe this is intended to get the name of the package rather than a branch within it, although it isn't a particularly precise algorithm.)

316
317
318
319
320
321
322
323
    //
    // Canonicalize workdir
    //
    Text cworkdir, cworkdirpar, workdirarc;
    if (!omit_work) {
      cworkdir = ReposUI::canonicalize(workdir, Text("/vesta-work/") + user);
      ReposUI::split(cworkdir, cworkdirpar, workdirarc, "work-dir");
    }

Similar to what we've seen earlier, canonicalize and split the working directory name.

325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
    //
    // Look up oldver, first replicating here if needed
    //
    VestaSource* vs_oldver = NULL;
    if (!omit_old) {
      VestaSource* vs_roldver = ReposUI::filenameToRealVS(coldver, hints);
      if (vs_roldver->type == VestaSource::stub) {
        cerr << program_name << ": " << coldver
             << " is not checked in yet" << endl;
        exit(2);
      } else if (vs_roldver->type == VestaSource::ghost) {
        cerr << program_name << ": " << coldver
             << " has been deleted" << endl;
        exit(2);
      }
      if (vs_roldver->host() == lhost && vs_roldver->port() == lport) {
        // A local replica exists
        vs_oldver = vs_roldver;
      } else if (!query) {
        // Get a local replica
        if (!quiet) { 
          cout << "Replicating " << coldver << " from "
               << vs_roldver->host() << ":" << vs_roldver->port() << endl;
        }
        Replicator repl(vs_roldver->host(), vs_roldver->port(), lhost, lport);
        Replicator::DirectiveSeq direcs;
        Replicator::Directive d('+', coldver.Sub(vlen));
        direcs.addhi(d);
        repl.replicate(&direcs, rflags);
        vs_oldver = ReposUI::filenameToVS(coldver, lhost, lport);
      }
    }

Locate and possibly replicate the old version to the local repository.

On line 330, ReposUI::filenameToRealVS is used to find a "real" (not a non-master ghost and not a non-master stub) copy of the old version.  If the object located is of type ghost or stub (which must be a master ghost or stub), print an error message and exit.

If the object is in the local repository (line 340), use it.  Otherwise, use the replicator to copy it to the local repository (lines 349-353), unless operating in "query" mode.  An instance of Replicator is created to perform the replication, using a single Replicator::Directive to copy the old version.

After replicating it, look it up in the local repository with ReposUI::filenameToVS.

358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
    //
    // Look up master of newverpar and sanity check
    //
    VestaSource* vs_mnewverpar = NULL;
    if (!omit_new) {  
      vs_mnewverpar = ReposUI::filenameToMasterVS(cnewverpar, hints);
      if (!force && !vs_mnewverpar->inAttribs("type", "package")) {
        cerr << program_name << ": " << cnewverpar
          << " is not a package or branch" << endl;
        exit(2);
      }
      if (!quiet) {
        cout << "Reserving version " << cnewver;
        if (vs_mnewverpar->host() != lhost ||
            vs_mnewverpar->port() != lport) {
          cout << " at "
               << vs_mnewverpar->host() << ":" << vs_mnewverpar->port();
        }
        cout << endl;
      }
    }

If we're reserving a new version, find the master copy of the new version's parent (line 363).

Check whether the new version's parent's "type" attribute contain the value "package" with the VestaSource::inAttribs member function. If it doesn't contain this value, print an error message and exit unless the "force" command-line option was given.

380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
    //
    // Look up master of sessdirpar and sanity check
    //
    if (!omit_sess) {
      if (!vs_msessdirpar) {
	vs_msessdirpar = ReposUI::filenameToMasterVS(csessdirpar, hints);
      }
      if (!force && !vs_msessdirpar->inAttribs("type", "checkout")) {
	cerr << program_name << ": " << csessdirpar <<
	  " is not a checkout directory" << endl;
	exit(2);
      }
      if (!quiet) {
	cout << "Creating session " << csessdir;
	if (vs_msessdirpar->host() != lhost ||
	    vs_msessdirpar->port() != lport) {
	  cout << " at "
	       << vs_msessdirpar->host() << ":" << vs_msessdirpar->port();
	}
	cout << endl;
      }
    }

If we're creating a session directory and we haven't already located the master copy of the session directory parent, find it now (line 385).

If the session directory parent's "type" attribute doesn't contain the value "checkout", print an error message and exit, unless the "force" command-line option was given.

409
	vs_workdirpar = ReposUI::filenameToVS(cworkdirpar, lhost, lport);

Look up the working directory parent in the local repository.

414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
	  Text cworkdirparpar, workdirpararc;
	  ReposUI::split(cworkdirpar, cworkdirparpar, workdirpararc,
			 "work-dir parent");
	  VestaSource* vs_workdirparpar =
	    ReposUI::filenameToVS(cworkdirparpar, lhost, lport);
	  VestaSource::errorCode err = vs_workdirparpar->
	    insertMutableDirectory(workdirpararc.cchars(), NULL, true,
				   NULL, VestaSource::dontReplace,
				   &vs_workdirpar);
	  if (err != VestaSource::ok) {
	    cerr << program_name << ": error creating "
		 << cworkdirpar << ": "
		 << ReposUI::errorCodeText(err) << endl;
	    exit(2);
	  }
	  err = vs_workdirpar->setAttrib("#mode", mode);
	  assert(err == VestaSource::ok);

If the working directory parent doesn't exist, split its path to get its parent (lines 415-416), look up the parent (lines 417-418), and create a new mutable directory for the working directory parent with the VestaSource::insertMutableDirectory member function (lines 419-422).  After creating it, give it a "#mode" attribute with the VestaSource::setAttrib member function (line 429).

This handles the case of a new user performing their first checkout.  Unless they've explicitly created the directory "/vesta-work/username", it won't exist, which is why vcheckout creates it here.

435
436
437
438
439
440
441
442
443
444
      if (vs_workdirpar && default_work) {
	// Make name unique by adding numeric suffix if needed
	VestaSource* vs_dummy;
	VestaSource::errorCode err = 
	  vs_workdirpar->lookup(workdirarc.cchars(), vs_dummy);
	if (err != VestaSource::notFound) {
	  workdirarc = ReposUI::uniquify(vs_workdirpar, workdirarc);
	}
	cworkdir = cworkdirpar + "/" + workdirarc;
      }

If the working directory name is to be automatically determined, see if the working directory name already exists by looking it up with the VestaSource::lookup member function (lines 438-439).  If it does exist, uniquify it by appending ".N" (line 441).

450
451
452
453
    if (query) {
      cerr << program_name << ": nothing done (-Q flag given)" << endl;
      exit(0);
    }

If operating in "query" mode, exit without making any more changes.

468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
    //
    // If needed, construct action to create newver remotely
    // and replicator directives to replicate it here.
    //
    VestaSource* vs_newverpar = NULL;
    VestaSource* vs_newver = NULL;
    if (!omit_new) {
      if (vs_mnewverpar->host() == lhost &&
	  vs_mnewverpar->port() == lport) {
	// Master is local
	vs_newverpar = vs_mnewverpar;
      } else {
	// Master is remote
	rvsa1 = new VestaSourceAtomic(vs_mnewverpar->host(),
				      vs_mnewverpar->port());
	VestaSourceAtomic::VSIndex vsi_mnewverpar =
	  rvsa1->decl(cnewverpar, vs_mnewverpar);
	VestaSourceAtomic::VSIndex vsi_mnewver =
	  rvsa1->insertStub(cnewver, vsi_mnewverpar, newverarc,
			    true, VestaSource::dontReplace);
	if (!omit_old) {
	  rvsa1->setAttrib(cnewver, vsi_mnewver, "old-version", coldver);
	}
	if (!omit_sess) {
	  rvsa1->setAttrib(cnewver, vsi_mnewver, "session-dir", csessdir);
	}
	rvsa1->setAttrib(cnewver, vsi_mnewver, "checkout-time", timebuf);
	rvsa1->setAttrib(cnewver, vsi_mnewver, "checkout-by", cuser);
	rvsa1->setAttrib(cnewver, vsi_mnewver, "checkout-from",
			 vs_mnewverpar->host() + ":" + vs_mnewverpar->port());
	rvsa1->setAttrib(cnewver, vsi_mnewver, "checkout-to",
			 lhost + ":" + lport);
	rvsa1->setAttrib(cnewver, vsi_mnewver, "#mode", mode);

	repl1 = new Replicator(vs_mnewverpar->host(), vs_mnewverpar->port(),
			       lhost, lport);
	direcs1 = new Replicator::DirectiveSeq;
	Replicator::Directive d('+', cnewver.Sub(vlen));
	direcs1->addhi(d);
      }
    }

Determine whether the master copy of the new version parent is local.  If it isn't, construct a repository atomic action and replication objects to create the new version remotely and replicate it to the local repository.

Lines 475-476 check whether the master new version parent is local.  If it is, there's no remote atomic action or replication required, as the new version can be created in the local repository.

Lines 481-482 create an instance of VestaSourceAtomic that will be used to create the new version in the remote master repository.  The first step in the atomic action (lines 483-484) is to declare the object we'll be working with (the new version's parent).  The second step (lines 485-487) is to create the new version reservation stub.  Subsequent actions (lines 488-500) set attributes on the newly created version reservation.

Finally, instances of Replicator and Replicator::DirectiveSeq are created to replicate the new version reservation stub to the local repository (lines 502-506).

510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
    //
    // If needed, construct action to create sessdir remotely
    // and replicator directives to replicate it here.
    //
    VestaSource* vs_sessdirpar = NULL;
    VestaSource* vs_sessdir = NULL;
    if (!omit_sess) {
      if (vs_msessdirpar->host() == lhost &&
	  vs_msessdirpar->port() == lport) {
	// Master is local
	vs_sessdirpar = vs_msessdirpar;
      } else {
	// Master is remote
	if (rvsa1 != NULL &&
	    vs_msessdirpar->host() == vs_mnewverpar->host() &&
	    vs_msessdirpar->port() == vs_mnewverpar->port()) {
	  rvsa2 = rvsa1;
	  repl2 = repl1;
	  direcs2 = direcs1;
	} else {
	  rvsa2 = new VestaSourceAtomic(vs_msessdirpar->host(),
					vs_msessdirpar->port());
	  repl2 = new Replicator(vs_msessdirpar->host(),
				 vs_msessdirpar->port(), lhost, lport);
	  direcs2 = new Replicator::DirectiveSeq;
	}
	VestaSourceAtomic::VSIndex vsi_msessdirpar =
	  rvsa2->decl(csessdirpar, vs_msessdirpar);
	VestaSourceAtomic::VSIndex vsi_msessdir =
	  rvsa2->insertAppendableDirectory(csessdir, vsi_msessdirpar,
					   sessdirarc, true,
					   VestaSource::dontReplace);
	if (!omit_old) {
	  rvsa2->setAttrib(csessdir, vsi_msessdir, "old-version", coldver);
	}
	if (!omit_new) {
	  rvsa2->setAttrib(csessdir, vsi_msessdir, "new-version", cnewver);
	}
	rvsa2->setAttrib(csessdir, vsi_msessdir, "checkout-time", timebuf);
	rvsa2->setAttrib(csessdir, vsi_msessdir, "checkout-by", cuser);
	rvsa2->setAttrib(csessdir, vsi_msessdir, "checkout-from",
			 (vs_msessdirpar->host() + ":" +
			  vs_msessdirpar->port()));
	rvsa2->setAttrib(csessdir, vsi_msessdir, "checkout-to",
			 (lhost + ":" + lport));
	rvsa2->setAttrib(csessdir, vsi_msessdir, "type", "session");
	rvsa2->setAttrib(csessdir, vsi_msessdir, "#mode", mode);

	Replicator::Directive d1('+', csessdir.Sub(vlen));
	Replicator::Directive d2('+', cslatest.Sub(vlen));
	direcs2->addhi(d1);
	direcs2->addhi(d2);
      }
    }

Similar to lines 472-508, determine whether the master copy of the session directory parent is local or in the same repository as the master copy of the new version parent.  If necessary, construct a repository atomic action and replication objects, or append to the existing ones.

Lines 517-518 check whether the the master copy of the session directory parent is local.  If it is, there's no remote atomic action or replication required.

Lines 523-528 handle the case where the master copy of the session directory parent is in the same repository as the master copy of the new version parent.  In this case, we add more atomic actions and replication directives to the existing objects.

Lines 530-534 handle the case where the master copy of the session directory parent is not in the local repository and not in the repository with the master copy of the new version parent.  In this case, new instances of VestaSourceAtomic, Replicator, and Replicator::DirectiveSeq are created.

Lines 536-556 add steps to the atomic action for creating and adding attributes to the session directory.

Lines 558-561 add replication directives to the replication object.

565
566
567
568
569
570
    //
    // Disable ^C signal to make it less likely that we will be
    // killed after having done one or more remote actions but
    // before having finished the whole job.
    //
    signal(SIGINT, SIG_IGN);

Ignore the interrupt signal (generated by the user hitting control-C).  This provides some minor protection against a half-completed checkout when multiple repositories are involved.

See the signal(2) man page.

572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
    //
    // Run the remote action(s) and replication(s)
    //
    if (rvsa1) {
      if (!rvsa1->run()) {
	VestaSource::errorCode err;
	if (rvsa1->lasterr == VestaSource::ok) {
	  err = rvsa1->okreplace;
	} else {
	  err = rvsa1->lasterr;
	}
	cerr << program_name << ": " << rvsa1->name(rvsa1->ndone) << " at "
	     << vs_mnewverpar->host() << ":" << vs_mnewverpar->port()
	     << ": " << ReposUI::errorCodeText(err) << endl;
	exit(2);
      }
      repl1->replicate(direcs1, rflags);
    }
    if (rvsa2 && rvsa2 != rvsa1) {
      if (!rvsa2->run()) {
	VestaSource::errorCode err;
	if (rvsa2->lasterr == VestaSource::ok) {
	  err = rvsa2->okreplace;
	} else {
	  err = rvsa2->lasterr;
	}
	cerr << program_name << ": " << rvsa2->name(rvsa2->ndone) << " at "
	     << vs_msessdirpar->host() << ":" << vs_msessdirpar->port()
	     << ": " << ReposUI::errorCodeText(err) << endl;
	exit(2);
      }
      repl2->replicate(direcs2, rflags);
    }

Run the one or two remote atomic actions and perform the one or two replications to the local repository.

606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
    //
    // Do the mastership transfer(s)
    //
    if (!omit_new && vs_newverpar == NULL) {
      VestaSource::errorCode err =
	VDirSurrogate::acquireMastership(cnewver.Sub(vlen).cchars(),
					 lhost, lport,
					 vs_mnewverpar->host(),
					 vs_mnewverpar->port());
      if (err != VestaSource::ok) {
	cerr << program_name << ": error acquiring mastership of "
	     << cnewver << ": " << ReposUI::errorCodeText(err) << endl;
	exit(2);
      }
      vs_newverpar = ReposUI::filenameToVS(cnewverpar, lhost, lport);
      vs_newver = ReposUI::filenameToVS(cnewver, lhost, lport);
    }
    if (!omit_sess && vs_sessdirpar == NULL) {
      VestaSource::errorCode err =
	VDirSurrogate::acquireMastership(csessdir.Sub(vlen).cchars(),
					 lhost, lport,
					 vs_msessdirpar->host(),
					 vs_msessdirpar->port());
      if (err != VestaSource::ok) {
	cerr << program_name << ": error acquiring mastership of "
	     << csessdir << ": " << ReposUI::errorCodeText(err) << endl;
	exit(2);
      }
      vs_sessdirpar = ReposUI::filenameToVS(csessdirpar, lhost, lport);
      vs_sessdir = ReposUI::filenameToVS(csessdir, lhost, lport);
    }

Transfer mastership of the remotely created objects to the local repository using VDirSurrogate::acquireMastership.  First, the local repository acquires mastership of the new version (lines 610-614).  Second, the local repository acquires mastership of the session directory (lines 624-628).

The local repository needs mastership of the session directory so that vadvance can take immutable snapshots of the working directory.  The local repository needs mastership of the new version reservation stub so that vcheckin can replace it with an immutable directory.

638
639
640
641
642
643
644
    //
    // Construct the local atomic action
    //
    VestaSourceAtomic vsa(lhost, lport);
    VestaSourceAtomic::VSIndex vsi_oldver = -1, vsi_newverpar = -1,
      vsi_sessdirpar = -1, vsi_workdirpar = -1,
      vsi_newver = -1, vsi_sessdir = -1;

Construct a VestaSourceAtomic object for performing the local action.  (This may include all the work of vcheckout if all objects are mastered locally.)  Declare several VestaSourceAtomic::VSIndex variables that will be used while constructing the local atomic action.

649
650
651
652
653
654
655
656
    if (!omit_old) {
      vsi_oldver = vsa.decl(coldver, vs_oldver);
      vsa.typeCheck(coldver, vsi_oldver, VestaSourceAtomic::
		    typebit(VestaSource::immutableDirectory),
		    VestaSource::inappropriateOp);
      vsa.accessCheck(coldver, vsi_oldver, AccessControl::read,
		      true, VestaSource::noPermission);
    }

If we have an old version, declare the old version to the atomic action.  Add steps to check that it is an immutable directory and that the user has read access to it.

658
      vsi_newverpar = vsa.decl(cnewverpar, vs_newverpar);

Declare the new version parent to the atomic action.

660
661
662
663
664
665
666
667
	// newver already exists
	vsi_newver = vsa.decl(cnewver, vs_newver);
	vsa.typeCheck(cnewver, vsi_newver, VestaSourceAtomic::
		      typebit(VestaSource::stub),
		      VestaSource::inappropriateOp);
	vsa.testMaster(cnewver, vsi_newver, true, VestaSource::notMaster);
	vsa.accessCheck(cnewver, vsi_newver, AccessControl::write, true,
			VestaSource::noPermission);

In the case where the new version already exists (because it was created in a remote repository), declare it to the atomic action.  Also, check that it is a stub, that its master flag is set, and that the user has write access to it.

669
670
671
672
673
674
675
676
677
678
679
680
681
	// newver will be created in newverpar
	vsa.typeCheck(cnewverpar, vsi_newverpar, VestaSourceAtomic::
		      typebit(VestaSource::appendableDirectory),
		      VestaSource::inappropriateOp);
	vsa.testMaster(cnewverpar, vsi_newverpar, true,
		       VestaSource::notMaster);
	vsa.accessCheck(cnewverpar, vsi_newverpar, AccessControl::write, true,
			VestaSource::noPermission);
	// Be sure new version doesn't already exist
	vsa.setTarget("", VestaSource::notFound,
		      VestaSource::notFound, VestaSource::nameInUse);
	vsa.lookup(cnewver, vsi_newverpar, newverarc);
	vsa.setTarget("");

In the case where the new version doesn't exist, check that the new version parent is an appendable directory, that its master flag is set, and that the user has write permission to it.  Also, check that the new version to be created doesn't already exist.

685
      vsi_sessdirpar = vsa.decl(csessdirpar, vs_sessdirpar);

Declare the session directory parent to the atomic action.

687
688
689
690
691
692
693
694
	// sessdir already exists
	vsi_sessdir = vsa.decl(csessdir, vs_sessdir);
	vsa.typeCheck(csessdir, vsi_sessdir, VestaSourceAtomic::
		      typebit(VestaSource::appendableDirectory),
		      VestaSource::inappropriateOp);
	vsa.testMaster(csessdir, vsi_sessdir, true, VestaSource::notMaster);
	vsa.accessCheck(csessdir, vsi_sessdir, AccessControl::write, true,
			VestaSource::noPermission);

In the case where the session directory already exists (because it was created in a remote repository), declare it to the atomic action.  Also, check that it is an appendable directory, that its master flag is set, and that the user has write access to it.

697
698
699
700
701
702
703
704
705
706
707
708
709
	// sessdir will be created in sessdirpar
	vsa.typeCheck(csessdirpar, vsi_sessdirpar, VestaSourceAtomic::
		      typebit(VestaSource::appendableDirectory),
		      VestaSource::inappropriateOp);
	vsa.testMaster(csessdirpar, vsi_sessdirpar, true,
		       VestaSource::notMaster);
	vsa.accessCheck(csessdirpar, vsi_sessdirpar, AccessControl::write,
			true, VestaSource::noPermission);
	// Be sure sessdir doesn't already exist
	vsa.setTarget("", VestaSource::notFound,
		      VestaSource::notFound, VestaSource::nameInUse);
	vsa.lookup(csessdir, vsi_sessdirpar, sessdirarc);
	vsa.setTarget("setTarget");

In the case where the session directory doesn't exist, check that the session directory parent is an appendable directory, that its master flag is set, and that the user has write permission to it.  Also, check that the session directory to be created doesn't already exist.

712
713
714
715
716
717
718
719
720
721
722
723
724
    if (!omit_work) {
      vsi_workdirpar = vsa.decl(cworkdirpar, vs_workdirpar);
      vsa.typeCheck(cworkdirpar, vsi_workdirpar, VestaSourceAtomic::
		    typebit(VestaSource::mutableDirectory),
		    VestaSource::inappropriateOp);
      vsa.accessCheck(cworkdirpar, vsi_workdirpar, AccessControl::write, true,
		      VestaSource::noPermission);
      // Be sure workdir doesn't already exist
      vsa.setTarget("", VestaSource::notFound,
		    VestaSource::notFound, VestaSource::nameInUse);
      vsa.lookup(cworkdir, vsi_workdirpar, workdirarc);
      vsa.setTarget("");
    }

If a working directory is being created, declare the working directory parent to the atomic action.  Then check that it is a mutable directory and that the user has write permission to it.  Finally, check that the working directory to be created doesn't already exist.

729
730
731
732
733
734
      if (!vs_newver) {
	// Insert reservation stub for newver
	vsi_newver =
	  vsa.insertStub(cnewver, vsi_newverpar, newverarc,
			 true, VestaSource::dontReplace);
      }

In the case where the new version needs to be created locally, add a step to create it.

736
737
738
739
740
741
742
743
744
745
746
747
748
      // Set attributes on newver stub
      if (!omit_old) {
	vsa.setAttrib(cnewver, vsi_newver, "old-version", coldver);
      }
      if (!omit_sess) {
	vsa.setAttrib(cnewver, vsi_newver, "session-dir", csessdir);
      }
      if (!omit_work) {
	vsa.setAttrib(cnewver, vsi_newver, "work-dir", cworkdir);
      }
      vsa.setAttrib(cnewver, vsi_newver, "checkout-time", timebuf);
      vsa.setAttrib(cnewver, vsi_newver, "checkout-by", cuser);
      vsa.setAttrib(cnewver, vsi_newver, "#mode", mode);

Add steps to the action to set attributes on the new version.

752
753
754
755
756
757
      if (!vs_sessdir) {
	// Insert session directory
	vsi_sessdir =
	  vsa.insertAppendableDirectory(csessdir, vsi_sessdirpar, sessdirarc,
					true, VestaSource::dontReplace);
      }

In the case where the session directory needs to be created locally, add a step to create it.

758
759
760
761
762
763
764
765
766
767
768
769
770
771
      // Set attribs on session directory
      if (!omit_old) {
	vsa.setAttrib(csessdir, vsi_sessdir, "old-version", coldver);
      }
      if (!omit_new) {
	vsa.setAttrib(csessdir, vsi_sessdir, "new-version", cnewver);
      }
      if (!omit_work) {
	vsa.setAttrib(csessdir, vsi_sessdir, "work-dir", cworkdir);
      }
      vsa.setAttrib(csessdir, vsi_sessdir, "checkout-time", timebuf);
      vsa.setAttrib(csessdir, vsi_sessdir, "checkout-by", cuser);
      vsa.setAttrib(csessdir, vsi_sessdir, "type", "session");
      vsa.setAttrib(csessdir, vsi_sessdir, "#mode", mode);

Add steps to the action to set attributes on the session directory.

773
774
775
776
777
778
      // Insert session version 0
      Text csessver0 = csessdir + "/0";
      if (!omit_old) {
	vsa.insertImmutableDirectory(csessver0, vsi_sessdir, "0", vsi_oldver,
				     true, VestaSource::dontReplace);
      }

If there's an old version, insert a copy of it in the session directory with the name "0".

780
781
782
783
784
785
      // Insert "latest" link in session
      Text clatest = csessdir + "/latest";
      VestaSourceAtomic::VSIndex vsi_latest =
	vsa.insertStub(clatest, vsi_sessdir, "latest", true,
		       VestaSource::dontReplace);
      vsa.setAttrib(clatest, vsi_latest, "symlink-to", "$LAST");

Add steps to the action to insert the "latest" symbolic link in the session directory.

789
790
791
792
793
794
      // Insert working directory
      // Note: okay for vsi_oldver to be -1 here;
      // this makes workdir empty.
      VestaSourceAtomic::VSIndex vsi_workdir =
	vsa.insertMutableDirectory(cworkdir, vsi_workdirpar, workdirarc,
				   vsi_oldver, true, VestaSource::dontReplace);

Add an action to create the working directory.

795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
      // Set attribs on workdir
      if (!omit_old) {
	vsa.setAttrib(cworkdir, vsi_workdir, "old-version", coldver);
      }
      if (!omit_new) {
	vsa.setAttrib(cworkdir, vsi_workdir, "new-version", cnewver);
      }
      if (!omit_sess) {
	vsa.setAttrib(cworkdir, vsi_workdir, "session-dir", csessdir);
	if (!omit_old) {
	  vsa.setAttrib(cworkdir, vsi_workdir, "session-ver-arc", "0");
	}
      }
      vsa.setAttrib(cworkdir, vsi_workdir, "checkout-time", timebuf);
      vsa.setAttrib(cworkdir, vsi_workdir, "checkout-by", cuser);

Add actions to set attributes on the working directory.

812
813
814
815
816
817
818
819
820
821
822
823
824
825
    //
    // Run the local action
    //
    if (!vsa.run()) {
      VestaSource::errorCode err;
      if (vsa.lasterr == VestaSource::ok) {
	err = vsa.okreplace;
      } else {
	err = vsa.lasterr;
      }
      cerr << program_name << ": " << vsa.name(vsa.ndone) << ": "
	   << ReposUI::errorCodeText(err) << endl;
      exit(2);
    }

Execute the local repository atomic action.  If it fails, print an error message and exit.

Class and Member Function Descriptions

class Text

A class for holding ASCII text strings.  Declared in Text.H (in /vesta/vestasys.org/basics/basics).

class Sequence

A template class used to hold a linear sequence of other objects.  Declared in Sequence.H (in /vesta/vestasys.org/basics/generics).

class VestaConfig

A collection of functions making up the interface to the Vesta configuration file.  Declared in VestaConfig.H (in /vesta/vestasys.org/vesta/config).

static member functions called by vcheckout:

class AccessControl

The repository's access control API. Declared in AccessControl.H (in /vesta/vestasys.org/vesta/repos).

Member types used in vcheckout:

static member functions used in vcheckout:

class VestaSource

A base class that represents objects in the repository.  In client applications, instances of the derived class VDirSurrogate are used.

See VestaSource.H (in /vesta/vestasys.org/vesta/repos) for the class declararion.

Member functions called in vcheckout:

class VDirSurrogate

A class derived from VestaSource used in clienat applications.  Most member functions implementations are actually remote procedure calls to the repository server.  Also has static member functions for other operations clients need.

See VDirSurrogate.H (in /vesta/vestasys.org/vesta/repos). for the class declaration.

static member functions called in vcheckout:

class Replicator

A helper class for performing replications between repositories.  An instance of Replicator is used for performing replications from a particular source repository to a particular destination repository.

See Replicator.H (in /vesta/vestasys.org/vesta/repltools) for the class declaration.

Member types used in vcheckout:

Member functions called in vcheckout:

class ReposUI

A collection of helper functions to make writing repository tools easier.  Declared in ReposUI.H (in /vesta/vestasys.org/vesta/repos_ui).

static member functions called in vcheckout:

class VestaSourceAtomic

An alternative interface to the repository which allows a sequence of operations to be performed in a single atomic step.  Most steps that can be sued in an atomic action correspond to VestaSource member functions.  Declared in VestaSourceAtomic.H (in /vesta/vestasys.org/vesta/repos).

Member types used in vcheckout:

Member functions called in vcheckout:


Kenneth C. Schalk <ken@xorian.net>
Last modified: Fri Oct 1 12:20:10 EDT 2004