~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/arch/backends/tla.py

  • Committer: Aaron Bentley
  • Date: 2008-03-01 04:27:02 UTC
  • Revision ID: aaron@aaronbentley.com-20080301042702-h3foyp8n9k68y3pv
Add link-tree command

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
# arch-tag: david@allouche.net - 2003-11-17 15:29:01 469107000
2
 
# Copyright (C) 2003  John Goerzen, David Allouche
3
 
# <jgoerzen@complete.org> <david@allouche.net>
4
 
#
5
 
#    This program is free software; you can redistribute it and/or modify
6
 
#    it under the terms of the GNU General Public License as published by
7
 
#    the Free Software Foundation; either version 2 of the License, or
8
 
#    (at your option) any later version.
9
 
#
10
 
#    This program is distributed in the hope that it will be useful,
11
 
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 
#    GNU General Public License for more details.
14
 
#
15
 
#    You should have received a copy of the GNU General Public License
16
 
#    along with this program; if not, write to the Free Software
17
 
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
18
 
 
19
 
"""Construction of tla commands
20
 
"""
21
 
 
22
 
import os.path
23
 
import re
24
 
from arch.pathname import DirName, FileName
25
 
from arch import errors
26
 
from arch._escaping import *
27
 
 
28
 
tlasyn = None
29
 
tlaobj = None
30
 
 
31
 
 
32
 
def gettlasyntax():
33
 
    global tlasyn, tlaobj
34
 
    if tlasyn != None:
35
 
        return tlasyn
36
 
    if text_cmd(['-V']).splitlines()[0].find('tla-1.0.') != -1:
37
 
        tlasyn = '1.0'
38
 
        tlaobj = Tla10()
39
 
    else:
40
 
        tlasyn = '1.1'
41
 
        tlaobj = Tla11()
42
 
    tlaobj.name_escaping = (0 == status_cmd(['escape'], (0,1)))
43
 
    return tlasyn
44
 
 
45
 
 
46
 
class Tla10:
47
 
    tagging_method = 'tagging-method'
48
 
    add = 'add-tag'
49
 
    move = 'move-tag'
50
 
    delete = 'delete-tag'
51
 
    update = 'update --in-place .'
52
 
    replay = 'replay --in-place .'
53
 
    ancestry_graph = None
54
 
    log_versions = 'logs'
55
 
    log_ls = 'log-ls'
56
 
    merges = None
57
 
    inventory_ids = ('inventory', '--tags')
58
 
    has_register_archive_wo_name = False
59
 
    get_changeset = 'get-patch'
60
 
 
61
 
 
62
 
class Tla11:
63
 
    tagging_method = 'id-tagging-method'
64
 
    add = 'add'
65
 
    move = 'move'
66
 
    delete = 'delete'
67
 
    update = 'update'
68
 
    replay = 'replay'
69
 
    ancestry_graph = 'ancestry-graph'
70
 
    log_versions = 'log-versions'
71
 
    log_ls = 'logs'
72
 
    merges = 'merges'
73
 
    inventory_ids = ('inventory', '--ids')
74
 
    has_register_archive_wo_name = True
75
 
    get_changeset = 'get-changeset'
76
 
 
77
 
 
78
 
def cmd():
79
 
    global tlaobj
80
 
    gettlasyntax()
81
 
    return tlaobj
82
 
 
83
 
 
84
 
### Utility functions ###
85
 
 
86
 
def _check_expected_param(expected):
87
 
    msg_format = ("'expected' param must be a sequence of positive"
88
 
                  " integers but was: %r")
89
 
    try:
90
 
        iterator = iter(expected)
91
 
    except TypeError:
92
 
        raise TypeError, msg_format % expected
93
 
    for status in iterator:
94
 
        if not isinstance(status, int):
95
 
            raise TypeError, msg_format % expected
96
 
        if status < 0:
97
 
            raise ValueError, msg_format % expected
98
 
 
99
 
 
100
 
### Subrocess spawning ###
101
 
 
102
 
def _spawner():
103
 
    import arch
104
 
    return arch.backend.spawner
105
 
 
106
 
def null_cmd(args, chdir=None):
107
 
    status = _spawner().status_cmd(args, chdir, (0,))
108
 
    assert status == 0
109
 
 
110
 
def one_cmd(args, chdir=None):
111
 
    status, line = status_one_cmd(args, chdir, (0,))
112
 
    assert status == 0
113
 
    return line
114
 
 
115
 
def sequence_cmd(args, chdir=None, expected=(0,)):
116
 
    _check_expected_param(expected)
117
 
    return _spawner().sequence_cmd(args, chdir, expected)
118
 
 
119
 
def text_cmd(args, chdir=None):
120
 
    status, text = _spawner().status_text_cmd(args, chdir, (0,))
121
 
    assert status == 0
122
 
    return text
123
 
 
124
 
def status_cmd(args, expected):
125
 
    _check_expected_param(expected)
126
 
    return _spawner().status_cmd(args, None, expected)
127
 
 
128
 
def status_one_cmd(args, chdir, expected):
129
 
    _check_expected_param(expected)
130
 
    seq = sequence_cmd(args, chdir, expected)
131
 
    try:
132
 
        out = seq.next()
133
 
    except StopIteration:
134
 
        return seq.status, None
135
 
    tail = list(seq)
136
 
    if not tail:
137
 
        return seq.status, out
138
 
    raise AssertionError, (
139
 
        'one liner actually produced multiline output:\n%s'
140
 
        % '\n'.join([out] + tail))
141
 
 
142
 
def status_text_cmd(args, expected):
143
 
    _check_expected_param(expected)
144
 
    return _spawner().status_text_cmd(args, None, expected)
145
 
 
146
 
 
147
 
### tla commands ###
148
 
 
149
 
# User Commands
150
 
 
151
 
def my_id():
152
 
    status, output = status_one_cmd(('my-id',), None, (0,2))
153
 
    # status is 2 if the user id is not set.
154
 
    # maybe an exception should be thrown instead of returning None.
155
 
    if status != 0: return None
156
 
    return output
157
 
 
158
 
 
159
 
def set_my_id(newid):
160
 
    null_cmd(('my-id', newid))
161
 
 
162
 
 
163
 
def default_archive():
164
 
    status, output = status_one_cmd(('my-default-archive',), None, (0,1))
165
 
    if status != 0: return None
166
 
    return output
167
 
 
168
 
 
169
 
def set_default_archive(archive):
170
 
    null_cmd(('my-default-archive', archive))
171
 
 
172
 
def make_archive(archive, location, signed=False, listing=False):
173
 
    args = ['make-archive']
174
 
    if signed: args.append('--signed')
175
 
    if listing: args.append('--listing')
176
 
    args.extend((archive, location))
177
 
    null_cmd(args)
178
 
 
179
 
def make_archive_mirror(master, name, location, signed=False, listing=False):
180
 
    args = ['make-archive', '--mirror', master]
181
 
    if signed: args.append('--signed')
182
 
    if listing: args.append('--listing')
183
 
    args.extend((name, location))
184
 
    return null_cmd(args)
185
 
 
186
 
def register_archive(archive, location):
187
 
    if archive is None:
188
 
        if not cmd().has_register_archive_wo_name:
189
 
            raise NotImplementedError, \
190
 
                  "register-archive without an archive name"\
191
 
                  " is not implemented in tla 1.0"
192
 
        null_cmd(('register-archive', location))
193
 
    else:
194
 
        null_cmd(('register-archive', archive, location))
195
 
 
196
 
 
197
 
def unregister_archive(archive):
198
 
    null_cmd(('register-archive', '--delete', archive))
199
 
 
200
 
def whereis_archive(archive):
201
 
    return one_cmd(('whereis-archive', archive))
202
 
 
203
 
def archive_meta_info(archive, key):
204
 
    status, output = status_one_cmd(
205
 
        ('archive-meta-info', '--archive', archive, key), None, (0, 1))
206
 
    if status == 1:
207
 
        raise KeyError, "No such meta-info in archive %s: %s" % (archive, key)
208
 
    return output
209
 
 
210
 
def archives():
211
 
    return sequence_cmd(('archives', '--names'))
212
 
 
213
 
def archive_registered(archive):
214
 
    return archive in archives()
215
 
 
216
 
 
217
 
# Project Tree commands
218
 
 
219
 
def init_tree(dir, version=None, nested=False):
220
 
    args = ['init-tree', '--dir', dir]
221
 
    if nested: args.append('--nested')
222
 
    if version is not None: args.append(version)
223
 
    null_cmd(args)
224
 
 
225
 
def tree_root(dir):
226
 
    status, output = status_text_cmd(('tree-root', dir), (0,1))
227
 
    if status == 1: raise errors.TreeRootError(dir)
228
 
    return output[:-1]
229
 
 
230
 
def tree_version(dir):
231
 
    vsn_name = dir/'{arch}'/'++default-version'
232
 
    try:
233
 
        return open(vsn_name, 'r').read().rstrip('\n')
234
 
    except IOError:
235
 
        if not os.path.exists(vsn_name): return None
236
 
        raise
237
 
 
238
 
def set_tree_version(dir, version):
239
 
    null_cmd(('set-tree-version', '--dir', dir, version))
240
 
 
241
 
def has_changes(dir):
242
 
    args = ('changes', '--dir', dir, '--quiet')
243
 
    return 1 == status_cmd(args, expected=(0,1))
244
 
 
245
 
def star_merge(dir, from_=None, reference=None, forward=False, diff3=False):
246
 
    args = ['star-merge', '--dir', dir]
247
 
    if reference is not None: args.extend(('--reference', reference))
248
 
    if forward: args.append('--forward')
249
 
    if diff3: args.append('--three-way')
250
 
    if from_ is not None: args.append(from_)
251
 
    # yes, we want to raise if there was a conflict, see the docstring
252
 
    # of arch.WorkingTree.star_merge for details.
253
 
    null_cmd(args)
254
 
 
255
 
def iter_star_merge(dir, from_=None, reference=None, forward=False,
256
 
                    diff3=False):
257
 
    args = ['star-merge', '--dir', dir]
258
 
    if reference is not None: args.extend(('--reference', reference))
259
 
    if forward: args.append('--forward')
260
 
    if diff3: args.append('--three-way')
261
 
    if from_ is not None: args.append(from_)
262
 
    return sequence_cmd(args, expected=(0,1))
263
 
 
264
 
def sync_tree(dir, revision):
265
 
    null_cmd(('sync-tree', '--dir', dir, revision))
266
 
 
267
 
def undo(dir, revision=None, output=None, quiet=False, throw_away=False):
268
 
    args = ['undo', '--dir', dir]
269
 
    if quiet: args.append('--quiet')
270
 
    if throw_away:
271
 
        args.append('--no-output')
272
 
    elif output:
273
 
        args.extend(('--output', output))
274
 
    if revision: args.append(revision)
275
 
    null_cmd(args)
276
 
 
277
 
def log_for_merge(dir):
278
 
    args = ['log-for-merge', '--dir', dir]
279
 
    return text_cmd(args)
280
 
 
281
 
 
282
 
def redo(dir, patch=None, keep=False, quiet=False):
283
 
    args = ['redo', '--dir', dir]
284
 
    if quiet: args.append('--quiet')
285
 
    if keep: args.append('--keep')
286
 
    if patch: args.extend(('--patch', patch))
287
 
    null_cmd(args)
288
 
 
289
 
def update(dir):
290
 
    """FIXME: add the other parameters."""
291
 
    ### Correct implementation
292
 
    # args = ['update', '--dir', dir]
293
 
    # return null_cmd(args)
294
 
    ### Work around bug in tla 1.2.1
295
 
    null_cmd(['update'], chdir=dir)
296
 
 
297
 
def replay(dir):
298
 
    """FIXME: add the other parameters."""
299
 
    args = ['replay', '--dir', dir]
300
 
    null_cmd(args)
301
 
 
302
 
 
303
 
# Project Tree Inventory commands
304
 
 
305
 
def __inventory_helper(dir, opts,
306
 
                       source, precious, backups, junk, unrecognized, trees,
307
 
                       directories, files, both, names, limit):
308
 
    __pychecker__ = 'maxargs=13'
309
 
    # Asserts there is only one kind and one type
310
 
    (source, precious, backups,
311
 
     junk, unrecognized, trees) = map(bool, (source, precious, backups,
312
 
                                             junk, unrecognized, trees))
313
 
    assert 1 == sum(map(int, (source, precious, backups,
314
 
                              junk, unrecognized, trees)))
315
 
    (directories, files, both) = map(bool, (directories, files, both))
316
 
    assert 1 == sum(map(int, (directories, files, both, trees)))
317
 
 
318
 
    if source: opts.append('--source')
319
 
    if precious: opts.append('--precious')
320
 
    if backups: opts.append('--backups')
321
 
    if junk: opts.append('--junk')
322
 
    if unrecognized: opts.append('--unrecognized')
323
 
    if trees: opts.append('--trees')
324
 
    if directories: opts.append('--directories')
325
 
    if files: opts.append('--files')
326
 
    if both: opts.append('--both')
327
 
    if names: opts.append('--names')
328
 
    if limit is not None: opts.append(limit)
329
 
    return sequence_cmd(opts, chdir=dir)
330
 
 
331
 
 
332
 
def iter_inventory(dir, source=False, precious=False, backups=False,
333
 
                   junk=False, unrecognized=False, trees=False,
334
 
                   directories=False, files=False, both=False, names=False,
335
 
                   limit=None):
336
 
    __pychecker__ = 'maxargs=12'
337
 
    return __inventory_helper(dir, ['inventory'], source, precious, backups,
338
 
                              junk, unrecognized, trees,
339
 
                              directories, files, both, names, limit)
340
 
 
341
 
 
342
 
def iter_inventory_ids(dir, source=False, precious=False, backups=False,
343
 
                       junk=False, unrecognized=False, trees=False,
344
 
                       directories=False, files=False, both=False,
345
 
                       limit=None):
346
 
    __pychecker__ = 'maxargs=11'
347
 
    for line in __inventory_helper(dir, list(cmd().inventory_ids),
348
 
                                   source, precious, backups,
349
 
                                   junk, unrecognized, trees,
350
 
                                   directories, files, both,
351
 
                                   names=False, limit=limit):
352
 
        yield line.split('\t')
353
 
 
354
 
 
355
 
def tagging_method(dir):
356
 
    return one_cmd((cmd().tagging_method, '--dir', dir))
357
 
 
358
 
def set_tagging_method(dir, method):
359
 
    null_cmd((cmd().tagging_method, '--dir', dir, method))
360
 
 
361
 
def add(file):
362
 
    null_cmd((cmd().add, file))
363
 
 
364
 
def delete(file):
365
 
    null_cmd((cmd().delete, file))
366
 
 
367
 
def move(src, dest):
368
 
    return null_cmd((cmd().move, src, dest))
369
 
 
370
 
def get_tag(file):
371
 
    status, output = status_one_cmd(('id', file), None, (0,1))
372
 
    if status == 1: return None
373
 
    return output.split('\t')[1]
374
 
 
375
 
 
376
 
# Patch Set Commands
377
 
 
378
 
def changeset(orig, mod, dest, files=()):
379
 
    return null_cmd(('changeset', orig, mod, dest) + files)
380
 
 
381
 
def iter_apply_changeset(changeset, tree, reverse=False):
382
 
    args = ['apply-changeset']
383
 
    if reverse: args.append('--reverse')
384
 
    args.extend((changeset, tree))
385
 
    return sequence_cmd(args, expected=(0,1))
386
 
 
387
 
 
388
 
# Archive Transaction Commands
389
 
 
390
 
def archive_setup(name):
391
 
    null_cmd(('archive-setup', name))
392
 
 
393
 
def import_(dir, log=None):
394
 
    args = ['import', '--dir', dir]
395
 
    if log is not None: args.extend(('--log', log))
396
 
    null_cmd(args)
397
 
 
398
 
def iter_commit(dir, version, log=None, strict=False, seal=False, fix=False,
399
 
           out_of_date_ok=False, file_list=None, base=None):
400
 
    args = ['commit']
401
 
    if log is not None: args.extend(('--log', log))
402
 
    if strict: args.append('--strict')
403
 
    assert not (seal and fix)
404
 
    if seal: args.append('--seal')
405
 
    if fix: args.append('--fix')
406
 
    if out_of_date_ok: args.append('--out-of-date-ok')
407
 
    if base is not None:
408
 
        args.extend(('--base', base))
409
 
    args.append(version)
410
 
    if file_list is not None:
411
 
        file_list = tuple(file_list)
412
 
        assert 0 < len(file_list)
413
 
        args.append('--')
414
 
        args.extend(map(str, file_list))
415
 
    return sequence_cmd(args, chdir=str(dir))
416
 
 
417
 
def get(revision, dir, link=False):
418
 
    command = ['get']
419
 
    if link: command.append('--link')
420
 
    command.extend((revision, dir))
421
 
    null_cmd(command)
422
 
 
423
 
def get_patch(revision, dir):
424
 
    P = NameParser(revision)
425
 
    # XXX Work around bug in 1.2.2rc2 and corresponding integration revisions
426
 
    # XXX where get-changeset would fail with "no default archive set".
427
 
    args = [cmd().get_changeset, '-A', P.get_archive(), P.get_nonarch(), dir]
428
 
    null_cmd(args)
429
 
 
430
 
def archive_mirror(from_, to, limit=None, no_cached=False, cached_tags=False):
431
 
    # --summary would be useful only in iter_archive_mirror
432
 
    args = ['archive-mirror']
433
 
    assert no_cached + cached_tags < 2
434
 
    if no_cached: args.append('--no-cached')
435
 
    if cached_tags: args.append('--cached-tags')
436
 
    args.append(from_)
437
 
    if to is not None: args.append(to)
438
 
    if limit is not None:
439
 
        assert 0 < len(limit)
440
 
        args.extend(limit)
441
 
    null_cmd(args)
442
 
 
443
 
# Archive Commands
444
 
 
445
 
def categories(archive):
446
 
    return sequence_cmd(('categories', '--archive', archive))
447
 
 
448
 
def branches(category):
449
 
    return sequence_cmd(('branches', category))
450
 
 
451
 
def versions(branch, reverse=False):
452
 
    if not reverse:
453
 
        return sequence_cmd(('versions', branch))
454
 
    else:
455
 
        return sequence_cmd(('versions', '--reverse', branch))
456
 
 
457
 
def revisions(version, reverse=False):
458
 
    if not reverse:
459
 
        return sequence_cmd(('revisions', version))
460
 
    else:
461
 
        return sequence_cmd(('revisions', '--reverse', version))
462
 
 
463
 
 
464
 
def category_exists(archive, category):
465
 
    if not archive_registered(archive):
466
 
        raise errors.ArchiveNotRegistered(archive)
467
 
    else:
468
 
        return category in categories(archive)
469
 
 
470
 
 
471
 
def branch_exists(archive, branch):
472
 
    category = NameParser(branch).get_category()
473
 
    if not category_exists(archive, category):
474
 
        return False
475
 
    else:
476
 
        return branch in branches(archive+"/"+category)
477
 
 
478
 
 
479
 
def version_exists(archive, version):
480
 
    package = NameParser(version).get_package()
481
 
    if not branch_exists(archive, package):
482
 
        return False
483
 
    else:
484
 
        return version in versions(archive+"/"+package)
485
 
 
486
 
 
487
 
def revision_exists(archive, version, patchlevel):
488
 
    try:
489
 
        return patchlevel in revisions(archive+"/"+version)
490
 
    except Exception:
491
 
        if not version_exists(archive, version):
492
 
            return False
493
 
        else:
494
 
            raise
495
 
 
496
 
def cat_archive_log(revision):
497
 
    return text_cmd(('cat-archive-log', revision))
498
 
 
499
 
 
500
 
def cacherev(revision, cache=None):
501
 
    if cache is None:
502
 
        return null_cmd(('cacherev', revision))
503
 
    else:
504
 
        return null_cmd(('cacherev', '--cache', cache, revision))
505
 
 
506
 
 
507
 
def cachedrevs(version):
508
 
    return sequence_cmd(('cachedrevs', version))
509
 
 
510
 
 
511
 
def uncacherev(revision):
512
 
    null_cmd(('uncacherev', revision))
513
 
 
514
 
 
515
 
def iter_merges(version1, version2=None, reverse=False, metoo=True):
516
 
    subcmd = cmd().merges
517
 
    if subcmd is None:
518
 
        raise NotImplementedError, \
519
 
              "merges is not implemented in tla 1.0"""
520
 
    args = [ subcmd ]
521
 
    if reverse: args.append('--reverse')
522
 
    args.append('--full')
523
 
    args.append(version1)
524
 
    if version2 is not None: args.append(version2)
525
 
    for line in sequence_cmd(args):
526
 
        target, source = line.split('\t')
527
 
        if metoo or '%s--%s' % (version1, target) != source:
528
 
            yield target, source
529
 
 
530
 
 
531
 
def iter_ancestry_graph(revision, merges=False, reverse=False):
532
 
    subcmd = cmd().ancestry_graph
533
 
    if subcmd is None:
534
 
        raise NotImplementedError, \
535
 
              "ancestry-graph is not implemented in tla 1.0"
536
 
    args = [ subcmd ]
537
 
    if merges: args.append('--merges')
538
 
    if reverse: args.append('--reverse')
539
 
    args.append(revision)
540
 
    for line in sequence_cmd(args):
541
 
        yield line.split('\t')
542
 
 
543
 
 
544
 
def ancestor(revision):
545
 
    subcmd = cmd().ancestry_graph
546
 
    if subcmd is None:
547
 
        raise NotImplementedError, \
548
 
              "ancestry-graph is not implemented in tla 1.0"
549
 
    rvsn = one_cmd((subcmd, '--immediate', revision))
550
 
    if rvsn == '(null)': return None
551
 
    # --immediate always return a fully qualified revision
552
 
    return rvsn
553
 
 
554
 
def previous(revision):
555
 
    subcmd = cmd().ancestry_graph
556
 
    if subcmd is None:
557
 
        raise NotImplementedError, \
558
 
              "ancestry-graph is not implemented in tla 1.0"
559
 
    rvsn = one_cmd((subcmd, '--previous', revision))
560
 
    if rvsn == '(null)': return None
561
 
    # --previous always returns a nonarch revision name part
562
 
    return NameParser(revision).get_archive()+'/'+rvsn
563
 
 
564
 
 
565
 
# Patch Log Commands
566
 
 
567
 
def make_log(dir, version=None):
568
 
    args = ['make-log', '--dir', dir]
569
 
    if version is not None:
570
 
        args.append(version)
571
 
    return text_cmd(args)[:-1]
572
 
 
573
 
def log_name(dir, version=None):
574
 
    if version is None:
575
 
        version = tree_version(dir)
576
 
    parse = NameParser(version)
577
 
    return dir/FileName('++log.%s--%s'
578
 
                        % (parse.get_nonarch(), parse.get_archive()))
579
 
 
580
 
def add_log_version(dir, version):
581
 
    null_cmd(('add-log-version', '--dir', dir, version))
582
 
 
583
 
def remove_log_version(dir, version):
584
 
    null_cmd(('remove-log-version', '--dir', dir, version))
585
 
 
586
 
 
587
 
def iter_log_versions(dir, reverse=False,
588
 
                      archive=None, category=None, branch=None, version=None):
589
 
    opts = (cmd().log_versions, '--dir', dir)
590
 
    if reverse: opts += ('--reverse',)
591
 
    if archive: opts += ('--archive', archive)
592
 
    if category: opts += ('--category', category)
593
 
    if branch: opts += ('--branch', branch)
594
 
    if version: opts += ('--vsn', version)
595
 
    return sequence_cmd(opts)
596
 
 
597
 
 
598
 
def iter_log_ls(dir, version, reverse=False):
599
 
    opts = [ cmd().log_ls, '--full', '--dir', dir ]
600
 
    if reverse: opts.append('--reverse')
601
 
    opts.append(version)
602
 
    return sequence_cmd(opts)
603
 
 
604
 
 
605
 
def cat_log(dir, revision):
606
 
    return text_cmd(('cat-log', '--dir', dir, revision))
607
 
 
608
 
 
609
 
# Multi-project Configuration Commands
610
 
 
611
 
# Commands for Branching and Merging
612
 
 
613
 
def tag(source_revision, tag_version):
614
 
    null_cmd(('tag', source_revision, tag_version))
615
 
 
616
 
def iter_delta (orig, mod, dest):
617
 
    args = ['delta', orig, mod, dest]
618
 
    return sequence_cmd(args)
619
 
 
620
 
 
621
 
# Local Cache Commands
622
 
 
623
 
def file_find(tree, file_name, revision):
624
 
    args = ('file-find', '--new-file', file_name, revision)
625
 
    # XXX Work around missing --silent option, ignore chatter
626
 
    output = text_cmd(args, chdir=tree)
627
 
    path = output.splitlines()[-1]
628
 
    if path == '/dev/null': return None
629
 
    if cmd().name_escaping:
630
 
        return name_unescape(path)
631
 
    else:
632
 
        return path
633
 
 
634
 
def iter_pristines(dir_name):
635
 
    args = ('pristines', '--dir', dir_name)
636
 
    return sequence_cmd(args)
637
 
 
638
 
def add_pristine(dir_name, revision):
639
 
    args = ('add-pristine', '--dir', dir_name, revision)
640
 
    null_cmd(args)
641
 
 
642
 
def find_pristine(dir_name, revision):
643
 
    args = ('find-pristine', '--dir', dir_name, revision)
644
 
    return one_cmd(args)
645
 
 
646
 
 
647
 
# Revision Library Commands
648
 
 
649
 
def iter_revision_libraries():
650
 
    args = ('my-revision-library', '--silent')
651
 
    try:
652
 
        for val in sequence_cmd(args):
653
 
            yield val
654
 
    except errors.ExecProblem, e:
655
 
        if e.proc.error != \
656
 
            "my-revision-library: no revision library path set\n":
657
 
            raise
658
 
 
659
 
def register_revision_library(dirname):
660
 
    null_cmd(('my-revision-library', '--silent', dirname))
661
 
 
662
 
def unregister_revision_library(dirname):
663
 
    null_cmd(('my-revision-library', '--silent', '--delete', dirname))
664
 
 
665
 
def library_archives():
666
 
    return sequence_cmd(('library-archives',))
667
 
 
668
 
def library_categories(archive):
669
 
    return sequence_cmd(('library-categories', '--archive', archive))
670
 
 
671
 
def library_branches(category):
672
 
    return  sequence_cmd(('library-branches', category))
673
 
 
674
 
def library_versions(branch, reverse=False):
675
 
    if not reverse:
676
 
        return sequence_cmd(('library-versions', branch))
677
 
    else:
678
 
        return sequence_cmd(('library-versions', '--reverse', branch))
679
 
 
680
 
def library_revisions(version, reverse=False):
681
 
    if not reverse:
682
 
        return sequence_cmd(('library-revisions', version))
683
 
    else:
684
 
        return sequence_cmd(('library-revisions', '--reverse', version))
685
 
 
686
 
 
687
 
def library_add(revision, library):
688
 
    args = ['library-add']
689
 
    if library is not None:
690
 
        args.extend(['-L', library])
691
 
    args.append(revision)
692
 
    return null_cmd(('library-add', revision))
693
 
 
694
 
def library_remove(revision):
695
 
    null_cmd(('library-remove', revision))
696
 
 
697
 
def library_find(revision):
698
 
    return text_cmd(('library-find', revision))[:-1]
699
 
 
700
 
def library_log(revision):
701
 
    return text_cmd(('library-log', revision))
702
 
 
703
 
 
704
 
### Name parsing ###
705
 
 
706
 
class ForkNameParser(str):
707
 
 
708
 
    """Parse Arch names with with tla.
709
 
 
710
 
    All operations run tla, this is generally too expensive for
711
 
    practical use. Use NameParser instead, which is implemented
712
 
    natively.
713
 
 
714
 
    This class is retained for testing purposes.
715
 
    """
716
 
 
717
 
    def __valid_package_name(self, opt, tolerant=True):
718
 
        opts = [ 'valid-package-name' ]
719
 
        if tolerant: opts.append('--tolerant')
720
 
        if opt: opts.append(opt)
721
 
        opts.append(self)
722
 
        return 0 == status_cmd(opts, expected=(0,1))
723
 
 
724
 
    def __parse_package_name(self, opt):
725
 
        opts = [ 'parse-package-name' ]
726
 
        if opt: opts.append(opt)
727
 
        opts.append(self)
728
 
        status, retval = status_one_cmd(opts, None, (0,1))
729
 
        return retval
730
 
 
731
 
    def is_category(self):
732
 
        return self.__valid_package_name('--category', tolerant=False)
733
 
    def is_package(self):
734
 
        return self.__valid_package_name('--package', tolerant=False)
735
 
    def is_version(self):
736
 
        return self.__valid_package_name('--vsn', tolerant=False)
737
 
    def is_patchlevel(self):
738
 
        return self.__valid_package_name('--patch-level', tolerant=False)
739
 
 
740
 
    def has_archive(self):
741
 
        return self.__valid_package_name('--archive')
742
 
    def has_category(self):
743
 
        return self.__valid_package_name('--category')
744
 
    def has_package(self):
745
 
        return self.__valid_package_name('--package')
746
 
    def has_version(self):
747
 
        return self.__valid_package_name('--vsn')
748
 
    def has_patchlevel(self):
749
 
        return self.__valid_package_name('--patch-level')
750
 
 
751
 
    def get_archive(self):
752
 
        return self.__parse_package_name('--arch')
753
 
    def get_nonarch(self):
754
 
        return self.__parse_package_name('--non-arch')
755
 
    def get_category(self):
756
 
        return self.__parse_package_name('--category')
757
 
    def get_branch(self):
758
 
        return self.__parse_package_name('--branch')
759
 
    def get_package(self):
760
 
        return self.__parse_package_name('--package')
761
 
    def get_version(self):
762
 
        if not self.has_version(): return None # work around a tla bug
763
 
        return self.__parse_package_name('--vsn')
764
 
    def get_package_version(self):
765
 
        return self.__parse_package_name('--package-version')
766
 
    def get_patchlevel(self):
767
 
        if not self.has_patchlevel(): return None # work around a tla bug
768
 
        return self.__parse_package_name('--patch-level')
769
 
 
770
 
 
771
 
class NameParser(str):
772
 
 
773
 
    """Parser for names in Arch archive namespace.
774
 
 
775
 
    Implements name parsing natively for performance reasons. It
776
 
    should behave exactly as tla, any discrepancy is to be considered
777
 
    a bug, unless tla is obviously buggy.
778
 
 
779
 
    Bare names of archives, category, branch, versions ids, and
780
 
    unqualified patchlevel names are not part of the archive
781
 
    namespace. They can be validated using static methods.
782
 
 
783
 
    :group Specificity level: is_category, is_package, is_version
784
 
 
785
 
    :group Presence name components: has_archive, has_category, has_branch,
786
 
        has_package, has_version, has_revision, has_patchlevel
787
 
 
788
 
    :group Getting name components: get_archive, get_nonarch, get_category,
789
 
        get_branch, get_package, get_version, get_package_version,
790
 
        get_patchlevel
791
 
 
792
 
    :group Validating name components: is_archive_name, is_category_name,
793
 
        is_branch_name, is_version_id, is_patchlevel
794
 
    """
795
 
 
796
 
    __archive_regex = re.compile('^[-a-zA-Z0-9]+(\.[-a-zA-Z0-9]+)*@'
797
 
                                 '[-a-zA-Z0-9.]*$')
798
 
    __name_regex = re.compile('^[a-zA-Z]([a-zA-Z0-9]|-[a-zA-Z0-9])*$')
799
 
    __version_regex = re.compile('^[0-9]+(\\.[0-9]+)*$')
800
 
    #__level_regex = re.compile('^base-0|version-0|(patch|versionfix)-[0-9]+$')
801
 
    # tla accepts --version-12, so mimick that:
802
 
    __level_regex = re.compile('^base-0|(version|patch|versionfix)-[0-9]+$')
803
 
 
804
 
    def __init__(self, s):
805
 
        """Create a parser object for the given string.
806
 
 
807
 
        :param s: string to parse.
808
 
        :type s: str
809
 
        """
810
 
        str.__init__(s)
811
 
        parts = self.__parse()
812
 
        if parts:
813
 
            self.__valid = True
814
 
        else:
815
 
            parts = None, None, None, None, None, None
816
 
            self.__valid = False
817
 
        (self.__archive, self.__nonarch, self.__category,
818
 
         self.__branch, self.__version, self.__level) = parts
819
 
 
820
 
    def __parse(self):
821
 
        parts = self.split('/')
822
 
        if len(parts) == 1:
823
 
            archive, nonarch = None, parts[0]
824
 
        elif len(parts) == 2:
825
 
            archive, nonarch = parts
826
 
        else:
827
 
            return False
828
 
 
829
 
        parts = nonarch.split('--')
830
 
        category, branch, version, level = None, None, None, None
831
 
        if len(parts) == 1:
832
 
            category = parts[0]
833
 
        elif len(parts) == 2:
834
 
            if self.__name_regex.match(parts[1]):
835
 
                category, branch = parts
836
 
            else:
837
 
                category, version = parts
838
 
        elif len(parts) == 3:
839
 
            if self.__name_regex.match(parts[1]):
840
 
                category, branch, version = parts
841
 
            else:
842
 
                category, version, level = parts
843
 
        elif len(parts) == 4:
844
 
            category, branch, version, level = parts
845
 
        else:
846
 
            return False
847
 
 
848
 
        if archive and not self.__archive_regex.match(archive):
849
 
            return False
850
 
        elif not self.__name_regex.match(category):
851
 
            return False
852
 
        elif branch and not self.__name_regex.match(branch):
853
 
            return False
854
 
        elif not version and not level: pass
855
 
        elif not self.__version_regex.match(version):
856
 
            return False
857
 
        elif not level: pass
858
 
        elif not self.__level_regex.match(level):
859
 
            return False
860
 
 
861
 
        return archive, nonarch, category, branch, version, level
862
 
 
863
 
    def is_category(self):
864
 
        """Is this a category name?
865
 
 
866
 
        :rtype: bool
867
 
        """
868
 
        return bool(self.__category) and not (self.__branch or self.__version)
869
 
 
870
 
    def is_package(self):
871
 
        """Is this a package name (category or branch name)?
872
 
 
873
 
        :rtype: bool
874
 
        """
875
 
        return bool(self.__category) and not self.__version
876
 
    def is_version(self):
877
 
        """Is this a version name?
878
 
 
879
 
        :rtype: bool
880
 
        """
881
 
        return bool(self.__version) and not self.__level
882
 
 
883
 
    def has_archive(self):
884
 
        """Does this include an archive name?
885
 
 
886
 
        :rtype: bool
887
 
        """
888
 
        return bool(self.__archive)
889
 
 
890
 
    def has_category(self):
891
 
        """Does this include an category name?
892
 
 
893
 
        :rtype: bool
894
 
        """
895
 
        return bool(self.__category)
896
 
 
897
 
    def has_package(self):
898
 
        """Does this include an package name?
899
 
 
900
 
        :rtype: bool
901
 
        """
902
 
        return bool(self.__category)
903
 
 
904
 
    def has_version(self):
905
 
        """Does this include a version name?
906
 
 
907
 
        :rtype: bool
908
 
        """
909
 
        return bool(self.__version)
910
 
 
911
 
    def has_patchlevel(self):
912
 
        """Does this include a revision name?
913
 
 
914
 
        :rtype: bool
915
 
        """
916
 
        return bool(self.__level)
917
 
 
918
 
    def get_archive(self):
919
 
        """Get the archive part of the name
920
 
 
921
 
        :return: archive part of the name, or the default archive name, or None
922
 
            if the name is invalid.
923
 
        :rtype: str, None
924
 
        """
925
 
        if not self.__valid: return None
926
 
        if not self.__archive: return default_archive()
927
 
        return self.__archive
928
 
 
929
 
    def get_nonarch(self):
930
 
        """Get Non-archive part of the name
931
 
 
932
 
        :return: the name without the archive component, or None if the name is
933
 
            invalid or has no archive component.
934
 
        :rtype: str, None
935
 
        """
936
 
        return self.__nonarch
937
 
 
938
 
    def get_category(self):
939
 
        """Get the Category name
940
 
 
941
 
        :return: part of the name which identifies the category within
942
 
            the archive, or None if the name is invalid or has no category
943
 
            component.
944
 
        :rtype: str, None
945
 
        """
946
 
        return self.__category
947
 
 
948
 
    def get_branch(self):
949
 
        """Get the branch part of name
950
 
 
951
 
        :return: part of the name which identifies the branch within the
952
 
            category, or None if the name is invalid or the empty string if the
953
 
            name has no branch component.
954
 
        :rtype: str, None
955
 
        """
956
 
        if not self.__valid: return None
957
 
        if not self.__branch: return str()
958
 
        return self.__branch
959
 
 
960
 
    def get_package(self):
961
 
        """Get the package name
962
 
 
963
 
        :return: part of the name including the category part and branch part
964
 
            (if present) of the name, or None if the name is not valid.
965
 
        :rtype: str, None
966
 
        """
967
 
        if not self.__valid: return None
968
 
        if self.__branch is None: return self.__category
969
 
        return self.__category + '--' + self.__branch
970
 
 
971
 
    def get_version(self):
972
 
        """Get the version id part of the name
973
 
 
974
 
        :return: part of the name identifying a version in a branch, or None if
975
 
            the name is invalid or does not contain a version id.
976
 
        :rtype: str, None
977
 
        """
978
 
        return self.__version
979
 
 
980
 
    def get_package_version(self):
981
 
        """Get the unqualified version name
982
 
 
983
 
        :return: part of the name identifying a version in an archive, or None
984
 
            if the name does not contain a version id or is invalid.
985
 
        :rtype: str, None
986
 
        """
987
 
        if not self.__version: return None
988
 
        return self.get_package() + '--' + self.__version
989
 
 
990
 
    def get_patchlevel(self):
991
 
        """Get the patch-level part of the name
992
 
 
993
 
        :return: part of the name identifying a patch in a version, or None if
994
 
            the name is not a revision or is invalid.
995
 
        :rtype: str, None
996
 
        """
997
 
        return self.__level
998
 
 
999
 
    def is_archive_name(klass, s):
1000
 
        """Is this string a valid archive name?
1001
 
 
1002
 
        :param s: string to validate.
1003
 
        :type s: str
1004
 
        :rtype: bool
1005
 
        """
1006
 
        return bool(klass.__archive_regex.match(s))
1007
 
    is_archive_name = classmethod(is_archive_name)
1008
 
 
1009
 
    def is_category_name(klass, s):
1010
 
        """Is this string a valid category name?
1011
 
 
1012
 
        Currently does the same thing as is_branch_name, but that might
1013
 
        change in the future when the namespace evolves and it is more
1014
 
        expressive to have different functions.
1015
 
 
1016
 
        :param s: string to validate.
1017
 
        :type s: str
1018
 
        :rtype: bool
1019
 
        """
1020
 
        return bool(klass.__name_regex.match(s))
1021
 
    is_category_name = classmethod(is_category_name)
1022
 
 
1023
 
    def is_branch_name(klass, s):
1024
 
        """Is this string a valid category name?
1025
 
 
1026
 
        Currently does the same thing as is_category_name, but that might
1027
 
        change in the future when the namespace evolves and it is more
1028
 
        expressive to have different functions.
1029
 
 
1030
 
        :param s: string to validate.
1031
 
        :type s: str
1032
 
        :rtype: bool
1033
 
        """
1034
 
        return bool(klass.__name_regex.match(s))
1035
 
    is_branch_name = classmethod(is_branch_name)
1036
 
 
1037
 
    def is_version_id(klass, s):
1038
 
        """Is this string a valid version id?
1039
 
 
1040
 
        :param s: string to validate.
1041
 
        :type s: str
1042
 
        :rtype: bool
1043
 
        """
1044
 
        return bool(klass.__version_regex.match(s))
1045
 
    is_version_id = classmethod(is_version_id)
1046
 
 
1047
 
    def is_patchlevel(klass, s):
1048
 
        """Is this string a valid unqualified patch-level name?
1049
 
 
1050
 
        :param s: string to validate.
1051
 
        :type s: str
1052
 
        :rtype: bool
1053
 
        """
1054
 
        return bool(klass.__level_regex.match(s))
1055
 
    is_patchlevel = classmethod(is_patchlevel)
1056
 
 
1057
 
 
1058
 
### Pika escaping ###
1059
 
 
1060
 
def tla_name_escape(text):
1061
 
   return text_cmd(('escape', text))
1062
 
 
1063
 
def tla_name_unescape(text):
1064
 
    return text_cmd(('escape', '--unescaped', text))
1065
 
 
1066
 
 
1067
 
### Misc features ###
1068
 
 
1069
 
def in_tree(dir):
1070
 
    """Return True if dir is, or is inside, a Arch source tree."""
1071
 
    # Must use tree-root and not tree-version because the tree-version
1072
 
    # may be unset (after init-tree)
1073
 
    status = status_cmd(('tree-root', dir), expected=(0,1))
1074
 
    return not status
1075
 
 
1076
 
 
1077
 
def is_tree_root(dir):
1078
 
    """Return True if dir is a Arch source tree."""
1079
 
    opts = [ 'tree-root', dir ]
1080
 
    status, out = status_text_cmd(opts, expected=(0,1))
1081
 
    if status != 0: return False
1082
 
    return os.path.realpath(dir) == out[:-1]
1083
 
 
1084
 
 
1085
 
def has_explicit_id(path):
1086
 
    assert os.path.exists(path)
1087
 
    arch_ids = DirName('.arch-ids')
1088
 
    if os.path.isfile(path):
1089
 
        dir_, base = FileName(path).splitname()
1090
 
        return os.path.exists(dir_/arch_ids/(base+'.id'))
1091
 
    if os.path.isdir(path):
1092
 
        return os.path.exists(DirName(path)/arch_ids/'=id')
1093
 
    raise AssertionError, 'not regular file or directory: ' + path