ChangeLog entry:

Thanks to Paul Eggert who suggested using better random numbers as
well as using the base62 format for compactness and provided the
sample divide_by and convert functions used here.

2005-09-29  Mark D. Baushke  <mdb@gnu.org>

	* man/rcsfile.5in: Document new commitid delta phrase.
	* man/rcsfile.5: Regenerated.

	* src/ci.c (RANDOM_BYTES, COMMITID_RAW_SIZE): New constants.
	(mainProg): Add commitid to delta records. Use
	random data and represent in base62 or fall back to using the
	same basic format construction as is used by CVS and CVSNT.
	(divide_by): New function used by convert.
	(convert): New fucntion to convert to base62.
	* rcsbase.h (commitidsize): Room for base62 encoded block or
	32bit pid plus a 32bit time rendered as hex plus one
	NUL byte round up to 64.
	(struct hshentry): Add new commitid field.
	* src/rcsgen.c (putdelta): Preserve old commitid entries.
	* src/rcssyn.c (Kcommitid): New global constant keyword.
	(getdelta): Add optional parsing for it.
	* src/rlog.c (putadelta): Print it out.

Index:man/rcsfile.5
--- man/rcsfile.5~	1995-06-16 06:58:26.000000000 +0000
+++ man/rcsfile.5	2005-09-27 20:53:01.023504000 +0000
@@ -1,4 +1,4 @@
-.lf 1 ./rcsfile.5in
+.lf 1 rcsfile.5in
 .\" Set p to 1 if your formatter can handle pic output.
 .if t .nr p 1
 .de Id
@@ -69,6 +69,7 @@ nonterminal symbols are in
 		\f3state\fP	{\f2id\fP}\f3;\fP
 		\f3branches\fP	{\f2num\fP}*\f3;\fP
 		\f3next\fP	{\f2num\fP}\f3;\fP
+		{ \f3commitid\fP \f2id\fP\f3;\fP }
 		{ \f2newphrase\fP }*
 .LP
 \f2desc\fP	::=	\f3desc\fP	\f2string\fP
@@ -128,6 +129,18 @@ and all the digits of years thereafter.
 Dates use the Gregorian calendar; times use UTC.
 .PP
 The
+.I commitid
+is followed by an
+.I id
+token. This token is intended to be unique across
+multiple files and is used to help group files as
+being a part of the same logical commit.
+This token must uniquely identify the commit
+operation that was applied to a set of RCS files.
+In particular, it must be unique among all the
+commitids in this file.
+.PP
+The
 .I newphrase
 productions in the grammar are reserved for future extensions
 to the format of \*r files.
@@ -230,7 +243,7 @@ The following diagram shows an example o
 .fi
 .\}
 .if \np \{\
-.lf 232
+.lf 245
 .PS 4.250i 3.812i
 .\" -2.0625 -4.25 1.75 0
 .\" 0.000i 4.250i 3.812i 0.000i
@@ -239,7 +252,7 @@ The following diagram shows an example o
 .nr 0x 1
 \h'3.812i'
 .sp -1
-.lf 242
+.lf 255
 \h'2.062i-(\w'Head'u/2u)'\v'0.125i-(0v/2u)+0v+0.22m'Head
 .sp -1
 \h'2.062i'\v'0.250i'\D'l0.000i 0.500i'
@@ -256,7 +269,7 @@ The following diagram shows an example o
 .sp -1
 \h'1.688i'\v'0.750i'\D'l0.000i 0.500i'
 .sp -1
-.lf 244
+.lf 257
 \h'2.062i-(\w'2.1'u/2u)'\v'1.000i-(0v/2u)+0v+0.22m'2.1
 .sp -1
 \h'2.062i'\v'1.250i'\D'l0.000i 0.500i'
@@ -265,7 +278,7 @@ The following diagram shows an example o
 .sp -1
 \h'2.062i'\v'1.750i'\D'l-0.025i -0.100i'
 .sp -1
-.lf 246
+.lf 259
 \h'2.062i-(\w'1.3'u/2u)'\v'2.000i-(1v/2u)+0v+0.22m'1.3
 .sp -1
 \h'2.062i'\v'2.250i'\D'l-0.375i -0.500i'
@@ -280,7 +293,7 @@ The following diagram shows an example o
 .sp -1
 \h'1.375i'\v'1.500i'\D'l0.025i 0.100i'
 .sp -1
-.lf 249
+.lf 262
 \h'1.375i-(\w'1.3.1.1'u/2u)'\v'1.250i-(1v/2u)+1v+0.22m'1.3.1.1
 .sp -1
 \h'1.375i'\v'1.000i'\D'l-0.375i 0.500i'
@@ -295,7 +308,7 @@ The following diagram shows an example o
 .sp -1
 \h'2.062i'\v'2.750i'\D'l-0.025i -0.100i'
 .sp -1
-.lf 252
+.lf 265
 \h'2.062i-(\w'1.2'u/2u)'\v'3.000i-(1v/2u)+0v+0.22m'1.2
 .sp -1
 \h'2.062i'\v'3.250i'\D'l-0.375i -0.500i'
@@ -310,7 +323,7 @@ The following diagram shows an example o
 .sp -1
 \h'0.375i'\v'2.500i'\D'l0.025i 0.100i'
 .sp -1
-.lf 255
+.lf 268
 \h'0.375i-(\w'1.2.1.1'u/2u)'\v'2.250i-(1v/2u)+1v+0.22m'1.2.1.1
 .sp -1
 \h'0.375i'\v'2.000i'\D'l-0.375i 0.500i'
@@ -325,7 +338,7 @@ The following diagram shows an example o
 .sp -1
 \h'0.375i'\v'1.500i'\D'l0.025i 0.100i'
 .sp -1
-.lf 257
+.lf 270
 \h'0.375i-(\w'1.2.1.3'u/2u)'\v'1.250i-(1v/2u)+1v+0.22m'1.2.1.3
 .sp -1
 \h'0.375i'\v'1.000i'\D'l-0.375i 0.500i'
@@ -340,7 +353,7 @@ The following diagram shows an example o
 .sp -1
 \h'2.750i'\v'2.500i'\D'l0.025i 0.100i'
 .sp -1
-.lf 261
+.lf 274
 \h'2.750i-(\w'1.2.2.1'u/2u)'\v'2.250i-(1v/2u)+1v+0.22m'1.2.2.1
 .sp -1
 \h'2.750i'\v'2.000i'\D'l-0.375i 0.500i'
@@ -355,7 +368,7 @@ The following diagram shows an example o
 .sp -1
 \h'3.438i'\v'1.250i'\D'l0.025i 0.100i'
 .sp -1
-.lf 264
+.lf 277
 \h'3.438i-(\w'\s-21.2.2.1.1.1\s0'u/2u)'\v'1.000i-(1v/2u)+1v+0.22m'\s-21.2.2.1.1.1\s0
 .sp -1
 \h'3.438i'\v'0.750i'\D'l-0.375i 0.500i'
@@ -370,7 +383,7 @@ The following diagram shows an example o
 .sp -1
 \h'2.750i'\v'1.500i'\D'l0.025i 0.100i'
 .sp -1
-.lf 267
+.lf 280
 \h'2.750i-(\w'1.2.2.2'u/2u)'\v'1.250i-(1v/2u)+1v+0.22m'1.2.2.2
 .sp -1
 \h'2.750i'\v'1.000i'\D'l-0.375i 0.500i'
@@ -385,7 +398,7 @@ The following diagram shows an example o
 .sp -1
 \h'2.062i'\v'3.750i'\D'l-0.025i -0.100i'
 .sp -1
-.lf 270
+.lf 283
 \h'2.062i-(\w'1.1'u/2u)'\v'4.000i-(1v/2u)+0v+0.22m'1.1
 .sp -1
 \h'2.062i'\v'4.250i'\D'l-0.375i -0.500i'
@@ -398,9 +411,9 @@ The following diagram shows an example o
 .if \n(00 .fi
 .br
 .nr 0x 0
-.lf 271
+.lf 284
 .PE
-.lf 272
+.lf 285
 .\}
 .PP
 .SH IDENTIFICATION
Index:man/rcsfile.5in
--- man/rcsfile.5in~	1995-06-05 08:28:35.000000000 +0000
+++ man/rcsfile.5in	2005-09-27 20:52:46.424504000 +0000
@@ -68,6 +68,7 @@ nonterminal symbols are in
 		\f3state\fP	{\f2id\fP}\f3;\fP
 		\f3branches\fP	{\f2num\fP}*\f3;\fP
 		\f3next\fP	{\f2num\fP}\f3;\fP
+		{ \f3commitid\fP \f2id\fP\f3;\fP }
 		{ \f2newphrase\fP }*
 .LP
 \f2desc\fP	::=	\f3desc\fP	\f2string\fP
@@ -127,6 +128,18 @@ and all the digits of years thereafter.
 Dates use the Gregorian calendar; times use UTC.
 .PP
 The
+.I commitid
+is followed by an
+.I id
+token. This token is intended to be unique across
+multiple files and is used to help group files as
+being a part of the same logical commit.
+This token must uniquely identify the commit
+operation that was applied to a set of RCS files.
+In particular, it must be unique among all the
+commitids in this file.
+.PP
+The
 .I newphrase
 productions in the grammar are reserved for future extensions
 to the format of \*r files.
Index:src/rcsbase.h
--- src/rcsbase.h~	1995-06-16 06:19:24.000000000 +0000
+++ src/rcsbase.h	2005-09-28 21:47:51.490505000 +0000
@@ -222,6 +222,11 @@ Report problems and direct all questions
                               /* 1 sets the default locking to strict;      */
                               /* used in production environments.           */
 
+/* base64_encode(128 random bits) needs 24 bytes + 1 for NUL */
+/* time_t may be 64bits on some machines needs 16 bytes + 1 as hex */
+#define commitidsize	   64 /* time+1+base64(128bits)+1 | pid+time+rand+1 */
+#define urandom_dev "/dev/urandom"
+
 #define yearlength	   16 /* (good through AD 9,999,999,999,999,999)    */
 #define datesize (yearlength+16)	/* size of output of time2date */
 #define RCSTMPPREFIX '_' /* prefix for temp files in working dir  */
@@ -358,6 +363,7 @@ struct hshentry {
 	char const	  * lockedby; /* who locks the revision		    */
 	char const	  * state;    /* state of revision (Exp by default) */
 	char const	  * name;     /* name (if any) by which retrieved   */
+	char const        * commitid; /* text string to associate commits   */
 	struct cbuf	    log;      /* log message requested at checkin   */
         struct branchhead * branches; /* list of first revisions on branches*/
 	struct cbuf	    ig;	      /* ignored phrases in admin part	    */
@@ -662,6 +668,7 @@ extern int               TotalDeltas;
 extern char const *const expand_names[];
 extern char const
 	Kaccess[], Kauthor[], Kbranch[], Kcomment[],
+	Kcommitid[],
 	Kdate[], Kdesc[], Kexpand[], Khead[], Klocks[], Klog[],
 	Knext[], Kstate[], Kstrict[], Ksymbols[], Ktext[];
 void unexpected_EOF P((void)) exiting;
Index:src/ci.c
--- src/ci.c~	1995-06-16 06:19:24.000000000 +0000
+++ src/ci.c	2005-09-29 21:57:57.814504000 +0000
@@ -262,6 +262,10 @@ static void cleanup P((void));
 static void incnum P((char const*,struct buf*));
 static void addassoclst P((int,char const*));
 
+enum {RANDOM_BYTES = 8};
+enum {COMMITID_RAW_SIZE = (sizeof(time_t) + RANDOM_BYTES)};
+static void convert P((char const input[COMMITID_RAW_SIZE], char *output));
+
 static FILE *exfile;
 static RILE *workptr;			/* working file pointer		*/
 static struct buf newdelnum;		/* new revision number		*/
@@ -285,6 +289,7 @@ mainProg(ciId, "ci", "$Id: ci.c,v 5.30 1
 	char olddate[datesize];
 	char newdatebuf[datesize + zonelenmax];
 	char targetdatebuf[datesize + zonelenmax];
+	char commitid[commitidsize];
 	char *a, **newargv, *textfile;
 	char const *author, *krev, *rev, *state;
 	char const *diffname, *expname;
@@ -309,6 +314,45 @@ mainProg(ciId, "ci", "$Id: ci.c,v 5.30 1
 	suffixes = X_DEFAULT;
 	nextassoc = &assoclst;
 
+	{
+		char buf[COMMITID_RAW_SIZE] = { 0, };
+		ssize_t len = 0;
+		time_t rightnow = time (NULL);
+		char *startrand = buf + sizeof (time_t);
+		unsigned char *p = (unsigned char *) startrand;
+		size_t randbytes = RANDOM_BYTES;
+		int flags = O_RDONLY;
+		int fd;
+#ifdef O_NOCTTY
+		flags |= O_NOCTTY;
+#endif
+		if (rightnow != (time_t)-1)
+			while (rightnow > 0) {
+				*--p = rightnow % (UCHAR_MAX + 1);
+				rightnow /= UCHAR_MAX + 1;
+			}
+		else {
+			/* try to use more random data */
+			randbytes = COMMITID_RAW_SIZE;
+			startrand = buf;
+		}
+		fd = open (urandom_dev, flags);
+		if (fd >= 0) {
+			len = read (fd, startrand, randbytes);
+			close (fd);
+		}
+		if (len <= 0) {
+			/* no random data was available so use pid */
+			long int pid = (long int)getpid ();
+			p = (unsigned char *) (startrand + sizeof (pid));
+			while (pid > 0) {
+			    *--p = pid % (UCHAR_MAX + 1);
+			    pid /= UCHAR_MAX + 1;
+			}
+		}
+		convert(buf, commitid);
+	}
+
 	argc = getRCSINIT(argc, argv, &newargv);
 	argv = newargv;
 	while (a = *++argv,  0<--argc && *a++=='-') {
@@ -532,6 +576,8 @@ mainProg(ciId, "ci", "$Id: ci.c,v 5.30 1
 	newdelta.name = 0;
 	clear_buf(&newdelta.ig);
 	clear_buf(&newdelta.igtext);
+	/* set commitid */
+	newdelta.commitid=commitid;
 	/* set author */
 	if (author)
 		newdelta.author=author;     /* set author given by -w         */
@@ -1317,3 +1363,38 @@ addassoclst(flag, sp)
 	*nextassoc = pt;
 	nextassoc = &pt->nextsym;
 }
+
+static char const alphabet[62] =
+  "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
+
+/* Divide BUF by D, returning the remainder.  Replace BUF by the
+   quotient.  BUF[0] is the most significant part of BUF.
+   D must not exceed UINT_MAX >> CHAR_BIT.  */
+static unsigned int
+divide_by (unsigned char buf[COMMITID_RAW_SIZE], unsigned int d)
+{
+  unsigned int carry = 0;
+  int i;
+  for (i = 0; i < COMMITID_RAW_SIZE; i++)
+    {
+      unsigned int byte = buf[i];
+      unsigned int dividend = (carry << CHAR_BIT) + byte;
+      buf[i] = dividend / d;
+      carry = dividend % d;
+    }
+  return carry;
+}
+
+static void
+convert (char const input[COMMITID_RAW_SIZE], char *output)
+{
+  static char const zero[COMMITID_RAW_SIZE] = { 0, };
+  unsigned char buf[COMMITID_RAW_SIZE];
+  size_t o = 0;
+  memcpy (buf, input, COMMITID_RAW_SIZE);
+  while (memcmp (buf, zero, COMMITID_RAW_SIZE) != 0)
+    output[o++] = alphabet[divide_by (buf, sizeof alphabet)];
+  if (! o)
+    output[o++] = '0';
+  output[o] = '\0';
+}
Index:src/rcsgen.c
--- src/rcsgen.c~	1995-06-16 06:19:24.000000000 +0000
+++ src/rcsgen.c	2005-09-27 22:08:47.421504000 +0000
@@ -547,6 +547,9 @@ putdelta(node, fout)
 
 	aprintf(fout, ";\n%s\t%s;\n", Knext, node->next?node->next->num:"");
 	awrite(node->ig.string, node->ig.size, fout);
+
+	if (node->commitid)
+		aprintf(fout, "%s\t%s;\n", Kcommitid, node->commitid);
 }
 
 
Index:src/rcssyn.c
--- src/rcssyn.c~	1995-06-16 06:19:24.000000000 +0000
+++ src/rcssyn.c	2005-09-27 22:08:47.429504000 +0000
@@ -171,6 +171,7 @@ char const
 	Kauthor[]   = "author",
 	Kbranch[]   = "branch",
 	Kcomment[]  = "comment",
+	Kcommitid[] = "commitid",
 	Kdate[]     = "date",
 	Kdesc[]     = "desc",
 	Kexpand[]   = "expand",
@@ -433,6 +434,13 @@ getdelta()
 	Delta->lockedby = 0;
 	Delta->log.string = 0;
 	Delta->selector = true;
+
+	if (getkeyopt(Kcommitid)) {
+		Delta->commitid = NextString;
+		nextlex();
+		getsemi(Kcommitid);
+        }
+
 	Delta->ig = getphrases(Kdesc);
         TotalDeltas++;
         return (true);
Index:src/rlog.c
--- src/rlog.c~	1995-06-16 06:19:24.000000000 +0000
+++ src/rlog.c	2005-09-26 17:23:55.257504000 +0000
@@ -591,6 +591,10 @@ putadelta(node,editscript,trunk)
 	      aprintf(out, insDelFormat,
                              editscript->insertlns, editscript->deletelns);
 
+	if ( node->commitid )
+	   aprintf(out, "%s commitid: %s", (editscript) ? ";" : "",
+		   node->commitid);
+
         newbranch = node->branches;
         if ( newbranch ) {
 	   bufautobegin(&branchnum);
