Re: Tracking "branch heads"

Michael Haggerty <[email protected]> Thu, 17 Dec 2009 09:57:21 +0100
Newsgroups gmane.comp.version-control.subversion.cvs2svn.devel
Message-ID <[email protected]>
Greg Ward wrote:
> [...]
> Cool.  OK, here is a preliminary draft.  This passes its test, but I
> have not yet used it in production.  I'll see how the design actually
> works within the next 24 hours.  For your amusement...
> 
> """
> # HG changeset patch
> # User Greg Ward <[email protected]>
> # Date 1260821833 18000
> # Node ID 1763b1aba3259d59ed84141f046f73a35331a7e0
> # Parent  64442bf496d83c4a1883bddc51a387493edeabd1
> Record the head of each branch as we generate Subversion revisions.
> This should make life easier for DVCS conversions, where it's often
> desirable to do special things with branch heads (close them, merge
> them, etc.)
> 
> * config.py: add SVN_BRANCH_HEADS file (a pickled dictionary)
> * passes.py: modify CreateRevsPass to record and write branch heads
> * run-tests.py: add a test
> 
> diff --git a/cvs2svn_lib/config.py b/cvs2svn_lib/config.py
> --- a/cvs2svn_lib/config.py
> +++ b/cvs2svn_lib/config.py
> @@ -159,6 +159,12 @@
>  # pickled SVNCommit instances.
>  CVS_REVS_TO_SVN_REVNUMS = 'cvs-revs-to-svn-revnums.dat'
> 
> +# Pickled dictionary mapping LOD as (id, name) tuple to branch head
> +# (highest svn revision number on that branch).  Only Trunk and Branch LODs
> +# will be recorded here.  The LOD name is only recorded for testing
> +# purposes; if you use this dict, you really should use the LOD id.
> +SVN_BRANCH_HEADS = 'svn-branch-heads.pck'
> +

OK, this answers the question in my last email: the name is included for
testing purposes.  Hmmm....

>  # This database maps Subversion revision numbers to pickled SVNCommit
>  # instances.
>  SVN_COMMITS_INDEX_TABLE = 'svn-commits-index.dat'
> diff --git a/cvs2svn_lib/passes.py b/cvs2svn_lib/passes.py
> --- a/cvs2svn_lib/passes.py
> +++ b/cvs2svn_lib/passes.py
> @@ -1541,6 +1541,7 @@
>      self._register_temp_file(config.SVN_COMMITS_STORE)
>      self._register_temp_file(config.CVS_REVS_TO_SVN_REVNUMS)
>      self._register_temp_file(config.SYMBOL_OPENINGS_CLOSINGS)
> +    self._register_temp_file(config.SVN_BRANCH_HEADS)
>      self._register_temp_file_needed(config.PROJECTS)
>      self._register_temp_file_needed(config.CVS_FILES_DB)
>      self._register_temp_file_needed(config.CVS_ITEMS_SORTED_STORE)
> @@ -1585,6 +1586,23 @@
>        for cvs_rev in svn_commit.cvs_revs:
>          Log().verbose(' %s %s' % (cvs_rev.cvs_path, cvs_rev.rev,))
> 
> +  def update_branch_heads(self, branch_heads, svn_commit):
> +    for cvs_rev in svn_commit.get_cvs_items():
> +      if isinstance(cvs_rev, CVSRevision):  # ordinary commit
> +        lod = cvs_rev.lod
> +        id = lod.id
> +        if isinstance(lod, Trunk):
> +          name = str(lod)
> +        elif isinstance(lod, Branch):
> +          name = lod.name
> +        else:
> +          continue
> +      else:
> +        continue
> +
> +      branch_heads[(id, name)] = svn_commit.revnum
> +
> +

Shouldn't this rather be "branch_heads[id] = (svn_commit.revnum, name)"?
 I don't see much purpose to requiring both id and name to look up an
entry in the map...

For that matter, you could just write "branch_heads[lod] =
svn_commit.revnum", since LinesOfDevelopment are hashable.  Then when
outputting the results you can add the extra information.

>    def run(self, run_options, stats_keeper):
>      Log().quiet("Mapping CVS revisions to Subversion commits...")
> 
> @@ -1599,6 +1617,7 @@
>          DB_OPEN_READ)
> 
>      Ctx()._symbolings_logger = SymbolingsLogger()
> +    branch_heads = {}                   # map (LOD id, name) to head revnum
> 
>      persistence_manager = PersistenceManager(DB_OPEN_NEW)
> 
> @@ -1606,6 +1625,12 @@
>      for svn_commit in self.get_svn_commits(creator):
>        self.log_svn_commit(svn_commit)
>        persistence_manager.put_svn_commit(svn_commit)
> +      self.update_branch_heads(branch_heads, svn_commit)
> +
> +    # Write the branch_heads dict.
> +    file = open(artifact_manager.get_temp_file(config.SVN_BRANCH_HEADS), 'wb')
> +    cPickle.dump(branch_heads, file, -1)
> +    file.close()

Then this could become

    cPickle.dump(
        [(lod.id, lod.name, revnum) for (lod, revnum) in branch_heads],
        file, -1,
        )

In other words, there is no need to commit to which direction the
dictionary should run, because there is no need to store this as a
dictionary at all.

> 
>      stats_keeper.set_svn_rev_count(creator.revnum_generator.get_last_id())
>      del creator
> @@ -1712,6 +1737,7 @@
>      self._register_temp_file_needed(config.SVN_COMMITS_INDEX_TABLE)
>      self._register_temp_file_needed(config.SVN_COMMITS_STORE)
>      self._register_temp_file_needed(config.CVS_REVS_TO_SVN_REVNUMS)
> +    self._register_temp_file_needed(config.SVN_BRANCH_HEADS)
>      Ctx().output_option.register_artifacts(self)
> 
>    def run(self, run_options, stats_keeper):
> diff --git a/run-tests.py b/run-tests.py
> --- a/run-tests.py
> +++ b/run-tests.py
> @@ -4584,6 +4584,33 @@
>          ('trunk/proj/sub1/default', [None]),
>          ])
> 
> +@Cvs2SvnTestFunction
> +def branch_heads():
> +  "record head of each branch"
> +  import cPickle
> +  from cvs2svn_lib.symbol import Trunk, Branch

cPickle can be imported at the top of the file.  Trunk and Branch don't
seem to be used at all.

> +
> +  conv = ensure_conversion('main', args=['--skip-cleanup',])
> +  file = open('cvs2svn-tmp/svn-branch-heads.pck', 'rb')
> +  branch_heads = cPickle.load(file)
> +  file.close()
> +
> +  expect = {
> +    # these are easy and obvious, since they all have CVS commits
> +    "Trunk":    33,
> +    "B_SPLIT":  32,
> +    "B_MIXED":  24,
> +
> +    }
> +
> +  actual = {}
> +  for ((id, name), revnum) in branch_heads.items():
> +    actual[name] = revnum
> +
> +  if expect != actual:
> +    raise Failure("branch heads: expected:\n%s\nbut got:\n%s"
> +                  % (expect, actual))
> +
> 
>  ########################################################################
>  # Run the tests
> @@ -4802,6 +4829,7 @@
>      collision_with_unlabeled_branch_name,
>      many_deletes,
>      cvs_description,
> +    branch_heads,
>      ]
> 
>  if __name__ == '__main__':
> """
> 
> Of note: I do *not* use branch creation events to update branch_heads.
>  I have a patch that adds that, but:
> 
>   * it ensures that CVS branches that never actually had any commits
> are present in
>     branch_heads... but it's not clear if that is a good thing or a bad thing
>   * it complicates matters by recording "split" CVS branches under two distinct
>     LOD IDs
> 
> Think about it.  I'll send the second patch if you want, but it feels
> wrong to me.

The user has a chance to change commitless branches into tags.  If he
hasn't, it might be because it is a legitimate branch that has been
created but not yet used.

I don't understand your second point.  What do you mean by "split" CVS
branch here?

Michael

------------------------------------------------------
http://cvs2svn.tigris.org/ds/viewMessage.do?dsForumId=1667&dsMessageId=2431079

To unsubscribe from this discussion, e-mail: [[email protected]].