You're welcome. I'm looking forward to seeing a patch!
Ok, I finally had a chance to look this at. Below is a WIP patch. Example output:
[neilc:/home/neilc/cvs_test]% ~/cvs-1.12.11/src/cvs log aaa.c
[...]
revision 1.4
date: 2005-03-10 14:57:28 +1100; author: neilc; state: dead; lines: +0 -13
File removed.
----------------------------
revision 1.3
date: 2005-03-10 14:53:21 +1100; author: neilc; state: Exp; lines: +8 -0
Add text to end of file
----------------------------
revision 1.2
date: 2005-03-10 13:26:34 +1100; author: neilc; state: Exp; lines: +0 -1
Delete a line.
----------------------------
revision 1.1
date: 2005-03-10 13:01:01 +1100; author: neilc; state: Exp; lines: +6 -0
File added.
=============================================================================
Known issues:
- I'm not sure how to handle branches. Apparently RCS stores branch patches "forward" (i.e. v1.1 => v1.2) rather than backward, which is how trunk patches are stored. So we can calculate the total # of lines in a given branch revision by finding the branch point, getting the total # of lines there (via ";total"), and then counting adds/deletes as we walk the branch. I'm not sure how to "walk the branch", though -- I've just been assuming that RCS stores revisions from HEAD => the initial version sequentially. Any suggestions?
- The code is a little more complex than I'd like (some logic in rcs.c, some in log.c). To some degree this is a result of the complexity of the task (transforming RCS' silliness into reasonable info), but any suggestions for simplification would be welcome.
- I've started updating the test suite for the new `log' output, but haven't finished (I want to get branches working properly first). I've also added a test for one of the bugs fixed by this patch (we recorded "lines modified" in a deleted revision _if_ the revision's text included keywords that were changed by the commit).
Any comments welcome.
-Neil diff -ru cvs-1.12.11_pristine/src/log.c cvs-1.12.11/src/log.c --- cvs-1.12.11_pristine/src/log.c 2004-09-16 06:15:29.000000000 +1000 +++ cvs-1.12.11/src/log.c 2005-03-10 15:49:13.000000000 +1100 @@ -1591,8 +1591,10 @@ } else if (ver->next == NULL) { - padd = NULL; - pdel = NULL; + /* First version of file -- lines added is the total # of + lines in this version of the file */ + padd = findnode (ver->other, ";total"); + pdel = NULL; } else { @@ -1613,7 +1615,10 @@ cvs_output_tagged ("text", " lines: +"); cvs_output_tagged ("text", padd->data); cvs_output_tagged ("text", " -"); - cvs_output_tagged ("text", pdel->data); + if (pdel) + cvs_output_tagged ("text", pdel->data); + else + cvs_output_tagged ("text", "0"); } cvs_output_tagged ("newline", NULL);
diff -ru cvs-1.12.11_pristine/src/rcs.c cvs-1.12.11/src/rcs.c --- cvs-1.12.11_pristine/src/rcs.c 2004-12-01 03:06:07.000000000 +1100 +++ cvs-1.12.11/src/rcs.c 2005-03-11 14:13:14.000000000 +1100 @@ -8,6 +8,7 @@ * manipulation */
+#include <assert.h> #include "cvs.h" #include "edit.h" #include "hardlink.h" @@ -691,7 +692,98 @@ return 0; }
+/* + * Given some text describing a delta between two versions + * (unpolished), figure out the number of lines added and removed by + * the delta (returned via out parameters). The text is polished -- + * i.e. it may be side-effected. + */ +static void +parse_lines_changed (RCSNode *rcs, RCSVers *vnode, + struct rcsbuffer *rcsbuf, char *text, + unsigned long *lines_added, + unsigned long *lines_deleted) +{ + size_t text_len; + const char *cp;
+ *lines_added = 0; + *lines_deleted = 0; + if (text == NULL) + return; + + rcsbuf_valpolish (rcsbuf, text, 0, &text_len); + cp = text; + + while (cp < text + text_len) + { + char op; + unsigned long count; + + op = *cp++; + if (op != 'a' && op != 'd') + error (1, 0, "\ +unrecognized operation '\\x%x' in %s", + op, rcs->print_path); + (void) strtoul (cp, (char **) &cp, 10); + if (*cp++ != ' ') + error (1, 0, "space expected in %s revision %s", + rcs->print_path, vnode->version); + count = strtoul (cp, (char **) &cp, 10); + if (*cp++ != '\012') + error (1, 0, "linefeed expected in %s revision %s", + rcs->print_path, vnode->version); + + if (op == 'd') + *lines_deleted += count; + else + { + *lines_added += count; + while (count != 0) + { + if (*cp == '\012') + --count; + else if (cp == text + text_len) + { + if (count != 1) + error (1, 0, "\ +premature end of value in %s revision %s", + rcs->print_path, vnode->version); + else + break; + } + ++cp; + } + } + } +} + +/* + * Add the specified key and data to the `other' list of the specified + * RCSVers node. We assume the node's type is RCSFIELD, since this is + * used for adding ";add", ";delete", and ";total" markers. + */ +static void +add_vnode_rcs_other (RCSNode *rcs, RCSVers *vnode, + const char *key, unsigned long data) +{ + char buf[50]; + Node *kv; + + snprintf(buf, sizeof(buf), "%lu", data); + kv = getnode (); + kv->type = RCSFIELD; + kv->key = xstrdup (key); + kv->data = xstrdup (buf); + if (addnode (vnode->other, kv) != 0) + { + error (0, 0, + "\ +warning: duplicate key `%s' in version `%s' of RCS file `%s'", + key, vnode->version, rcs->print_path); + freenode (kv); + } +}
/* * Fully parse the RCS file. Store all keyword/value pairs, fetch the @@ -702,14 +794,17 @@ * delete counts are stored on the OTHER field of the RCSVERSNODE * structure, under the names ";add" and ";delete", so that we don't * waste the memory space of extra fields in RCSVERSNODE for code - * which doesn't need this information. + * which doesn't need this information. We also store the total number + * of lines in each revision under the name ";total" -- this is used + * to calculate the number of lines "changed" by revisions that + * represent file additions and deletions, for example. */ void RCS_fully_parse (RCSNode *rcs) { FILE *fp; struct rcsbuffer rcsbuf; - + unsigned long running_total = 0; RCS_reparsercsfile (rcs, &fp, &rcsbuf);
while (1) @@ -733,10 +828,10 @@
while (rcsbuf_getkey (&rcsbuf, &key, &value)) { + Node *kv; + if (!STREQ (key, "text")) { - Node *kv; - if (vnode->other == NULL) vnode->other = getlist (); kv = getnode (); @@ -756,94 +851,118 @@ continue; }
- if (!STREQ (vnode->version, rcs->head))
+ /*
+ Figure out the number of lines added and deleted by
+ each version of the file. This is complicated by the
+ fact that RCS doesn't store diffs for file removals and
+ additions. To display accurate lines added/removed
+ figures in that case, we also keep track of the total
+ number of lines in the file as of each version.
+
+ So, in the head version we count the number of lines in
+ the file. In the versions that follow it, we adjust the
+ total number of lines via the lines added/deleted
+ figures. If a version represents a file removal, we
+ tweak things so that the revision _following_ the
+ removal is credited with adding all the lines in the
+ file. There is some additional logic in log_fileproc to
+ treat the first revision of a file (the original add)
+ specially. Yes, this is all rather complex...
+ */
+ if (vnode->dead)
+ {
+ RCSVers *next_vnode;
+ Node *n;
+
+ /*
+ This revision is dead. Therefore, (a) there must be
+ a revision following it that isn't deleted (since
+ you can't add a file in state `dead') (b) we want
+ to adjust the lines added/deleted from dead => next
+ to credit the next version with adding all the
+ lines in the file. So store a marker in the next
+ version to remember this fact.
+
+ XXX: is there a cleaner way to do this?
+ */
+ n = findnode (rcs->versions, vnode->next);
+ if (!n)
+ error (1, 0,
+ "\
+missing revision `%s' following dead revision `%s' in RCS file `%s'",
+ vnode->version, vnode->next, rcs->print_path);
+
+ next_vnode = n->data;
+ kv = getnode ();
+ kv->key = xstrdup (";follows_delete");
+ kv->data = 0;
+ if (next_vnode->other == NULL)
+ next_vnode->other = getlist ();
+ addnode (next_vnode->other, kv);
+ }
+
+ if (STREQ(vnode->version, rcs->head))
+ {
+ /* This is the head revision, so count the number of
+ lines in the file and store it as ";total". */
+ running_total = 0;
+ if (value != NULL)
+ {
+ const char *cp;
+ for (cp = value; *cp != '\0'; cp++)
+ {
+ if (*cp == '\n')
+ running_total++;
+ }
+ }
+ }
+ else
{
unsigned long add, del;
- char buf[50];
- Node *kv;
-
/* This is a change text. Store the add and delete
- counts. */
- add = 0;
- del = 0;
- if (value != NULL)
- {
- size_t vallen;
- const char *cp;
-
- rcsbuf_valpolish (&rcsbuf, value, 0, &vallen);
- cp = value;
- while (cp < value + vallen)
- {
- char op;
- unsigned long count;
-
- op = *cp++;
- if (op != 'a' && op != 'd')
- error (1, 0, "\
-unrecognized operation '\\x%x' in %s",
- op, rcs->print_path);
- (void) strtoul (cp, (char **) &cp, 10);
- if (*cp++ != ' ')
- error (1, 0, "space expected in %s revision %s",
- rcs->print_path, vnode->version);
- count = strtoul (cp, (char **) &cp, 10);
- if (*cp++ != '\012')
- error (1, 0, "linefeed expected in %s revision %s",
- rcs->print_path, vnode->version);
-
- if (op == 'd')
- del += count;
- else
- {
- add += count;
- while (count != 0)
- {
- if (*cp == '\012')
- --count;
- else if (cp == value + vallen)
- {
- if (count != 1)
- error (1, 0, "\
-premature end of value in %s revision %s",
- rcs->print_path, vnode->version);
- else
- break;
- }
- ++cp;
- }
- }
- }
- }
-
- sprintf (buf, "%lu", add);
- kv = getnode ();
- kv->type = RCSFIELD;
- kv->key = xstrdup (";add");
- kv->data = xstrdup (buf);
- if (addnode (vnode->other, kv) != 0)
- {
- error (0, 0,
- "\
-warning: duplicate key `%s' in version `%s' of RCS file `%s'",
- key, vnode->version, rcs->print_path);
- freenode (kv);
- }
+ counts, update `total' appropriately */
+ parse_lines_changed (rcs, vnode, &rcsbuf, value, &add, &del);
- sprintf (buf, "%lu", del); - kv = getnode (); - kv->type = RCSFIELD; - kv->key = xstrdup (";delete"); - kv->data = xstrdup (buf); - if (addnode (vnode->other, kv) != 0) - { - error (0, 0, - "\ -warning: duplicate key `%s' in version `%s' of RCS file `%s'", - key, vnode->version, rcs->print_path); - freenode (kv); - } - } + if (vnode->dead) + { + unsigned long tmp; + + tmp = running_total; + running_total += add; + running_total -= del; + del = tmp; + add = 0; + } + else + { + Node *n; + + /* Does this version follow a delete? */ + n = findnode (vnode->other, ";follows_delete"); + if (n) + { + /* Remove the follows_delete marker */ + delnode (n); + add = running_total; + del = 0; + } + else + { + running_total += add; + running_total -= del; + } + } + + add_vnode_rcs_other (rcs, vnode, ";add", add); + add_vnode_rcs_other (rcs, vnode, ";delete", del); + } + + /* Add the "total" figure for this version. This is either + the number of lines in this revision of the file. */ + if (vnode->dead) + add_vnode_rcs_other (rcs, vnode, ";total", 0UL); + else + add_vnode_rcs_other (rcs, vnode, ";total", running_total);
/* We have found the "text" key which ends the data for this revision. Break out of the loop and go on to the @@ -1620,7 +1739,7 @@ if (val == NULL) { if (lenp != NULL) - *lenp= 0; + *lenp = 0; return; }
diff -ru cvs-1.12.11_pristine/src/rcs.h cvs-1.12.11/src/rcs.h
--- cvs-1.12.11_pristine/src/rcs.h 2004-10-29 00:12:20.000000000 +1000
+++ cvs-1.12.11/src/rcs.h 2005-03-09 15:11:22.000000000 +1100
@@ -152,10 +152,10 @@
int outdated;
Deltatext *text;
List *branches;
- /* Newphrase fields from deltatext nodes. Also contains ";add" and
- ";delete" magic fields (see rcs.c, log.c). I think this is
- only used by log.c (where it looks up "log"). Duplicates the
- other field in struct deltatext, I think. */
+ /* Newphrase fields from deltatext nodes. Also contains ";add",
+ ";delete" and ";total" magic fields (see rcs.c, log.c). I
+ think this is only used by log.c (where it looks up "log").
+ Duplicates the other field in struct deltatext, I think. */
List *other;
/* Newphrase fields from delta nodes. */
List *other_delta;
diff -ru cvs-1.12.11_pristine/src/sanity.sh cvs-1.12.11/src/sanity.sh
--- cvs-1.12.11_pristine/src/sanity.sh 2004-12-10 08:17:37.000000000 +1100
+++ cvs-1.12.11/src/sanity.sh 2005-03-11 11:02:52.000000000 +1100
@@ -3020,7 +3020,7 @@
modify-it
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
add-it
============================================================================="
dotest basica-o8 "${testcvs} -q update -p -r 1.1 ./ssfile" "ssfile"
@@ -4015,7 +4015,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
=============================================================================
@@ -4032,7 +4032,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
=============================================================================
${SPROG} log: Logging first-dir/dir1
@@ -4051,7 +4051,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
=============================================================================
@@ -4068,7 +4068,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
=============================================================================
${SPROG} log: Logging first-dir/dir1/dir2
@@ -4087,7 +4087,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
=============================================================================
@@ -4104,7 +4104,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
second dive
============================================================================="
@@ -7225,7 +7225,7 @@
trunk-before-branch
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
add-it
----------------------------
revision 1\.2\.2\.2
@@ -7809,7 +7809,7 @@
description:
----------------------------
revision 1.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
add
============================================================================="
@@ -8829,7 +8829,7 @@
description:
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
branches: 1\.1\.2; 1\.1\.4;
add-it
----------------------------
@@ -8948,11 +8948,11 @@
description:
----------------------------
revision 1\.2
-date: ${ISO8601DATE}; author: ${username}; state: dead; lines: ${PLUS}0 -0
+date: ${ISO8601DATE}; author: ${username}; state: dead; lines: ${PLUS}0 -1
local-changes
----------------------------
revision 1\.1
-date: ${ISO8601DATE}; author: ${username}; state: Exp;
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}1 -0
branches: 1\.1\.1;
Initial revision
----------------------------
@@ -23578,6 +23578,8 @@
# "binfiles" (and this test) test "cvs update -k".
# "binwrap" tests setting the mode from wrappers.
# "keyword2" tests "cvs update -kk -j" with text and binary files
+ # "lines_mod" tests that keyword expansion interacts with "lines
+ # modified" feature of "log"
# I don't think any test is testing "cvs import -k".
# Other keyword expansion tests:
# keywordlog - $Log.
@@ -23776,10 +23778,61 @@
dotest keyword-24 "cat file1" '\$'"Name: "'\$'"
change"
+ cd ..
+ mkdir second-dir
+ dotest keyword-25 "${testcvs} add second-dir" \
+"Directory ${CVSROOT_DIRNAME}/second-dir added to the repository"
+ cd second-dir
+ echo '$''Id$' > file1
+ echo '$''Header$' >> file1
+ dotest keyword-26 "${testcvs} add file1" \
+"${SPROG} add: scheduling file .file1. for addition
+${SPROG} add: use .${SPROG} commit. to add this file permanently"
+ dotest keyword-27 "${testcvs} -q ci -m add" \
+"$CVSROOT_DIRNAME/second-dir/file1,v <-- file1
+initial revision: 1\.1"
+ echo "\nsome more text\n" >> file1
+ dotest keyword-28 "${testcvs} ci -m modify file1" \
+"${CVSROOT_DIRNAME}/second-dir/file1,v <-- file1
+new revision: 1\.2; previous revision: 1\.1"
+ rm file1
+ dotest keyword-29 "${testcvs} remove file1" \
+"${SPROG} remove: scheduling .file1. for removal
+${SPROG} remove: use .${SPROG} commit. to remove this file permanently"
+ dotest keyword-30 "${testcvs} ci -m remove" \
+"${SPROG} commit: Examining .
+${CVSROOT_DIRNAME}/second-dir/file1,v <-- file1
+new revision: delete; previous revision: 1.2"
+ dotest keyword-31 "${testcvs} log file1" "
+RCS file: ${CVSROOT_DIRNAME}/second-dir/Attic/file1,v
+Working file: file1
+head: 1\.3
+branch:
+locks: strict
+access list:
+symbolic names:
+keyword substitution: kv
+total revisions: 3; selected revisions: 3
+description:
+----------------------------
+revision 1\.3
+date: ${ISO8601DATE}; author: ${username}; state: dead; lines: ${PLUS}0 -3
+remove
+----------------------------
+revision 1\.2
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}3 -2
+modify
+----------------------------
+revision 1\.1
+date: ${ISO8601DATE}; author: ${username}; state: Exp; lines: ${PLUS}2 -0
+add
+============================================================================="
+
dokeep
cd ../..
rm -r 1
modify_repo rm -rf $CVSROOT_DIRNAME/first-dir
+ modify_repo rm -rf $CVSROOT_DIRNAME/second-dir
;;
Only in cvs-1.12.11_pristine/windows-NT: config.h.in
_______________________________________________ Bug-cvs mailing list Bug-cvs@gnu.org http://lists.gnu.org/mailman/listinfo/bug-cvs