| 1 | # -*- mode: python; coding: utf-8 -*- |
|---|
| 2 | # :Progetto: vcpx -- Syncable targets |
|---|
| 3 | # :Creato: ven 04 giu 2004 00:27:07 CEST |
|---|
| 4 | # :Autore: Lele Gaifax <lele@nautilus.homeip.net> |
|---|
| 5 | # :Licenza: GNU General Public License |
|---|
| 6 | # |
|---|
| 7 | |
|---|
| 8 | """ |
|---|
| 9 | Syncronizable targets are the simplest abstract wrappers around a |
|---|
| 10 | working directory under two different version control systems. |
|---|
| 11 | """ |
|---|
| 12 | |
|---|
| 13 | __docformat__ = 'reStructuredText' |
|---|
| 14 | |
|---|
| 15 | import socket |
|---|
| 16 | from signal import signal, SIGINT, SIG_IGN |
|---|
| 17 | from vcpx import TailorException |
|---|
| 18 | from vcpx.workdir import WorkingDir |
|---|
| 19 | |
|---|
| 20 | |
|---|
| 21 | HOST = socket.getfqdn() |
|---|
| 22 | AUTHOR = "tailor" |
|---|
| 23 | BOOTSTRAP_PATCHNAME = 'Tailorization' |
|---|
| 24 | BOOTSTRAP_CHANGELOG = """\ |
|---|
| 25 | Import of the upstream sources from |
|---|
| 26 | %(source_repository)s |
|---|
| 27 | Revision: %(revision)s |
|---|
| 28 | """ |
|---|
| 29 | |
|---|
| 30 | |
|---|
| 31 | class TargetInitializationFailure(TailorException): |
|---|
| 32 | "Failure initializing the target VCS" |
|---|
| 33 | |
|---|
| 34 | |
|---|
| 35 | class ChangesetReplayFailure(TailorException): |
|---|
| 36 | "Failure replaying the changeset on the target system" |
|---|
| 37 | |
|---|
| 38 | |
|---|
| 39 | class SynchronizableTargetWorkingDir(WorkingDir): |
|---|
| 40 | """ |
|---|
| 41 | This is an abstract working dir usable as a *shadow* of another |
|---|
| 42 | kind of VC, sharing the same working directory. |
|---|
| 43 | |
|---|
| 44 | Most interesting entry points are: |
|---|
| 45 | |
|---|
| 46 | replayChangeset |
|---|
| 47 | to replay an already applied changeset, to mimic the actions |
|---|
| 48 | performed by the upstream VC system on the tree such as |
|---|
| 49 | renames, deletions and adds. This is an useful argument to |
|---|
| 50 | feed as ``replay`` to ``applyUpstreamChangesets`` |
|---|
| 51 | |
|---|
| 52 | importFirstRevision |
|---|
| 53 | to initialize a pristine working directory tree under this VC |
|---|
| 54 | system, possibly extracted under a different kind of VC |
|---|
| 55 | |
|---|
| 56 | Subclasses MUST override at least the _underscoredMethods. |
|---|
| 57 | """ |
|---|
| 58 | |
|---|
| 59 | PATCH_NAME_FORMAT = '[%(project)s @ %(revision)s]' |
|---|
| 60 | """ |
|---|
| 61 | The format string used to compute the patch name, used by underlying VCS. |
|---|
| 62 | """ |
|---|
| 63 | |
|---|
| 64 | REMOVE_FIRST_LOG_LINE = False |
|---|
| 65 | """ |
|---|
| 66 | When true, remove the first line from the upstream changelog. |
|---|
| 67 | """ |
|---|
| 68 | |
|---|
| 69 | def __getPatchNameAndLog(self, changeset): |
|---|
| 70 | """ |
|---|
| 71 | Return a tuple (patchname, changelog) interpolating changeset's |
|---|
| 72 | information with the template above. |
|---|
| 73 | """ |
|---|
| 74 | |
|---|
| 75 | if changeset.log == '': |
|---|
| 76 | firstlogline = 'Empty log message' |
|---|
| 77 | remaininglog = '' |
|---|
| 78 | else: |
|---|
| 79 | loglines = changeset.log.split('\n') |
|---|
| 80 | if len(loglines)>1: |
|---|
| 81 | firstlogline = loglines[0] |
|---|
| 82 | remaininglog = '\n'.join(loglines[1:]) |
|---|
| 83 | else: |
|---|
| 84 | firstlogline = changeset.log |
|---|
| 85 | remaininglog = '' |
|---|
| 86 | |
|---|
| 87 | patchname = self.PATCH_NAME_FORMAT % { |
|---|
| 88 | 'project': self.repository.projectref().name, |
|---|
| 89 | 'revision': changeset.revision, |
|---|
| 90 | 'author': changeset.author, |
|---|
| 91 | 'date': changeset.date, |
|---|
| 92 | 'firstlogline': firstlogline, |
|---|
| 93 | 'remaininglog': remaininglog} |
|---|
| 94 | if self.REMOVE_FIRST_LOG_LINE: |
|---|
| 95 | changelog = remaininglog |
|---|
| 96 | else: |
|---|
| 97 | changelog = changeset.log |
|---|
| 98 | return patchname, changelog |
|---|
| 99 | |
|---|
| 100 | def replayChangeset(self, changeset): |
|---|
| 101 | """ |
|---|
| 102 | Do whatever is needed to replay the changes under the target |
|---|
| 103 | VC, to register the already applied (under the other VC) |
|---|
| 104 | changeset. |
|---|
| 105 | """ |
|---|
| 106 | |
|---|
| 107 | try: |
|---|
| 108 | changeset = self._adaptChangeset(changeset) |
|---|
| 109 | except: |
|---|
| 110 | self.log.exception("Failure adapting: %s", str(changeset)) |
|---|
| 111 | raise |
|---|
| 112 | |
|---|
| 113 | if changeset is None: |
|---|
| 114 | return |
|---|
| 115 | |
|---|
| 116 | try: |
|---|
| 117 | self._replayChangeset(changeset) |
|---|
| 118 | except: |
|---|
| 119 | self.log.exception("Failure replaying: %s", str(changeset)) |
|---|
| 120 | raise |
|---|
| 121 | patchname, log = self.__getPatchNameAndLog(changeset) |
|---|
| 122 | entries = self._getCommitEntries(changeset) |
|---|
| 123 | previous = signal(SIGINT, SIG_IGN) |
|---|
| 124 | try: |
|---|
| 125 | self._commit(changeset.date, changeset.author, patchname, log, |
|---|
| 126 | entries) |
|---|
| 127 | if changeset.tags: |
|---|
| 128 | for tag in changeset.tags: |
|---|
| 129 | self._tag(tag) |
|---|
| 130 | finally: |
|---|
| 131 | signal(SIGINT, previous) |
|---|
| 132 | |
|---|
| 133 | try: |
|---|
| 134 | self._dismissChangeset(changeset) |
|---|
| 135 | except: |
|---|
| 136 | self.log.exception("Failure dismissing: %s", str(changeset)) |
|---|
| 137 | raise |
|---|
| 138 | |
|---|
| 139 | def __getPrefixToSource(self): |
|---|
| 140 | """ |
|---|
| 141 | Compute and return the "offset" between source and target basedirs, |
|---|
| 142 | or None when not using shared directories, or there's no offset. |
|---|
| 143 | """ |
|---|
| 144 | |
|---|
| 145 | project = self.repository.projectref() |
|---|
| 146 | ssubdir = project.source.subdir |
|---|
| 147 | tsubdir = project.target.subdir |
|---|
| 148 | if self.shared_basedirs and ssubdir <> tsubdir: |
|---|
| 149 | if tsubdir == '.': |
|---|
| 150 | prefix = ssubdir |
|---|
| 151 | else: |
|---|
| 152 | if not tsubdir.endswith('/'): |
|---|
| 153 | tsubdir += '/' |
|---|
| 154 | prefix = ssubdir[len(tsubdir):] |
|---|
| 155 | return prefix |
|---|
| 156 | else: |
|---|
| 157 | return None |
|---|
| 158 | |
|---|
| 159 | def _normalizeEntryPaths(self, entry): |
|---|
| 160 | """ |
|---|
| 161 | Normalize the name and old_name of an entry. |
|---|
| 162 | |
|---|
| 163 | The ``name`` and ``old_name`` of an entry are pathnames coming |
|---|
| 164 | from the upstream system, and is usually (although there is no |
|---|
| 165 | guarantee it actually is) a UNIX style path with forward |
|---|
| 166 | slashes "/" as separators. |
|---|
| 167 | |
|---|
| 168 | This implementation uses normpath to adapt the path to the |
|---|
| 169 | actual OS convention, but subclasses may eventually override |
|---|
| 170 | this to use their own canonicalization of ``name`` and |
|---|
| 171 | ``old_name``. |
|---|
| 172 | """ |
|---|
| 173 | |
|---|
| 174 | from os.path import normpath |
|---|
| 175 | |
|---|
| 176 | entry.name = normpath(entry.name) |
|---|
| 177 | if entry.old_name: |
|---|
| 178 | entry.old_name = normpath(entry.old_name) |
|---|
| 179 | |
|---|
| 180 | def __adaptEntriesPath(self, changeset): |
|---|
| 181 | """ |
|---|
| 182 | If the source basedir is a subdirectory of the target, adjust |
|---|
| 183 | all the pathnames adding the prefix computed by difference. |
|---|
| 184 | """ |
|---|
| 185 | |
|---|
| 186 | from copy import deepcopy |
|---|
| 187 | from os.path import join |
|---|
| 188 | |
|---|
| 189 | if not changeset.entries: |
|---|
| 190 | return changeset |
|---|
| 191 | |
|---|
| 192 | prefix = self.__getPrefixToSource() |
|---|
| 193 | adapted = deepcopy(changeset) |
|---|
| 194 | for e in adapted.entries: |
|---|
| 195 | if prefix: |
|---|
| 196 | e.name = join(prefix, e.name) |
|---|
| 197 | if e.old_name: |
|---|
| 198 | e.old_name = join(prefix, e.old_name) |
|---|
| 199 | self._normalizeEntryPaths(e) |
|---|
| 200 | return adapted |
|---|
| 201 | |
|---|
| 202 | def _adaptEntries(self, changeset): |
|---|
| 203 | """ |
|---|
| 204 | Do whatever is needed to adapt entries to the target system. |
|---|
| 205 | |
|---|
| 206 | This implementation adds a prefix to each path if needed, when |
|---|
| 207 | the target basedir *contains* the source basedir. Also, each |
|---|
| 208 | path is normalized thru ``normpath()`` or whatever equivalent |
|---|
| 209 | operation provided by the specific target. It operates on and |
|---|
| 210 | returns a copy of the given changeset. |
|---|
| 211 | |
|---|
| 212 | Subclasses shall eventually extend this to exclude unwanted |
|---|
| 213 | entries, eventually returning None when all entries were |
|---|
| 214 | excluded, to avoid the commit on target of an empty changeset. |
|---|
| 215 | """ |
|---|
| 216 | |
|---|
| 217 | adapted = self.__adaptEntriesPath(changeset) |
|---|
| 218 | return adapted |
|---|
| 219 | |
|---|
| 220 | def _adaptChangeset(self, changeset): |
|---|
| 221 | """ |
|---|
| 222 | Do whatever needed before replay and return the adapted changeset. |
|---|
| 223 | |
|---|
| 224 | This implementation calls ``self._adaptEntries()``, then |
|---|
| 225 | executes the adapters defined by before-commit on the project: |
|---|
| 226 | each adapter is run in turn, and may return False to indicate |
|---|
| 227 | that the changeset shouldn't be replayed at all. They are |
|---|
| 228 | otherwise free to alter the changeset in any meaningful way. |
|---|
| 229 | """ |
|---|
| 230 | |
|---|
| 231 | from copy import copy |
|---|
| 232 | |
|---|
| 233 | adapted = self._adaptEntries(changeset) |
|---|
| 234 | if adapted: |
|---|
| 235 | project = self.repository.projectref() |
|---|
| 236 | if project.before_commit: |
|---|
| 237 | adapted = copy(adapted) |
|---|
| 238 | |
|---|
| 239 | for adapter in project.before_commit: |
|---|
| 240 | if not adapter(self, adapted): |
|---|
| 241 | return None |
|---|
| 242 | return adapted |
|---|
| 243 | |
|---|
| 244 | def _dismissChangeset(self, changeset): |
|---|
| 245 | """ |
|---|
| 246 | Do whatever needed after commit. |
|---|
| 247 | |
|---|
| 248 | This execute the adapters defined by after-commit on the project, |
|---|
| 249 | for example tagging in some way the target repository upon some |
|---|
| 250 | particular kind of changeset. |
|---|
| 251 | """ |
|---|
| 252 | |
|---|
| 253 | project = self.repository.projectref() |
|---|
| 254 | if project.after_commit: |
|---|
| 255 | for farewell in project.after_commit: |
|---|
| 256 | farewell(self, changeset) |
|---|
| 257 | |
|---|
| 258 | def _getCommitEntries(self, changeset): |
|---|
| 259 | """ |
|---|
| 260 | Extract the names of the entries for the commit phase. |
|---|
| 261 | """ |
|---|
| 262 | |
|---|
| 263 | # Since the commit may use cli tools to do its job, and the |
|---|
| 264 | # machinery may split the list into smaller chunks to avoid |
|---|
| 265 | # too long command lines, anticipates added stuff. I think |
|---|
| 266 | # this is needed only when coming from CVS (or HG or in |
|---|
| 267 | # general from systems that don't handle directories): its |
|---|
| 268 | # _applyChangeset *appends* to the entries a fake ADD for |
|---|
| 269 | # each new subdir. |
|---|
| 270 | |
|---|
| 271 | entries = [] |
|---|
| 272 | added = 0 |
|---|
| 273 | for e in changeset.entries: |
|---|
| 274 | if e.action_kind == e.ADDED: |
|---|
| 275 | entries.insert(added, e.name) |
|---|
| 276 | added += 1 |
|---|
| 277 | else: |
|---|
| 278 | # Add also the name of the old file: for some systems |
|---|
| 279 | # it may not be strictly needed, but it is for most. |
|---|
| 280 | if e.action_kind == e.RENAMED: |
|---|
| 281 | entries.append(e.old_name) |
|---|
| 282 | entries.append(e.name) |
|---|
| 283 | return entries |
|---|
| 284 | |
|---|
| 285 | def _replayChangeset(self, changeset): |
|---|
| 286 | """ |
|---|
| 287 | Replicate the actions performed by the changeset on the tree of |
|---|
| 288 | files. |
|---|
| 289 | """ |
|---|
| 290 | |
|---|
| 291 | from os.path import join, isdir |
|---|
| 292 | from changes import ChangesetEntry |
|---|
| 293 | |
|---|
| 294 | added = [] |
|---|
| 295 | actions = { ChangesetEntry.ADDED: self._addEntries, |
|---|
| 296 | ChangesetEntry.DELETED: self._removeEntries, |
|---|
| 297 | ChangesetEntry.RENAMED: self._renameEntries, |
|---|
| 298 | ChangesetEntry.UPDATED: self._editEntries |
|---|
| 299 | } |
|---|
| 300 | |
|---|
| 301 | # Group the changes by kind and perform the corresponding action |
|---|
| 302 | |
|---|
| 303 | last = None |
|---|
| 304 | group = [] |
|---|
| 305 | for e in changeset.entries: |
|---|
| 306 | if last is None or last.action_kind == e.action_kind: |
|---|
| 307 | last = e |
|---|
| 308 | group.append(e) |
|---|
| 309 | if last.action_kind != e.action_kind: |
|---|
| 310 | action = actions.get(last.action_kind) |
|---|
| 311 | if action is not None: |
|---|
| 312 | action(group) |
|---|
| 313 | group = [e] |
|---|
| 314 | last = e |
|---|
| 315 | if e.action_kind == e.ADDED: |
|---|
| 316 | added.append(e) |
|---|
| 317 | if group: |
|---|
| 318 | action = actions.get(group[0].action_kind) |
|---|
| 319 | if action is not None: |
|---|
| 320 | action(group) |
|---|
| 321 | |
|---|
| 322 | # Finally, deal with "copied" directories. The simple way is |
|---|
| 323 | # executing an _addSubtree on each of them, evenif this may |
|---|
| 324 | # cause "warnings" on items just moved/added above... |
|---|
| 325 | |
|---|
| 326 | while added: |
|---|
| 327 | subdir = added.pop(0).name |
|---|
| 328 | if isdir(join(self.repository.basedir, subdir)): |
|---|
| 329 | self._addSubtree(subdir) |
|---|
| 330 | added = [e for e in added if not e.name.startswith(subdir)] |
|---|
| 331 | |
|---|
| 332 | def _addEntries(self, entries): |
|---|
| 333 | """ |
|---|
| 334 | Add a sequence of entries |
|---|
| 335 | """ |
|---|
| 336 | |
|---|
| 337 | self._addPathnames([e.name for e in entries]) |
|---|
| 338 | |
|---|
| 339 | def _addPathnames(self, names): |
|---|
| 340 | """ |
|---|
| 341 | Add some new filesystem objects. |
|---|
| 342 | """ |
|---|
| 343 | |
|---|
| 344 | raise "%s should override this method" % self.__class__ |
|---|
| 345 | |
|---|
| 346 | def _addSubtree(self, subdir): |
|---|
| 347 | """ |
|---|
| 348 | Add a whole subtree. |
|---|
| 349 | |
|---|
| 350 | This implementation crawl down the whole subtree, adding |
|---|
| 351 | entries (subdirs, skipping the usual VC-specific control |
|---|
| 352 | directories such as ``.svn``, ``_darcs`` or ``CVS``, and |
|---|
| 353 | files). |
|---|
| 354 | |
|---|
| 355 | Subclasses may use a better way, if the backend implements |
|---|
| 356 | a recursive add that skips the various metadata directories. |
|---|
| 357 | """ |
|---|
| 358 | |
|---|
| 359 | from os.path import join |
|---|
| 360 | from os import walk |
|---|
| 361 | from dualwd import IGNORED_METADIRS |
|---|
| 362 | |
|---|
| 363 | exclude = [] |
|---|
| 364 | |
|---|
| 365 | if self.state_file.filename.startswith(self.repository.basedir): |
|---|
| 366 | sfrelname = self.state_file.filename[len(self.repository.basedir)+1:] |
|---|
| 367 | exclude.append(sfrelname) |
|---|
| 368 | exclude.append(sfrelname+'.old') |
|---|
| 369 | exclude.append(sfrelname+'.journal') |
|---|
| 370 | |
|---|
| 371 | if self.logfile.startswith(self.repository.basedir): |
|---|
| 372 | exclude.append(self.logfile[len(self.repository.basedir)+1:]) |
|---|
| 373 | |
|---|
| 374 | if subdir and subdir<>'.': |
|---|
| 375 | self._addPathnames([subdir]) |
|---|
| 376 | |
|---|
| 377 | for dir, subdirs, files in walk(join(self.repository.basedir, subdir)): |
|---|
| 378 | for excd in IGNORED_METADIRS: |
|---|
| 379 | if excd in subdirs: |
|---|
| 380 | subdirs.remove(excd) |
|---|
| 381 | |
|---|
| 382 | for excf in exclude: |
|---|
| 383 | if excf in files: |
|---|
| 384 | files.remove(excf) |
|---|
| 385 | |
|---|
| 386 | if subdirs or files: |
|---|
| 387 | self._addPathnames([join(dir, df)[len(self.repository.basedir)+1:] |
|---|
| 388 | for df in subdirs + files]) |
|---|
| 389 | |
|---|
| 390 | def _commit(self, date, author, patchname, changelog=None, entries=None): |
|---|
| 391 | """ |
|---|
| 392 | Commit the changeset. |
|---|
| 393 | """ |
|---|
| 394 | |
|---|
| 395 | raise "%s should override this method" % self.__class__ |
|---|
| 396 | |
|---|
| 397 | def _removeEntries(self, entries): |
|---|
| 398 | """ |
|---|
| 399 | Remove a sequence of entries. |
|---|
| 400 | """ |
|---|
| 401 | |
|---|
| 402 | self._removePathnames([e.name for e in entries]) |
|---|
| 403 | |
|---|
| 404 | def _removePathnames(self, names): |
|---|
| 405 | """ |
|---|
| 406 | Remove some filesystem object. |
|---|
| 407 | """ |
|---|
| 408 | |
|---|
| 409 | raise "%s should override this method" % self.__class__ |
|---|
| 410 | |
|---|
| 411 | def _editEntries(self, entries): |
|---|
| 412 | """ |
|---|
| 413 | Records a sequence of entries as updated. |
|---|
| 414 | """ |
|---|
| 415 | |
|---|
| 416 | self._editPathnames([e.name for e in entries]) |
|---|
| 417 | |
|---|
| 418 | def _editPathnames(self, names): |
|---|
| 419 | """ |
|---|
| 420 | Records a sequence of filesystem objects as updated. |
|---|
| 421 | """ |
|---|
| 422 | |
|---|
| 423 | pass |
|---|
| 424 | |
|---|
| 425 | def _renameEntries(self, entries): |
|---|
| 426 | """ |
|---|
| 427 | Rename a sequence of entries, adding all the parent directories |
|---|
| 428 | of each entry. |
|---|
| 429 | """ |
|---|
| 430 | |
|---|
| 431 | from os import rename |
|---|
| 432 | from os.path import split, join, exists |
|---|
| 433 | |
|---|
| 434 | added = [] |
|---|
| 435 | for e in entries: |
|---|
| 436 | parents = [] |
|---|
| 437 | parent = split(e.name)[0] |
|---|
| 438 | while parent: |
|---|
| 439 | if not parent in added: |
|---|
| 440 | parents.append(parent) |
|---|
| 441 | added.append(parent) |
|---|
| 442 | parent = split(parent)[0] |
|---|
| 443 | if parents: |
|---|
| 444 | parents.reverse() |
|---|
| 445 | self._addPathnames(parents) |
|---|
| 446 | |
|---|
| 447 | if self.shared_basedirs: |
|---|
| 448 | # Check to see if the oldentry is still there. If it is, |
|---|
| 449 | # that probably means one thing: it's been moved and then |
|---|
| 450 | # replaced, see svn 'R' event. In this case, rename the |
|---|
| 451 | # existing old entry to something else to trick targets |
|---|
| 452 | # (that will assume the move was already done manually) and |
|---|
| 453 | # finally restore its name. |
|---|
| 454 | |
|---|
| 455 | absold = join(self.repository.basedir, e.old_name) |
|---|
| 456 | renamed = exists(absold) |
|---|
| 457 | if renamed: |
|---|
| 458 | rename(absold, absold + '-TAILOR-HACKED-TEMP-NAME') |
|---|
| 459 | else: |
|---|
| 460 | # With disjunct directories, old entries are *always* |
|---|
| 461 | # there because we dropped the --delete option to rsync. |
|---|
| 462 | # So, instead of renaming the old entry, we temporarily |
|---|
| 463 | # rename the new one, perform the target system rename |
|---|
| 464 | # and replace back the real content (it may be a |
|---|
| 465 | # renamed+edited event). |
|---|
| 466 | renamed = False |
|---|
| 467 | absnew = join(self.repository.basedir, e.name) |
|---|
| 468 | renamed = exists(absnew) |
|---|
| 469 | if renamed: |
|---|
| 470 | rename(absnew, absnew + '-TAILOR-HACKED-TEMP-NAME') |
|---|
| 471 | |
|---|
| 472 | try: |
|---|
| 473 | self._renamePathname(e.old_name, e.name) |
|---|
| 474 | finally: |
|---|
| 475 | if renamed: |
|---|
| 476 | if self.shared_basedirs: |
|---|
| 477 | rename(absold + '-TAILOR-HACKED-TEMP-NAME', absold) |
|---|
| 478 | else: |
|---|
| 479 | rename(absnew + '-TAILOR-HACKED-TEMP-NAME', absnew) |
|---|
| 480 | |
|---|
| 481 | def _renamePathname(self, oldname, newname): |
|---|
| 482 | """ |
|---|
| 483 | Rename a filesystem object to some other name/location. |
|---|
| 484 | """ |
|---|
| 485 | |
|---|
| 486 | raise "%s should override this method" % self.__class__ |
|---|
| 487 | |
|---|
| 488 | def prepareWorkingDirectory(self, source_repo): |
|---|
| 489 | """ |
|---|
| 490 | Do anything required to setup the hosting working directory. |
|---|
| 491 | """ |
|---|
| 492 | |
|---|
| 493 | self._prepareWorkingDirectory(source_repo) |
|---|
| 494 | |
|---|
| 495 | def _prepareWorkingDirectory(self, source_repo): |
|---|
| 496 | """ |
|---|
| 497 | Possibly checkout a working copy of the target VC, that will host the |
|---|
| 498 | upstream source tree, when overriden by subclasses. |
|---|
| 499 | """ |
|---|
| 500 | |
|---|
| 501 | def prepareTargetRepository(self): |
|---|
| 502 | """ |
|---|
| 503 | Do anything required to host the target repository. |
|---|
| 504 | """ |
|---|
| 505 | |
|---|
| 506 | from os import makedirs |
|---|
| 507 | from os.path import join, exists |
|---|
| 508 | |
|---|
| 509 | if not exists(self.repository.basedir): |
|---|
| 510 | makedirs(self.repository.basedir) |
|---|
| 511 | |
|---|
| 512 | self._prepareTargetRepository() |
|---|
| 513 | |
|---|
| 514 | prefix = self.__getPrefixToSource() |
|---|
| 515 | if prefix: |
|---|
| 516 | if not exists(join(self.repository.basedir, prefix)): |
|---|
| 517 | # At bootstrap time, we assume that if the user |
|---|
| 518 | # extracted the source manually, she added |
|---|
| 519 | # the subdir, before doing that. |
|---|
| 520 | makedirs(join(self.repository.basedir, prefix)) |
|---|
| 521 | self._addPathnames([prefix]) |
|---|
| 522 | |
|---|
| 523 | def _prepareTargetRepository(self): |
|---|
| 524 | """ |
|---|
| 525 | Possibly create or connect to the repository, when overriden |
|---|
| 526 | by subclasses. |
|---|
| 527 | """ |
|---|
| 528 | |
|---|
| 529 | def importFirstRevision(self, source_repo, changeset, initial): |
|---|
| 530 | """ |
|---|
| 531 | Initialize a new working directory, just extracted from |
|---|
| 532 | some other VC system, importing everything's there. |
|---|
| 533 | """ |
|---|
| 534 | |
|---|
| 535 | self._initializeWorkingDir() |
|---|
| 536 | # Execute the precommit hooks, but ignore None results |
|---|
| 537 | changeset = self._adaptChangeset(changeset) or changeset |
|---|
| 538 | revision = changeset.revision |
|---|
| 539 | source_repository = str(source_repo) |
|---|
| 540 | if initial: |
|---|
| 541 | author = changeset.author |
|---|
| 542 | patchname, log = self.__getPatchNameAndLog(changeset) |
|---|
| 543 | else: |
|---|
| 544 | author = "%s@%s" % (AUTHOR, HOST) |
|---|
| 545 | patchname = BOOTSTRAP_PATCHNAME |
|---|
| 546 | log = BOOTSTRAP_CHANGELOG % locals() |
|---|
| 547 | self._commit(changeset.date, author, patchname, log) |
|---|
| 548 | |
|---|
| 549 | if changeset.tags: |
|---|
| 550 | for tag in changeset.tags: |
|---|
| 551 | self._tag(tag) |
|---|
| 552 | |
|---|
| 553 | self._dismissChangeset(changeset) |
|---|
| 554 | |
|---|
| 555 | def _initializeWorkingDir(self): |
|---|
| 556 | """ |
|---|
| 557 | Assuming the ``basedir`` directory contains a working copy ``module`` |
|---|
| 558 | extracted from some VC repository, add it and all its content |
|---|
| 559 | to the target repository. |
|---|
| 560 | |
|---|
| 561 | This implementation recursively add every file in the subtree. |
|---|
| 562 | Subclasses should override this method doing whatever is |
|---|
| 563 | appropriate for the backend. |
|---|
| 564 | """ |
|---|
| 565 | |
|---|
| 566 | self._addSubtree('.') |
|---|
| 567 | |
|---|
| 568 | def _tag(self, tagname): |
|---|
| 569 | """ |
|---|
| 570 | Tag the current version, if the VC type supports it, otherwise |
|---|
| 571 | do nothing. |
|---|
| 572 | """ |
|---|
| 573 | pass |
|---|