source: CMT/v1r22/mgr/cmt_svn_checkout.py

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

See C.L. 403

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