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