1 /* Check out working files from revisions of RCS files.
2
3 Copyright (C) 2010-2020 Thien-Thi Nguyen
4 Copyright (C) 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5 Copyright (C) 1982, 1988, 1989 Walter Tichy
6
7 This file is part of GNU RCS.
8
9 GNU RCS is free software: you can redistribute it and/or modify it
10 under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
13
14 GNU RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty
16 of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17 See the GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with this program. If not, see <http://www.gnu.org/licenses/>.
21 */
22
23 #include "base.h"
24 #include <string.h>
25 #include <errno.h>
26 #include <stdlib.h>
27 #include "same-inode.h"
28 #include "timespec.h"
29 #include "co.help"
30 #include "b-complain.h"
31 #include "b-divvy.h"
32 #include "b-esds.h"
33 #include "b-excwho.h"
34 #include "b-fb.h"
35 #include "b-feph.h"
36 #include "b-fro.h"
37 #include "b-isr.h"
38 #include "b-peer.h"
39
40 static const char ks_hws[] = " \t";
41
42 #define SKIP_HWS(var) var += strspn (var, ks_hws)
43
44 struct work
45 {
46 struct stat st;
47 bool force;
48 };
49
50 static char const quietarg[] = "-q";
51
52 /* State for -j. */
53 struct jstuff
54 {
55 struct divvy *jstuff;
56 struct link head, *tp;
57 struct symdef *merge;
58 char const *expand, *suffix, *version, *zone;
59
60 struct delta *d;
61 /* Final delta to be generated. */
62
63 char const **ls;
64 /* Revisions to be joined. */
65
66 int lastidx;
67 /* Index of last element in `ls'. */
68 };
69
70 static void
cleanup(int * exitstatus,FILE ** neworkptr)71 cleanup (int *exitstatus, FILE **neworkptr)
72 {
73 FILE *mstdout = MANI (standard_output);
74
75 if (FLOW (erroneous))
76 *exitstatus = exit_failure;
77 fro_zclose (&FLOW (from));
78 ORCSclose ();
79 if (FLOW (from)
80 && STDIO_P (FLOW (from))
81 && FLOW (res)
82 && FLOW (res) != mstdout)
83 Ozclose (&FLOW (res));
84 if (*neworkptr != mstdout)
85 Ozclose (neworkptr);
86 dirtempunlink ();
87 }
88
89 static bool
rmworkfile(struct work * work)90 rmworkfile (struct work *work)
91 /* Prepare to remove ‘MANI (filename)’, if it exists, and if it is read-only.
92 Otherwise (file writable), if !quietmode, ask the user whether to
93 really delete it (default: fail); otherwise fail.
94 Return true if permission is gotten. */
95 {
96 if (work->st.st_mode & (S_IWUSR | S_IWGRP | S_IWOTH) && !work->force)
97 {
98 char const *mani_filename = MANI (filename);
99
100 /* File is writable. */
101 if (!yesorno (false, "writable %s exists%s; remove it",
102 mani_filename, (stat_mine_p (&work->st)
103 ? ""
104 : ", and you do not own it")))
105 {
106 if (!BE (quiet) && ttystdin ())
107 PERR ("checkout aborted");
108 else
109 PERR ("writable %s exists; checkout aborted", mani_filename);
110 return false;
111 }
112 }
113 /* Actual unlink is done later by caller. */
114 return true;
115 }
116
117 static int
rmlock(struct delta const * delta)118 rmlock (struct delta const *delta)
119 /* Remove the lock held by caller on ‘delta’. Return -1 if
120 someone else holds the lock, 0 if there is no lock on delta,
121 and 1 if a lock was found and removed. */
122 {
123 struct link box, *tp;
124 struct rcslock const *rl;
125
126 box.next = GROK (locks);
127 if (! (tp = lock_delta_memq (&box, delta)))
128 /* No lock on ‘delta’. */
129 return 0;
130 rl = tp->next->entry;
131 if (!caller_login_p (rl->login))
132 /* Found a lock on ‘delta’ by someone else. */
133 {
134 RERR ("revision %s locked by %s; use co -r or rcs -u",
135 delta->num, rl->login);
136 return -1;
137 }
138 /* Found a lock on ‘delta’ by caller; delete it. */
139 lock_drop (&box, tp);
140 return 1;
141 }
142
143 static void
jpush(char const * rev,struct jstuff * js)144 jpush (char const *rev, struct jstuff *js)
145 {
146 js->tp = extend (js->tp, rev, js->jstuff);
147 js->lastidx++;
148 }
149
150 static char *
addjoin(char * spec,struct jstuff * js)151 addjoin (char *spec, struct jstuff *js)
152 /* Tokenize ‘spec’; try to resolve the first token to an
153 existing delta; if found, ‘jpush’ its fully-numeric revno
154 and return the "rest" of ‘spec’ (position after first token).
155 If no delta can be determined, return NULL. */
156 {
157 const char delims[] = " \t\n:,;";
158 char *eot, save;
159 struct delta *cool;
160 struct cbuf numrev;
161
162 /* Locate bounds of token. */
163 SKIP_HWS (spec);
164 eot = spec + strcspn (spec, delims);
165
166 /* Save the delim, jam a terminating NUL there for the sake of
167 ‘fully_numeric_no_k’, and restore it after the call. Blech. */
168 save = *eot;
169 *eot = '\0';
170 cool = (fully_numeric_no_k (&numrev, spec)
171 ? delta_from_ref (numrev.string)
172 : NULL);
173 *eot = save;
174
175 if (! cool)
176 return NULL;
177
178 jpush (cool->num, js);
179 SKIP_HWS (eot);
180 return eot;
181 }
182
183 static char const *
getancestor(char const * r1,char const * r2)184 getancestor (char const *r1, char const *r2)
185 /* Return the common ancestor of ‘r1’ and ‘r2’ if successful,
186 ‘NULL’ otherwise. Work reliably only if ‘r1’ and ‘r2’ are not
187 branch numbers. */
188 {
189 char const *t1, *t2;
190 int l1, l2, l3;
191 char const *r;
192
193 /* TODO: Don't bother saving in ‘PLEXUS’. */
194
195 l1 = countnumflds (r1);
196 l2 = countnumflds (r2);
197 if ((2 < l1 || 2 < l2) && !NUM_EQ (r1, r2))
198 {
199 /* Not on main trunk or identical. */
200 l3 = 0;
201 while (NUMF_EQ (1 + l3, r1, r2)
202 && NUMF_EQ (2 + l3, r1, r2))
203 l3 += 2;
204 /* This will terminate since ‘r1’ and ‘r2’ are not the
205 same; see above. */
206 if (l3 == 0)
207 {
208 /* No common prefix; common ancestor on main trunk. */
209 t1 = TAKE (l1 > 2 ? 2 : l1, r1);
210 t2 = TAKE (l2 > 2 ? 2 : l2, r2);
211 r = NUM_LT (t1, t2) ? t1 : t2;
212 if (!NUM_EQ (r, r1) && !NUM_EQ (r, r2))
213 return str_save (r);
214 }
215 else if (!NUMF_EQ (1 + l3, r1, r2))
216 return str_save (TAKE (l3, r1));
217 }
218 RERR ("common ancestor of %s and %s undefined", r1, r2);
219 return NULL;
220 }
221
222 static bool
preparejoin(register char * argv,struct jstuff * js)223 preparejoin (register char *argv, struct jstuff *js)
224 /* Parse join pairs from ‘argv’; ‘jpush’ their revision numbers.
225 Set ‘js->lastidx’ to the last index of the list.
226 Return ‘true’ if all went well, otherwise ‘false’. */
227 {
228 /* This is a two level parse, w/ whitespace complications:
229 top-level is comma-delimited (straightforward), but
230 join pairs are of the form: HWS REV1 HWS [":" HWS REV2 HWS]
231 where HWS is optional horizontal whitespace (SPC, TAB).
232 Also, the first "pair" can be comprised solely of REV1;
233 in that case, the ":" and REV2 are also optional.
234
235 Most of the HWS skipping is done in subroutine ‘addjoin’.
236
237 TODO: Investigate why on addjoin "failure", it's OK to
238 simply return ‘false’ (and not going through ‘done’). */
239
240 const char ks_comma[] = ",";
241 char *s, *save, *j;
242 bool rv = true;
243
244 js->jstuff = make_space ("jstuff");
245 js->head.next = NULL;
246 js->tp = &js->head;
247 if (! js->merge)
248 {
249 js->merge = ZLLOC (1, struct symdef);
250 js->merge->meaningful = "merge";
251 }
252
253 js->lastidx = -1;
254 for (s = argv;
255 (j = strtok_r (s, ks_comma, &save));
256 s = NULL)
257 {
258 if (!(j = addjoin (j, js)))
259 return false;
260 if (*j++ == ':')
261 {
262 SKIP_HWS (j);
263 if (*j == '\0')
264 goto incomplete;
265 if (!(j = addjoin (j, js)))
266 return false;
267 }
268 else
269 {
270 if (js->lastidx == 0) /* first pair */
271 {
272 char const *two = js->tp->entry;
273
274 /* Common ancestor missing. */
275 jpush (two, js);
276 /* Derive common ancestor. */
277 if (! (js->tp->entry = getancestor (js->d->num, two)))
278 rv = false;
279 }
280 else
281 {
282 incomplete:
283 RFATAL ("join pair incomplete");
284 }
285 }
286 }
287 if (js->lastidx < 1)
288 RFATAL ("empty join");
289
290 js->ls = pointer_array (PLEXUS, 1 + js->lastidx);
291 js->tp = js->head.next;
292 for (int i = 0; i <= js->lastidx; i++, js->tp = js->tp->next)
293 js->ls[i] = js->tp->entry;
294 close_space (js->jstuff);
295 js->jstuff = NULL;
296 return rv;
297 }
298
299 /* Elements in the constructed command line prior to this index are
300 boilerplate. From this index on, things are data-dependent. */
301 #define VX 3
302
303 static bool
buildjoin(char const * initialfile,struct jstuff * js)304 buildjoin (char const *initialfile, struct jstuff *js)
305 /* Merge pairs of elements in ‘js->ls’ into ‘initialfile’.
306 If ‘MANI (standard_output)’ is set, copy result to stdout.
307 All unlinking of ‘initialfile’, ‘rev2’, and ‘rev3’
308 should be done by ‘tempunlink’. */
309 {
310 char const *rev2, *rev3;
311 int i;
312 char const *cov[8 + VX], *mergev[11];
313 char const **p;
314 size_t len;
315 char const *subs = NULL;
316
317 rev2 = maketemp (0);
318 rev3 = maketemp (3); /* ‘buildrevision’ may use 1 and 2 */
319
320 cov[1] = PEER_SUPER ();
321 cov[2] = "co";
322 /* ‘cov[VX]’ setup below. */
323 p = &cov[1 + VX];
324 if (js->expand)
325 *p++ = js->expand;
326 if (js->suffix)
327 *p++ = js->suffix;
328 if (js->version)
329 *p++ = js->version;
330 if (js->zone)
331 *p++ = js->zone;
332 *p++ = quietarg;
333 *p++ = REPO (filename);
334 *p = '\0';
335
336 mergev[1] = find_peer_prog (js->merge);
337 mergev[2] = mergev[4] = "-L";
338 /* Rest of ‘mergev’ setup below. */
339
340 i = 0;
341 while (i < js->lastidx)
342 {
343 #define ACCF(...) accf (SINGLE, __VA_ARGS__)
344 /* Prepare marker for merge. */
345 if (i == 0)
346 subs = js->d->num;
347 else
348 {
349 ACCF ("%s,%s:%s", subs, js->ls[i - 2], js->ls[i - 1]);
350 subs = finish_string (SINGLE, &len);
351 }
352 diagnose ("revision %s", js->ls[i]);
353 ACCF ("-p%s", js->ls[i]);
354 cov[VX] = finish_string (SINGLE, &len);
355 if (runv (-1, rev2, cov))
356 goto badmerge;
357 diagnose ("revision %s", js->ls[i + 1]);
358 ACCF ("-p%s", js->ls[i + 1]);
359 cov[VX] = finish_string (SINGLE, &len);
360 if (runv (-1, rev3, cov))
361 goto badmerge;
362 diagnose ("merging...");
363 mergev[3] = subs;
364 mergev[5] = js->ls[i + 1];
365 p = &mergev[6];
366 if (BE (quiet))
367 *p++ = quietarg;
368 if (js->lastidx <= i + 2 && MANI (standard_output))
369 *p++ = "-p";
370 *p++ = initialfile;
371 *p++ = rev2;
372 *p++ = rev3;
373 *p = '\0';
374 if (DIFF_TROUBLE == runv (-1, NULL, mergev))
375 goto badmerge;
376 i = i + 2;
377 #undef ACCF
378 }
379 return true;
380
381 badmerge:
382 FLOW (erroneous) = true;
383 return false;
384 }
385
386 DECLARE_PROGRAM (co, BOG_FULL);
387
388 static int
co_main(const char * cmd,int argc,char ** argv)389 co_main (const char *cmd, int argc, char **argv)
390 {
391 int exitstatus = EXIT_SUCCESS;
392 struct work work = { .force = false };
393 struct jstuff jstuff;
394 FILE *neworkptr = NULL;
395 int lockflag = 0; /* -1: unlock, 0: do nothing, 1: lock. */
396 bool mtimeflag = false;
397 char *a, *joinflag, **newargv;
398 char const *author, *date, *rev, *state;
399 char const *joinname, *newdate, *neworkname;
400 /* 1 if a lock has been changed, -1 if error. */
401 int changelock;
402 int expmode, r, workstatstat;
403 bool tostdout, Ttimeflag;
404 bool selfsame = false;
405 char finaldate[DATESIZE];
406 #if OPEN_O_BINARY
407 int stdout_mode = 0;
408 #endif
409 struct wlink *deltas; /* Deltas to be generated. */
410
411 CHECK_HV (cmd);
412 gnurcs_init (&program);
413 memset (&jstuff, 0, sizeof (struct jstuff));
414
415 setrid ();
416 author = date = rev = state = NULL;
417 joinflag = NULL;
418 expmode = -1;
419 tostdout = false;
420 Ttimeflag = false;
421
422 argc = getRCSINIT (argc, argv, &newargv);
423 argv = newargv;
424 while (a = *++argv, 0 < --argc && *a++ == '-')
425 {
426 switch (*a++)
427 {
428
429 case 'r':
430 revno:
431 chk_set_rev (&rev, a);
432 break;
433
434 case 'f':
435 work.force = true;
436 goto revno;
437
438 case 'l':
439 if (lockflag < 0)
440 {
441 PWARN ("-u overridden by -l.");
442 }
443 lockflag = 1;
444 goto revno;
445
446 case 'u':
447 if (0 < lockflag)
448 {
449 PWARN ("-l overridden by -u.");
450 }
451 lockflag = -1;
452 goto revno;
453
454 case 'p':
455 tostdout = true;
456 goto revno;
457
458 case 'I':
459 BE (interactive) = true;
460 goto revno;
461
462 case 'q':
463 BE (quiet) = true;
464 goto revno;
465
466 case 'd':
467 if (date)
468 redefined ('d');
469 str2date (a, finaldate);
470 date = finaldate;
471 break;
472
473 case 'j':
474 if (*a)
475 {
476 if (joinflag)
477 redefined ('j');
478 joinflag = a;
479 }
480 break;
481
482 case 'M':
483 mtimeflag = true;
484 goto revno;
485
486 case 's':
487 if (*a)
488 {
489 if (state)
490 redefined ('s');
491 state = a;
492 }
493 break;
494
495 case 'S':
496 selfsame = true;
497 break;
498
499 case 'T':
500 if (*a)
501 goto unknown;
502 Ttimeflag = true;
503 break;
504
505 case 'w':
506 if (author)
507 redefined ('w');
508 author = (*a)
509 ? a
510 : getcaller ();
511 break;
512
513 case 'x':
514 jstuff.suffix = *argv;
515 BE (pe) = a;
516 break;
517
518 case 'V':
519 jstuff.version = *argv;
520 setRCSversion (jstuff.version);
521 break;
522
523 case 'z':
524 jstuff.zone = *argv;
525 zone_set (a);
526 break;
527
528 case 'k':
529 /* Set keyword expand mode. */
530 jstuff.expand = *argv;
531 if (0 <= expmode)
532 redefined ('k');
533 if (0 <= (expmode = str2expmode (a)))
534 break;
535 /* fall into */
536 default:
537 unknown:
538 bad_option (*argv);
539
540 };
541 }
542 /* (End of option processing.) */
543
544 /* Now handle all filenames. */
545 if (FLOW (erroneous))
546 cleanup (&exitstatus, &neworkptr);
547 else if (argc < 1)
548 PFATAL ("no input file");
549 else
550 for (; 0 < argc; cleanup (&exitstatus, &neworkptr), ++argv, --argc)
551 {
552 struct stat *repo_stat;
553 char const *mani_filename;
554 int kws;
555
556 ffree ();
557
558 if (pairnames
559 (argc, argv,
560 lockflag ? rcswriteopen : rcsreadopen,
561 true, false) <= 0)
562 continue;
563
564 /* ‘REPO (filename)’ contains the name of the RCS file, and
565 ‘FLOW (from)’ points at it. ‘MANI (filename)’ contains the
566 name of the working file. Also, ‘REPO (stat)’ has been set. */
567 repo_stat = &REPO (stat);
568 mani_filename = MANI (filename);
569 kws = BE (kws);
570 diagnose ("%s --> %s", REPO (filename),
571 tostdout ? "standard output" : mani_filename);
572
573 workstatstat = -1;
574 if (tostdout)
575 {
576 #if OPEN_O_BINARY
577 int newmode = kws == kwsub_b ? OPEN_O_BINARY : 0;
578 if (stdout_mode != newmode)
579 {
580 stdout_mode = newmode;
581 oflush ();
582 setmode (STDOUT_FILENO, newmode);
583 }
584 #endif
585 neworkname = NULL;
586 neworkptr = MANI (standard_output) = stdout;
587 }
588 else
589 {
590 workstatstat = stat (mani_filename, &work.st);
591 if (!PROB (workstatstat) && SAME_INODE (REPO (stat), work.st))
592 {
593 RERR ("RCS file is the same as working file %s.",
594 mani_filename);
595 continue;
596 }
597 neworkname = makedirtemp (true);
598 if (!(neworkptr = fopen_safer (neworkname, FOPEN_W_WORK)))
599 {
600 if (errno == EACCES)
601 MERR ("permission denied on parent directory");
602 else
603 syserror_errno (neworkname);
604 continue;
605 }
606 }
607
608 if (!REPO (tip))
609 {
610 /* No revisions; create empty file. */
611 diagnose ("no revisions present; generating empty revision 0.0");
612 if (lockflag)
613 PWARN ("no revisions, so nothing can be %slocked",
614 lockflag < 0 ? "un" : "");
615 Ozclose (&FLOW (res));
616 if (!PROB (workstatstat))
617 if (!rmworkfile (&work))
618 continue;
619 changelock = 0;
620 newdate = NULL;
621 }
622 else
623 {
624 struct cbuf numericrev;
625 int locks = lockflag ? findlock (false, &jstuff.d) : 0;
626 struct fro *from = FLOW (from);
627
628 if (rev)
629 {
630 /* Expand symbolic revision number. */
631 if (!fully_numeric_no_k (&numericrev, rev))
632 continue;
633 }
634 else
635 {
636 switch (locks)
637 {
638 default:
639 continue;
640 case 0:
641 numericrev.string = GROK (branch) ? GROK (branch) : "";
642 break;
643 case 1:
644 numericrev.string = str_save (jstuff.d->num);
645 break;
646 }
647 }
648 /* Get numbers of deltas to be generated. */
649 if (! (jstuff.d = genrevs (numericrev.string, date, author,
650 state, &deltas)))
651 continue;
652 /* Check reservations. */
653 changelock = lockflag < 0
654 ? rmlock (jstuff.d)
655 : (lockflag == 0
656 ? 0
657 : addlock_maybe (jstuff.d, selfsame, true));
658
659 if (changelock < 0
660 || (changelock && !checkaccesslist ())
661 || PROB (dorewrite (lockflag, changelock)))
662 continue;
663
664 if (0 <= expmode)
665 kws = BE (kws) = expmode;
666 if (0 < lockflag && kws == kwsub_v)
667 {
668 RERR ("cannot combine -kv and -l");
669 continue;
670 }
671
672 if (joinflag && !preparejoin (joinflag, &jstuff))
673 continue;
674
675 diagnose ("revision %s%s", jstuff.d->num,
676 0 < lockflag ? " (locked)" :
677 lockflag < 0 ? " (unlocked)" : "");
678 SAME_AFTER (from, jstuff.d->text);
679
680 /* Prepare to remove old working file if necessary. */
681 if (!PROB (workstatstat))
682 if (!rmworkfile (&work))
683 continue;
684
685 /* Skip description (don't echo). */
686 write_desc_maybe (FLOW (to));
687
688 BE (inclusive_of_Locker_in_Id_val) = 0 < lockflag;
689 jstuff.d->name = namedrev (rev, jstuff.d);
690 joinname = buildrevision (deltas, jstuff.d,
691 joinflag && tostdout ? NULL : neworkptr,
692 kws < MIN_UNEXPAND);
693 if (FLOW (res) == neworkptr)
694 FLOW (res) = NULL; /* Don't close it twice. */
695 if (changelock && deltas->entry != jstuff.d)
696 fro_trundling (true, from);
697
698 if (PROB (donerewrite (changelock,
699 file_mtime (Ttimeflag, repo_stat))))
700 continue;
701
702 if (changelock)
703 {
704 locks += lockflag;
705 if (1 < locks)
706 RWARN ("You now have %d locks.", locks);
707 }
708
709 newdate = jstuff.d->date;
710 if (joinflag)
711 {
712 newdate = NULL;
713 if (!joinname)
714 {
715 aflush (neworkptr);
716 joinname = neworkname;
717 }
718 if (kws == kwsub_b)
719 MERR ("merging binary files");
720 if (!buildjoin (joinname, &jstuff))
721 continue;
722 }
723 }
724 if (!tostdout)
725 {
726 mode_t m = WORKMODE (repo_stat->st_mode,
727 !(kws == kwsub_v
728 || (lockflag <= 0 && BE (strictly_locking))));
729 time_t t = mtimeflag && newdate
730 ? date2time (newdate)
731 : TIME_UNSPECIFIED;
732 aflush (neworkptr);
733 IGNOREINTS ();
734 r = chnamemod (&neworkptr, neworkname, mani_filename, 1, m,
735 make_timespec (t, ZERO_NANOSECONDS));
736 keepdirtemp (neworkname);
737 RESTOREINTS ();
738 if (PROB (r))
739 {
740 syserror_errno (mani_filename);
741 PERR ("see %s", neworkname);
742 continue;
743 }
744 diagnose ("done");
745 }
746 }
747
748 tempunlink ();
749 Ozclose (&MANI (standard_output));
750 gnurcs_goodbye ();
751 return exitstatus;
752 }
753
754 static const uint8_t co_aka[13] =
755 {
756 2 /* count */,
757 2,'c','o',
758 8,'c','h','e','c','k','o','u','t'
759 };
760
761 YET_ANOTHER_COMMAND (co);
762
763 /*:help
764 [options] file ...
765 Options:
766 -f[REV] Force overwrite of working file.
767 -I[REV] Interactive.
768 -p[REV] Write to stdout instead of the working file.
769 -q[REV] Quiet mode.
770 -r[REV] Normal checkout.
771 -l[REV] Like -r, but also lock.
772 -u[REV] Like -l, but unlock.
773 -M[REV] Reset working file mtime (relevant for -l, -u).
774 -kSUBST Use SUBST substitution, one of: kv, kvl, k, o, b, v.
775 -dDATE Select latest before or on DATE.
776 -jJOINS Merge using JOINS, a list of REV:REV pairs;
777 this option is obsolete -- see rcsmerge(1).
778 -sSTATE Select matching state STATE.
779 -S Enable "self-same" mode.
780 -T Preserve the modification time on the RCS file
781 even if it changes because a lock is added or removed.
782 -wWHO Select matching login WHO.
783 -V Obsolete; do not use.
784 -VN Emulate RCS version N.
785 -xSUFF Specify SUFF as a slash-separated list of suffixes
786 used to identify RCS file names.
787 -zZONE Specify date output format in keyword-substitution
788 and also the default timezone for -dDATE.
789
790 Multiple flags in {fIlMpqru} may be used, except for -r, -l, -u, which are
791 mutually exclusive. If specified, REV can be symbolic, numeric, or mixed:
792 symbolic -- must have been defined previously (see ci(1))
793 $ -- determine the revision number from keyword values
794 in the working file
795 .N -- prepend default branch => DEFBR.N
796 BR.N -- use this
797 BR -- latest revision on branch BR
798 If REV is omitted, take it to be the latest on the default branch.
799 */
800
801 /* co.c ends here */
802