[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]
Re: lines modified in newly-added files
From: |
Neil Conway |
Subject: |
Re: lines modified in newly-added files |
Date: |
Fri, 11 Mar 2005 15:13:41 +1100 |
User-agent: |
Debian Thunderbird 1.0 (X11/20050116) |
Derek Price wrote:
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
- Re: lines modified in newly-added files,
Neil Conway <=