{"thread":{"id":"2287","subject":"[OT] ancestor branch in cvsps is wrong for branch-of-branch","startedAt":"2005-11-01T03:51:48Z","lastAt":"2005-11-10T01:41:06Z","messageCount":3,"participants":["Chris Shoemaker"],"isPatch":false,"patchVersion":null,"patchTotal":null},"messages":[{"id":"10916","messageId":"20051101035148.GA11807@pe.Belkin","threadId":"2287","inReplyTo":null,"subject":"[OT] ancestor branch in cvsps is wrong for branch-of-branch","fromName":"Chris Shoemaker","fromEmail":"c.shoemaker@cox.net","sentAt":"2005-11-01T03:51:48Z","receivedAt":"2005-11-01T03:51:48Z","isPatch":false,"sender":{"key":"c.shoemaker@cox.net","avatar":null},"body":"[ Maybe the git list is interested in a problem with cvsps? ]\n\nSummary: When creating a patchset for the first modification in a\nbranch-off-a-branch, cvsps (version 2.1) declares that the ancestor\nbranch is HEAD.  Presumably this is because the file in the patchset\nhadn't been modified since it was on HEAD.  But this means that files\nthat are *added* to the first branch are never counted as added to the\nbranch-off-a-branch.\n\nFigure:\n\n-HEAD-(add file 'a')--+-------------------------\n                       \\\n                        \\-stable-(add file 'b')-+\n                                                 \\\n                                                  \\-dev-(change 'a')-->\n\nIn CVS, branch \"dev\" clearly contains both 'a' and 'b'.  But cvsps\nmakes it look like this:\n\n                        /-dev-(change 'a') ---->\n                       /\n-HEAD-(add file 'a')--+-------------------------\n                       \\\n                        \\-stable-(add file 'b')---->\n\nSo that in the imported git repository, the \"dev\" branch contains ONLY\n'a'.\n\nBelow are: a script to reproduce this example, the cvs log output, and\ncvsps output.  It's clear from the cvs log that 'b' has the 'dev'\nsymbolic tag, so it seems like we should be able to recreate 'dev'.\n\nBefore I look into fixing this, I wanted to see if there were any\ncomments from the list.  David?\n\n-chris\n\n#!/bin/sh\ndirname=`date +\"%Y%m%d%H%M%S\"`\nmkdir $dirname && cd $dirname\n\n# create the repository\nmkdir cvsroot && cd cvsroot\nexport CVSROOT=`pwd`\ncd ..\ncvs init\n\n# import an empty project\nmkdir project && cd project\ncvs import -m \"Create initial repo\" project yoyo start\ncd ..\nrmdir project\n\n# checkout the project\ncvs co project && cd project\n\n# create a new file on HEAD\necho 1 >a\ncvs add a\ncvs ci -m \"added file a to HEAD\" a\n\n# create and switch to \"stable\" branch\ncvs tag -b stable\ncvs update -r stable \n\n# add a new file to stable branch\necho 2 > b\ncvs add b\ncvs ci -m \"added file b to 'stable' branch\" b\n\n# create and switch to \"dev\" branch from \"stable\"\ncvs tag -b dev\ncvs update -r dev\n\n# modify file 'a' on \"dev\" branch\necho \"change\" >> a\ncvs ci -m \"changed file a on dev branch\" a\n\n# see what CVS thinks the \"dev\" branch looks like\ncd ..\nrm -rf project\ncvs checkout -r dev project\nls project && echo \"Note how b is PRESENT\"\n\n# check output of cvsps\ncd project\ncvs log > ../cvslog\ncd ..\ncvsps -A --test-log cvslog project > cvsps.out\n\n# check behavior of git-cvsimport\ngit-cvsimport -i -d $CVSROOT -C project.git project\ncd project.git\ngit-checkout -f dev\nls && echo \"Note how b is ABSENT!\"\n#***********EOF*********************\n\ncvsps output\n---------------------\nPatchSet 1 \nDate: 2005/10/31 22:30:46\nAuthor: chris\nBranch: HEAD\nTag: (none) \nLog:\nadded file a to HEAD\n\nMembers: \n\ta:INITIAL->1.1 \n\n---------------------\nPatchSet 2 \nDate: 2005/10/31 22:30:47\nAuthor: chris\nBranch: stable\nAncestor branch: HEAD\nTag: (none) \nLog:\nadded file b to 'stable' branch\n\nMembers: \n\tb:1.1->1.1.2.1 \n\n---------------------\nPatchSet 3 \nDate: 2005/10/31 22:30:48\nAuthor: chris\nBranch: dev\nAncestor branch: HEAD\nTag: (none) \nLog:\nchanged file a on dev branch\n\nMembers: \n\ta:1.1->1.1.4.1 \n\n******************EOF*********\ncvs log output\nRCS file: /home/chris/import/20051031223046/cvsroot/project/a,v\nWorking file: a\nhead: 1.1\nbranch:\nlocks: strict\naccess list:\nsymbolic names:\n\tdev: 1.1.0.4\n\tstable: 1.1.0.2\nkeyword substitution: kv\ntotal revisions: 2;\tselected revisions: 2\ndescription:\n----------------------------\nrevision 1.1\ndate: 2005/11/01 03:30:46;  author: chris;  state: Exp;\nbranches:  1.1.4;\nadded file a to HEAD\n----------------------------\nrevision 1.1.4.1\ndate: 2005/11/01 03:30:48;  author: chris;  state: Exp;  lines: +1 -0\nchanged file a on dev branch\n=============================================================================\n\nRCS file: /home/chris/import/20051031223046/cvsroot/project/Attic/b,v\nWorking file: b\nhead: 1.1\nbranch:\nlocks: strict\naccess list:\nsymbolic names:\n\tdev: 1.1.2.1.0.2\n\tstable: 1.1.0.2\nkeyword substitution: kv\ntotal revisions: 2;\tselected revisions: 2\ndescription:\n----------------------------\nrevision 1.1\ndate: 2005/11/01 03:30:47;  author: chris;  state: dead;\nbranches:  1.1.2;\nfile b was initially added on branch stable.\n----------------------------\nrevision 1.1.2.1\ndate: 2005/11/01 03:30:47;  author: chris;  state: Exp;  lines: +1 -0\nadded file b to 'stable' branch\n=============================================================================\n"},{"id":"11415","messageId":"20051109222634.GA19209@pe.Belkin","threadId":"2287","inReplyTo":"20051101035148.GA11807@pe.Belkin","subject":"[PATCH] CVSps fixed, git-cvsimport still buggy (WAS: [OT] ancestor branch in cvsps is wrong for branch-of-branch)","fromName":"Chris Shoemaker","fromEmail":"c.shoemaker@cox.net","sentAt":"2005-11-09T22:26:34Z","receivedAt":"2005-11-09T22:26:34Z","isPatch":true,"sender":{"key":"c.shoemaker@cox.net","avatar":null},"body":"[This is now on-topic.]\n\nI tried the git-cvsimport script and found that in the\nresulting git tree, on a branch-off-a-branch, files were missing from\nthe branch if they had been added to the branch's immediate parent.  I\nreally wanted this to work better, so I looked into it and learned\nthat CVSps was incorrectly reporting that the \"ancestor\" branch was\nHEAD for a branch-off-a-branch.\n\nSince CVSps wasn't reporting the correct branch relationships, I\ncouldn't blame git-cvsimport.  Trying to improve CVSps to get better\nparent-detection proved much harder than I expected.  I had to use a\ncompletely different algorithm that loops over all files instead of\njust the files in the patchsets that are on the child branch.\n\nNow that I've done that, [David, please see attached patch] CVSps does\nreport more accurate branch parents.  But, testing git-cvsimport with\nthe corrected CVSps output shows that it *still* doesn't produce the\nright tree.  In fact, it's WORSE!  In the simple test script, not only\nis file 'b' not on the 'dev' branch, but the 'stable' branch and the\nfile 'b' is completely MISSING form the git tree!\n\nI could cut git-cvsimport some slack for failing to infer that file\n'b' is also on the 'dev' branch, since I learned how hard that is when\nI taught CVSps to make that inference.  But completely missing a\nbranch and all files added to it is not good.  \n\nI'd *really* like to be told I'm off the deep end here, so if anyone\ncan set me straight please do.\n\nIf you want to reproduce this bug, use the test-script from my\noriginal post (actually, I'll attach it again - it's been a while).\nBut you must first apply the attached patch to CVSps; or you can\ninstead use the '-P cvsps.out' option [which is quite handy BTW] to\ngit-cvsimport after you correct the one \"Ancestor:\" line from \"HEAD\"\nto \"stable\" which is the only effect the new algorithm has on this\nsimple test case.  Note: the script demonstrates that 'b' is missing\nfrom 'dev'.  To notice that 'stable' and 'b' are missing altogether\nyou have to say 'git-branch'.\n\nIf this stuff makes your head hurt like it does mine, you may want to\nrefer to the notes that I had to take in order to work through\nthis a half-hour at a time: http://www.codesifter.com/cvsps-notes.txt\n\n-chris\n\n\n cvsps.c       |  242 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++-\n cvsps_types.h |    2 \n 2 files changed, 240 insertions(+), 4 deletions(-)\n\nIndex: cvsps-2.1/cvsps.c\n===================================================================\n--- cvsps-2.1.orig/cvsps.c\n+++ cvsps-2.1/cvsps.c\n@@ -81,9 +81,12 @@ static int ignore_cache;\n static int do_write_cache;\n static int statistics;\n static const char * test_log_file;\n+/* branch_heads actually store branch \"roots\": [char *] to [PatchSet *]\n+ * It's only used by the track_branch_ancestry option. */\n static struct hash_table * branch_heads;\n static struct list_head all_patch_sets;\n static struct list_head collisions;\n+static struct list_head all_branches;\n \n /* settable via options */\n static int timestamp_fuzz_factor = 300;\n@@ -143,8 +146,10 @@ static void set_psm_initial(PatchSetMemb\n static int check_rev_funk(PatchSet *, CvsFileRevision *);\n static CvsFileRevision * rev_follow_branch(CvsFileRevision *, const char *);\n static int before_tag(CvsFileRevision * rev, const char * tag);\n-static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps);\n+static void determine_branch_ancestor(const PatchSet * ps, PatchSet * head_ps);\n static void handle_collisions();\n+static void find_branch_parent(PatchSet *ps);\n+static int is_ancestor_rev(const char *rev1, const char *rev2);\n \n int main(int argc, char *argv[])\n {\n@@ -181,6 +186,7 @@ int main(int argc, char *argv[])\n     branch_heads = create_hash_table(1023);\n     INIT_LIST_HEAD(&all_patch_sets);\n     INIT_LIST_HEAD(&collisions);\n+    INIT_LIST_HEAD(&all_branches);\n \n     /* this parses some of the CVS/ files, and initializes\n      * the repository_path and other variables \n@@ -1259,6 +1265,29 @@ static int get_branch(char * buff, const\n     return get_branch_ext(buff, rev, NULL);\n }\n \n+/* Doesn't have to handle magic branches because parse_sym already did. */\n+static int is_ancestor_rev(const char *rev1, const char *rev2)\n+{\n+    char b1[REV_STR_MAX], b2[REV_STR_MAX];\n+    int len1 = strlen(rev1);\n+    int len2 = strlen(rev2);\n+    int leaf1, leaf2;\n+\n+    if (len1 > len2)\n+        return 0;\n+\n+    if (strncmp(rev1, rev2, len1) == 0 || strcmp(rev1, \"INITIAL\") == 0)\n+        return 1;\n+\n+    if (!get_branch_ext(b1, rev1, &leaf1) || !get_branch_ext(b2, rev2, &leaf2))\n+        return 0;\n+\n+    if (strcmp(b1, b2) == 0 && leaf2 >= leaf1)\n+        return 1;\n+\n+    return 0;\n+}\n+\n /* \n  * the goal if this function is to determine what revision to assign to\n  * the psm->pre_rev field.  usually, the log file is strictly \n@@ -1499,11 +1528,15 @@ static void assign_patchset_id(PatchSet \n \t    PatchSet * head_ps = (PatchSet*)get_hash_object(branch_heads, ps->branch);\n \t    if (!head_ps) \n \t    {\n+                Tag *tag;\n \t\thead_ps = ps;\n \t\tput_hash_object(branch_heads, ps->branch, head_ps);\n+                find_branch_parent(ps);\n+                tag = (Tag*)malloc(sizeof(*tag));\n+                tag->tag = ps->branch;\n+                /* This is our own private tag list, so we use global_link.*/\n+                list_add(&tag->global_link,  &all_branches);\n \t    }\n-\t    \n-\t    determine_branch_ancestor(ps, head_ps);\n \t}\n     }\n     else\n@@ -2394,6 +2427,8 @@ void patch_set_add_member(PatchSet * ps,\n \n static void set_psm_initial(PatchSetMember * psm)\n {\n+    char *p;\n+\n     psm->pre_rev = NULL;\n     if (psm->post_rev->dead)\n     {\n@@ -2405,6 +2440,27 @@ static void set_psm_initial(PatchSetMemb\n \t    debug(DEBUG_APPMSG1, \"WARNING: branch_add already set!\");\n \tpsm->ps->branch_add = 1;\n     }\n+\n+    /* Maybe this is the best place to record which branch a file was\n+       initially added on.  It seems the most accurate way is\n+       also painful - read the commit message. */\n+    if (psm->file->initial_branch)\n+        debug(DEBUG_APPMSG1, \"WARNING: initial_branch already set!\");\n+\n+    psm->file->initial_ps = psm->ps;\n+\n+    p = strstr(psm->ps->descr, \" was initially added on branch \");\n+    if (p) {\n+        char * end;\n+        p += strlen(\" was initially added on branch \");\n+        end = strchr(p, '.');\n+        if (end) {\n+            *end = 0;\n+            psm->file->initial_branch = get_string(p);\n+            return;\n+        }\n+    }\n+    psm->file->initial_branch = psm->ps->branch;\n }\n \n /* \n@@ -2532,7 +2588,13 @@ static void check_norc(int argc, char * \n     }\n }\n \n-static void determine_branch_ancestor(PatchSet * ps, PatchSet * head_ps)\n+/* When we track ancestor branches, we run determine_branch_ancestor()\n+ * for every ps not on HEAD.  But, we only record the ancestor in\n+ * head_ps.  head_ps is the first (i.e. \"root\") ps on the same branch as\n+ * ps (which may be the same as ps).  In other words, we record the\n+ * ancestor branch in the first patch that \"branched-off\".\n+ */\n+static void determine_branch_ancestor(const PatchSet * ps, PatchSet * head_ps)\n {\n     struct list_head * next;\n     CvsFileRevision * rev;\n@@ -2599,6 +2661,178 @@ static void determine_branch_ancestor(Pa\n     }\n }\n \n+/* only needs to be called once for each new branch we start */\n+static void find_branch_parent(PatchSet *ps)\n+{\n+    const char* child = ps->branch;\n+    char* parent;\n+    struct list_head *next;\n+    struct hash_entry * he_file;\n+    Tag *tag;\n+\n+    /* We use the 'rev' struct member unconventionally: as a flag to mark\n+       which branches have not yet been eliminated from consideration. */\n+\n+    /* Mark every branch for consideration. */\n+    for (next = all_branches.next; next != &all_branches; next = next->next)\n+    {\n+        tag = list_entry(next, Tag, global_link);\n+        tag->rev = (CvsFileRevision *) 1; /* cast just to silence compiler */\n+    }\n+\n+    reset_hash_iterator(file_hash);\n+    while ((he_file = next_hash_entry(file_hash)))\n+    {\n+\tCvsFile *file = (CvsFile*)he_file->he_obj;\n+\n+        if (!file->initial_branch) {\n+            debug(DEBUG_APPERROR, \"invalid initial_branch for file %s, probably from old cache, run with -x.\", file->filename);\n+            exit(1);\n+        }\n+\n+        /* If the file was introduced later than the branch point, we\n+           don't consider it.  NOTE: this won't catch the case where a\n+           file is added to branch A, then later branch C branches off\n+           of branch B, then branch A is merged into branch C.  In\n+           that case, the initial date for the file is earlier than\n+           the root-branch patchset, so the absence of the file from\n+           branch B *DOES* eliminate B from being the parent of C. */\n+        if (file->initial_ps->date > ps->date)\n+            continue;\n+\n+        /* If the file was added on this branch, we can't use it to\n+           eliminate other potential parents just because they don't\n+           have this file. */\n+        if (strcmp(file->initial_branch, child) == 0)\n+            continue;\n+\n+        /* If we haven't yet seen the branch that file was initially\n+           added to, then *that* branch may be a child of *this*\n+           child.  In that case, we shouldn't expect that this file is\n+           necessarily also on the parent branch - for the same reason\n+           we ignore files added on the child branch immediately. */\n+        if (strcmp(file->initial_branch, \"HEAD\") != 0 &&\n+            NULL == get_hash_object(branch_heads, file->initial_branch))\n+            continue;\n+\n+        /* Pick a branch to consider */\n+        for (next = all_branches.next; next != &all_branches; next = next->next)\n+        {\n+            char *child_rev, *parent_rev;\n+            tag = list_entry(next, Tag, global_link);\n+            if (!tag->rev)\n+                continue;  /* skip branches already eliminated */\n+\n+            parent = tag->tag;\n+            child_rev = get_hash_object(file->branches_sym, child);\n+            parent_rev = get_hash_object(file->branches_sym, parent);\n+            if (child_rev) {\n+                if (parent_rev) {\n+                    if (!is_ancestor_rev(parent_rev, child_rev)) {\n+                        tag->rev = NULL;\n+                        debug(DEBUG_STATUS,\n+                              \"%s not parent of %s: %s REV %s vs. %s\",\n+                              parent, child, file->filename, parent_rev,\n+                              child_rev);\n+                    }\n+                } else {\n+                    /* If this file is on the child branch but not on\n+                       the parent branch, then it's not the true\n+                       parent.  WARNING: as commented above, this may\n+                       falsely eliminate a parent if a file added to\n+                       some other branch before this child's branch\n+                       point is later merged onto the child.  That\n+                       file may not be on the true parent, but we\n+                       eliminate that parent anyway. Bah!\n+                       Corner-cases?!  How often do people merge with\n+                       children branches?  Anyway, this is too\n+                       effective at eliminating incorrect parents to\n+                       ignore. */\n+                    tag->rev = NULL;\n+                    debug(DEBUG_STATUS, \"%s not parent of %s: parent missing %s\",\n+                          parent, child, file->filename);\n+                }\n+            } else {\n+                /* What about the file on the parent but not the child?\n+                   The file could have been added to the parent later than\n+                   the branch point. But we catch that above.  More\n+                   importantly, it could have been added to some other\n+                   branch time-wise earlier than the branch point, and\n+                   then merged onto this parent after the branch point.\n+                   That's too hard to detect.  Also, there's the evil of\n+                   partial-tree tagging.  The most common way this arises\n+                   is that a child branches off just a portion (usually a\n+                   sub-directory) of the tree.  In that case, lots of\n+                   files are on the parent but not on the child.  So we'll\n+                   be lenient and let this potential parent live another\n+                   day. */\n+#if 0\n+                /* TODO?: maybe an optional \"strict\" mode could enable\n+                   this, if the user is willing to assume there haven't\n+                   been any partial-tree taggings. */\n+                if (parent_rev) {\n+                    tag->rev = NULL;\n+                    debug(DEBUG_STATUS, \"%s not parent of %s: child missing %s\",\n+                          parent, child, file->filename);\n+                }\n+#endif\n+\n+            }\n+        } /* end of loop over potential parents */\n+    }\n+\n+    /* Any remaining branches are possible parents. But, if any of\n+       these branches are parents of other possible parents, we'll\n+       say they're \"grandparents\" and not consider them for\n+       parenthood. */\n+    for (next = all_branches.next; next != &all_branches; next = next->next)\n+    {\n+        struct list_head *j;\n+        tag = list_entry(next, Tag, global_link);\n+        if (!tag->rev) continue;\n+        for (j = all_branches.next; j != &all_branches; j = j->next)\n+        {\n+            Tag *tag2 = list_entry(j, Tag, global_link);\n+            PatchSet *root_ps;\n+            if (!tag2->rev) continue;\n+            if (j == next) continue;\n+            root_ps = (PatchSet*)get_hash_object(branch_heads, tag2->tag);\n+            if (root_ps && strcmp(root_ps->ancestor_branch, tag->tag) == 0) {\n+                tag->rev = NULL;\n+                debug(DEBUG_STATUS, \"eliminate %s: it's GRANDparent of %s via %s\",\n+                      tag->tag, child, tag2->tag);\n+                break;\n+            }\n+        }\n+    }\n+\n+    parent = NULL;\n+    debug(DEBUG_STATUS, \"Remaining parents for %s:\", child);\n+    for (next = all_branches.next; next != &all_branches; next = next->next)\n+    {\n+        tag = list_entry(next, Tag, global_link);\n+        if (tag->rev) {\n+            debug(DEBUG_STATUS, \"    %s\", tag->tag);\n+            if (parent)\n+                debug(DEBUG_STATUS, \"Alternative ambiguous parent of %s: %s\",\n+                      child, tag->tag);\n+            else\n+                parent = tag->tag;\n+        }\n+    }\n+\n+    if (parent) {\n+        debug(DEBUG_STATUS, \"Assigning %s as parent of %s\", parent, child);\n+        ps->ancestor_branch = parent;\n+    } else {\n+        debug(DEBUG_STATUS, \"HEAD parent of %s by default\", child);\n+        ps->ancestor_branch = \"HEAD\";\n+        /* Strictly speaking, we didn't actually verify that HEAD\n+           is a possible parent, but what else can we do if there\n+           are no other branches left? */\n+    }\n+}\n+\n static void handle_collisions()\n {\n     struct list_head *next;\nIndex: cvsps-2.1/cvsps_types.h\n===================================================================\n--- cvsps-2.1.orig/cvsps_types.h\n+++ cvsps-2.1/cvsps_types.h\n@@ -71,6 +71,8 @@ struct _CvsFile\n      * with the branch attribute NULL.  Later we need to resolve these.\n      */\n     int have_branches;\n+    char *initial_branch;\n+    PatchSet *initial_ps;\n };\n \n struct _PatchSetMember\n\n\n#!/bin/sh\ndirname=`date +\"%Y%m%d%H%M%S\"`\nmkdir $dirname && cd $dirname\n\n# create the repository\nmkdir cvsroot && cd cvsroot\nexport CVSROOT=`pwd`\ncd ..\ncvs init\n\n# import an empty project\nmkdir project && cd project\ncvs import -m \"Create initial repo\" project yoyo start\ncd ..\nrmdir project\n\n# checkout the project\ncvs co project && cd project\n\n# create a new file on HEAD\necho 1 >a\ncvs add a\ncvs ci -m \"added file a to HEAD\" a\n\n# create and switch to \"stable\" branch\ncvs tag -b stable\ncvs update -r stable \n\n# add a new file to stable branch\necho 2 > b\ncvs add b\ncvs ci -m \"added file b to 'stable' branch\" b\n\n# create and switch to \"dev\" branch from \"stable\"\ncvs tag -b dev\ncvs update -r dev\n\n# modify file 'a' on \"dev\" branch\necho \"change\" >> a\ncvs ci -m \"changed file a on dev branch\" a\n\n# see what CVS thinks the \"dev\" branch looks like\ncd ..\nrm -rf project\ncvs checkout -r dev project\nls project && echo \"Note how b is PRESENT\"\n\n# check output of cvsps\ncd project\ncvs log > ../cvslog\ncd ..\ncvsps -A -x --test-log cvslog project > cvsps.out\n\n# check behavior of git-cvsimport\ngit-cvsimport -i -d $CVSROOT -C project.git project\ncd project.git\ngit-checkout -f dev\nls && echo \"Note how b is ABSENT!\"\n\n"},{"id":"11449","messageId":"20051110014106.GA19835@pe.Belkin","threadId":"2287","inReplyTo":"20051109222634.GA19209@pe.Belkin","subject":"CVSps fixed; git-cvsimport works, too","fromName":"Chris Shoemaker","fromEmail":"c.shoemaker@cox.net","sentAt":"2005-11-10T01:41:06Z","receivedAt":"2005-11-10T01:41:06Z","isPatch":false,"sender":{"key":"c.shoemaker@cox.net","avatar":null},"body":"On Wed, Nov 09, 2005 at 05:26:34PM -0500, Chris Shoemaker wrote:\n> I could cut git-cvsimport some slack for failing to infer that file\n> 'b' is also on the 'dev' branch, since I learned how hard that is when\n> I taught CVSps to make that inference.  But completely missing a\n> branch and all files added to it is not good.\n\nOk.  Whew, I'm glad to report that I was mistaken.  git-cvsimport\nperforms beautifully when given the new CVSps output.  Sorry for the\nnoise.\n\nI'll change this then to an announcement that with that CVSps patch,\nCVSps produces more accurate branch ancestry, and git-cvsimport can\ncorrectly reproduce branch-of-a-branch histories.\n\nOn a related note, the new '-P' option doesn't behave exactly as I\nexpected.  You must provide the _full_ pathname to the input file\nCVSps file.  If that's not a bug, it should at least be documented.\n\n-chris\n"}]}