in contrib/client-side/svnmerge/svnmerge-migrate-history.py [0:0]
def convert_path_history(self, root, revnum, path):
"Migrate the merge history for PATH at ROOT at REVNUM."
### Bother to handle any pre-existing, inherited svn:mergeinfo?
# Retrieve existing Subversion 1.5 mergeinfo.
mergeinfo_prop_val = svn.fs.node_prop(root, path,
svn.core.SVN_PROP_MERGEINFO)
if mergeinfo_prop_val is not None:
self.log("Discovered pre-existing Subversion mergeinfo of '%s'" \
% (self.flatten_prop(mergeinfo_prop_val)))
# Retrieve svnmerge.py's merge history meta data, and roll it into
# Subversion 1.5 mergeinfo.
integrated_prop_val = svn.fs.node_prop(root, path, "svnmerge-integrated")
if integrated_prop_val is not None:
self.log("Discovered svnmerge.py mergeinfo of '%s'" \
% (self.flatten_prop(integrated_prop_val)))
### LATER: We handle svnmerge-blocked by converting it into
### svn:mergeinfo, until revision blocking becomes available in
### Subversion's core.
blocked_prop_val = svn.fs.node_prop(root, path, "svnmerge-blocked")
if blocked_prop_val is not None:
self.log("Discovered svnmerge.py blocked revisions of '%s'" \
% (self.flatten_prop(blocked_prop_val)))
# Convert property values into real mergeinfo structures.
svn_mergeinfo = None
if mergeinfo_prop_val is not None:
svn_mergeinfo = svn.core.svn_mergeinfo_parse(mergeinfo_prop_val)
integrated_mergeinfo = self.svnmerge_prop_to_mergeinfo(integrated_prop_val)
blocked_mergeinfo = self.svnmerge_prop_to_mergeinfo(blocked_prop_val)
# Add our various bits of stored mergeinfo together.
new_mergeinfo = self.mergeinfo_merge(svn_mergeinfo, integrated_mergeinfo)
new_mergeinfo = self.mergeinfo_merge(new_mergeinfo, blocked_mergeinfo)
if new_mergeinfo is not None:
self.log("Combined mergeinfo is '%s'" \
% (self.flatten_prop(mergeinfo2str(new_mergeinfo))))
# Unless we're doing a naive migration (or we've no, or only
# empty, mergeinfo anyway), start trying to cleanup after
# svnmerge.py's history-ignorant initialization.
if not self.naive_mode and new_mergeinfo:
# We begin by subtracting the natural history of the merge
# target from its own mergeinfo.
rev = svn.fs.revision_root_revision(root)
implicit_mergeinfo = self.get_natural_history(path, rev)
self.log("Subtracting natural mergeinfo of '%s'" \
% (self.flatten_prop(mergeinfo2str(implicit_mergeinfo))))
new_mergeinfo = svn.core.svn_mergeinfo_remove(implicit_mergeinfo,
new_mergeinfo)
self.log("Remaining mergeinfo is '%s'" \
% (self.flatten_prop(mergeinfo2str(new_mergeinfo))))
# Unfortunately, svnmerge.py tends to initialize using oft-bogus
# revision ranges like 1-SOMETHING when the merge source didn't
# even exist in r1. So if the natural history of a branch
# begins in some revision other than r1, there's still going to
# be cruft revisions left in NEW_MERGEINFO after subtracting the
# natural history. So, we also examine the natural history of
# the merge sources, and use that as a filter for the explicit
# mergeinfo we've calculated so far.
self.log("Filtering mergeinfo by reconstruction from source history ...")
filtered_mergeinfo = {}
for source_path, ranges in new_mergeinfo.items():
### If by some chance it is the case that /path:RANGE1 and
### /path:RANGE2 a) represent different lines of history, and
### b) were combined into /path:RANGE1+RANGE2 (due to the
### ranges being contiguous), we'll foul this up. But the
### chances are preeeeeeeetty slim.
for range in ranges:
try:
source_history = self.get_natural_history(source_path,
range.end,
range.start + 1)
self.log("... adding '%s'" \
% (self.flatten_prop(mergeinfo2str(source_history))))
filtered_mergeinfo = \
svn.core.svn_mergeinfo_merge(filtered_mergeinfo,
source_history)
except svn.core.SubversionException as e:
if not (e.apr_err == svn.core.SVN_ERR_FS_NOT_FOUND
or e.apr_err == svn.core.SVN_ERR_FS_NO_SUCH_REVISION):
raise
self.log("... done.")
new_mergeinfo = filtered_mergeinfo
# Turn our to-be-written mergeinfo back into a property value.
new_mergeinfo_prop_val = None
if new_mergeinfo is not None:
new_mergeinfo_prop_val = mergeinfo2str(new_mergeinfo)
# If we need to change the value of the svn:mergeinfo property or
# delete any svnmerge-* properties, let's do so.
if (new_mergeinfo_prop_val != mergeinfo_prop_val) \
or (integrated_prop_val is not None) \
or (blocked_prop_val is not None):
# If this not a dry-run, begin a transaction in which we'll
# manipulate merge-related properties. Open the transaction root.
if not self.dry_run:
txn = svn.fs.begin_txn2(self.fs, revnum, 0)
root = svn.fs.txn_root(txn)
# Manipulate the merge history.
if new_mergeinfo_prop_val != mergeinfo_prop_val:
# Run the final version of the new svn:mergeinfo through the
# parser to ensure it is in canonical form, e.g. no overlapping
# or unordered rangelists, see
# https://issues.apache.org/jira/browse/SVN-3302.
mergeinfo = svn.core.svn_mergeinfo_parse(new_mergeinfo_prop_val)
new_mergeinfo_prop_val = mergeinfo2str(mergeinfo)
self.log("Queuing change of %s to '%s'"
% (svn.core.SVN_PROP_MERGEINFO,
self.flatten_prop(new_mergeinfo_prop_val)))
if not self.dry_run:
svn.fs.change_node_prop(root, path, svn.core.SVN_PROP_MERGEINFO,
new_mergeinfo_prop_val)
# Remove old property values.
if integrated_prop_val is not None:
self.log("Queuing removal of svnmerge-integrated")
if not self.dry_run:
svn.fs.change_node_prop(root, path, "svnmerge-integrated", None)
if blocked_prop_val is not None:
self.log("Queuing removal of svnmerge-blocked")
if not self.dry_run:
svn.fs.change_node_prop(root, path, "svnmerge-blocked", None)
# Commit the transaction containing our property manipulation.
self.log("Committing the transaction containing the above changes")
if not self.dry_run:
conflict, new_revnum = svn.fs.commit_txn(txn)
if conflict:
raise Exception("Conflict encountered (%s)" % conflict)
self.log("Migrated merge history on '%s' in r%d"
% (path, new_revnum), False)
else:
self.log("Migrated merge history on '%s' in r???" % (path), False)
return True
else:
# No merge history to manipulate.
self.log("No merge history on '%s'" % (path))
return False