Index: htags/manual.in
===================================================================
RCS file: /sources/global/global/htags/manual.in,v
retrieving revision 1.95
diff -u -p -r1.95 manual.in
--- htags/manual.in	17 Aug 2006 10:45:33 -0000	1.95
+++ htags/manual.in	26 Aug 2006 12:29:23 -0000
@@ -60,6 +60,8 @@
 		See @file{HTML/.htaccess} that is generated by htags.
 	@item{@option{--cvsweb} @arg{url}}
 		Include cvsweb URL. @arg{url} is used as base of URL.
+		When directory @file{CVS} exists in the source tree,
+		content of @file{CVS/Repository} is used as relative path from the base.
 	@item{@option{--cvsweb-cvsroot} @arg{cvsroot}}
 		Specifies cvsroot in cvsweb URL.
 	@item{@option{-D}, @option{--dynamic}}
Index: htags/src2html.c
===================================================================
RCS file: /sources/global/global/htags/src2html.c,v
retrieving revision 1.51
diff -u -p -r1.51 src2html.c
--- htags/src2html.c	23 Aug 2006 11:41:36 -0000	1.51
+++ htags/src2html.c	26 Aug 2006 12:29:24 -0000
@@ -686,6 +686,49 @@ encode(STRBUF *sb, const char *url)
 	}
 }
 /*
+ * get_cvs_module: return CVS module of source file.
+ *
+ *	i)	file		source path
+ *	o)	basename	If basename is not NULL, store pointer to
+ *				the last component of source path.
+ *	r)		!=NULL : relative path from repository top
+ *			==NULL : CVS/Repository is not readable.
+ */
+static const char *
+get_cvs_module(const char *file, const char **basename)
+{
+	const char *p;
+	STATIC_STRBUF(dir);
+	static char prev_dir[MAXPATHLEN+1];
+	STATIC_STRBUF(module);
+	FILE *ip;
+
+	strbuf_clear(dir);
+	p = locatestring(file, "/", MATCH_LAST);
+	if (p != NULL) {
+		strbuf_nputs(dir, file, p - file);
+		p++;
+	} else {
+		strbuf_putc(dir, '.');
+		p = file;
+	}
+	if (basename != NULL)
+		*basename = p;
+	if (strcmp(strbuf_value(dir), prev_dir) != 0) {
+		strlimcpy(prev_dir, strbuf_value(dir), sizeof(prev_dir));
+		strbuf_clear(module);
+		strbuf_puts(dir, "/CVS/Repository");
+		ip = fopen(strbuf_value(dir), "r");
+		if (ip != NULL) {
+			strbuf_fgets(module, ip, STRBUF_NOCRLF);
+			fclose(ip);
+		}
+	}
+	if (strbuf_getlen(module) > 0)
+		return strbuf_value(module);
+	return NULL;
+}
+/*
  *
  * src2html: convert source code into HTML
  *
@@ -722,10 +765,18 @@ src2html(const char *src, const char *ht
         fputs(fill_anchor(indexlink, src), out);
 	if (cvsweb_url) {
 		STATIC_STRBUF(sb);
+		const char *module, *basename;
 
 		strbuf_clear(sb);
 		strbuf_puts(sb, cvsweb_url);
-		encode(sb, src);
+		module = get_cvs_module(src, &basename);
+		if (module != NULL) {
+			encode(sb, module);
+			strbuf_putc(sb, '/');
+			encode(sb, basename);
+		} else {
+			encode(sb, src);
+		}
 		if (cvsweb_cvsroot) {
 			strbuf_puts(sb, "?cvsroot=");
 			strbuf_puts(sb, cvsweb_cvsroot);
