source: CMT/HEAD/mgr/cmt_svn_checkout.py @ 513

Last change on this file since 513 was 513, checked in by rybkin, 15 years ago

See C.L. 402

  • Property svn:executable set to *
File size: 20.4 KB
Line 
1#!/usr/bin/env python
2
3"""cmt_svn_checkout.py: Checkout CMT package(s) from Subversion repository.
4
5Usage: cmt_svn_checkout.py [OPTION]... PATH...
6Checkout PATH(s) relative to or being Subversion repository URL(s).
7
8Mandatory arguments to long options are mandatory for short options too.
9  -r, --version-tag=REV        checkout version tag REV of PATH
10      --version-dir=DIR        use DIR as version directory instead of version tag
11  -d, --directory=DIR          checkout into DIR instead of (basename of) PATH
12  -o, --offset=OFFSET          checkout PATH at OFFSET relative to repository URL
13      --no_config              disable config step upon checkout
14      --without_version_directory do not create version directory upon PATH checkout
15      --with_version_directory create version directory upon PATH checkout (default)
16      --url=URL                checkout PATH from repository URL
17  -h, --help                   display this help and exit
18      --version                output version information and exit
19
20The SVNROOT, SVNTRUNK, SVNTAGS, and SVNBRANCHES environment variables specify repository URL of PATH, location of PATH trunk, tags, and branches (relatively to PATH) respectively.
21
22Report bugs to <CMT-L@IN2P3.FR>.
23"""
24
25__version__ = '0.3.0'
26__date__ = 'Thu Jun 27 2009'
27__author__ = 'Grigory Rybkin'
28
29import sys
30import getopt
31import os
32import posixpath
33import os.path
34import urlparse
35# for Python 2.3 and older
36if sys.version_info[0] == 2 and sys.version_info[1] < 4:
37    for p in ('svn', 'svn+ssh'):
38        if p not in urlparse.uses_netloc:
39            urlparse.uses_netloc.append(p)
40import tempfile
41import commands
42import re
43
44self = 'cmt_svn_checkout.py'
45# try:
46#     from svn import client, core
47# except ImportError, e:
48#     print >>sys.stderr, '%s: cannot import Subversion Python bindings: %s' \
49#           % (self, str(e))
50#     sys.exit(1)
51
52class ClientContext(object):
53
54    schemes = ('http', 'https', 'svn', 'svn+ssh', 'file')
55
56    def svn_path_canonicalize(self, path):
57        """Return a new path (or URL) like path, but transformed such that some types of path specification redundancies are removed.
58
59        This involves collapsing redundant "/./" elements, removing multiple adjacent separator characters, removing trailing separator characters, and possibly other semantically inoperative transformations. Convert the scheme and hostname to lowercase."""
60        scheme, netloc, path, query, fragment = urlparse.urlsplit(path)
61        scheme = scheme.lower()
62        netloc = netloc.lower()
63        if path.startswith('/'): b = '/'
64        else: b = ''
65        path = b + '/'.join([s for s in path.split('/') if s and s != '.'])
66        return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
67
68    def urljoin(self, *args):
69        urls = [urlparse.urlsplit(arg) for arg in args]
70        if not urls: return ''
71       
72        schemes = [url[0] for url in urls]
73        schemes.reverse()
74        for i, s in enumerate(schemes):
75            if s and s in self.schemes:
76                scheme = s
77                index = i
78                break
79        else:
80            scheme = ''
81            index = len(urls) - 1
82       
83        netlocs = [url[1] for url in urls]
84        netlocs.reverse()
85        for i, s in enumerate(netlocs[:index + 1]):
86            if s:
87                netloc = s
88                index = i
89                break
90        else:
91            netloc = ''
92       
93        path = posixpath.join(*[url[2] for url in urls][len(urls) - 1 - index:])
94
95        query = fragment = ''
96        return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
97
98#         scheme = self.last_nonempty([url[0] for url in urls if url[0] in self.schemes])
99#         netloc = self.last_nonempty([url[1] for url in urls])
100#         path = posixpath.join(*[url[2] for url in urls])
101#         query = self.last_nonempty([url[3] for url in urls])
102#         fragment = self.last_nonempty([url[4] for url in urls])
103       
104#         return urlparse.urlunsplit((scheme, netloc, path, query, fragment))
105
106    def last_nonempty(self, list, default = ''):
107        list.reverse()
108        for i in list:
109            if i: return i
110        else:
111            return default
112       
113#     def __init__(self):
114#         core.svn_config_ensure(None)
115#         self.ctx = client.create_context()
116#         self.providers = [
117#             client.get_simple_provider(),
118#             client.get_username_provider(),
119#             client.get_ssl_server_trust_file_provider(),
120#             client.get_ssl_client_cert_file_provider(),
121#             client.get_ssl_client_cert_pw_file_provider()
122#             ]
123#         self.ctx.auth_baton = core.svn_auth_open(self.providers)
124#         self.ctx.config = core.svn_config_get_config(None)
125
126#     def __call__(self):
127#         return self.ctx
128
129def cwd():
130    _gcwd = os.getcwd()
131    try:
132        _cwd = os.environ['PWD']
133        if _cwd and os.path.samefile(_cwd, _gcwd):
134            return _cwd
135        else:
136            return _gcwd
137    except (KeyError, AttributeError):
138        return _gcwd
139
140def cd(path):
141    new = os.path.normpath(os.path.join(cwd(), path))
142    os.chdir(path)
143    _gcwd = os.getcwd()
144    try:
145        if os.path.samefile(new, _gcwd):
146            os.environ['PWD'] = new
147        else:
148            os.environ['PWD'] = _gcwd
149    except AttributeError:
150        pass
151
152def error(instance, location='', file = sys.stderr):
153    try:
154        message = ': '.join([str(arg) for arg in instance.args])
155    except AttributeError:
156        message = instance
157    if location: location += ': '
158    print >> file, "%s%s" % (location, message)
159   
160class CmtContext(object):
161    def __init__(self,
162                 config = True,
163                 with_version_directory = True,
164                 cleanup = False,
165                 head_version = None):
166        self.config = config
167        self.with_version_directory = with_version_directory
168        self.cleanup = cleanup
169        self.head_version = head_version
170        self.env()
171
172    def env(self):
173        if self.head_version is None:
174            self.set_head_version(os.getenv('CMTHEADVERSION', 'HEAD'))
175#        print >>sys.stderr, 'env: set head_version: %s' % self.head_version
176
177    def set_head_version(self, version):
178        self.head_version = str(version).replace('<package>', '%(package)s').replace('<PACKAGE>', '%(PACKAGE)s').replace('<revision>', '%(revision)i')
179
180    def eval_head_version(self, m):
181        return self.head_version % \
182               {'package' : m.package,
183                'PACKAGE' : m.package.upper(),
184                'revision' : m.info.last_changed_rev}
185
186    def write(self, p, version):
187        #print >> sys.stderr, 'write:', p, version
188        try:
189            t = version + '\n'
190            if os.path.exists(p):
191                f = open(p, 'r+')
192                b = f.tell()
193                v = f.read()
194                if v != t:
195                    f.seek(b)
196                    f.write(t)
197                    f.truncate()
198            else:
199                f = open(p, 'w')
200                f.write(t)
201            f.close()
202        except IOError, e:
203            print >> sys.stderr, e
204            return 1
205        return 0
206       
207    def generate(self, p):
208        #print >> sys.stderr, 'generate:', p
209        curdir = cwd()
210#        cmd = 'cmt -disable_warnings'
211        cmd = 'cmt -q'
212        if self.with_version_directory:
213            cmd += ' -with_version_directory'
214        else:
215            cmd += ' -without_version_directory'
216        if self.cleanup:
217            cmd += ' -cleanup'
218        else:
219            cmd += ' -no_cleanup'
220        cmd += ' config'
221        cd(p)
222        sc = os.system(cmd)
223        if sc != 0: sc = 1
224        cd(curdir)
225#        return sc
226        return 0
227
228    def configure(self, path, version):
229        sc = 0
230        for d in ('cmt', 'mgr'):
231            p = os.path.join(path, d)
232            if os.path.isdir(p):
233                if not self.with_version_directory:
234                    sc += self.write(os.path.join(p,'version.cmt'), version)
235                if self.config:
236                    sc += self.generate(p)
237                return sc
238        return sc
239        #print >> sys.stderr, 'Cannot configure %s ' % (path, version)
240
241class Module(object):
242    def __init__(self, module):
243        self.module = module
244        self.init = False
245        class info(object):
246            last_changed_rev = 0
247        self.info = info
248#         print >>sys.stderr, 'init module %s: last_changed_rev %i' % \
249#               (self.module, self.info.last_changed_rev)
250       
251class Checkout(object):
252    def __init__(self,
253                 url = None,
254                 trunk = None,
255                 tags = None,
256                 branches = None,
257                 version = None,
258                 version_dir = None,
259                 directory = None,
260                 offset = '',
261                 modules = []):
262        self.url = url
263        self.trunk = trunk
264        self.tags = tags
265        self.branches = branches
266        self.version = version
267        self.version_dir = version_dir
268        self.directory = directory
269        self.offset = offset
270        self.modules = modules
271        self.reposLayout()
272       
273    def reposLayout(self):
274        if self.url is None:
275            self.url = os.getenv('SVNROOT', '')
276#             try:
277#                 self.url = os.environ['SVNROOT']
278#             except KeyError:
279#                 pass
280        if self.trunk is None:
281            self.trunk = os.getenv('SVNTRUNK', 'trunk')
282        if self.tags is None:
283            self.tags = os.getenv('SVNTAGS', 'tags')
284        if self.branches is None:
285            self.branches = os.getenv('SVNBRANCHES', 'branches')
286
287    def cmtRepos(self):
288        self.url = 'https://svn.lal.in2p3.fr/projects/CMT'
289        self.trunk = 'HEAD'
290        self.tags= '.'
291        self.branches = '.'
292
293    def add(self, module):
294        self.modules.append(module)
295
296    def info_receiver(self, path, info, pool):
297        self.path = path
298        self.info = info
299        pool.info = info
300
301    def cmp(self, path1, path2, client_context):
302        cmd = 'svn diff %s %s' % (path1, path2)
303#        cmd = 'svn diff --summarize %s %s' % (path1, path2)
304        sc, out = commands.getstatusoutput(cmd)
305        if sc != 0:
306            return 2
307        if out: return 1
308        else: return 0
309
310#         outfile = tempfile.TemporaryFile('wb')
311#         errfile = tempfile.TemporaryFile('wb')
312#         outb = outfile.tell()
313#         errb = errfile.tell()
314#         client.diff([]
315#                     , path1
316#                     , self.head_revision
317#                     , path2
318#                     , self.head_revision
319#                     , True, True, False
320#                     , outfile
321#                     , errfile
322#                     , client_context())
323
324#         # position at the end of the file
325#         outfile.seek(0, 2)
326#         errfile.seek(0, 2)
327#         oute = outfile.tell()
328#         erre = errfile.tell()
329
330#         if erre > errb: return 2
331#         elif oute > outb: return 1
332#         else: return 0
333
334    def trunk_tag(self, module, client_context):
335        """Attempt to determine the tag of the module's trunk.
336       
337        Return the tag if its contents are the same as those of the trunk, and
338        its last_changed_rev is greater than the trunk created_rev, None otherwise.
339        """
340        trunk = posixpath.join(module.url, self.trunk)
341#        trunk = posixpath.normpath(posixpath.join(module.url, self.trunk))
342        cmd = 'svn info %s' % trunk
343        sc, out = commands.getstatusoutput(cmd)
344        if sc != 0:
345            return None
346        p = r'last\s+changed\s+rev:\s+(?P<rev>\d+)'
347        m = re.search(p, out, re.I)
348        if m:
349            class info(object): pass
350            info.last_changed_rev = int(m.group('rev'))
351            self.info_receiver(trunk, info, module)
352#            self.info_receiver(trunk, info, None)
353#             print >>sys.stderr, '%s: last_changed_rev %i' % \
354#                   (trunk, self.info.last_changed_rev)
355#            last_changed_rev = int(m.group('rev'))
356        else:
357            return None
358
359        tags = posixpath.join(module.url, self.tags)
360#        tags = posixpath.normpath(posixpath.join(module.url, self.tags))
361        cmd = 'svn ls -v %s' % tags
362        sc, out = commands.getstatusoutput(cmd)
363        if sc != 0:
364            return None
365
366        tags_dirent = [line.split() for line in out.splitlines()]
367        rev_tag = dict([(int(line[0]), line[-1].rstrip(posixpath.sep)) for line in tags_dirent if line[-1].endswith(posixpath.sep)])
368        revs = rev_tag.keys()
369        revs.sort()
370        revs.reverse()
371
372        for rev in revs:
373            if rev < self.info.last_changed_rev: break
374#            if rev < last_changed_rev: break
375            tag = posixpath.join(tags, rev_tag[rev])
376#            tag = posixpath.normpath(posixpath.join(tags, rev_tag[rev]))
377            if 0 == self.cmp(trunk, tag, client_context):
378                return rev_tag[rev]
379
380        return None
381
382#         try:
383#             trunk = core.svn_path_canonicalize(posixpath.join(module.url, self.trunk))
384#             client.info(trunk,
385#                         self.head_revision,
386#                         self.head_revision,
387#                         self.info_receiver,
388#                         False,
389#                         client_context())
390           
391#             tags = core.svn_path_canonicalize(posixpath.join(module.url, self.tags))
392#             tags_dirent = client.ls(tags,
393#                                     self.head_revision,
394#                                     False,
395#                                     client_context())
396           
397#             rev_tag = dict([(tags_dirent[p].created_rev, p) for p in tags_dirent if tags_dirent[p].kind == core.svn_node_dir])
398#             revs = rev_tag.keys()
399#             revs.sort()
400#             revs.reverse()
401
402#             for rev in revs:
403#                 if rev < self.info.last_changed_rev: break
404#                 tag = core.svn_path_canonicalize(posixpath.join(tags, rev_tag[rev]))
405#                 if 0 == self.cmp(trunk, tag, client_context):
406#                     return rev_tag[rev]
407
408#             return None
409#         except core.SubversionException, e:
410#             return None
411
412    def initialize(self, cmt_context, client_context):
413        sc = 0
414#         self.head_revision = core.svn_opt_revision_t()
415#         self.head_revision.kind = core.svn_opt_revision_head
416
417        # canonicalize:
418        self.url = client_context.svn_path_canonicalize(self.url)
419        self.offset = client_context.svn_path_canonicalize(self.offset)
420
421        for m in self.modules:
422            m.module = client_context.svn_path_canonicalize(m.module)
423            m.url = client_context.urljoin(self.url, self.offset, m.module)
424
425            if urlparse.urlparse(m.url)[0] not in client_context.schemes:
426                error('%s: Not a valid Subversion URL' % m.url)
427                sc += 1; continue
428#             print >>sys.stderr, '%s: URL constructed from %s %s %s' % \
429#                   (m.url, `self.url`, `self.offset`, `m.module`)
430
431            m.package = posixpath.basename(m.url)
432
433            if self.directory is not None:
434                m.path = os.path.normpath(self.directory)
435            else:
436                scheme, netloc, path, query, fragment = urlparse.urlsplit(m.module)
437                if not scheme and not netloc:
438                    m.path = os.path.normpath(os.path.join(*path.split(posixpath.sep)))
439                else:
440                    m.path = posixpath.basename(m.url)
441
442            if self.version is None:
443                m.head = True
444                m.version = self.trunk_tag(m, client_context) or \
445                            cmt_context.eval_head_version(m)
446#                print >>sys.stderr, 'set version: %s' % m.version
447            else:
448                m.head = False
449                m.version = self.version
450
451            if m.head:
452                m.URL = [posixpath.join(m.url, self.trunk)]
453            else:
454                m.URL = [posixpath.join(m.url, p, m.version)
455                         for p in (self.tags, self.branches)]
456            m.URL = [client_context.svn_path_canonicalize(url) for url in m.URL]
457#            m.URL = [core.svn_path_canonicalize(url) for url in m.URL]
458
459            if cmt_context.with_version_directory:
460                if self.version_dir is None:
461                    m.path = os.path.join(m.path, m.version)
462                else:
463                    m.path = os.path.join(m.path, self.version_dir)
464
465            m.init = True
466#            print m.URL, m.path, m.init
467           
468#        for m in self.modules:
469#            print m.url, m.path, m.init
470        return sc
471
472    def execute(self, cmt_context, client_context):
473        sc = 0
474
475        for m in self.modules:
476            if not m.init: continue
477            done = False
478            err = []
479            for url in m.URL:
480#                cmd = 'svn checkout -q %s %s 2>/dev/null' % (url, m.path)
481                cmd = 'svn checkout -q %s %s' % (url, m.path)
482                sc, e = commands.getstatusoutput(cmd)
483                #sc = os.system(cmd)
484                if 0 == sc:
485#                 try:
486#                     #print 'client.checkout2:', url, m.path
487#                     result_rev = client.checkout2(url,
488#                                                   m.path,
489#                                                   self.head_revision,
490#                                                   self.head_revision,
491#                                                   True,
492#                                                   True,
493#                                                   client_context())
494#                 except core.SubversionException, e:
495#                     err.append(e)
496#                     continue
497                    done = True
498                    break
499                else:
500                    err.append(e)
501                    continue
502
503            if not done:
504                for e in err:
505                    error(e)
506#                     #print >> sys.stderr, e
507#                     sc += 1
508#                 print >> sys.stderr, 'Failed to checkout %s into %s.' % \
509#                       (' or '.join(m.URL), m.path)
510                sc += 1
511                continue
512
513#            print 'Checked out revision %i.' % result_rev
514            scc = cmt_context.configure(m.path, m.version)
515            if scc != 0:
516                print >> sys.stderr, \
517                      '%s %s: configure returned %i.' % (m.path, m.version, scc)
518            sc += scc
519
520        return sc
521
522def main(argv=[__name__]):
523    self = os.path.basename(argv[0])
524    try:
525        opts, args = getopt.getopt(argv[1:],
526                                   "hr:d:o:",
527                                   ["help", "version", "version-tag=",
528                                    "version-dir=", "directory=",
529                                    "offset=", "no_config",
530                                    "with_version_directory",
531                                    "without_version_directory", "url="])
532    except getopt.error, e:
533        print >>sys.stderr, '%s: %s' % (self, str(e))
534        print >>sys.stderr, "Try '%s --help' for more information." % self
535        return 1
536
537    cmt_context = CmtContext()
538    checkout = Checkout()
539
540    for o, v in opts:
541        if o in ("-h", "--help"):
542            print sys.modules[__name__].__doc__
543            return 0
544        elif o in ("--version",):
545            print '%s %s (%s)' % (self, __version__, __date__)
546            print '%sWritten by %s.' % (os.linesep, __author__)
547            return 0
548        elif o in ("-r", "--version-tag"):
549            checkout.version = v
550        elif o in ("--version-dir",):
551            checkout.version_dir = v
552        elif o in ("-d", "--directory"):
553            checkout.directory = v
554        elif o in ("-o", "--offset"):
555            checkout.offset = v
556        elif o in ("--no_config",):
557            cmt_context.config = False
558        elif o in ("--without_version_directory",):
559            cmt_context.with_version_directory = False
560        elif o in ("--with_version_directory",):
561            cmt_context.with_version_directory = True
562        elif o in ("--url",):
563            checkout.url = v
564
565    if not args:
566        print >>sys.stderr, '%s: missing path argument' % self
567        print >>sys.stderr, "Try '%s --help' for more information." % self
568        return 1
569
570    for arg in args:
571        checkout.add(Module(arg))
572
573    client_context = ClientContext()
574    sci = checkout.initialize(cmt_context, client_context)
575    sce = checkout.execute(cmt_context, client_context)
576
577    if sci != 0 or sce !=0:
578        return 1
579    else:
580        return 0
581
582if __name__ == '__main__':
583    sys.exit(main(sys.argv))
Note: See TracBrowser for help on using the repository browser.