source: tailor/vcpx/repository/svn.py @ 1391

Revision 1391, 31.5 KB checked in by henry.ne@…, 6 years ago (diff)

svn-target-branches.patch
Subversion: Supports branches as target.

Add support for 'branches' on subversion targets. Modules, that starts
with 'branches/...' will be export under "branches".
Add 'svn-branches' to config, can change the path.

Hint: The 'trunk' is non special in tailor, simple set 'trunk' as module.

Full example from Monotone source to Subversion:

[DEFAULT]
start-revision = INITIAL
root-directory = /tmp/rootdir-Monotone
source-repository = $HOME/Monotone-database-file/monotone.mtn
target-repository =  file:///tmp/rootdir-Monotone/svn-repository-monotone
use-propset = True

# Projects
[net.venge.monotone.cvssync]
source = monotone:net.venge.monotone.cvssync
target = svn:net.venge.monotone.cvssync

[net.venge.monotone.de]
source = monotone:net.venge.monotone.de
target = svn:net.venge.monotone.de

[net.venge.monotone.svn_import]
source = monotone:net.venge.monotone.svn_import
target = svn:net.venge.monotone.svn_import

[net.venge.monotone]
source = monotone:net.venge.monotone
target = svn:net.venge.monotone

# Sources
[monotone:net.venge.monotone.cvssync]
module = net.venge.monotone.cvssync
subdir = mtnside-net.venge.monotone.cvssync

[monotone:net.venge.monotone.de]
module = net.venge.monotone.de
subdir = mtnside-net.venge.monotone.de

[monotone:net.venge.monotone.svn_import]
module = net.venge.monotone.svn_import
subdir = mtnside-net.venge.monotone.svn_import

[monotone:net.venge.monotone]
module = net.venge.monotone
subdir = mtnside-net.venge.monotone

# Targets
[svn:net.venge.monotone.cvssync]
module = branches/net.venge.monotone.cvssync
subdir = svnside-net.venge.monotone.cvssync

[svn:net.venge.monotone.de]
module = branches/net.venge.monotone.de
subdir = svnside-net.venge.monotone.de

[svn:net.venge.monotone.svn_import]
module = branches/net.venge.monotone.svn_import
subdir = svnside-net.venge.monotone.svn_import

[svn:net.venge.monotone]
module = trunk
subdir = svnside-net.venge.monotone

RevLine 
[12]1# -*- mode: python; coding: utf-8 -*-
2# :Progetto: vcpx -- Subversion details
3# :Creato:   ven 18 giu 2004 15:00:52 CEST
4# :Autore:   Lele Gaifax <lele@nautilus.homeip.net>
[305]5# :Licenza:  GNU General Public License
[12]6#
7
[18]8"""
9This module contains supporting classes for Subversion.
10"""
11
[12]12__docformat__ = 'reStructuredText'
13
[1179]14from vcpx.repository import Repository
15from vcpx.shwrap import ExternalCommand, PIPE, ReopenableNamedTemporaryFile
16from vcpx.source import UpdatableSourceWorkingDir, ChangesetApplicationFailure
17from vcpx.target import SynchronizableTargetWorkingDir, TargetInitializationFailure
18from vcpx.config import ConfigurationError
[1216]19from vcpx.tzinfo import UTC
[1179]20
21
22class SvnRepository(Repository):
23    METADIR = '.svn'
24
25    def command(self, *args, **kwargs):
26        if kwargs.get('svnadmin', False):
27            kwargs['executable'] = self.__svnadmin
28        return Repository.command(self, *args, **kwargs)
29
30    def _load(self, project):
31        Repository._load(self, project)
32        cget = project.config.get
33        self.EXECUTABLE = cget(self.name, 'svn-command', 'svn')
34        self.__svnadmin = cget(self.name, 'svnadmin-command', 'svnadmin')
35        self.use_propset = cget(self.name, 'use-propset', False)
[1273]36        self.propset_date = cget(self.name, 'propset-date', True)
[1179]37        self.filter_badchars = cget(self.name, 'filter-badchars', False)
38        self.use_limit = cget(self.name, 'use-limit', True)
39        self.trust_root = cget(self.name, 'trust-root', False)
40        self.ignore_externals = cget(self.name, 'ignore-externals', True)
[1388]41        self.commit_all_files = cget(self.name, 'commit-all-files', True)
[1389]42        self.tags_path = cget(self.name, 'svn-tags', '/tags')
[1391]43        self.branches_path = cget(self.name, 'svn-branches', '/branches')
[1389]44        self._setupTagsDirectory = None
45
46    def setupTagsDirectory(self):
47        if self._setupTagsDirectory == None:
48            self._setupTagsDirectory = False
49            if self.module and self.module <> '/':
50
51                # Check the existing tags directory
52                cmd = self.command("ls")
53                svnls = ExternalCommand(command=cmd)
54                svnls.execute(self.repository + self.tags_path)
55                if svnls.exit_status:
56                    # create it, if not exist
57                    cmd = self.command("mkdir", "-m",
58                                       "This directory will host the tags")
59                    svnmkdir = ExternalCommand(command=cmd)
60                    svnmkdir.execute(self.repository + self.tags_path)
61                    if svnmkdir.exit_status:
62                        raise TargetInitializationFailure(
63                                    "Was not able to create tags directory '%s'"
64                                    % self.tags_path)
65                else:
66                    self.log.debug("Directory '%s' already exists"
67                                   % self.tags_path)
68                self._setupTagsDirectory = True
69            else:
70                self.log.debug("Tags needs module setup other than '/'")
71
72        return self._setupTagsDirectory
73
[1179]74
75    def _validateConfiguration(self):
76        from vcpx.config import ConfigurationError
77
78        Repository._validateConfiguration(self)
79
80        if not self.repository:
81            self.log.critical('Missing repository information in %r', self.name)
82            raise ConfigurationError("Must specify the root of the "
83                                     "Subversion repository used "
84                                     "as %s with the option "
85                                     "'repository'" % self.which)
86        elif self.repository.endswith('/'):
87            self.log.debug("Removing final slash from %r in %r",
88                           self.repository, self.name)
89            self.repository = self.repository.rstrip('/')
90
91        if not self.module:
92            self.log.critical('Missing module information in %r', self.name)
93            raise ConfigurationError("Must specify the path within the "
94                                     "Subversion repository as 'module'")
95
96        if self.module == '.':
97            self.log.warning("Replacing '.' with '/' in module name in %r",
98                             self.name)
99            self.module = '/'
100        elif not self.module.startswith('/'):
101            self.log.debug("Prepending '/' to module %r in %r",
102                           self.module, self.name)
103            self.module = '/' + self.module
104
[1389]105        if not self.tags_path.startswith('/'):
106            self.log.debug("Prepending '/' to svn-tags %r in %r",
107                           self.tags_path, self.name)
108            self.tags_path = '/' + self.tags_path
109
[1391]110        if not self.branches_path.startswith('/'):
111            self.log.debug("Prepending '/' to svn-branches %r in %r",
112                           self.branches_path, self.name)
113            self.branches_path = '/' + self.branches_path
114
[1215]115    def create(self):
116        """
[1360]117        Create a local SVN repository, if it does not exist, and configure it.
[1215]118        """
119
[1360]120        from os.path import join, exists
[1215]121        from sys import platform
122
123        # Verify the existence of repository by listing its root
124        cmd = self.command("ls")
125        svnls = ExternalCommand(command=cmd)
126        svnls.execute(self.repository)
127
[1360]128        # Create it if it isn't a valid repository
129        if svnls.exit_status:
130            if not self.repository.startswith('file:///'):
131                raise TargetInitializationFailure("%r does not exist and "
132                                                  "cannot be created since "
133                                                  "it's not a local (file:///) "
134                                                  "repository" %
135                                                  self.repository)
[1215]136
[1360]137            repodir = self.repository[7:]
138            cmd = self.command("create", "--fs-type", "fsfs", svnadmin=True)
139            svnadmin = ExternalCommand(command=cmd)
140            svnadmin.execute(repodir)
[1215]141
[1360]142            if svnadmin.exit_status:
143                raise TargetInitializationFailure("Was not able to create a 'fsfs' "
144                                                  "svn repository at %r" %
145                                                  self.repository)
[1215]146        if self.use_propset:
[1366]147            if not self.repository.startswith('file:///'):
148                self.log.warning("Repository is remote, cannot verify if it "
149                                 "has the 'pre-revprop-change' hook active, needed "
150                                 "by 'use-propset=True'. Assuming it does...")
151            else:
152                repodir = self.repository[7:]
153                hookname = join(repodir, 'hooks', 'pre-revprop-change')
154                if platform == 'win32':
155                    hookname += '.bat'
156                if not exists(hookname):
157                    prehook = open(hookname, 'w')
158                    if platform <> 'win32':
159                        prehook.write('#!/bin/sh\n')
160                    prehook.write('exit 0\n')
161                    prehook.close()
162                    if platform <> 'win32':
163                        from os import chmod
164                        chmod(hookname, 0755)
[1179]165
[1215]166        if self.module and self.module <> '/':
[1360]167            cmd = self.command("ls")
168            svnls = ExternalCommand(command=cmd)
169            svnls.execute(self.repository + self.module)
170            if svnls.exit_status:
[1391]171
172                paths = []
173
174                # Auto detect missing "branches/"
175                if self.module.startswith(self.branches_path + '/'):
176                    path = self.repository + self.branches_path
177                    cmd = self.command("ls")
178                    svnls = ExternalCommand(command=cmd)
179                    svnls.execute(path)
180                    if svnls.exit_status:
181                        paths.append(path)
182
183                paths.append(self.repository + self.module)
[1360]184                cmd = self.command("mkdir", "-m",
185                                   "This directory will host the upstream sources")
186                svnmkdir = ExternalCommand(command=cmd)
[1391]187                svnmkdir.execute(paths)
[1360]188                if svnmkdir.exit_status:
189                    raise TargetInitializationFailure("Was not able to create the "
190                                                      "module %r, maybe more than "
191                                                      "one level directory?" %
192                                                      self.module)
[12]193
[1120]194def changesets_from_svnlog(log, repository, chunksize=2**15):
[933]195    from xml.sax import make_parser
196    from xml.sax.handler import ContentHandler, ErrorHandler
[165]197    from datetime import datetime
[1179]198    from vcpx.changes import ChangesetEntry, Changeset
[165]199
[1120]200    def get_entry_from_path(path, module=repository.module):
[165]201        # Given the repository url of this wc, say
202        #   "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
203        # extract the "entry" portion (a relative path) from what
204        # svn log --xml says, ie
205        #   "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
206        # that is to say "tests/PloneTestCase.py"
207
[1346]208        if not module.endswith('/'):
209            module = module + '/'
[243]210        if path.startswith(module):
211            relative = path[len(module):]
[1346]212            return relative
[444]213
[168]214        # The path is outside our tracked tree...
[1120]215        repository.log.warning('Ignoring %r since it is not under %r',
216                               path, module)
[168]217        return None
[444]218
[165]219    class SvnXMLLogHandler(ContentHandler):
[237]220        # Map between svn action and tailor's.
221        # NB: 'R', in svn parlance, means REPLACED, something other
222        # system may view as a simpler ADD, taking the following as
223        # the most common idiom::
224        #
225        #   # Rename the old file with a better name
226        #   $ svn mv somefile nicer-name-scheme.py
227        #
228        #   # Be nice with lazy users
229        #   $ echo "exec nicer-name-scheme.py" > somefile
230        #
231        #   # Add the wrapper with the old name
232        #   $ svn add somefile
233        #
234        #   $ svn commit -m "Longer name for somefile"
235
236        ACTIONSMAP = {'R': 'R', # will be ChangesetEntry.ADDED
237                      'M': ChangesetEntry.UPDATED,
238                      'A': ChangesetEntry.ADDED,
239                      'D': ChangesetEntry.DELETED}
[444]240
[165]241        def __init__(self):
242            self.changesets = []
243            self.current = None
244            self.current_field = []
[168]245            self.renamed = {}
[1048]246            self.copies = []
[444]247
[165]248        def startElement(self, name, attributes):
249            if name == 'logentry':
250                self.current = {}
251                self.current['revision'] = attributes['revision']
252                self.current['entries'] = []
[1048]253                self.copies = []
[165]254            elif name in ['author', 'date', 'msg']:
255                self.current_field = []
256            elif name == 'path':
257                self.current_field = []
258                if attributes.has_key('copyfrom-path'):
259                    self.current_path_action = (
260                        attributes['action'],
[243]261                        attributes['copyfrom-path'],
[165]262                        attributes['copyfrom-rev'])
263                else:
264                    self.current_path_action = attributes['action']
265
266        def endElement(self, name):
267            if name == 'logentry':
268                # Sort the paths to make tests easier
[168]269                self.current['entries'].sort(lambda a,b: cmp(a.name, b.name))
270
[213]271                # Eliminate "useless" entries: SVN does not have atomic
272                # renames, but rather uses a ADD+RM duo.
273                #
274                # So cycle over all entries of this patch, discarding
275                # the deletion of files that were actually renamed, and
276                # at the same time change related entry from ADDED to
277                # RENAMED.
278
[842]279                # When copying a directory from another location in the
280                # repository (outside the tracked tree), SVN will report files
281                # below this dir that are not being committed as being
[840]282                # removed.
283
284                # We thus need to change the action_kind for all entries
[842]285                # that are below a dir that was "copyfrom" from a path
[840]286                # outside of this module:
[842]287                #  D -> Remove entry completely (it's not going to be in here)
288                #  (M,A,R) -> A
[840]289
[213]290                mv_or_cp = {}
291                for e in self.current['entries']:
292                    if e.action_kind == e.ADDED and e.old_name is not None:
293                        mv_or_cp[e.old_name] = e
[444]294
[1048]295                def parent_was_copied(n):
296                    for p in self.copies:
297                        if n.startswith(p+'/'):
[840]298                            return True
299                    return False
300
[213]301                entries = []
302                for e in self.current['entries']:
303                    if e.action_kind==e.DELETED and mv_or_cp.has_key(e.name):
304                        mv_or_cp[e.name].action_kind = e.RENAMED
[239]305                    elif e.action_kind=='R':
[840]306                        # In svn parlance, 'R' means Replaced: a typical
[820]307                        # scenario is
308                        #   $ svn mv a.txt b.txt
309                        #   $ touch a.txt
310                        #   $ svn add a.txt
[239]311                        if mv_or_cp.has_key(e.name):
312                            mv_or_cp[e.name].action_kind = e.RENAMED
313                        e.action_kind = e.ADDED
314                        entries.append(e)
[1048]315                    elif parent_was_copied(e.name):
[842]316                        if e.action_kind != e.DELETED:
[840]317                            e.action_kind = e.ADDED
318                            entries.append(e)
[213]319                    else:
[444]320                        entries.append(e)
321
[165]322                svndate = self.current['date']
323                # 2004-04-16T17:12:48.000000Z
324                y,m,d = map(int, svndate[:10].split('-'))
325                hh,mm,ss = map(int, svndate[11:19].split(':'))
326                ms = int(svndate[20:-1])
[1216]327                timestamp = datetime(y, m, d, hh, mm, ss, ms, UTC)
[444]328
[168]329                changeset = Changeset(self.current['revision'],
330                                      timestamp,
[474]331                                      self.current.get('author'),
[168]332                                      self.current['msg'],
333                                      entries)
334                self.changesets.append(changeset)
[165]335                self.current = None
336            elif name in ['author', 'date', 'msg']:
337                self.current[name] = ''.join(self.current_field)
338            elif name == 'path':
[243]339                path = ''.join(self.current_field)
[168]340                entrypath = get_entry_from_path(path)
341                if entrypath:
342                    entry = ChangesetEntry(entrypath)
[165]343
[168]344                    if type(self.current_path_action) == type( () ):
[1048]345                        self.copies.append(entry.name)
[168]346                        old = get_entry_from_path(self.current_path_action[1])
347                        if old:
[237]348                            entry.action_kind = self.ACTIONSMAP[self.current_path_action[0]]
[168]349                            entry.old_name = old
350                            self.renamed[entry.old_name] = True
351                        else:
352                            entry.action_kind = entry.ADDED
353                    else:
[237]354                        entry.action_kind = self.ACTIONSMAP[self.current_path_action]
[165]355
[168]356                    self.current['entries'].append(entry)
357
[165]358        def characters(self, data):
359            self.current_field.append(data)
360
[933]361    parser = make_parser()
[165]362    handler = SvnXMLLogHandler()
[933]363    parser.setContentHandler(handler)
364    parser.setErrorHandler(ErrorHandler())
365
366    chunk = log.read(chunksize)
367    while chunk:
368        parser.feed(chunk)
369        for cs in handler.changesets:
370            yield cs
371        handler.changesets = []
372        chunk = log.read(chunksize)
373    parser.close()
374    for cs in handler.changesets:
375        yield cs
[165]376
377
[1113]378class SvnWorkingDir(UpdatableSourceWorkingDir, SynchronizableTargetWorkingDir):
[12]379
380    ## UpdatableSourceWorkingDir
381
[527]382    def _getUpstreamChangesets(self, sincerev=None):
[44]383        if sincerev:
384            sincerev = int(sincerev)
385        else:
386            sincerev = 0
[393]387
[777]388        cmd = self.repository.command("log", "--verbose", "--xml",
389                                      "--revision", "%d:HEAD" % (sincerev+1))
[1209]390        svnlog = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[1007]391        log = svnlog.execute('.', stdout=PIPE, TZ='UTC0')[0]
[444]392
[44]393        if svnlog.exit_status:
[148]394            return []
[188]395
[897]396        if self.repository.filter_badchars:
397            from string import maketrans
398            from cStringIO import StringIO
399
400            # Apparently some (SVN repo contains)/(SVN server dumps) some
401            # characters that are illegal in an XML stream. This was the case
402            # with Twisted Matrix master repository. To be safe, we replace
403            # all of them with a question mark.
404
[1053]405            if isinstance(self.repository.filter_badchars, basestring):
[897]406                allbadchars = self.repository.filter_badchars
407            else:
408                allbadchars = "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09" \
409                              "\x0B\x0C\x0E\x0F\x10\x11\x12\x13\x14\x15" \
410                              "\x16\x17\x18\x19\x1A\x1B\x1C\x1D\x1E\x1F\x7f"
411
412            tt = maketrans(allbadchars, "?"*len(allbadchars))
413            log = StringIO(log.read().translate(tt))
414
[1120]415        return changesets_from_svnlog(log, self.repository)
[12]416
[527]417    def _applyChangeset(self, changeset):
[672]418        from time import sleep
419
[1042]420        cmd = self.repository.command("update")
421        if self.repository.ignore_externals:
422            cmd.append("--ignore-externals")
423        cmd.extend(["--revision", changeset.revision])
[1209]424        svnup = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[672]425
426        retry = 0
427        while True:
[1042]428            out, err = svnup.execute(".", stdout=PIPE, stderr=PIPE)
[672]429
430            if svnup.exit_status == 1:
431                retry += 1
432                if retry>3:
433                    break
434                delay = 2**retry
[940]435                self.log.error("%s returned status %s saying\n%s",
436                               str(svnup), svnup.exit_status, err.read())
437                self.log.warning("Retrying in %d seconds...", delay)
[672]438                sleep(delay)
439            else:
440                break
[151]441
[51]442        if svnup.exit_status:
443            raise ChangesetApplicationFailure(
[940]444                "%s returned status %s saying\n%s" % (str(svnup),
[870]445                                                     svnup.exit_status,
446                                                     err.read()))
[444]447
[940]448        self.log.debug("%s updated to %s",
449                       ','.join([e.name for e in changeset.entries]),
450                       changeset.revision)
[444]451
[26]452        result = []
[23]453        for line in out:
454            if len(line)>2 and line[0] == 'C' and line[1] == ' ':
[940]455                self.log.warning("Conflict after svn update: %r", line)
[26]456                result.append(line[2:-1])
[444]457
[23]458        return result
[444]459
[527]460    def _checkoutUpstreamRevision(self, revision):
[51]461        """
462        Concretely do the checkout of the upstream revision.
463        """
[444]464
[201]465        from os.path import join, exists
[51]466
[891]467        # Verify that the we have the root of the repository: do that
468        # iterating an "svn ls" over the hierarchy until one fails
469
470        lastok = self.repository.repository
[978]471        if not self.repository.trust_root:
472            cmd = self.repository.command("ls")
473            svnls = ExternalCommand(command=cmd)
[1093]474
475            # First verify that we have a valid repository
[978]476            svnls.execute(self.repository.repository)
[1093]477            if svnls.exit_status:
478                lastok = None
[1099]479            else:
480                # Then verify it really points to the root of the
481                # repository: this is needed because later the svn log
482                # parser needs to know the "offset".
[978]483
[1099]484                reporoot = lastok[:lastok.rfind('/')]
[1093]485
[1099]486                # Even if it would be enough asserting that the uplevel
487                # directory is not a repository, find the real root to
488                # suggest it in the exception.  But don't go too far, that
489                # is, stop when you hit schema://...
490                while '//' in reporoot:
491                    svnls.execute(reporoot)
492                    if svnls.exit_status:
493                        break
494                    lastok = reporoot
495                    reporoot = reporoot[:reporoot.rfind('/')]
[891]496
[1093]497        if lastok is None:
498            raise ConfigurationError("%r is not the root of a svn repository." %
499                                     self.repository.repository)
500        elif lastok <> self.repository.repository:
[891]501            module = self.repository.repository[len(lastok):]
502            module += self.repository.module
503            raise ConfigurationError("Non-root svn repository %r. "
504                                     "Please specify that as 'repository=%s' "
505                                     "and 'module=%s'." %
506                                     (self.repository.repository,
507                                      lastok, module.rstrip('/')))
508
[432]509        if revision == 'INITIAL':
510            initial = True
[777]511            cmd = self.repository.command("log", "--verbose", "--xml",
[936]512                                          "--stop-on-copy",
[777]513                                          "--revision", "1:HEAD")
[936]514            if self.repository.use_limit:
515                cmd.extend(["--limit", "1"])
[499]516            svnlog = ExternalCommand(command=cmd)
[868]517            out, err = svnlog.execute("%s%s" % (self.repository.repository,
518                                                self.repository.module),
519                                      stdout=PIPE, stderr=PIPE)
[432]520
521            if svnlog.exit_status:
[738]522                raise TargetInitializationFailure(
[940]523                    "%s returned status %d saying\n%s" %
[868]524                    (str(svnlog), svnlog.exit_status, err.read()))
[432]525
[1120]526            csets = changesets_from_svnlog(out, self.repository)
[969]527            last = csets.next()
528            revision = last.revision
[432]529        else:
530            initial = False
531
[1209]532        if not exists(join(self.repository.basedir, '.svn')):
[973]533            self.log.debug("Checking out a working copy")
[1042]534
535            cmd = self.repository.command("co", "--quiet")
536            if self.repository.ignore_externals:
537                cmd.append("--ignore-externals")
538            cmd.extend(["--revision", revision])
[527]539            svnco = ExternalCommand(command=cmd)
[1050]540
[1315]541            out, err = svnco.execute("%s%s@%s" % (self.repository.repository,
542                                                  self.repository.module,
543                                                  revision),
[1209]544                                     self.repository.basedir, stdout=PIPE, stderr=PIPE)
[51]545            if svnco.exit_status:
546                raise TargetInitializationFailure(
[940]547                    "%s returned status %s saying\n%s" % (str(svnco),
[868]548                                                         svnco.exit_status,
549                                                         err.read()))
[189]550        else:
[940]551            self.log.debug("%r already exists, assuming it's "
[1209]552                           "a svn working dir", self.repository.basedir)
[393]553
[432]554        if not initial:
[869]555            if revision=='HEAD':
556                revision = 'COMMITTED'
[777]557            cmd = self.repository.command("log", "--verbose", "--xml",
[869]558                                          "--revision", revision)
[1209]559            svnlog = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[868]560            out, err = svnlog.execute(stdout=PIPE, stderr=PIPE)
[432]561
562            if svnlog.exit_status:
[738]563                raise TargetInitializationFailure(
[940]564                    "%s returned status %d saying\n%s" %
[991]565                    (str(svnlog), svnlog.exit_status, err.read()))
[432]566
[1120]567            csets = changesets_from_svnlog(out, self.repository)
[969]568            last = csets.next()
[444]569
[940]570        self.log.debug("Working copy up to svn revision %s", last.revision)
[426]571
572        return last
[444]573
[1113]574    ## SynchronizableTargetWorkingDir
[12]575
[527]576    def _addPathnames(self, names):
[12]577        """
[291]578        Add some new filesystem objects.
[12]579        """
580
[777]581        cmd = self.repository.command("add", "--quiet", "--no-auto-props",
582                                      "--non-recursive")
[1209]583        ExternalCommand(cwd=self.repository.basedir, command=cmd).execute(names)
[444]584
[1390]585    def _propsetRevision(self, out, command, date, author):
586
587        from re import search
588
589        encode = self.repository.encode
590
591        line = out.readline()
592        if not line:
593            # svn did not find anything to commit
594            self.log.warning('svn did not find anything to commit')
595            return
596
597        # Assume svn output the revision number in the last output line
598        while line:
599            lastline = line
600            line = out.readline()
601        revno = search('\d+', lastline)
602        if revno is None:
603            out.seek(0)
604            raise ChangesetApplicationFailure("%s wrote unrecognizable "
605                                              "revision number:\n%s" %
606                                              (str(command), out.read()))
607
608        revision = revno.group(0)
609
610        if self.repository.use_propset:
611
612            cmd = self.repository.command("propset", "%(propname)s",
613                                          "--quiet", "--revprop",
614                                          "--revision", revision)
615            pset = ExternalCommand(cwd=self.repository.basedir, command=cmd)
616            if self.repository.propset_date:
617                date = date.astimezone(UTC).replace(microsecond=0, tzinfo=None)
618                pset.execute(date.isoformat()+".000000Z", propname='svn:date')
619            pset.execute(encode(author), propname='svn:author')
620
621        return revision
622
623    def _tag(self, tag, date, author):
[1389]624        """
625        TAG current revision.
626        """
627        if self.repository.setupTagsDirectory():
628            src = self.repository.repository + self.repository.module
629            dest = self.repository.repository + self.repository.tags_path \
630                                              + '/' + tag.replace('/', '_')
631
632            cmd = self.repository.command("copy", src, dest, "-m", tag)
633            svntag = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[1390]634            out, err = svntag.execute(stdout=PIPE, stderr=PIPE)
635
636            if svntag.exit_status:
637                raise ChangesetApplicationFailure("%s returned status %d saying\n%s"
638                                              % (str(svntag),
639                                                 svntag.exit_status,
640                                                 err.read()))
641
642            self._propsetRevision(out, svntag, date, author)
643
[1389]644
[1326]645    def _commit(self, date, author, patchname, changelog=None, entries=None,
[1329]646                tags = [], isinitialcommit = False):
[12]647        """
648        Commit the changeset.
649        """
650
[1016]651        encode = self.repository.encode
[444]652
[433]653        logmessage = []
[513]654        if patchname:
[1016]655            logmessage.append(patchname)
[13]656        if changelog:
[1016]657            logmessage.append(changelog)
[429]658
659        # If we cannot use propset, fall back to old behaviour of
660        # appending these info to the changelog
[444]661
[1215]662        if not self.repository.use_propset:
[433]663            logmessage.append('')
[1016]664            logmessage.append('Original author: %s' % encode(author))
[433]665            logmessage.append('Date: %s' % date)
[1273]666        elif not self.repository.propset_date:
667            logmessage.append('')
668            logmessage.append('Date: %s' % date)
[444]669
[433]670        rontf = ReopenableNamedTemporaryFile('svn', 'tailor')
671        log = open(rontf.name, "w")
[1016]672        log.write(encode('\n'.join(logmessage)))
[444]673        log.close()
[393]674
[873]675        cmd = self.repository.command("commit", "--file", rontf.name)
[1209]676        commit = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[444]677
[1388]678        if not entries or self.repository.commit_all_files:
[393]679            entries = ['.']
[444]680
[1066]681        out, err = commit.execute(entries, stdout=PIPE, stderr=PIPE)
[547]682
683        if commit.exit_status:
[940]684            raise ChangesetApplicationFailure("%s returned status %d saying\n%s"
[870]685                                              % (str(commit),
686                                                 commit.exit_status,
687                                                 err.read()))
[1390]688
689        revision = self._propsetRevision(out, commit, date, author)
690        if not revision:
[808]691            # svn did not find anything to commit
692            return
693
[1042]694        cmd = self.repository.command("update", "--quiet")
695        if self.repository.ignore_externals:
696            cmd.append("--ignore-externals")
697        cmd.extend(["--revision", revision])
[1050]698
[1209]699        ExternalCommand(cwd=self.repository.basedir, command=cmd).execute()
[429]700
[527]701    def _removePathnames(self, names):
[12]702        """
[291]703        Remove some filesystem objects.
[12]704        """
705
[777]706        cmd = self.repository.command("remove", "--quiet", "--force")
[1209]707        remove = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[393]708        remove.execute(names)
[12]709
[527]710    def _renamePathname(self, oldname, newname):
[12]711        """
[291]712        Rename a filesystem object.
[12]713        """
714
[1162]715        from os import rename
716        from os.path import join, exists
[1032]717
718        # --force in case the file has been changed and moved in one revision
719        cmd = self.repository.command("mv", "--quiet", "--force")
[1282]720        # Subversion does not seem to allow
721        #   $ mv a.txt b.txt
722        #   $ svn mv a.txt b.txt
723        # Here we are in this situation, since upstream VCS already
724        # moved the item.
725        # It may be better to let subversion do the move itself. For one thing,
726        # svn's cp+rm is different from rm+add (cp preserves history).
727        unmoved = False
728        oldpath = join(self.repository.basedir, oldname)
729        newpath = join(self.repository.basedir, newname)
730        if not exists(oldpath):
731            try:
732                rename(newpath, oldpath)
733            except OSError:
734                self.log.critical('Cannot rename %r back to %r',
735                                  newpath, oldpath)
736                raise
737            unmoved = True
[1209]738        move = ExternalCommand(cwd=self.repository.basedir, command=cmd)
[1128]739        out, err = move.execute(oldname, newname, stdout=PIPE, stderr=PIPE)
[393]740        if move.exit_status:
[1282]741            if unmoved:
[1032]742                rename(oldpath, newpath)
[1143]743            raise ChangesetApplicationFailure("%s returned status %d saying\n%s"
[1032]744                                              % (str(move), move.exit_status,
745                                                 err.read()))
[12]746
[625]747    def _prepareTargetRepository(self):
[455]748        """
749        Check for target repository existence, eventually create it.
750        """
751
[535]752        if not self.repository.repository:
753            return
754
[1215]755        self.repository.create()
[455]756
[533]757    def _prepareWorkingDirectory(self, source_repo):
[455]758        """
759        Checkout a working copy of the target SVN repository.
760        """
761
[688]762        from os.path import join, exists
763
[1209]764        if not self.repository.repository or exists(join(self.repository.basedir, '.svn')):
[535]765            return
766
[777]767        cmd = self.repository.command("co", "--quiet")
[1042]768        if self.repository.ignore_externals:
769            cmd.append("--ignore-externals")
[1050]770
[455]771        svnco = ExternalCommand(command=cmd)
[527]772        svnco.execute("%s%s" % (self.repository.repository,
[1209]773                                self.repository.module), self.repository.basedir)
[455]774
[527]775    def _initializeWorkingDir(self):
[14]776        """
[16]777        Add the given directory to an already existing svn working tree.
[14]778        """
[46]779
780        from os.path import exists, join
[1209]781
782        if not exists(join(self.repository.basedir, '.svn')):
[46]783            raise TargetInitializationFailure("'%s' needs to be an SVN working copy already under SVN" % self.repository.basedir)
[1113]784
[46]785        SynchronizableTargetWorkingDir._initializeWorkingDir(self)
Note: See TracBrowser for help on using the repository browser.