1 # (Be in -*- python -*- mode.)
3 # ====================================================================
4 # Copyright (c) 2007-2009 CollabNet. All rights reserved.
6 # This software is licensed as described in the file COPYING, which
7 # you should have received as part of this distribution. The terms
8 # are also available at http://subversion.tigris.org/license-1.html.
9 # If newer versions of this license are posted there, you may use a
10 # newer version instead, at your option.
12 # This software consists of voluntary contributions made by many
13 # individuals. For exact contribution history, see the revision
14 # history and logs, available at http://cvs2svn.tigris.org/.
15 # ====================================================================
17 """Miscellaneous utility code common to DVCS backends (like
18 Git, Mercurial, or Bazaar).
23 from cvs2svn_lib
import config
24 from cvs2svn_lib
.common
import FatalError
25 from cvs2svn_lib
.common
import InternalError
26 from cvs2svn_lib
.run_options
import RunOptions
27 from cvs2svn_lib
.log
import Log
28 from cvs2svn_lib
.context
import Ctx
29 from cvs2svn_lib
.artifact_manager
import artifact_manager
30 from cvs2svn_lib
.project
import Project
31 from cvs2svn_lib
.svn_revision_range
import RevisionScores
32 from cvs2svn_lib
.openings_closings
import SymbolingsReader
33 from cvs2svn_lib
.repository_mirror
import RepositoryMirror
34 from cvs2svn_lib
.output_option
import OutputOption
37 class DVCSRunOptions(RunOptions
):
38 """Dumping ground for whatever is common to GitRunOptions and
40 def __init__(self
, progname
, cmd_args
, pass_manager
):
41 Ctx().cross_project_commits
= False
42 Ctx().cross_branch_commits
= False
43 RunOptions
.__init
__(self
, progname
, cmd_args
, pass_manager
)
47 project_cvs_repos_path
,
48 symbol_transforms
=None,
49 symbol_strategy_rules
=[],
51 """Set the project to be converted.
53 If a project had already been set, overwrite it.
55 Most arguments are passed straight through to the Project
56 constructor. SYMBOL_STRATEGY_RULES is an iterable of
57 SymbolStrategyRules that will be applied to symbols in this
60 symbol_strategy_rules
= list(symbol_strategy_rules
)
64 project_cvs_repos_path
,
65 symbol_transforms
=symbol_transforms
,
68 self
.projects
= [project
]
69 self
.project_symbol_strategy_rules
= [symbol_strategy_rules
]
71 def process_options(self
):
72 # Consistency check for options and arguments.
73 if len(self
.args
) == 0:
77 if len(self
.args
) > 1:
78 Log().error(error_prefix
+ ": must pass only one CVS repository.\n")
82 cvsroot
= self
.args
[0]
84 self
.process_extraction_options()
85 self
.process_output_options()
86 self
.process_symbol_strategy_options()
87 self
.process_property_setter_options()
92 symbol_transforms
=self
.options
.symbol_transforms
,
93 symbol_strategy_rules
=self
.options
.symbol_strategy_rules
,
97 class DVCSOutputOption(OutputOption
):
98 # name of output format (for error messages); must be set by
103 self
._mirror
= RepositoryMirror()
104 self
._symbolings
_reader
= None
106 def normalize_author_transforms(self
, author_transforms
):
107 """Return a new dict with the same content as author_transforms, but all
108 strings encoded to UTF-8. Also turns None into the empty dict."""
110 if author_transforms
is not None:
111 for (cvsauthor
, (name
, email
,)) in author_transforms
.iteritems():
112 cvsauthor
= to_utf8(cvsauthor
)
114 email
= to_utf8(email
)
115 result
[cvsauthor
] = (name
, email
,)
118 def register_artifacts(self
, which_pass
):
119 # These artifacts are needed for SymbolingsReader:
120 artifact_manager
.register_temp_file_needed(
121 config
.SYMBOL_OPENINGS_CLOSINGS_SORTED
, which_pass
123 artifact_manager
.register_temp_file_needed(
124 config
.SYMBOL_OFFSETS_DB
, which_pass
126 self
._mirror
.register_artifacts(which_pass
)
129 if Ctx().cross_project_commits
:
131 '%s output is not supported with cross-project commits' % self
.name
133 if Ctx().cross_branch_commits
:
135 '%s output is not supported with cross-branch commits' % self
.name
137 if Ctx().username
is None:
139 '%s output requires a default commit username' % self
.name
142 def setup(self
, svn_rev_count
):
143 self
._symbolings
_reader
= SymbolingsReader()
148 self
._symbolings
_reader
.close()
149 del self
._symbolings
_reader
151 def _get_source_groups(self
, svn_commit
):
152 """Return groups of sources for SVN_COMMIT.
154 SVN_COMMIT is an instance of SVNSymbolCommit. Yield tuples
155 (source_lod, svn_revnum, cvs_symbols) where source_lod is the line
156 of development and svn_revnum is the revision that should serve as
157 a source, and cvs_symbols is a list of CVSSymbolItems that can be
158 copied from that source. The groups are returned in arbitrary
161 # Get a map {CVSSymbol : SVNRevisionRange}:
162 range_map
= self
._symbolings
_reader
.get_range_map(svn_commit
)
164 # range_map, split up into one map per LOD; i.e., {LOD :
165 # {CVSSymbol : SVNRevisionRange}}:
168 for (cvs_symbol
, range) in range_map
.iteritems():
169 lod_range_map
= lod_range_maps
.get(range.source_lod
)
170 if lod_range_map
is None:
172 lod_range_maps
[range.source_lod
] = lod_range_map
173 lod_range_map
[cvs_symbol
] = range
175 # Sort the sources so that the branch that serves most often as
176 # parent is processed first:
177 lod_ranges
= lod_range_maps
.items()
179 lambda (lod1
,lod_range_map1
),(lod2
,lod_range_map2
):
180 -cmp(len(lod_range_map1
), len(lod_range_map2
)) or cmp(lod1
, lod2
)
183 for (lod
, lod_range_map
) in lod_ranges
:
185 revision_scores
= RevisionScores(lod_range_map
.values())
186 (source_lod
, revnum
, score
) = revision_scores
.get_best_revnum()
187 assert source_lod
== lod
189 for (cvs_symbol
, range) in lod_range_map
.items():
191 cvs_symbols
.append(cvs_symbol
)
192 del lod_range_map
[cvs_symbol
]
193 yield (lod
, revnum
, cvs_symbols
)
195 def _is_simple_copy(self
, svn_commit
, source_groups
):
196 """Return True iff SVN_COMMIT can be created as a simple copy.
198 SVN_COMMIT is an SVNTagCommit. Return True iff it can be created
199 as a simple copy from an existing revision (i.e., if the fixup
200 branch can be avoided for this tag creation)."""
202 # The first requirement is that there be exactly one source:
203 if len(source_groups
) != 1:
206 (source_lod
, svn_revnum
, cvs_symbols
) = source_groups
[0]
208 # The second requirement is that the destination LOD not already
211 self
._mirror
.get_current_lod_directory(svn_commit
.symbol
)
213 # The LOD doesn't already exist. This is good.
216 # The LOD already exists. It cannot be created by a copy.
219 # The third requirement is that the source LOD contains exactly
220 # the same files as we need to add to the symbol:
222 source_node
= self
._mirror
.get_old_lod_directory(source_lod
, svn_revnum
)
224 raise InternalError('Source %r does not exist' % (source_lod
,))
226 set([cvs_symbol
.cvs_file
for cvs_symbol
in cvs_symbols
])
227 == set(self
._get
_all
_files
(source_node
))
230 def _get_all_files(self
, node
):
231 """Generate all of the CVSFiles under NODE."""
233 for cvs_path
in node
:
234 subnode
= node
[cvs_path
]
238 for sub_cvs_path
in self
._get
_all
_files
(subnode
):
243 if isinstance(s
, unicode):
244 return s
.encode('utf8')