bug-cvs
[Top][All Lists]
Advanced

[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




reply via email to

[Prev in Thread] Current Thread [Next in Thread]