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