Teach rcs utils about commitid phrase.
[dragonfly.git] / gnu / usr.bin / rcs / ci / ci.c
1 /* Check in revisions of RCS files from working files.  */
2
3 /* Copyright 1982, 1988, 1989 Walter Tichy
4    Copyright 1990, 1991, 1992, 1993, 1994, 1995 Paul Eggert
5    Distributed under license by the Free Software Foundation, Inc.
6
7 This file is part of RCS.
8
9 RCS is free software; you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation; either version 2, or (at your option)
12 any later version.
13
14 RCS is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 GNU General Public License for more details.
18
19 You should have received a copy of the GNU General Public License
20 along with RCS; see the file COPYING.
21 If not, write to the Free Software Foundation,
22 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23
24 Report problems and direct all questions to:
25
26     rcs-bugs@cs.purdue.edu
27
28 */
29
30 /*
31  * $FreeBSD: src/gnu/usr.bin/rcs/ci/ci.c,v 1.7 1999/08/27 23:36:38 peter Exp $
32  * $DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $
33  *
34  * Revision 5.30  1995/06/16 06:19:24  eggert
35  * Update FSF address.
36  *
37  * Revision 5.29  1995/06/01 16:23:43  eggert
38  * (main): Add -kb.
39  * Use `cmpdate', not `cmpnum', to compare dates.
40  * This is for MKS RCS's incompatible 20th-century date format.
41  * Don't worry about errno after ftruncate fails.
42  * Fix input file rewinding bug when large_memory && !maps_memory
43  * and checking in a branch tip.
44  *
45  * (fixwork): Fall back on chmod if fchmod fails, since it might be ENOSYS.
46  *
47  * Revision 5.28  1994/03/20 04:52:58  eggert
48  * Do not generate a corrupted RCS file if the user modifies the working file
49  * while `ci' is running.
50  * Do not remove the lock when `ci -l' reverts.
51  * Move buffer-flushes out of critical sections, since they aren't critical.
52  * Use ORCSerror to clean up after a fatal error.
53  * Specify subprocess input via file descriptor, not file name.
54  *
55  * Revision 5.27  1993/11/09 17:40:15  eggert
56  * -V now prints version on stdout and exits.  Don't print usage twice.
57  *
58  * Revision 5.26  1993/11/03 17:42:27  eggert
59  * Add -z.  Don't subtract from RCS file timestamp even if -T.
60  * Scan for and use Name keyword if -k.
61  * Don't discard ignored phrases.  Improve quality of diagnostics.
62  *
63  * Revision 5.25  1992/07/28  16:12:44  eggert
64  * Add -i, -j, -V.  Check that working and RCS files are distinct.
65  *
66  * Revision 5.24  1992/02/17  23:02:06  eggert
67  * `-rREV' now just specifies a revision REV; only bare `-r' reverts to default.
68  * Add -T.
69  *
70  * Revision 5.23  1992/01/27  16:42:51  eggert
71  * Always unlock branchpoint if caller has a lock.
72  * Add support for bad_chmod_close, bad_creat0.  lint -> RCS_lint
73  *
74  * Revision 5.22  1992/01/06  02:42:34  eggert
75  * Invoke utime() before chmod() to keep some buggy systems happy.
76  *
77  * Revision 5.21  1991/11/20  17:58:07  eggert
78  * Don't read the delta tree from a nonexistent RCS file.
79  *
80  * Revision 5.20  1991/10/07  17:32:46  eggert
81  * Fix log bugs.  Remove lint.
82  *
83  * Revision 5.19  1991/09/26  23:10:30  eggert
84  * Plug file descriptor leak.
85  *
86  * Revision 5.18  1991/09/18  07:29:10  eggert
87  * Work around a common ftruncate() bug.
88  *
89  * Revision 5.17  1991/09/10  22:15:46  eggert
90  * Fix test for redirected stdin.
91  *
92  * Revision 5.16  1991/08/19  23:17:54  eggert
93  * When there are no changes, revert to previous revision instead of aborting.
94  * Add piece tables, -M, -r$.  Tune.
95  *
96  * Revision 5.15  1991/04/21  11:58:14  eggert
97  * Ensure that working file is newer than RCS file after ci -[lu].
98  * Add -x, RCSINIT, MS-DOS support.
99  *
100  * Revision 5.14  1991/02/28  19:18:47  eggert
101  * Don't let a setuid ci create a new RCS file; rcs -i -a must be run first.
102  * Fix ci -ko -l mode bug.  Open work file at most once.
103  *
104  * Revision 5.13  1991/02/25  07:12:33  eggert
105  * getdate -> getcurdate (SVR4 name clash)
106  *
107  * Revision 5.12  1990/12/31  01:00:12  eggert
108  * Don't use uninitialized storage when handling -{N,n}.
109  *
110  * Revision 5.11  1990/12/04  05:18:36  eggert
111  * Use -I for prompts and -q for diagnostics.
112  *
113  * Revision 5.10  1990/11/05  20:30:10  eggert
114  * Don't remove working file when aborting due to no changes.
115  *
116  * Revision 5.9  1990/11/01  05:03:23  eggert
117  * Add -I and new -t behavior.  Permit arbitrary data in logs.
118  *
119  * Revision 5.8  1990/10/04  06:30:09  eggert
120  * Accumulate exit status across files.
121  *
122  * Revision 5.7  1990/09/25  20:11:46  hammer
123  * fixed another small typo
124  *
125  * Revision 5.6  1990/09/24  21:48:50  hammer
126  * added cleanups from Paul Eggert.
127  *
128  * Revision 5.5  1990/09/21  06:16:38  hammer
129  * made it handle multiple -{N,n}'s.  Also, made it treat re-directed stdin
130  * the same as the terminal
131  *
132  * Revision 5.4  1990/09/20  02:38:51  eggert
133  * ci -k now checks dates more thoroughly.
134  *
135  * Revision 5.3  1990/09/11  02:41:07  eggert
136  * Fix revision bug with `ci -k file1 file2'.
137  *
138  * Revision 5.2  1990/09/04  08:02:10  eggert
139  * Permit adjacent revisions with identical time stamps (possible on fast hosts).
140  * Improve incomplete line handling.  Standardize yes-or-no procedure.
141  *
142  * Revision 5.1  1990/08/29  07:13:44  eggert
143  * Expand locker value like co.  Clean old log messages too.
144  *
145  * Revision 5.0  1990/08/22  08:10:00  eggert
146  * Don't require a final newline.
147  * Make lock and temp files faster and safer.
148  * Remove compile-time limits; use malloc instead.
149  * Permit dates past 1999/12/31.  Switch to GMT.
150  * Add setuid support.  Don't pass +args to diff.  Check diff's output.
151  * Ansify and Posixate.  Add -k, -V.  Remove snooping.  Tune.
152  * Check diff's output.
153  *
154  * Revision 4.9  89/05/01  15:10:54  narten
155  * changed copyright header to reflect current distribution rules
156  *
157  * Revision 4.8  88/11/08  13:38:23  narten
158  * changes from root@seismo.CSS.GOV (Super User)
159  * -d with no arguments uses the mod time of the file it is checking in
160  *
161  * Revision 4.7  88/08/09  19:12:07  eggert
162  * Make sure workfile is a regular file; use its mode if RCSfile doesn't have one.
163  * Use execv(), not system(); allow cc -R; remove lint.
164  * isatty(fileno(stdin)) -> ttystdin()
165  *
166  * Revision 4.6  87/12/18  11:34:41  narten
167  * lint cleanups (from Guy Harris)
168  *
169  * Revision 4.5  87/10/18  10:18:48  narten
170  * Updating version numbers. Changes relative to revision 1.1 are actually
171  * relative to 4.3
172  *
173  * Revision 1.3  87/09/24  13:57:19  narten
174  * Sources now pass through lint (if you ignore printf/sprintf/fprintf
175  * warnings)
176  *
177  * Revision 1.2  87/03/27  14:21:33  jenkins
178  * Port to suns
179  *
180  * Revision 4.3  83/12/15  12:28:54  wft
181  * ci -u and ci -l now set mode of working file properly.
182  *
183  * Revision 4.2  83/12/05  13:40:54  wft
184  * Merged with 3.9.1.1: added calls to clearerr(stdin).
185  * made rewriteflag external.
186  *
187  * Revision 4.1  83/05/10  17:03:06  wft
188  * Added option -d and -w, and updated assingment of date, etc. to new delta.
189  * Added handling of default branches.
190  * Option -k generates std. log message; fixed undef. pointer in reading of log.
191  * Replaced getlock() with findlock(), link--unlink with rename(),
192  * getpwuid() with getcaller().
193  * Moved all revision number generation to new routine addelta().
194  * Removed calls to stat(); now done by pairfilenames().
195  * Changed most calls to catchints() with restoreints().
196  * Directed all interactive messages to stderr.
197  *
198  * Revision 3.9.1.1  83/10/19  04:21:03  lepreau
199  * Added clearerr(stdin) to getlogmsg() for re-reading stdin.
200  *
201  * Revision 3.9  83/02/15  15:25:44  wft
202  * 4.2 prerelease
203  *
204  * Revision 3.9  83/02/15  15:25:44  wft
205  * Added call to fastcopy() to copy remainder of RCS file.
206  *
207  * Revision 3.8  83/01/14  15:34:05  wft
208  * Added ignoring of interrupts while new RCS file is renamed;
209  * Avoids deletion of RCS files by interrupts.
210  *
211  * Revision 3.7  82/12/10  16:09:20  wft
212  * Corrected checking of return code from diff.
213  *
214  * Revision 3.6  82/12/08  21:34:49  wft
215  * Using DATEFORM to prepare date of checked-in revision;
216  * Fixed return from addbranch().
217  *
218  * Revision 3.5  82/12/04  18:32:42  wft
219  * Replaced getdelta() with gettree(), SNOOPDIR with SNOOPFILE. Updated
220  * field lockedby in removelock(), moved getlogmsg() before calling diff.
221  *
222  * Revision 3.4  82/12/02  13:27:13  wft
223  * added option -k.
224  *
225  * Revision 3.3  82/11/28  20:53:31  wft
226  * Added mustcheckin() to check for redundant checkins.
227  * Added xpandfile() to do keyword expansion for -u and -l;
228  * -m appends linefeed to log message if necessary.
229  * getlogmsg() suppresses prompt if stdin is not a terminal.
230  * Replaced keeplock with lockflag, fclose() with ffclose(),
231  * %02d with %.2d, getlogin() with getpwuid().
232  *
233  * Revision 3.2  82/10/18  20:57:23  wft
234  * An RCS file inherits its mode during the first ci from the working file,
235  * otherwise it stays the same, except that write permission is removed.
236  * Fixed ci -l, added ci -u (both do an implicit co after the ci).
237  * Fixed call to getlogin(), added call to getfullRCSname(), added check
238  * for write error.
239  * Changed conflicting identifiers.
240  *
241  * Revision 3.1  82/10/13  16:04:59  wft
242  * fixed type of variables receiving from getc() (char -> int).
243  * added include file dbm.h for getting BYTESIZ. This is used
244  * to check the return code from diff portably.
245  */
246
247 #include "rcsbase.h"
248
249 struct Symrev {
250        char const *ssymbol;
251        int override;
252        struct Symrev * nextsym;
253 };
254
255 static char const *getcurdate P((void));
256 static int addbranch P((struct hshentry*,struct buf*,int));
257 static int addelta P((void));
258 static int addsyms P((char const*));
259 static int fixwork P((mode_t,time_t));
260 static int removelock P((struct hshentry*));
261 static int xpandfile P((RILE*,struct hshentry const*,char const**,int));
262 static struct cbuf getlogmsg P((void));
263 static void cleanup P((void));
264 static void incnum P((char const*,struct buf*));
265 static void addassoclst P((int,char const*));
266
267 enum {RANDOM_BYTES = 8};
268 enum {COMMITID_RAW_SIZE = (sizeof(time_t) + RANDOM_BYTES)};
269 static void convert P((char const input[COMMITID_RAW_SIZE], char *output));
270
271 static FILE *exfile;
272 static RILE *workptr;                   /* working file pointer         */
273 static struct buf newdelnum;            /* new revision number          */
274 static struct cbuf msg;
275 static int exitstatus;
276 static int forceciflag;                 /* forces check in              */
277 static int keepflag, keepworkingfile, rcsinitflag;
278 static struct hshentries *gendeltas;    /* deltas to be generated       */
279 static struct hshentry *targetdelta;    /* old delta to be generated    */
280 static struct hshentry newdelta;        /* new delta to be inserted     */
281 static struct stat workstat;
282 static struct Symrev *assoclst, **nextassoc;
283
284 mainProg(ciId, "ci", "$DragonFly: src/gnu/usr.bin/rcs/ci/ci.c,v 1.3 2007/01/17 17:56:23 y0netan1 Exp $")
285 {
286         static char const cmdusage[] =
287                 "\nci usage: ci -{fIklMqru}[rev] -d[date] -mmsg -{nN}name -sstate -ttext -T -Vn -wwho -xsuff -zzone file ...";
288         static char const default_state[] = DEFAULTSTATE;
289
290         char altdate[datesize];
291         char olddate[datesize];
292         char newdatebuf[datesize + zonelenmax];
293         char targetdatebuf[datesize + zonelenmax];
294         char commitid[commitidsize];
295         char *a, **newargv, *textfile;
296         char const *author, *krev, *rev, *state;
297         char const *diffname, *expname;
298         char const *newworkname;
299         int initflag, mustread;
300         int lockflag, lockthis, mtimeflag, removedlock, Ttimeflag;
301         int r;
302         int changedRCS, changework, dolog, newhead;
303         int usestatdate; /* Use mod time of file for -d.  */
304         mode_t newworkmode; /* mode for working file */
305         time_t mtime, wtime;
306         struct hshentry *workdelta;
307
308         setrid();
309
310         author = rev = state = textfile = 0;
311         initflag = lockflag = mustread = false;
312         mtimeflag = false;
313         Ttimeflag = false;
314         altdate[0]= '\0'; /* empty alternate date for -d */
315         usestatdate=false;
316         suffixes = X_DEFAULT;
317         nextassoc = &assoclst;
318
319         {
320                 char buf[COMMITID_RAW_SIZE] = { 0, };
321                 ssize_t len = 0;
322                 time_t rightnow = time (NULL);
323                 char *startrand = buf + sizeof (time_t);
324                 unsigned char *p = (unsigned char *) startrand;
325                 size_t randbytes = RANDOM_BYTES;
326                 int flags = O_RDONLY;
327                 int fd;
328 #ifdef O_NOCTTY
329                 flags |= O_NOCTTY;
330 #endif
331                 if (rightnow != (time_t)-1)
332                         while (rightnow > 0) {
333                                 *--p = rightnow % (UCHAR_MAX + 1);
334                                 rightnow /= UCHAR_MAX + 1;
335                         }
336                 else {
337                         /* try to use more random data */
338                         randbytes = COMMITID_RAW_SIZE;
339                         startrand = buf;
340                 }
341                 fd = open (urandom_dev, flags);
342                 if (fd >= 0) {
343                         len = read (fd, startrand, randbytes);
344                         close (fd);
345                 }
346                 if (len <= 0) {
347                         /* no random data was available so use pid */
348                         long int pid = (long int)getpid ();
349                         p = (unsigned char *) (startrand + sizeof (pid));
350                         while (pid > 0) {
351                             *--p = pid % (UCHAR_MAX + 1);
352                             pid /= UCHAR_MAX + 1;
353                         }
354                 }
355                 convert(buf, commitid);
356         }
357
358         argc = getRCSINIT(argc, argv, &newargv);
359         argv = newargv;
360         while (a = *++argv,  0<--argc && *a++=='-') {
361                 switch (*a++) {
362
363                 case 'r':
364                         if (*a)
365                                 goto revno;
366                         keepworkingfile = lockflag = false;
367                         break;
368
369                 case 'l':
370                         keepworkingfile = lockflag = true;
371                 revno:
372                         if (*a) {
373                                 if (rev) warn("redefinition of revision number");
374                                 rev = a;
375                         }
376                         break;
377
378                 case 'u':
379                         keepworkingfile=true; lockflag=false;
380                         goto revno;
381
382                 case 'i':
383                         initflag = true;
384                         goto revno;
385
386                 case 'j':
387                         mustread = true;
388                         goto revno;
389
390                 case 'I':
391                         interactiveflag = true;
392                         goto revno;
393
394                 case 'q':
395                         quietflag=true;
396                         goto revno;
397
398                 case 'f':
399                         forceciflag=true;
400                         goto revno;
401
402                 case 'k':
403                         keepflag=true;
404                         goto revno;
405
406                 case 'm':
407                         if (msg.size) redefined('m');
408                         msg = cleanlogmsg(a, strlen(a));
409                         if (!msg.size)
410                                 error("missing message for -m option");
411                         break;
412
413                 case 'n':
414                         if (!*a) {
415                                 error("missing symbolic name after -n");
416                                 break;
417                         }
418                         checkssym(a);
419                         addassoclst(false, a);
420                         break;
421
422                 case 'N':
423                         if (!*a) {
424                                 error("missing symbolic name after -N");
425                                 break;
426                         }
427                         checkssym(a);
428                         addassoclst(true, a);
429                         break;
430
431                 case 's':
432                         if (*a) {
433                                 if (state) redefined('s');
434                                 checksid(a);
435                                 state = a;
436                         } else
437                                 error("missing state for -s option");
438                         break;
439
440                 case 't':
441                         if (*a) {
442                                 if (textfile) redefined('t');
443                                 textfile = a;
444                         }
445                         break;
446
447                 case 'd':
448                         if (altdate[0] || usestatdate)
449                                 redefined('d');
450                         altdate[0] = '\0';
451                         if (!(usestatdate = !*a))
452                                 str2date(a, altdate);
453                         break;
454
455                 case 'M':
456                         mtimeflag = true;
457                         goto revno;
458
459                 case 'w':
460                         if (*a) {
461                                 if (author) redefined('w');
462                                 checksid(a);
463                                 author = a;
464                         } else
465                                 error("missing author for -w option");
466                         break;
467
468                 case 'x':
469                         suffixes = a;
470                         break;
471
472                 case 'V':
473                         setRCSversion(*argv);
474                         break;
475
476                 case 'z':
477                         zone_set(a);
478                         break;
479
480                 case 'T':
481                         if (!*a) {
482                                 Ttimeflag = true;
483                                 break;
484                         }
485                         /* fall into */
486                 default:
487                         error("unknown option: %s%s", *argv, cmdusage);
488                 };
489         }  /* end processing of options */
490
491         /* Handle all pathnames.  */
492         if (nerror) cleanup();
493         else if (argc < 1) faterror("no input file%s", cmdusage);
494         else for (;  0 < argc;  cleanup(), ++argv, --argc) {
495         targetdelta = 0;
496         ffree();
497
498         switch (pairnames(argc, argv, rcswriteopen, mustread, false)) {
499
500         case -1:                /* New RCS file */
501 #               if has_setuid && has_getuid
502                     if (euid() != ruid()) {
503                         workerror("setuid initial checkin prohibited; use `rcs -i -a' first");
504                         continue;
505                     }
506 #               endif
507                 rcsinitflag = true;
508                 break;
509
510         case 0:                 /* Error */
511                 continue;
512
513         case 1:                 /* Normal checkin with prev . RCS file */
514                 if (initflag) {
515                         rcserror("already exists");
516                         continue;
517                 }
518                 rcsinitflag = !Head;
519         }
520
521         /*
522          * RCSname contains the name of the RCS file, and
523          * workname contains the name of the working file.
524          * If the RCS file exists, finptr contains the file descriptor for the
525          * RCS file, and RCSstat is set. The admin node is initialized.
526          */
527
528         diagnose("%s  <--  %s\n", RCSname, workname);
529
530         if (!(workptr = Iopen(workname, FOPEN_R_WORK, &workstat))) {
531                 eerror(workname);
532                 continue;
533         }
534
535         if (finptr) {
536                 if (same_file(RCSstat, workstat, 0)) {
537                         rcserror("RCS file is the same as working file %s.",
538                                 workname
539                         );
540                         continue;
541                 }
542                 if (!checkaccesslist())
543                         continue;
544         }
545
546         krev = rev;
547         if (keepflag) {
548                 /* get keyword values from working file */
549                 if (!getoldkeys(workptr)) continue;
550                 if (!rev  &&  !*(krev = prevrev.string)) {
551                         workerror("can't find a revision number");
552                         continue;
553                 }
554                 if (!*prevdate.string && *altdate=='\0' && usestatdate==false)
555                         workwarn("can't find a date");
556                 if (!*prevauthor.string && !author)
557                         workwarn("can't find an author");
558                 if (!*prevstate.string && !state)
559                         workwarn("can't find a state");
560         } /* end processing keepflag */
561
562         /* Read the delta tree.  */
563         if (finptr)
564             gettree();
565
566         /* expand symbolic revision number */
567         if (!fexpandsym(krev, &newdelnum, workptr))
568             continue;
569
570         /* splice new delta into tree */
571         if ((removedlock = addelta()) < 0)
572             continue;
573
574         newdelta.num = newdelnum.string;
575         newdelta.branches = 0;
576         newdelta.lockedby = 0; /* This might be changed by addlock().  */
577         newdelta.selector = true;
578         newdelta.name = 0;
579         clear_buf(&newdelta.ig);
580         clear_buf(&newdelta.igtext);
581         /* set commitid */
582         newdelta.commitid = commitid;
583         /* set author */
584         if (author)
585                 newdelta.author=author;     /* set author given by -w         */
586         else if (keepflag && *prevauthor.string)
587                 newdelta.author=prevauthor.string; /* preserve old author if possible*/
588         else    newdelta.author=getcaller();/* otherwise use caller's id      */
589         newdelta.state = default_state;
590         if (state)
591                 newdelta.state=state;       /* set state given by -s          */
592         else if (keepflag && *prevstate.string)
593                 newdelta.state=prevstate.string;   /* preserve old state if possible */
594         if (usestatdate) {
595             time2date(workstat.st_mtime, altdate);
596         }
597         if (*altdate!='\0')
598                 newdelta.date=altdate;      /* set date given by -d           */
599         else if (keepflag && *prevdate.string) {
600                 /* Preserve old date if possible.  */
601                 str2date(prevdate.string, olddate);
602                 newdelta.date = olddate;
603         } else
604                 newdelta.date = getcurdate();  /* use current date */
605         /* now check validity of date -- needed because of -d and -k          */
606         if (targetdelta &&
607             cmpdate(newdelta.date,targetdelta->date) < 0) {
608                 rcserror("Date %s precedes %s in revision %s.",
609                         date2str(newdelta.date, newdatebuf),
610                         date2str(targetdelta->date, targetdatebuf),
611                         targetdelta->num
612                 );
613                 continue;
614         }
615
616
617         if (lockflag  &&  addlock(&newdelta, true) < 0) continue;
618
619         if (keepflag && *prevname.string)
620             if (addsymbol(newdelta.num, prevname.string, false)  <  0)
621                 continue;
622         if (!addsyms(newdelta.num))
623             continue;
624
625
626         putadmin();
627         puttree(Head,frewrite);
628         putdesc(false,textfile);
629
630         changework = Expand < MIN_UNCHANGED_EXPAND;
631         dolog = true;
632         lockthis = lockflag;
633         workdelta = &newdelta;
634
635         /* build rest of file */
636         if (rcsinitflag) {
637                 diagnose("initial revision: %s\n", newdelta.num);
638                 /* get logmessage */
639                 newdelta.log=getlogmsg();
640                 putdftext(&newdelta, workptr, frewrite, false);
641                 RCSstat.st_mode = workstat.st_mode;
642                 RCSstat.st_nlink = 0;
643                 changedRCS = true;
644         } else {
645                 diffname = maketemp(0);
646                 newhead  =  Head == &newdelta;
647                 if (!newhead)
648                         foutptr = frewrite;
649                 expname = buildrevision(
650                         gendeltas, targetdelta, (FILE*)0, false
651                 );
652                 if (
653                     !forceciflag  &&
654                     strcmp(newdelta.state, targetdelta->state) == 0  &&
655                     (changework = rcsfcmp(
656                         workptr, &workstat, expname, targetdelta
657                     )) <= 0
658                 ) {
659                     diagnose("file is unchanged; reverting to previous revision %s\n",
660                         targetdelta->num
661                     );
662                     if (removedlock < lockflag) {
663                         diagnose("previous revision was not locked; ignoring -l option\n");
664                         lockthis = 0;
665                     }
666                     dolog = false;
667                     if (! (changedRCS = lockflag<removedlock || assoclst))
668                         workdelta = targetdelta;
669                     else {
670                         /*
671                          * We have started to build the wrong new RCS file.
672                          * Start over from the beginning.
673                          */
674                         long hwm = ftell(frewrite);
675                         int bad_truncate;
676                         Orewind(frewrite);
677
678                         /*
679                         * Work around a common ftruncate() bug:
680                         * NFS won't let you truncate a file that you
681                         * currently lack permissions for, even if you
682                         * had permissions when you opened it.
683                         * Also, Posix 1003.1b-1993 sec 5.6.7.2 p 128 l 1022
684                         * says ftruncate might fail because it's not supported.
685                         */
686 #                       if !has_ftruncate
687 #                           undef ftruncate
688 #                           define ftruncate(fd,length) (-1)
689 #                       endif
690                         bad_truncate = ftruncate(fileno(frewrite), (off_t)0);
691
692                         Irewind(finptr);
693                         Lexinit();
694                         getadmin();
695                         gettree();
696                         if (!(workdelta = genrevs(
697                             targetdelta->num, (char*)0, (char*)0, (char*)0,
698                             &gendeltas
699                         )))
700                             continue;
701                         workdelta->log = targetdelta->log;
702                         if (newdelta.state != default_state)
703                             workdelta->state = newdelta.state;
704                         if (lockthis<removedlock && removelock(workdelta)<0)
705                             continue;
706                         if (!addsyms(workdelta->num))
707                             continue;
708                         if (dorewrite(true, true) != 0)
709                             continue;
710                         fastcopy(finptr, frewrite);
711                         if (bad_truncate)
712                             while (ftell(frewrite) < hwm)
713                                 /* White out any earlier mistake with '\n's.  */
714                                 /* This is unlikely.  */
715                                 afputc('\n', frewrite);
716                     }
717                 } else {
718                     int wfd = Ifileno(workptr);
719                     struct stat checkworkstat;
720                     char const *diffv[6 + !!OPEN_O_BINARY], **diffp;
721 #                   if large_memory && !maps_memory
722                         FILE *wfile = workptr->stream;
723                         long wfile_off;
724 #                   endif
725 #                   if !has_fflush_input && !(large_memory && maps_memory)
726                         off_t wfd_off;
727 #                   endif
728
729                     diagnose("new revision: %s; previous revision: %s\n",
730                         newdelta.num, targetdelta->num
731                     );
732                     newdelta.log = getlogmsg();
733 #                   if !large_memory
734                         Irewind(workptr);
735 #                       if has_fflush_input
736                             if (fflush(workptr) != 0)
737                                 Ierror();
738 #                       endif
739 #                   else
740 #                       if !maps_memory
741                             if (
742                                 (wfile_off = ftell(wfile)) == -1
743                              || fseek(wfile, 0L, SEEK_SET) != 0
744 #                            if has_fflush_input
745                              || fflush(wfile) != 0
746 #                            endif
747                             )
748                                 Ierror();
749 #                       endif
750 #                   endif
751 #                   if !has_fflush_input && !(large_memory && maps_memory)
752                         wfd_off = lseek(wfd, (off_t)0, SEEK_CUR);
753                         if (wfd_off == -1
754                             || (wfd_off != 0
755                                 && lseek(wfd, (off_t)0, SEEK_SET) != 0))
756                             Ierror();
757 #                   endif
758                     diffp = diffv;
759                     *++diffp = DIFF;
760                     *++diffp = DIFFFLAGS;
761 #                   if OPEN_O_BINARY
762                         if (Expand == BINARY_EXPAND)
763                             *++diffp = "--binary";
764 #                   endif
765                     *++diffp = newhead ? "-" : expname;
766                     *++diffp = newhead ? expname : "-";
767                     *++diffp = 0;
768                     switch (runv(wfd, diffname, diffv)) {
769                         case DIFF_FAILURE: case DIFF_SUCCESS: break;
770                         default: rcsfaterror("diff failed");
771                     }
772 #                   if !has_fflush_input && !(large_memory && maps_memory)
773                         if (lseek(wfd, wfd_off, SEEK_CUR) == -1)
774                             Ierror();
775 #                   endif
776 #                   if large_memory && !maps_memory
777                         if (fseek(wfile, wfile_off, SEEK_SET) != 0)
778                             Ierror();
779 #                   endif
780                     if (newhead) {
781                         Irewind(workptr);
782                         putdftext(&newdelta, workptr, frewrite, false);
783                         if (!putdtext(targetdelta,diffname,frewrite,true)) continue;
784                     } else
785                         if (!putdtext(&newdelta,diffname,frewrite,true)) continue;
786
787                     /*
788                     * Check whether the working file changed during checkin,
789                     * to avoid producing an inconsistent RCS file.
790                     */
791                     if (
792                         fstat(wfd, &checkworkstat) != 0
793                      || workstat.st_mtime != checkworkstat.st_mtime
794                      || workstat.st_size != checkworkstat.st_size
795                     ) {
796                         workerror("file changed during checkin");
797                         continue;
798                     }
799
800                     changedRCS = true;
801                 }
802         }
803
804         /* Deduce time_t of new revision if it is needed later.  */
805         wtime = (time_t)-1;
806         if (mtimeflag | Ttimeflag)
807                 wtime = date2time(workdelta->date);
808
809         if (donerewrite(changedRCS,
810                 !Ttimeflag ? (time_t)-1
811                 : finptr && wtime < RCSstat.st_mtime ? RCSstat.st_mtime
812                 : wtime
813         ) != 0)
814                 continue;
815
816         if (!keepworkingfile) {
817                 Izclose(&workptr);
818                 r = un_link(workname); /* Get rid of old file */
819         } else {
820                 newworkmode = WORKMODE(RCSstat.st_mode,
821                         !   (Expand==VAL_EXPAND  ||  lockthis < StrictLocks)
822                 );
823                 mtime = mtimeflag ? wtime : (time_t)-1;
824
825                 /* Expand if it might change or if we can't fix mode, time.  */
826                 if (changework  ||  (r=fixwork(newworkmode,mtime)) != 0) {
827                     Irewind(workptr);
828                     /* Expand keywords in file.  */
829                     locker_expansion = lockthis;
830                     workdelta->name =
831                         namedrev(
832                                 assoclst ? assoclst->ssymbol
833                                 : keepflag && *prevname.string ? prevname.string
834                                 : rev,
835                                 workdelta
836                         );
837                     switch (xpandfile(
838                         workptr, workdelta, &newworkname, dolog
839                     )) {
840                         default:
841                             continue;
842
843                         case 0:
844                             /*
845                              * No expansion occurred; try to reuse working file
846                              * unless we already tried and failed.
847                              */
848                             if (changework)
849                                 if ((r=fixwork(newworkmode,mtime)) == 0)
850                                     break;
851                             /* fall into */
852                         case 1:
853                             Izclose(&workptr);
854                             aflush(exfile);
855                             ignoreints();
856                             r = chnamemod(&exfile, newworkname,
857                                     workname, 1, newworkmode, mtime
858                             );
859                             keepdirtemp(newworkname);
860                             restoreints();
861                     }
862                 }
863         }
864         if (r != 0) {
865             eerror(workname);
866             continue;
867         }
868         diagnose("done\n");
869
870         }
871
872         tempunlink();
873         exitmain(exitstatus);
874 }       /* end of main (ci) */
875
876         static void
877 cleanup()
878 {
879         if (nerror) exitstatus = EXIT_FAILURE;
880         Izclose(&finptr);
881         Izclose(&workptr);
882         Ozclose(&exfile);
883         Ozclose(&fcopy);
884         ORCSclose();
885         dirtempunlink();
886 }
887
888 #if RCS_lint
889 #       define exiterr ciExit
890 #endif
891         void
892 exiterr()
893 {
894         ORCSerror();
895         dirtempunlink();
896         tempunlink();
897         _exit(EXIT_FAILURE);
898 }
899
900 /*****************************************************************/
901 /* the rest are auxiliary routines                               */
902
903
904         static int
905 addelta()
906 /* Function: Appends a delta to the delta tree, whose number is
907  * given by newdelnum.  Updates Head, newdelnum, newdelnumlength,
908  * and the links in newdelta.
909  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
910  */
911 {
912         register char *tp;
913         register int i;
914         int removedlock;
915         int newdnumlength;  /* actual length of new rev. num. */
916
917         newdnumlength = countnumflds(newdelnum.string);
918
919         if (rcsinitflag) {
920                 /* this covers non-existing RCS file and a file initialized with rcs -i */
921                 if (newdnumlength==0 && Dbranch) {
922                         bufscpy(&newdelnum, Dbranch);
923                         newdnumlength = countnumflds(Dbranch);
924                 }
925                 if (newdnumlength==0) bufscpy(&newdelnum, "1.1");
926                 else if (newdnumlength==1) bufscat(&newdelnum, ".1");
927                 else if (newdnumlength>2) {
928                     rcserror("Branch point doesn't exist for revision %s.",
929                         newdelnum.string
930                     );
931                     return -1;
932                 } /* newdnumlength == 2 is OK;  */
933                 Head = &newdelta;
934                 newdelta.next = 0;
935                 return 0;
936         }
937         if (newdnumlength==0) {
938                 /* derive new revision number from locks */
939                 switch (findlock(true, &targetdelta)) {
940
941                   default:
942                     /* found two or more old locks */
943                     return -1;
944
945                   case 1:
946                     /* found an old lock */
947                     /* check whether locked revision exists */
948                     if (!genrevs(targetdelta->num,(char*)0,(char*)0,(char*)0,&gendeltas))
949                         return -1;
950                     if (targetdelta==Head) {
951                         /* make new head */
952                         newdelta.next=Head;
953                         Head= &newdelta;
954                     } else if (!targetdelta->next && countnumflds(targetdelta->num)>2) {
955                         /* new tip revision on side branch */
956                         targetdelta->next= &newdelta;
957                         newdelta.next = 0;
958                     } else {
959                         /* middle revision; start a new branch */
960                         bufscpy(&newdelnum, "");
961                         return addbranch(targetdelta, &newdelnum, 1);
962                     }
963                     incnum(targetdelta->num, &newdelnum);
964                     return 1; /* successful use of existing lock */
965
966                   case 0:
967                     /* no existing lock; try Dbranch */
968                     /* update newdelnum */
969                     if (StrictLocks || !myself(RCSstat.st_uid)) {
970                         rcserror("no lock set by %s", getcaller());
971                         return -1;
972                     }
973                     if (Dbranch) {
974                         bufscpy(&newdelnum, Dbranch);
975                     } else {
976                         incnum(Head->num, &newdelnum);
977                     }
978                     newdnumlength = countnumflds(newdelnum.string);
979                     /* now fall into next statement */
980                 }
981         }
982         if (newdnumlength<=2) {
983                 /* add new head per given number */
984                 if(newdnumlength==1) {
985                     /* make a two-field number out of it*/
986                     if (cmpnumfld(newdelnum.string,Head->num,1)==0)
987                         incnum(Head->num, &newdelnum);
988                     else
989                         bufscat(&newdelnum, ".1");
990                 }
991                 if (cmpnum(newdelnum.string,Head->num) <= 0) {
992                     rcserror("revision %s too low; must be higher than %s",
993                           newdelnum.string, Head->num
994                     );
995                     return -1;
996                 }
997                 targetdelta = Head;
998                 if (0 <= (removedlock = removelock(Head))) {
999                     if (!genrevs(Head->num,(char*)0,(char*)0,(char*)0,&gendeltas))
1000                         return -1;
1001                     newdelta.next = Head;
1002                     Head = &newdelta;
1003                 }
1004                 return removedlock;
1005         } else {
1006                 /* put new revision on side branch */
1007                 /*first, get branch point */
1008                 tp = newdelnum.string;
1009                 for (i = newdnumlength - ((newdnumlength&1) ^ 1);  --i;  )
1010                         while (*tp++ != '.')
1011                                 continue;
1012                 *--tp = 0; /* Kill final dot to get old delta temporarily. */
1013                 if (!(targetdelta=genrevs(newdelnum.string,(char*)0,(char*)0,(char*)0,&gendeltas)))
1014                     return -1;
1015                 if (cmpnum(targetdelta->num, newdelnum.string) != 0) {
1016                     rcserror("can't find branch point %s", newdelnum.string);
1017                     return -1;
1018                 }
1019                 *tp = '.'; /* Restore final dot. */
1020                 return addbranch(targetdelta, &newdelnum, 0);
1021         }
1022 }
1023
1024
1025
1026         static int
1027 addbranch(branchpoint, num, removedlock)
1028         struct hshentry *branchpoint;
1029         struct buf *num;
1030         int removedlock;
1031 /* adds a new branch and branch delta at branchpoint.
1032  * If num is the null string, appends the new branch, incrementing
1033  * the highest branch number (initially 1), and setting the level number to 1.
1034  * the new delta and branchhead are in globals newdelta and newbranch, resp.
1035  * the new number is placed into num.
1036  * Return -1 on error, 1 if a lock is removed, 0 otherwise.
1037  * If REMOVEDLOCK is 1, a lock was already removed.
1038  */
1039 {
1040         struct branchhead *bhead, **btrail;
1041         struct buf branchnum;
1042         int result;
1043         int field, numlength;
1044         static struct branchhead newbranch;  /* new branch to be inserted */
1045
1046         numlength = countnumflds(num->string);
1047
1048         if (!branchpoint->branches) {
1049                 /* start first branch */
1050                 branchpoint->branches = &newbranch;
1051                 if (numlength==0) {
1052                         bufscpy(num, branchpoint->num);
1053                         bufscat(num, ".1.1");
1054                 } else if (numlength&1)
1055                         bufscat(num, ".1");
1056                 newbranch.nextbranch = 0;
1057
1058         } else if (numlength==0) {
1059                 /* append new branch to the end */
1060                 bhead=branchpoint->branches;
1061                 while (bhead->nextbranch) bhead=bhead->nextbranch;
1062                 bhead->nextbranch = &newbranch;
1063                 bufautobegin(&branchnum);
1064                 getbranchno(bhead->hsh->num, &branchnum);
1065                 incnum(branchnum.string, num);
1066                 bufautoend(&branchnum);
1067                 bufscat(num, ".1");
1068                 newbranch.nextbranch = 0;
1069         } else {
1070                 /* place the branch properly */
1071                 field = numlength - ((numlength&1) ^ 1);
1072                 /* field of branch number */
1073                 btrail = &branchpoint->branches;
1074                 while (0 < (result=cmpnumfld(num->string,(*btrail)->hsh->num,field))) {
1075                         btrail = &(*btrail)->nextbranch;
1076                         if (!*btrail) {
1077                                 result = -1;
1078                                 break;
1079                         }
1080                 }
1081                 if (result < 0) {
1082                         /* insert/append new branchhead */
1083                         newbranch.nextbranch = *btrail;
1084                         *btrail = &newbranch;
1085                         if (numlength&1) bufscat(num, ".1");
1086                 } else {
1087                         /* branch exists; append to end */
1088                         bufautobegin(&branchnum);
1089                         getbranchno(num->string, &branchnum);
1090                         targetdelta = genrevs(
1091                                 branchnum.string, (char*)0, (char*)0, (char*)0,
1092                                 &gendeltas
1093                         );
1094                         bufautoend(&branchnum);
1095                         if (!targetdelta)
1096                             return -1;
1097                         if (cmpnum(num->string,targetdelta->num) <= 0) {
1098                                 rcserror("revision %s too low; must be higher than %s",
1099                                       num->string, targetdelta->num
1100                                 );
1101                                 return -1;
1102                         }
1103                         if (!removedlock
1104                             && 0 <= (removedlock = removelock(targetdelta))
1105                         ) {
1106                             if (numlength&1)
1107                                 incnum(targetdelta->num,num);
1108                             targetdelta->next = &newdelta;
1109                             newdelta.next = 0;
1110                         }
1111                         return removedlock;
1112                         /* Don't do anything to newbranch.  */
1113                 }
1114         }
1115         newbranch.hsh = &newdelta;
1116         newdelta.next = 0;
1117         if (branchpoint->lockedby)
1118             if (strcmp(branchpoint->lockedby, getcaller()) == 0)
1119                 return removelock(branchpoint); /* This returns 1.  */
1120         return removedlock;
1121 }
1122
1123         static int
1124 addsyms(num)
1125         char const *num;
1126 {
1127         register struct Symrev *p;
1128
1129         for (p = assoclst;  p;  p = p->nextsym)
1130                 if (addsymbol(num, p->ssymbol, p->override)  <  0)
1131                         return false;
1132         return true;
1133 }
1134
1135
1136         static void
1137 incnum(onum,nnum)
1138         char const *onum;
1139         struct buf *nnum;
1140 /* Increment the last field of revision number onum by one and
1141  * place the result into nnum.
1142  */
1143 {
1144         register char *tp, *np;
1145         register size_t l;
1146
1147         l = strlen(onum);
1148         bufalloc(nnum, l+2);
1149         np = tp = nnum->string;
1150         VOID strcpy(np, onum);
1151         for (tp = np + l;  np != tp;  )
1152                 if (isdigit(*--tp)) {
1153                         if (*tp != '9') {
1154                                 ++*tp;
1155                                 return;
1156                         }
1157                         *tp = '0';
1158                 } else {
1159                         tp++;
1160                         break;
1161                 }
1162         /* We changed 999 to 000; now change it to 1000.  */
1163         *tp = '1';
1164         tp = np + l;
1165         *tp++ = '0';
1166         *tp = 0;
1167 }
1168
1169
1170
1171         static int
1172 removelock(delta)
1173 struct hshentry * delta;
1174 /* function: Finds the lock held by caller on delta,
1175  * removes it, and returns nonzero if successful.
1176  * Print an error message and return -1 if there is no such lock.
1177  * An exception is if !StrictLocks, and caller is the owner of
1178  * the RCS file. If caller does not have a lock in this case,
1179  * return 0; return 1 if a lock is actually removed.
1180  */
1181 {
1182         register struct rcslock *next, **trail;
1183         char const *num;
1184
1185         num=delta->num;
1186         for (trail = &Locks;  (next = *trail);  trail = &next->nextlock)
1187             if (next->delta == delta)
1188                 if (strcmp(getcaller(), next->login) == 0) {
1189                     /* We found a lock on delta by caller; delete it.  */
1190                     *trail = next->nextlock;
1191                     delta->lockedby = 0;
1192                     return 1;
1193                 } else {
1194                     rcserror("revision %s locked by %s", num, next->login);
1195                     return -1;
1196                 }
1197         if (!StrictLocks && myself(RCSstat.st_uid))
1198             return 0;
1199         rcserror("no lock set by %s for revision %s", getcaller(), num);
1200         return -1;
1201 }
1202
1203
1204
1205         static char const *
1206 getcurdate()
1207 /* Return a pointer to the current date.  */
1208 {
1209         static char buffer[datesize]; /* date buffer */
1210
1211         if (!buffer[0])
1212                 time2date(now(), buffer);
1213         return buffer;
1214 }
1215
1216         static int
1217 #if has_prototypes
1218 fixwork(mode_t newworkmode, time_t mtime)
1219   /* The `#if has_prototypes' is needed because mode_t might promote to int.  */
1220 #else
1221   fixwork(newworkmode, mtime)
1222         mode_t newworkmode;
1223         time_t mtime;
1224 #endif
1225 {
1226         return
1227                         1 < workstat.st_nlink
1228                     ||  (newworkmode&S_IWUSR && !myself(workstat.st_uid))
1229                     ||  setmtime(workname, mtime) != 0
1230                 ?   -1
1231             :   workstat.st_mode == newworkmode  ?  0
1232 #if has_fchmod
1233             :   fchmod(Ifileno(workptr), newworkmode) == 0  ?  0
1234 #endif
1235 #if bad_chmod_close
1236             :   -1
1237 #else
1238             :   chmod(workname, newworkmode)
1239 #endif
1240         ;
1241 }
1242
1243         static int
1244 xpandfile(unexfile, delta, exname, dolog)
1245         RILE *unexfile;
1246         struct hshentry const *delta;
1247         char const **exname;
1248         int dolog;
1249 /*
1250  * Read unexfile and copy it to a
1251  * file, performing keyword substitution with data from delta.
1252  * Return -1 if unsuccessful, 1 if expansion occurred, 0 otherwise.
1253  * If successful, stores the stream descriptor into *EXFILEP
1254  * and its name into *EXNAME.
1255  */
1256 {
1257         char const *targetname;
1258         int e, r;
1259
1260         targetname = makedirtemp(1);
1261         if (!(exfile = fopenSafer(targetname, FOPEN_W_WORK))) {
1262                 eerror(targetname);
1263                 workerror("can't build working file");
1264                 return -1;
1265         }
1266         r = 0;
1267         if (MIN_UNEXPAND <= Expand)
1268                 fastcopy(unexfile,exfile);
1269         else {
1270                 for (;;) {
1271                         e = expandline(
1272                                 unexfile, exfile, delta, false, (FILE*)0, dolog
1273                         );
1274                         if (e < 0)
1275                                 break;
1276                         r |= e;
1277                         if (e <= 1)
1278                                 break;
1279                 }
1280         }
1281         *exname = targetname;
1282         return r & 1;
1283 }
1284
1285
1286
1287
1288 /* --------------------- G E T L O G M S G --------------------------------*/
1289
1290
1291         static struct cbuf
1292 getlogmsg()
1293 /* Obtain and yield a log message.
1294  * If a log message is given with -m, yield that message.
1295  * If this is the initial revision, yield a standard log message.
1296  * Otherwise, reads a character string from the terminal.
1297  * Stops after reading EOF or a single '.' on a
1298  * line. getlogmsg prompts the first time it is called for the
1299  * log message; during all later calls it asks whether the previous
1300  * log message can be reused.
1301  */
1302 {
1303         static char const
1304                 emptych[] = EMPTYLOG,
1305                 initialch[] = "Initial revision";
1306         static struct cbuf const
1307                 emptylog = { emptych, sizeof(emptych)-sizeof(char) },
1308                 initiallog = { initialch, sizeof(initialch)-sizeof(char) };
1309         static struct buf logbuf;
1310         static struct cbuf logmsg;
1311
1312         register char *tp;
1313         register size_t i;
1314         char const *caller;
1315
1316         if (msg.size) return msg;
1317
1318         if (keepflag) {
1319                 /* generate std. log message */
1320                 caller = getcaller();
1321                 i = sizeof(ciklog)+strlen(caller)+3;
1322                 bufalloc(&logbuf, i + datesize + zonelenmax);
1323                 tp = logbuf.string;
1324                 VOID sprintf(tp, "%s%s at ", ciklog, caller);
1325                 VOID date2str(getcurdate(), tp+i);
1326                 logmsg.string = tp;
1327                 logmsg.size = strlen(tp);
1328                 return logmsg;
1329         }
1330
1331         if (!targetdelta && (
1332                 cmpnum(newdelnum.string,"1.1")==0 ||
1333                 cmpnum(newdelnum.string,"1.0")==0
1334         ))
1335                 return initiallog;
1336
1337         if (logmsg.size) {
1338                 /*previous log available*/
1339             if (yesorno(true, "reuse log message of previous file? [yn](y): "))
1340                 return logmsg;
1341         }
1342
1343         /* now read string from stdin */
1344         logmsg = getsstdin("m", "log message", "", &logbuf);
1345
1346         /* now check whether the log message is not empty */
1347         if (logmsg.size)
1348                 return logmsg;
1349         return emptylog;
1350 }
1351
1352 /*  Make a linked list of Symbolic names  */
1353
1354         static void
1355 addassoclst(flag, sp)
1356         int flag;
1357         char const *sp;
1358 {
1359         struct Symrev *pt;
1360
1361         pt = talloc(struct Symrev);
1362         pt->ssymbol = sp;
1363         pt->override = flag;
1364         pt->nextsym = 0;
1365         *nextassoc = pt;
1366         nextassoc = &pt->nextsym;
1367 }
1368
1369 static char const alphabet[62] =
1370   "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
1371
1372 /* Divide BUF by D, returning the remainder.  Replace BUF by the
1373    quotient.  BUF[0] is the most significant part of BUF.
1374    D must not exceed UINT_MAX >> CHAR_BIT.  */
1375 static unsigned int
1376 divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d)
1377 {
1378   unsigned int carry = 0;
1379   int i;
1380   for (i = 0; i < COMMITID_RAW_SIZE; i++)
1381     {
1382       unsigned int byte = buf[i];
1383       unsigned int dividend = (carry << CHAR_BIT) + byte;
1384       buf[i] = dividend / d;
1385       carry = dividend % d;
1386     }
1387   return carry;
1388 }
1389
1390 static void
1391 convert (char const input[COMMITID_RAW_SIZE], char *output)
1392 {
1393   static char const zero[COMMITID_RAW_SIZE] = { 0, };
1394   unsigned char buf[COMMITID_RAW_SIZE];
1395   size_t o = 0;
1396   memcpy (buf, input, COMMITID_RAW_SIZE);
1397   while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0)
1398     output[o++] = alphabet[divide_by (buf, sizeof alphabet)];
1399   if (! o)
1400     output[o++] = '0';
1401   output[o] = '\0';
1402 }