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>
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.
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.
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
19
"""Construction of tla commands
24
from arch.pathname import DirName, FileName
25
from arch import errors
26
from arch._escaping import *
36
if text_cmd(['-V']).splitlines()[0].find('tla-1.0.') != -1:
42
tlaobj.name_escaping = (0 == status_cmd(['escape'], (0,1)))
47
tagging_method = 'tagging-method'
51
update = 'update --in-place .'
52
replay = 'replay --in-place .'
57
inventory_ids = ('inventory', '--tags')
58
has_register_archive_wo_name = False
59
get_changeset = 'get-patch'
63
tagging_method = 'id-tagging-method'
69
ancestry_graph = 'ancestry-graph'
70
log_versions = 'log-versions'
73
inventory_ids = ('inventory', '--ids')
74
has_register_archive_wo_name = True
75
get_changeset = 'get-changeset'
84
### Utility functions ###
86
def _check_expected_param(expected):
87
msg_format = ("'expected' param must be a sequence of positive"
88
" integers but was: %r")
90
iterator = iter(expected)
92
raise TypeError, msg_format % expected
93
for status in iterator:
94
if not isinstance(status, int):
95
raise TypeError, msg_format % expected
97
raise ValueError, msg_format % expected
100
### Subrocess spawning ###
104
return arch.backend.spawner
106
def null_cmd(args, chdir=None):
107
status = _spawner().status_cmd(args, chdir, (0,))
110
def one_cmd(args, chdir=None):
111
status, line = status_one_cmd(args, chdir, (0,))
115
def sequence_cmd(args, chdir=None, expected=(0,)):
116
_check_expected_param(expected)
117
return _spawner().sequence_cmd(args, chdir, expected)
119
def text_cmd(args, chdir=None):
120
status, text = _spawner().status_text_cmd(args, chdir, (0,))
124
def status_cmd(args, expected):
125
_check_expected_param(expected)
126
return _spawner().status_cmd(args, None, expected)
128
def status_one_cmd(args, chdir, expected):
129
_check_expected_param(expected)
130
seq = sequence_cmd(args, chdir, expected)
133
except StopIteration:
134
return seq.status, None
137
return seq.status, out
138
raise AssertionError, (
139
'one liner actually produced multiline output:\n%s'
140
% '\n'.join([out] + tail))
142
def status_text_cmd(args, expected):
143
_check_expected_param(expected)
144
return _spawner().status_text_cmd(args, None, expected)
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
159
def set_my_id(newid):
160
null_cmd(('my-id', newid))
163
def default_archive():
164
status, output = status_one_cmd(('my-default-archive',), None, (0,1))
165
if status != 0: return None
169
def set_default_archive(archive):
170
null_cmd(('my-default-archive', archive))
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))
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)
186
def register_archive(archive, location):
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))
194
null_cmd(('register-archive', archive, location))
197
def unregister_archive(archive):
198
null_cmd(('register-archive', '--delete', archive))
200
def whereis_archive(archive):
201
return one_cmd(('whereis-archive', archive))
203
def archive_meta_info(archive, key):
204
status, output = status_one_cmd(
205
('archive-meta-info', '--archive', archive, key), None, (0, 1))
207
raise KeyError, "No such meta-info in archive %s: %s" % (archive, key)
211
return sequence_cmd(('archives', '--names'))
213
def archive_registered(archive):
214
return archive in archives()
217
# Project Tree commands
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)
226
status, output = status_text_cmd(('tree-root', dir), (0,1))
227
if status == 1: raise errors.TreeRootError(dir)
230
def tree_version(dir):
231
vsn_name = dir/'{arch}'/'++default-version'
233
return open(vsn_name, 'r').read().rstrip('\n')
235
if not os.path.exists(vsn_name): return None
238
def set_tree_version(dir, version):
239
null_cmd(('set-tree-version', '--dir', dir, version))
241
def has_changes(dir):
242
args = ('changes', '--dir', dir, '--quiet')
243
return 1 == status_cmd(args, expected=(0,1))
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.
255
def iter_star_merge(dir, from_=None, reference=None, forward=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))
264
def sync_tree(dir, revision):
265
null_cmd(('sync-tree', '--dir', dir, revision))
267
def undo(dir, revision=None, output=None, quiet=False, throw_away=False):
268
args = ['undo', '--dir', dir]
269
if quiet: args.append('--quiet')
271
args.append('--no-output')
273
args.extend(('--output', output))
274
if revision: args.append(revision)
277
def log_for_merge(dir):
278
args = ['log-for-merge', '--dir', dir]
279
return text_cmd(args)
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))
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)
298
"""FIXME: add the other parameters."""
299
args = ['replay', '--dir', dir]
303
# Project Tree Inventory commands
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)))
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)
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,
336
__pychecker__ = 'maxargs=12'
337
return __inventory_helper(dir, ['inventory'], source, precious, backups,
338
junk, unrecognized, trees,
339
directories, files, both, names, limit)
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,
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')
355
def tagging_method(dir):
356
return one_cmd((cmd().tagging_method, '--dir', dir))
358
def set_tagging_method(dir, method):
359
null_cmd((cmd().tagging_method, '--dir', dir, method))
362
null_cmd((cmd().add, file))
365
null_cmd((cmd().delete, file))
368
return null_cmd((cmd().move, src, dest))
371
status, output = status_one_cmd(('id', file), None, (0,1))
372
if status == 1: return None
373
return output.split('\t')[1]
378
def changeset(orig, mod, dest, files=()):
379
return null_cmd(('changeset', orig, mod, dest) + files)
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))
388
# Archive Transaction Commands
390
def archive_setup(name):
391
null_cmd(('archive-setup', name))
393
def import_(dir, log=None):
394
args = ['import', '--dir', dir]
395
if log is not None: args.extend(('--log', log))
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):
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')
408
args.extend(('--base', base))
410
if file_list is not None:
411
file_list = tuple(file_list)
412
assert 0 < len(file_list)
414
args.extend(map(str, file_list))
415
return sequence_cmd(args, chdir=str(dir))
417
def get(revision, dir, link=False):
419
if link: command.append('--link')
420
command.extend((revision, dir))
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]
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')
437
if to is not None: args.append(to)
438
if limit is not None:
439
assert 0 < len(limit)
445
def categories(archive):
446
return sequence_cmd(('categories', '--archive', archive))
448
def branches(category):
449
return sequence_cmd(('branches', category))
451
def versions(branch, reverse=False):
453
return sequence_cmd(('versions', branch))
455
return sequence_cmd(('versions', '--reverse', branch))
457
def revisions(version, reverse=False):
459
return sequence_cmd(('revisions', version))
461
return sequence_cmd(('revisions', '--reverse', version))
464
def category_exists(archive, category):
465
if not archive_registered(archive):
466
raise errors.ArchiveNotRegistered(archive)
468
return category in categories(archive)
471
def branch_exists(archive, branch):
472
category = NameParser(branch).get_category()
473
if not category_exists(archive, category):
476
return branch in branches(archive+"/"+category)
479
def version_exists(archive, version):
480
package = NameParser(version).get_package()
481
if not branch_exists(archive, package):
484
return version in versions(archive+"/"+package)
487
def revision_exists(archive, version, patchlevel):
489
return patchlevel in revisions(archive+"/"+version)
491
if not version_exists(archive, version):
496
def cat_archive_log(revision):
497
return text_cmd(('cat-archive-log', revision))
500
def cacherev(revision, cache=None):
502
return null_cmd(('cacherev', revision))
504
return null_cmd(('cacherev', '--cache', cache, revision))
507
def cachedrevs(version):
508
return sequence_cmd(('cachedrevs', version))
511
def uncacherev(revision):
512
null_cmd(('uncacherev', revision))
515
def iter_merges(version1, version2=None, reverse=False, metoo=True):
516
subcmd = cmd().merges
518
raise NotImplementedError, \
519
"merges is not implemented in tla 1.0"""
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:
531
def iter_ancestry_graph(revision, merges=False, reverse=False):
532
subcmd = cmd().ancestry_graph
534
raise NotImplementedError, \
535
"ancestry-graph is not implemented in tla 1.0"
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')
544
def ancestor(revision):
545
subcmd = cmd().ancestry_graph
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
554
def previous(revision):
555
subcmd = cmd().ancestry_graph
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
567
def make_log(dir, version=None):
568
args = ['make-log', '--dir', dir]
569
if version is not None:
571
return text_cmd(args)[:-1]
573
def log_name(dir, version=None):
575
version = tree_version(dir)
576
parse = NameParser(version)
577
return dir/FileName('++log.%s--%s'
578
% (parse.get_nonarch(), parse.get_archive()))
580
def add_log_version(dir, version):
581
null_cmd(('add-log-version', '--dir', dir, version))
583
def remove_log_version(dir, version):
584
null_cmd(('remove-log-version', '--dir', dir, version))
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)
598
def iter_log_ls(dir, version, reverse=False):
599
opts = [ cmd().log_ls, '--full', '--dir', dir ]
600
if reverse: opts.append('--reverse')
602
return sequence_cmd(opts)
605
def cat_log(dir, revision):
606
return text_cmd(('cat-log', '--dir', dir, revision))
609
# Multi-project Configuration Commands
611
# Commands for Branching and Merging
613
def tag(source_revision, tag_version):
614
null_cmd(('tag', source_revision, tag_version))
616
def iter_delta (orig, mod, dest):
617
args = ['delta', orig, mod, dest]
618
return sequence_cmd(args)
621
# Local Cache Commands
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)
634
def iter_pristines(dir_name):
635
args = ('pristines', '--dir', dir_name)
636
return sequence_cmd(args)
638
def add_pristine(dir_name, revision):
639
args = ('add-pristine', '--dir', dir_name, revision)
642
def find_pristine(dir_name, revision):
643
args = ('find-pristine', '--dir', dir_name, revision)
647
# Revision Library Commands
649
def iter_revision_libraries():
650
args = ('my-revision-library', '--silent')
652
for val in sequence_cmd(args):
654
except errors.ExecProblem, e:
656
"my-revision-library: no revision library path set\n":
659
def register_revision_library(dirname):
660
null_cmd(('my-revision-library', '--silent', dirname))
662
def unregister_revision_library(dirname):
663
null_cmd(('my-revision-library', '--silent', '--delete', dirname))
665
def library_archives():
666
return sequence_cmd(('library-archives',))
668
def library_categories(archive):
669
return sequence_cmd(('library-categories', '--archive', archive))
671
def library_branches(category):
672
return sequence_cmd(('library-branches', category))
674
def library_versions(branch, reverse=False):
676
return sequence_cmd(('library-versions', branch))
678
return sequence_cmd(('library-versions', '--reverse', branch))
680
def library_revisions(version, reverse=False):
682
return sequence_cmd(('library-revisions', version))
684
return sequence_cmd(('library-revisions', '--reverse', version))
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))
694
def library_remove(revision):
695
null_cmd(('library-remove', revision))
697
def library_find(revision):
698
return text_cmd(('library-find', revision))[:-1]
700
def library_log(revision):
701
return text_cmd(('library-log', revision))
706
class ForkNameParser(str):
708
"""Parse Arch names with with tla.
710
All operations run tla, this is generally too expensive for
711
practical use. Use NameParser instead, which is implemented
714
This class is retained for testing purposes.
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)
722
return 0 == status_cmd(opts, expected=(0,1))
724
def __parse_package_name(self, opt):
725
opts = [ 'parse-package-name' ]
726
if opt: opts.append(opt)
728
status, retval = status_one_cmd(opts, None, (0,1))
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)
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')
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')
771
class NameParser(str):
773
"""Parser for names in Arch archive namespace.
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.
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.
783
:group Specificity level: is_category, is_package, is_version
785
:group Presence name components: has_archive, has_category, has_branch,
786
has_package, has_version, has_revision, has_patchlevel
788
:group Getting name components: get_archive, get_nonarch, get_category,
789
get_branch, get_package, get_version, get_package_version,
792
:group Validating name components: is_archive_name, is_category_name,
793
is_branch_name, is_version_id, is_patchlevel
796
__archive_regex = re.compile('^[-a-zA-Z0-9]+(\.[-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]+$')
804
def __init__(self, s):
805
"""Create a parser object for the given string.
807
:param s: string to parse.
811
parts = self.__parse()
815
parts = None, None, None, None, None, None
817
(self.__archive, self.__nonarch, self.__category,
818
self.__branch, self.__version, self.__level) = parts
821
parts = self.split('/')
823
archive, nonarch = None, parts[0]
824
elif len(parts) == 2:
825
archive, nonarch = parts
829
parts = nonarch.split('--')
830
category, branch, version, level = None, None, None, None
833
elif len(parts) == 2:
834
if self.__name_regex.match(parts[1]):
835
category, branch = parts
837
category, version = parts
838
elif len(parts) == 3:
839
if self.__name_regex.match(parts[1]):
840
category, branch, version = parts
842
category, version, level = parts
843
elif len(parts) == 4:
844
category, branch, version, level = parts
848
if archive and not self.__archive_regex.match(archive):
850
elif not self.__name_regex.match(category):
852
elif branch and not self.__name_regex.match(branch):
854
elif not version and not level: pass
855
elif not self.__version_regex.match(version):
858
elif not self.__level_regex.match(level):
861
return archive, nonarch, category, branch, version, level
863
def is_category(self):
864
"""Is this a category name?
868
return bool(self.__category) and not (self.__branch or self.__version)
870
def is_package(self):
871
"""Is this a package name (category or branch name)?
875
return bool(self.__category) and not self.__version
876
def is_version(self):
877
"""Is this a version name?
881
return bool(self.__version) and not self.__level
883
def has_archive(self):
884
"""Does this include an archive name?
888
return bool(self.__archive)
890
def has_category(self):
891
"""Does this include an category name?
895
return bool(self.__category)
897
def has_package(self):
898
"""Does this include an package name?
902
return bool(self.__category)
904
def has_version(self):
905
"""Does this include a version name?
909
return bool(self.__version)
911
def has_patchlevel(self):
912
"""Does this include a revision name?
916
return bool(self.__level)
918
def get_archive(self):
919
"""Get the archive part of the name
921
:return: archive part of the name, or the default archive name, or None
922
if the name is invalid.
925
if not self.__valid: return None
926
if not self.__archive: return default_archive()
927
return self.__archive
929
def get_nonarch(self):
930
"""Get Non-archive part of the name
932
:return: the name without the archive component, or None if the name is
933
invalid or has no archive component.
936
return self.__nonarch
938
def get_category(self):
939
"""Get the Category name
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
946
return self.__category
948
def get_branch(self):
949
"""Get the branch part of name
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.
956
if not self.__valid: return None
957
if not self.__branch: return str()
960
def get_package(self):
961
"""Get the package name
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.
967
if not self.__valid: return None
968
if self.__branch is None: return self.__category
969
return self.__category + '--' + self.__branch
971
def get_version(self):
972
"""Get the version id part of the name
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.
978
return self.__version
980
def get_package_version(self):
981
"""Get the unqualified version name
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.
987
if not self.__version: return None
988
return self.get_package() + '--' + self.__version
990
def get_patchlevel(self):
991
"""Get the patch-level part of the name
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.
999
def is_archive_name(klass, s):
1000
"""Is this string a valid archive name?
1002
:param s: string to validate.
1006
return bool(klass.__archive_regex.match(s))
1007
is_archive_name = classmethod(is_archive_name)
1009
def is_category_name(klass, s):
1010
"""Is this string a valid category name?
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.
1016
:param s: string to validate.
1020
return bool(klass.__name_regex.match(s))
1021
is_category_name = classmethod(is_category_name)
1023
def is_branch_name(klass, s):
1024
"""Is this string a valid category name?
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.
1030
:param s: string to validate.
1034
return bool(klass.__name_regex.match(s))
1035
is_branch_name = classmethod(is_branch_name)
1037
def is_version_id(klass, s):
1038
"""Is this string a valid version id?
1040
:param s: string to validate.
1044
return bool(klass.__version_regex.match(s))
1045
is_version_id = classmethod(is_version_id)
1047
def is_patchlevel(klass, s):
1048
"""Is this string a valid unqualified patch-level name?
1050
:param s: string to validate.
1054
return bool(klass.__level_regex.match(s))
1055
is_patchlevel = classmethod(is_patchlevel)
1058
### Pika escaping ###
1060
def tla_name_escape(text):
1061
return text_cmd(('escape', text))
1063
def tla_name_unescape(text):
1064
return text_cmd(('escape', '--unescaped', text))
1067
### Misc features ###
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))
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]
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