15
15
# along with this program; if not, write to the Free Software
16
16
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
19
from pybaz.backends.baz import sequence_cmd
24
from pylon.errors import *
25
from pylon import errors
26
from pylon import paths
27
from pylon import util
31
import dateutil.parser
34
from optparse import OptionGroup
36
from add_tagline import AlreadyTagged, NoCommentSyntax
22
39
__docformat__ = "restructuredtext"
23
40
__doc__ = "Utility functions to be used by commands"
25
def direct_merges(merges, excludes=[]):
43
def alias_endpoint(spectype, spec):
44
"""Determines the endpoint for iteration. Assumes input is of the form \
47
:param spectype: The leading portion of the specification, e.g. "tprev"
48
:type spectype: string
49
:param spec: The actual spec
51
:return: The endpoint to use
54
if len(spec)>len(spectype+":"):
56
endpoint=int(spec[len(spectype+":"):])
58
raise errors.CantDetermineRevision(spec,
59
"\""+spec[len(spectype+":"):]\
60
+"\" is not an integer.")
62
raise errors.CantDetermineRevision(spec, "\""+str(endpoint)+\
70
def determine_version_arch(arg, tree):
72
Returns an `arch.Version`, using the default archive if necessary.
74
:param arg: version name
76
:rtype: `arch.Version`
78
name=arch.NameParser(expand_alias(arg, tree))
79
if not name.is_version():
80
raise CantDetermineVersion(arg, "\"%s\" is not a version" % name)
81
if not name.has_archive():
83
str(arch.default_archive())+"/"+ name.get_package_version())
85
return arch.Version(name)
87
def determine_version_tree(arg, tree):
89
Returns an `arch.Version`, using the tree-version archive if necessary.
91
:param arg: version name
93
:param tree: The tree to use for getting the archive (if needed).
94
:type tree: `arch.ArchSourceTree`
95
:rtype: `arch.Version`
98
return tree.tree_version
99
name=arch.NameParser(expand_alias(arg, tree))
100
if not name.is_version():
101
raise CantDetermineVersion(arg, "\"%s\" is not a version" % name)
102
if not name.has_archive():
104
str(tree.tree_version.archive)+"/"+
105
name.get_package_version())
107
return arch.Version(name)
110
def determine_version_or_revision_tree(tree, spec):
111
name=arch.NameParser(expand_alias(arg.tree))
112
if name.is_version():
113
return determine_version_tree(name, tree)
115
return determine_revision_tree(tree, name)
118
def expand_prefix_alias(args, tree, prefix="^"):
120
Treats all arguments that have the prefix as aliases, and expands them.
122
:param args: The list of arguments.
123
:type args: List of str
124
:param tree: The current tree (or None, if there is no current tree)
125
:type tree: `arch.ArchSourceTree`
126
:param prefix: The prefix to use for aliases
132
if segment.startswith(prefix):
133
cropped=segment[len(prefix):]
134
expansion=expand_alias(cropped, tree)
135
if expansion != cropped:
136
expanded.append(str(expansion))
138
expanded.append(segment)
143
"""Determine the latest revision of the version of the tag source.
145
:param tree: The tree to determine the current revision of
146
:type tree: `arch.WorkingTree`
147
:return: The archive-latest revision of the tag source version
148
:rtype: `arch.Revision`
151
raise CantDetermineRevision("tagcur", "No source tree available.")
152
spec = pylon.tag_source(tree)
154
raise CantDetermineRevision("tagcur",
155
"This revision has no ancestor version.")
156
ensure_archive_registered(spec.version.archive)
157
spec = spec.version.iter_revisions(reverse=True).next()
161
def expand_alias(spec, tree):
163
Attempts to perform alias expansion on a given spec. Will expand both \
164
automatic aliases and user-specified aliases
166
:param spec: The specification to expand.
168
:param tree: The current working tree
169
:type tree: `arch.ArchSourceTree`
170
:return: The expanded result, or spec if no expansion was done
171
:rtype: string or Revision
174
name = arch.NameParser(spec)
175
if name.is_version() or name.has_patchlevel():
177
if spec.startswith("^"):
179
if pylon.paths.is_url_path(spec):
181
spec, dummy=pylon.paths.full_path_decode(spec)
182
except pylon.paths.CantDecode, e:
185
elif spec.startswith("tprev"):
187
raise CantDetermineRevision(spec, "No source tree available.")
189
iter = tree.iter_logs(reverse=True)
190
endpoint=alias_endpoint("tprev", spec)
192
for q in range(endpoint):
194
spec = iter.next().revision
195
except StopIteration:
196
raise CantDetermineRevision(spec,
197
"Value \"%d\" is out of range." % endpoint)
198
elif spec.startswith("tcur"):
200
raise CantDetermineRevision(spec, "No source tree available.")
204
version=determine_version_tree(expand_alias(spec[5:], tree),
206
spec = tree.iter_logs(version, reverse=True).next().revision
207
except StopIteration:
209
version = "default version (%s)" % str(tree.tree_version)
210
raise CantDetermineRevision(spec,
211
"Tree contains no logs for %s." % str(version))
214
raise CantDetermineRevision(spec, "No source tree available.")
215
spec = pylon.tag_source(tree)
217
raise CantDetermineRevision("ttag",
218
"This revision has no ancestor version.")
220
elif spec == "tagcur":
223
elif spec.startswith("tanc"):
224
iter = pylon.iter_ancestry(tree)
225
endpoint=alias_endpoint("tanc", spec)
227
for q in range(endpoint):
230
except StopIteration:
231
raise CantDetermineRevision(spec,
232
"Value \"%d\" is out of range." % endpoint)
234
elif spec.startswith("tmod:"):
236
return modified_iter(spec[5:], tree).next().revision
237
except StopIteration, e:
238
raise CantDetermineRevision(spec,
239
"Can't find a revision that changed \"%s\"." % spec[5:])
242
elif spec.startswith("tdate:"):
243
default=datetime.datetime.now().replace(hour=23, minute=59, second=59,
246
date = dateutil.parser.parse(spec[6:], default=default).timetuple()
247
except ValueError, e:
248
raise CantDetermineRevision(spec, str(e))
249
if date > default.timetuple():
250
raise CantDetermineRevision(spec, "Date is in the future.")
252
for log in pylon.iter_any_ancestry(tree, tree.tree_revision):
253
if not isinstance(log, arch.Patchlog):
256
revision = log.revision
259
raise CantDetermineRevision(spec, "No logs found for date \"%s\"."\
263
elif spec.startswith("acur"):
265
version=determine_version_arch(expand_alias(spec[5:], tree), tree)
267
version=tree.tree_version
269
raise CantDetermineRevision(spec, "No source tree available.")
270
ensure_archive_registered(version.archive)
271
spec=version.iter_revisions(True).next()
273
elif spec.startswith("mergeanc"):
275
raise CantDetermineRevision(spec, "No source tree available.")
277
if spec == "mergeanc":
279
other_version = pylon.iter_partners(tree).next()
280
other_revision = other_version.iter_revisions(True).next()
281
except StopIteration, e:
282
other_revision = tag_cur(tree)
284
other_revision = determine_revision_arch(tree,
285
spec[len("mergeanc:"):])
286
other_tree = pylon.find_or_make_local_revision(other_revision)
287
merge_anc = pylon.merge_ancestor2(tree, other_tree,
289
if merge_anc is None:
290
raise CantDetermineRevision(spec, "Can't find a common ancestor.")
293
elif spec.startswith("micro"):
294
specsection = spec.split(':')
295
if len(specsection) > 1:
296
version = determine_version_arch(specsection[1], tree)
298
version = tree.tree_version
300
return pylon.iter_micro(tree, version).next().revision
301
except StopIteration, e:
302
raise CantDetermineRevision(spec, "No next microbranch revision")
308
for parts in pylon.iter_all_alias(tree):
309
if spec.startswith(parts[0]):
310
expansion=parts[1]+spec[len(parts[0]):]
311
if expansion != None:
316
def determine_revision_arch(tree, spec=None, check_existence=True, allow_package=False):
318
Determines a revision from a revision, patchlevel, package-version, or package.
319
Uses the archive to supply missing elements.
321
:param tree: The working tree, used to interpret patchlevels
322
:type tree: `arch.WorkingTree`
323
:param spec: The name specification
328
spec=expand_alias(spec, tree)
329
name = arch.NameParser(spec)
331
name = arch.NameParser(tree.tree_version)
333
raise CantDetermineRevision("", "Not using a source tree.")
334
if name.has_patchlevel() or name.is_version() or (allow_package and name.is_package()):
335
if not name.has_archive():
336
archive=arch.default_archive()
338
raise CantDetermineRevision(spec, "No default archive is set")
339
name=arch.NameParser(str(archive)+"/"+str(name))
340
if name.has_patchlevel():
341
return arch.Revision(name)
343
if name.is_version():
344
version = arch.Version(name)
345
ensure_archive_registered(version.archive)
347
return get_latest_revision_anywhere(version)
348
except StopIteration:
349
if not arch.Version(name).exists():
350
raise CantDetermineRevision(spec, "The version \""+str(name)+"\" does not exist")
352
raise CantDetermineRevision(spec, "The version \""+str(name)+"\" has no revisions")
356
raise CantDetermineRevision(spec,
357
"Error listing revisions for \""+str(name)+"\".")
359
elif (allow_package and name.is_package()):
360
branch = arch.Branch(name)
361
ensure_archive_registered(branch.archive)
363
return get_latest_revision_anywhere(branch)
364
except StopIteration:
365
if not arch.Branch(name).exists():
366
raise CantDetermineRevision(spec, "The package \""+str(name)+"\" does not exist")
368
raise CantDetermineRevision(spec, "The package \""+str(name)+"\" has no revisions")
372
raise CantDetermineRevision(spec,
373
"Error listing revisions for \""+str(name)+"\".")
377
raise CantDetermineRevision(spec, "Not in a project tree")
378
name=arch.NameParser(str(tree.tree_version)+"--"+spec)
379
if not name.has_patchlevel():
380
raise CantDetermineRevision(spec,
381
"Not a revision, package-version, or patchlevel.")
382
return arch.Revision(name)
384
def determine_revision_tree(tree, spec=None):
386
Determines a revision from a revision, patchlevel, or package-version.
388
Uses the tree to supply missing elements
390
:param tree: The tree to use by default
391
:type tree: `arch.ArchSourceTree`
392
:param spec: The revision specification
394
:rtype: `arch.Revision`
398
spec=expand_alias(spec, tree)
399
name = arch.NameParser(spec)
401
name = arch.NameParser(str(tree.tree_version))
404
if name.is_version():
406
version = name.get_package_version()
407
if name.has_archive():
408
version = name.get_archive()+"/"+version
410
version = arch.Version(str(tree.tree_version.archive)
412
revision = tree.iter_logs(version, True).next().revision
413
except StopIteration:
414
raise CantDetermineRevision(spec,
415
"No revisions present for this version.")
417
if not name.has_patchlevel():
418
name = arch.NameParser (str(tree.tree_version) +"--"+ name)
419
if name.has_patchlevel():
420
revision=name.get_nonarch()
421
if not name.has_archive():
422
name = arch.NameParser (str(tree.tree_version))
423
revision = name.get_archive()+"/"+revision
425
raise CantDetermineRevision(spec,
426
"Not a revision, package-version, or patchlevel.")
427
return arch.Revision(revision)
429
def determine_version_revision_tree(spec, tree):
430
"""Get a version or revision from tree and input. Version preferred.
432
:param spec: A revision, version, patchlevel or alias
434
:param tree: The tree to use for determining revision/version info
435
:type tree: `arch.ArchSourceTree`
436
:return: The version or revision specified
437
:rtype: `arch.Version` or `arch.Revision`
440
return determine_version_tree(spec, tree)
441
except errors.CantDetermineVersion, e:
442
return determine_revision_tree(tree, spec)
445
def show_diffs(changeset):
447
Print diff output for a changeset.
449
:param changeset: the name of the changeset
450
:type changeset: string
453
for line in pylon.diff_iter2(changeset):
457
def show_custom_diffs(changeset, diffargs, orig, mod):
459
Generates and displays alternative diffs for a changeset.
460
:param changeset: The (initialized) ChangesetMunger to generate diffs for
461
:type changeset: `pylon.ChangesetMunger`
462
:param diffargs: The arguments to pass to diff
463
:type diffargs: list of str
464
:param orig: The path to the ORIG directory
466
:param mod: The path to the MOD directory
470
for entry in changeset.get_entries().itervalues():
471
if entry.diff is None:
474
print pylon.invoke_diff(orig, entry.orig_name, mod,
476
diffargs).rstrip("\n")
477
except pylon.BinaryFiles:
479
except arch.util.ExecProblem, e:
483
def colorize (item, suppress_chatter=False):
485
Colorizes and prints tla-style output, according to class type.
487
Colorizing can be disabled by setting options.colorize to false.
489
:param item: The item to display
490
:type item: Any tla output object, string
491
:param suppress_chatter: If true, no chatter output is displayed.
492
:type suppress_chatter: bool
494
if options.colorize not in ('yes', 'no', 'maybe'): raise BadOption
495
colorize = options.colorize
496
if colorize == 'maybe':
497
if sys.stdout.isatty() and terminal.has_ansi_colors():
505
if isinstance(item, arch.Chatter):
506
if not suppress_chatter:
507
print terminal.colorstring(str(item), 'yellow')
508
#sys.stdout.write(term_title("Fai: %s" % item.text))
509
elif isinstance(item, arch.PatchConflict):
510
print terminal.colorstring(str(item), 'white', True, 'red')
511
elif isinstance(item, arch.FileAddition):
512
print terminal.colorstring(str(item), 'green')
513
elif isinstance(item, arch.FileDeletion):
514
print terminal.colorstring(str(item), 'red', True)
515
elif isinstance(item, arch.FileModification):
516
print terminal.colorstring(str(item), 'magenta', True)
517
elif isinstance(item, arch.FilePermissionsChange):
518
print terminal.colorstring(str(item), 'magenta')
519
elif isinstance(item, arch.FileRename):
520
print "=> "+terminal.colorstring(item.oldname, 'red', True)+"\t"+terminal.colorstring(item.name, 'green')
521
elif isinstance(item, pylon.DiffFilenames):
522
print terminal.colorstring("---"+item.orig, 'red', True)
523
print terminal.colorstring("+++"+item.mod, 'green')
524
elif isinstance(item, pylon.DiffHunk):
525
print terminal.colorstring(str(item), 'blue')
526
elif isinstance(item, pylon.DiffAddLine):
527
print terminal.colorstring(str(item), 'green')
528
elif isinstance(item, pylon.DiffRemoveLine):
529
print terminal.colorstring(str(item), 'red', True)
530
elif isinstance(item, pylon.DiffLine):
537
def confirm(question, default=False):
539
Prompts a user to confirm or deny an action.
541
:param question: The question to ask
542
:type question: string
543
:param default: The default choice
544
:type default: boolean
548
sys.stdout.write(question)
550
sys.stdout.write(" [Y/n] ")
552
sys.stdout.write(" [y/N] ")
553
answer=sys.stdin.readline().strip().lower()
564
Prompts the user to decide whether to perform an action
566
Prompts can be overridden by setting their option to "always" or "never"
567
:param type: the prompt type, as defined in options
570
if options.prompts[type]=="always":
572
elif options.prompts[type]=="never":
574
elif options.prompts[type]=="occasionally":
575
return confirm(options.promptquestions[type], False)
576
elif options.prompts[type]=="often":
577
return confirm(options.promptquestions[type], True)
581
def get_mirror_source(archive):
583
Returns the source of a mirror, if available. If not available, or
584
if the archive is not a mirror, returns None. We check whether it's a
585
mirror second, to reduce network use.
587
:param archive: The archive to find the source for
588
:type archive: `arch.Archive`
591
sourcename=str(archive)+"-SOURCE"
592
for arch_entry in arch.iter_archives():
593
if str(arch_entry) == sourcename and archive.is_mirror:
597
def get_best_branch_or_version(branch_version):
599
Returns the most authentic Branch or Version available. If there is a mirror source,
600
returns the version for that. Otherwise, returns the input.
602
:param branch_version: The `arch.Version` to retrieve the version of
603
:type branch_version: `arch.Version` or `arch.Branch`
606
maybe_source = arch.Archive(str(branch_version.archive)+"-SOURCE")
607
if not (maybe_source.is_registered() and prompt("Source latest revision")):
608
return branch_version
610
source=get_mirror_source(branch_version.archive)
612
return branch_version
613
elif arch.NameParser(branch_version).is_package():
614
return arch.Branch(source+"/"+branch_version.nonarch)
616
return arch.Version(source+"/"+branch_version.nonarch)
618
def get_latest_revision_anywhere(branch_version):
620
Returns the latest revision of the branch or version. If a mirror source is
621
available, uses that. Otherwise, uses the normal archive. Revisions
622
are returned in terms of their official name, not -SOURCE
624
:param branch_version: The version to find the latest revision for
625
:type branch_version: `arch.Version` or `arch.Branch`
626
:rtype: `arch.Revision`
628
ensure_archive_registered(branch_version.archive)
629
source_branch_version = get_best_branch_or_version(branch_version)
630
if not source_branch_version.exists():
632
source_revision = source_branch_version.iter_revisions(True).next()
633
revision = arch.Revision(str(branch_version.archive) + "/"+
634
source_revision.nonarch)
638
def update(revision, tree, patch_forward=False):
640
Puts the given revision in a tree, preserving uncommitted changes
642
:param revision: The revision to get
643
:param tree: The tree to update
645
command=['update', '--dir', str(tree)]
647
command.append('--forward')
648
command.append(str(revision))
649
return arch.util.exec_safe_iter_stdout('tla', command, expected=(0, 1, 2))
652
def apply_delta(a_spec, b_spec, tree, ignore_present=False):
653
"""Apply the difference between two things to tree.
655
:param a_spec: The first thing to compare
656
:type a_spec: `arch.Revision`, `arch.ArchSourceTree`
657
:param b_spec: The first thing to compare
658
:type b_spec: `arch.Revision`, `arch.ArchSourceTree`
659
:param tree: The tree to apply changes to
660
:type tree: `arch.ArchSourceTree`
661
:rtype: Iterator of changeset application output
663
tmp=pylon.util.tmpdir(tree)
664
changeset=tmp+"/changeset"
665
delt=arch.iter_delta(a_spec, b_spec, changeset)
668
yield arch.Chatter("* applying changeset")
669
for line in delt.changeset.iter_apply(tree, ignore_present):
674
def iter_apply_delta_filter(iter):
675
show_file_changes=False
677
if pylon.chattermatch(line, "applying changeset"):
678
show_file_changes=True
679
if pylon.chattermatch(line, "changeset:"):
681
elif not show_file_changes and isinstance(line, arch.MergeOutcome):
689
Finds an editor to use. Uses the EDITOR environment variable.
693
if os.environ.has_key("EDITOR") and os.environ["EDITOR"] != "":
694
return os.environ["EDITOR"]
696
raise NoEditorSpecified
699
def invoke_editor(filename):
701
Invokes user-specified editor.
703
:param filename: The file to edit
704
:type filename: string
706
os.system("%s %s" % (find_editor(), filename))
708
def iter_reverse(iter):
709
"""Produce brute-force reverse iterator.
711
:param iter: The iterator to run through in reverse
712
:type iter: Iterator of any
713
:rtype: reversed list of any
715
reversed = list(iter)
719
def iter_cacherevs(version, reverse=False):
720
"""Iterates through cached revisions as revisions, not patchlevels"""
721
iter = version.iter_cachedrevs()
725
return iter_reverse(iter)
727
def iter_present(tree, version, reverse=False):
728
"""The inverse of skip-present. Iterates through revisions that are
729
missing, but merge revisions already present in the tree.
730
:param tree: The tree to look for patchlogs in
731
:type tree: `arch.ArchSourceTree`
732
:param version: The version of patchlogs to look for
733
:type version: `arch.Version`
734
:param reverse: If true, go in reverse
737
miss = pylon.iter_missing(tree, version, reverse)
738
miss_skip = pylon.iter_missing(tree, version, reverse, skip_present=True)
739
for skip_revision in miss_skip:
740
for miss_revision in miss:
741
if miss_revision == skip_revision:
744
for miss_revision in miss:
748
def iter_partner_missing(tree, reverse=False, skip_present=False):
749
"""Generate an iterator of all missing partner revisions.
751
:param tree: The tree that may be missing revisions from partners
752
:type tree: `arch.ArchSourceTree`
753
:param reverse: If true, perform iteration in reverse
755
:return: Iterator of missing partner revisions
756
:rtype: Iterator of `arch.Revision`
758
pylon.valid_tree_root(tree)
759
for version in pylon.iter_partners(tree, tree.tree_version):
760
ensure_archive_registered(version.archive)
761
for revision in pylon.iter_missing(tree, version, reverse,
765
def iter_skip(iter, skips=0):
766
"""Ajusts an iteration list by skipping items at beginning or end. May
767
modify original iterator.
769
:param iter: The base iterator to use.
770
:type iter: Iterator of anything
771
:param skips: Positive numbers skip from the beginning. Negative numbers
774
:return: an iterator that starts or stops at a different place
775
:rtype: iterator of iter's iter type
780
for q in range(skips):
784
return iter_skip_end(iter, -skips)
786
def iter_skip_end(iter, skips):
787
"""Generates an iterator based on iter that skips the last skips items
789
:param skips: number of items to skip from the end.
791
:return: an iterator that stops at a different place
792
:rtype: iterator of iter's iteration type
794
iter = iter_skip(iter, -skips + 1)
801
def modified_iter(modified, tree, originator=False):
802
sections = modified.split(':')
803
revision = tree.tree_revision
804
if len(sections) == 1:
805
return pylon.iter_changedfile(tree, revision, sections[0])
806
elif len(sections) == 2 and originator:
807
return pylon.iter_changed_file_line_orig(tree, revision,
810
elif len(sections) == 2:
811
return pylon.iter_changed_file_line(tree, revision,
816
def iter_combine(iterators):
817
"""Generate an iterator that iterates through the values in several.
818
:param iterators: The iterators to get values from
819
:type iterators: List of iterator
820
:rtype: iterator of input iterators' value type
822
for iterator in iterators:
823
for value in iterator:
827
def is_tla_command(command):
828
"""Determines whether the specified string is a tla command.
830
:param command: The string to check
833
for line in pylon.iter_tla_commands():
834
if (line == command):
840
class BadCommandOption:
841
def __init__(self, message):
847
def raise_get_help(option, opt, value, parser):
851
class CmdOptionParser(optparse.OptionParser):
852
def __init__(self, usage):
853
optparse.OptionParser.__init__(self, usage)
854
self.remove_option("-h")
855
self.add_option("-h", "--help", action="callback",
856
callback=raise_get_help, help="Print a help message")
858
def error(self, message):
859
raise BadCommandOption(message)
861
def iter_options(self):
862
return iter_combine([self._short_opt.iterkeys(),
863
self._long_opt.iterkeys()])
866
def add_tagline_or_explicit_id(file, tltl=False, implicit=False):
867
"""This is basically a wrapper for add-tagline
869
:param file: The file to add an id for
871
:param tltl: Use lord-style tagline
874
opt = optparse.Values()
875
opt.__dict__["tltl"] = tltl
876
opt.__dict__["id"] = None
877
opt.__dict__["implicit"] = implicit
879
if not os.path.isfile(file) or os.path.islink(file):
883
add_tagline.tagFile(file, opt)
884
except add_tagline.NoCommentSyntax:
885
print "Can't use tagline for \"%s\" because no comment sytax known." % \
887
if prompt("Fallthrough to explicit"):
894
def iter_browse_memo(search):
895
"""Memoized version of iter_browse.
896
:param search: The string to find entries for
898
:return: Any categories, branches, versions that matched
899
:rtype: iterator of `arch.Category`, `arch.Branch`, `arch.Version`, \
903
if arch.NameParser.is_archive_name(search):
906
name = arch.NameParser(search)
907
archive = name.get_archive()
908
if not browse_memo.has_key(str(archive)):
909
browse_memo[str(archive)] = list(pylon.iter_browse(archive))
911
for entry in browse_memo[str(archive)]:
912
if str(entry).startswith(search):
916
def iter_revision_completions(arg, tree):
917
if arg.count('/') == 0:
918
for completion in arch.iter_archives():
919
yield str(completion)+"/"
920
for completion in pylon.iter_all_alias(tree):
923
default_archive = str(arch.default_archive())
924
if default_archive is not None:
925
default_archive_arg = default_archive+'/'+arg
926
for completion in iter_browse_memo(default_archive_arg):
927
completion = str(completion)
928
if completion.startswith(default_archive_arg) and \
929
completion != default_archive_arg:
930
yield completion[len(default_archive)+1:]
933
if arg.endswith('/'):
936
expansion = expand_alias(arg, tree)
937
for completion in iter_browse_memo(expansion):
938
completion = str(completion)
939
if completion.startswith(expansion) and completion != expansion:
940
yield arg + completion[len(expansion):]
945
def ensure_archive_registered(archive, location=None):
946
if archive.is_registered():
948
user_supplied = False
950
colorize (arch.Chatter("* Looking up archive %s" % archive))
955
colorize (arch.Chatter("* Checking bonehunter.rulez.org"))
956
home = pylon.bh_lookup(archive)
957
except ArchiveLookupError, e:
961
print "Found location %s" % home
962
if prompt("Use lookup location"):
966
colorize (arch.Chatter("* Checking sourcecontrol.net"))
967
(home, mirror) = pylon.sc_lookup(archive)
969
print "Found location %s" % home
970
if prompt("Use lookup location"):
972
if location is None and mirror is not None:
973
print "Found mirror location %s" % mirror
974
if prompt("Use lookup mirror location"):
976
except ArchiveLookupError, e:
979
print "If you know the location for %s, please enter it here." % \
981
location = raw_input("Fai> ")
988
raise arch.errors.ArchiveNotRegistered(archive)
990
print "Registering archive \""+str(archive)+"\""
991
if location_cacheable(location) and prompt("Register cached"):
992
cached_location="cached:"+location
993
ar = register_archive(str(archive), cached_location, str(archive))
994
elif prompt ("Make local mirror"):
995
ar = arch.Archive(str(archive)+"-SOURCE")
996
if not ar.is_registered():
997
ar = register_archive(str(archive)+"-SOURCE", location, str(archive))
998
ar.make_mirror(str(archive),
999
os.path.expanduser(options.mirror_path)+"/"+str(archive))
1002
ar = register_archive(str(archive), location, str(archive))
1003
if user_supplied and prompt("Submit user-supplied"):
1004
pylon.bh_submit(archive, location)
1006
pylon.ensure_archive_registered = ensure_archive_registered
1008
def location_cacheable(location):
1009
return not location.startswith('/') and not location.startswith('cached:')\
1010
and pylon.ArchParams().exists('=arch-cache')
1012
def ensure_revision_exists(revision):
1013
ensure_archive_registered(revision.archive)
1014
if revision.exists():
1016
source = get_mirror_source(revision.archive)
1017
if source is not None:
1018
if arch.Revision(str(source)+"/"+revision.nonarch).exists():
1019
cmd = pylon.mirror_archive(str(source), str(revision.archive),
1020
str(revision.version.nonarch))
1021
for line in arch.classify_chatter(cmd):
1024
raise NoSuchRevision(revision)
1026
pylon.ensure_revision_exists = ensure_revision_exists
1029
def merge_ancestor(mine, other, other_start):
1030
"""Determines an ancestor suitable for merging, according to the tree logs.
1032
:param mine: Tree to find an ancestor for (usually the working tree)
1033
:type mine: `arch.WorkingTree`
1034
:param other: Tree that merged or was merged by mine
1035
:type other: `arch.ArchSourceTree`
1036
:param other_start: The start revision to use for the other tree
1037
:type other_start: `arch.Revision`
1038
:return: Log of the merged revision
1039
:rtype: `arch.Patchlog`
1041
my_ancestors = list (pylon.iter_ancestry_logs(mine))
1042
other_ancestors = list (pylon.iter_ancestry_logs(other,
1045
other_ancestor = None
1046
mine_ancestor = None
1048
for log in my_ancestors:
1049
for oth_log in other_ancestors:
1050
if log.continuation_of:
1051
if oth_log.revision == log.continuation_of:
1052
mine_ancestor = oth_log
1054
elif oth_log.revision in log.new_patches:
1055
mine_ancestor = oth_log
1057
if mine_ancestor is not None:
1060
for log in other_ancestors:
1061
if log == mine_ancestor:
1063
for my_log in my_ancestors:
1064
if log.continuation_of:
1065
if my_log.revision == my_log:
1066
other_ancestor = my_log
1068
elif my_log.revision in log.new_patches:
1069
other_ancestor = my_log
1071
if other_ancestor is not None:
1074
if other_ancestor is not None:
1075
return other_ancestor
1077
return mine_ancestor
1082
def iter_supported_switches(cmd):
1083
for line in pylon.iter_tla_cmd_help(cmd):
1084
if not line.startswith(" -"):
1086
chunks = line[2:].split()
1087
if chunks[0][-1] == ",":
1088
yield chunks[0][:-1]
1093
def supports_switch(cmd, switch):
1094
return switch in list(iter_supported_switches(cmd))
1097
def register_archive(name, location, expected_name=None):
1098
"""Wrapper that can require a certain official_name for the location
1099
:param name: The name of the archive to register
1100
:param location: The location where a version is stored
1101
:param expected_name: If supplied, the location's expected official_name
1103
ar = arch.register_archive(name, location)
1104
if expected_name is not None:
1105
arname = ar.official_name
1106
if arname!=expected_name:
1107
print "The archive at this location is wrong: %s" %\
1109
if not prompt("Register wrong name"):
1111
raise WrongArchName(location, expected_name, arname)
1115
def direct_merges(merges):
26
1116
"""Get a list of direct merges, from a list of direct and indirect
28
1118
:param merges: Iterator of merge patchlogs
29
:type merges: iter of `pybaz.Patchlog`
1119
:type merges: iter of `arch.Patchlog`
30
1120
:return: The direct merges
31
:rtype: list of `pybaz.Patchlog`
1121
:rtype: list of `arch.Patchlog`
34
1125
logs = list(merges)
39
this_indirect = set([str(f) for f in log.new_patches
40
if f != log.revision and
41
str(f) not in indirect])
42
except pybaz.errors.NamespaceError:
44
print "log ", log, " unusable, attempting to use archive copy."
45
log = pybaz.Revision(str(log.revision)).patchlog
46
this_indirect = set([str(f) for f in log.new_patches
47
if f != log.revision and
48
str(f) not in indirect])
49
indirect.update(this_indirect)
1129
indirect.extend([f for f in log.new_patches if f != log.revision])
50
1130
if log.continuation_of is not None:
51
1131
# continuations list everything in new_patches
87
1165
if revision.patchlevel == 'version-0':
88
1166
raise RuntimeError("cannot determine prior namespace level for a "
89
1167
"version-0 patch ('%s')" % revision)
90
if revision.patchlevel == 'versionfix-1':
1168
if revision.patchlevel == 'version-1':
91
1169
return revision.version['version-0']
92
if revision.patchlevel.startswith('versionfix'):
93
level = int(revision.patchlevel[len('versionfix-'):]) -1
94
return revision.version['versionfix-%d' % level]
1170
if revision.patchlevel.startswith('version'):
1171
level = int(revision.patchlevel[len('version-'):]) -1
1172
return revision.version['version-%d' % level]
95
1173
raise NotImplementedError
97
def iter_new_merges(tree, version, reverse=False):
98
"""List patchlogs that are new in this tree since the last commit.
100
:param tree: The working tree to calculate new revisions in
102
:param version: The version to use when determining new logs
104
:param reverse: If true, list backwards, from newest to oldest
106
:return: An iterator for new revision logs in this tree
107
:rtype: Iterator of `pybaz.Patchlog`
109
assert (isinstance(version, pybaz.Version))
110
for line in _iter_new_merges(tree, version.fullname, reverse):
111
yield pybaz.Patchlog(line, tree)
113
def _iter_new_merges(directory, version, reverse):
114
"""List patchlogs that are new in this tree since the last commit.
116
:param directory: The working tree to calculate new revisions in
118
:param version: The version to use when determining new logs
120
:param reverse: If true, list backwards, from newest to oldest
122
:return: An iterator for names of revisions new in this tree
123
:rtype: Iterator of str
125
args = [ 'new-merges', '--dir', directory ]
127
args.append('--reverse')
129
return sequence_cmd(args)
1175
def require_version_exists(version, spec):
1176
if not version.exists():
1177
raise errors.CantDetermineVersion(spec,
1178
"The version %s does not exist." \
1182
def revision_iterator(tree, type, args, reverse, modified, shallow):
1183
"""Produce an iterator of revisions
1185
:param tree: The project tree or None
1186
:type tree: `arch.ArchSourceTree`
1187
:param type: The type of iterator to produce
1189
:param args: The commandline positional arguments
1190
:type args: list of str
1191
:param reverse: If true, iterate in reverse
1193
:param modified: if non-null, use to create a file modification iterator
1200
if modified is not None:
1201
iter = modified_iter(modified, tree, shallow != True)
1205
return iter_reverse(iter)
1206
elif type == "archive":
1209
raise CantDetermineRevision("", "Not in a project tree")
1210
version = determine_version_tree(spec, tree)
1212
version = determine_version_arch(spec, tree)
1213
ensure_archive_registered(version.archive)
1214
require_version_exists(version, spec)
1215
return version.iter_revisions(reverse)
1216
elif type == "cacherevs":
1219
raise CantDetermineRevision("", "Not in a project tree")
1220
version = determine_version_tree(spec, tree)
1222
version = determine_version_arch(spec, tree)
1223
ensure_archive_registered(version.archive)
1224
require_version_exists(version, spec)
1225
return iter_cacherevs(version, reverse)
1226
elif type == "library":
1229
raise CantDetermineRevision("",
1230
"Not in a project tree")
1231
version = determine_version_tree(spec, tree)
1233
version = determine_version_arch(spec, tree)
1234
return version.iter_library_revisions(reverse)
1235
elif type == "logs":
1237
raise CantDetermineRevision("", "Not in a project tree")
1238
return tree.iter_logs(determine_version_tree(spec, \
1240
elif type == "missing" or type == "skip-present":
1242
raise CantDetermineRevision("", "Not in a project tree")
1243
skip = (type == "skip-present")
1244
version = determine_version_tree(spec, tree)
1245
ensure_archive_registered(version.archive)
1246
require_version_exists(version, spec)
1247
return pylon.iter_missing(tree, version, reverse, skip_present=skip)
1249
elif type == "present":
1251
raise CantDetermineRevision("", "Not in a project tree")
1252
version = determine_version_tree(spec, tree)
1253
ensure_archive_registered(version.archive)
1254
require_version_exists(version, spec)
1255
return iter_present(tree, version, reverse)
1257
elif type == "new-merges" or type == "direct-merges":
1259
raise CantDetermineRevision("", "Not in a project tree")
1260
version = determine_version_tree(spec, tree)
1261
ensure_archive_registered(version.archive)
1262
require_version_exists(version, spec)
1263
iter = pylon.iter_new_merges(tree, version, reverse)
1264
if type == "new-merges":
1266
elif type == "direct-merges":
1267
return pylon.direct_merges(iter)
1269
elif type == "missing-from":
1271
raise CantDetermineRevision("", "Not in a project tree")
1272
revision = determine_revision_tree(tree, spec)
1273
libtree = pylon.find_or_make_local_revision(revision)
1274
return pylon.iter_missing(libtree, tree.tree_version, reverse)
1276
elif type == "partner-missing":
1277
return iter_partner_missing(tree, reverse)
1279
elif type == "ancestry":
1280
revision = determine_revision_tree(tree, spec)
1281
iter = pylon.iter_any_ancestry(tree, revision)
1285
return iter_reverse(iter)
1287
elif type == "archive-ancestry":
1288
revision = determine_revision_tree(tree, spec)
1289
iter = revision.iter_ancestors(metoo=True)
1293
return iter_reverse(iter)
1295
elif type == "dependencies" or type == "non-dependencies" or \
1296
type == "with-deps":
1297
nondeps = (type == "non-dependencies")
1298
revision = determine_revision_tree(tree, spec)
1299
anc_tree = pylon.find_or_make_local_revision(revision)
1300
anc_iter = pylon.iter_any_ancestry(anc_tree, revision)
1301
iter = pylon.iter_depends(anc_iter, nondeps)
1302
if type == "with-deps":
1303
iter = iter_combine(((revision,),
1304
pylon.iter_to_present(iter, tree)))
1308
return iter_reverse(iter)
1310
elif type == "micro":
1311
return pylon.iter_micro(tree)
1313
elif type == "added-log":
1314
revision = determine_revision_tree(tree, spec)
1315
iter = pylon.iter_ancestry_logs(tree)
1316
return pylon.iter_added_log(iter, revision)
1318
elif type == "skip-conflicts":
1319
#this only works for replay
1321
raise Exception("Skip-conflicts doesn't work in reverse.")
1322
version = determine_version_tree(spec, tree)
1323
return pylon.iter_skip_replay_conflicts(tree, version)
1325
def add_revision_iter_options(select):
1326
select.add_option("", "--archive", action="store_const",
1327
const="archive", dest="type", default="default",
1328
help="List all revisions in the archive")
1329
select.add_option("", "--cacherevs", action="store_const",
1330
const="cacherevs", dest="type",
1331
help="List all revisions stored in the archive as "
1333
select.add_option("", "--logs", action="store_const",
1334
const="logs", dest="type",
1335
help="List revisions that have a patchlog in the "
1337
select.add_option("", "--missing", action="store_const",
1338
const="missing", dest="type",
1339
help="List revisions from the specified version that"
1340
" have no patchlog in the tree")
1341
select.add_option("", "--skip-present", action="store_const",
1342
const="skip-present", dest="type",
1343
help="List revisions from the specified version that"
1344
" have no patchlogs at all in the tree")
1345
select.add_option("", "--present", action="store_const",
1346
const="present", dest="type",
1347
help="List revisions from the specified version that"
1348
" have no patchlog in the tree, but can't be merged")
1349
select.add_option("", "--missing-from", action="store_const",
1350
const="missing-from", dest="type",
1351
help="List revisions from the specified revision "
1352
"that have no patchlog for the tree version")
1353
select.add_option("", "--partner-missing", action="store_const",
1354
const="partner-missing", dest="type",
1355
help="List revisions in partner versions that are"
1357
select.add_option("", "--new-merges", action="store_const",
1358
const="new-merges", dest="type",
1359
help="List revisions that have had patchlogs added"
1360
" to the tree since the last commit")
1361
select.add_option("", "--direct-merges", action="store_const",
1362
const="direct-merges", dest="type",
1363
help="List revisions that have been directly added"
1364
" to tree since the last commit ")
1365
select.add_option("", "--library", action="store_const",
1366
const="library", dest="type",
1367
help="List revisions in the revision library")
1368
select.add_option("", "--archive-ancestry", action="store_const",
1369
const="archive-ancestry", dest="type",
1370
help="List revisions that are ancestors of the "
1371
"current tree version")
1372
select.add_option("", "--ancestry", action="store_const",
1373
const="ancestry", dest="type",
1374
help="List revisions that are ancestors of the "
1375
"current tree version")
1377
select.add_option("", "--dependencies", action="store_const",
1378
const="dependencies", dest="type",
1379
help="List revisions that the given revision "
1382
select.add_option("", "--non-dependencies", action="store_const",
1383
const="non-dependencies", dest="type",
1384
help="List revisions that the given revision "
1385
"does not depend on")
1387
select.add_option("", "--with-deps", action="store_const",
1388
const="with-deps", dest="type",
1389
help="The specified revision, plus its dependencies"
1390
"since the last present dependency")
1392
select.add_option("--micro", action="store_const",
1393
const="micro", dest="type",
1394
help="List partner revisions aimed for this "
1397
select.add_option("", "--modified", dest="modified",
1398
help="List tree ancestor revisions that modified a "
1399
"given file", metavar="FILE[:LINE]")
1401
select.add_option("", "--shallow", dest="shallow", action="store_true",
1402
help="Don't scan merges when looking for revisions"
1403
" that modified a line")
1404
select.add_option("--added-log", action="store_const",
1405
const="added-log", dest="type",
1406
help="List revisions that added this log")
1408
def log_for_merge2(tree, version):
1409
merges = list(pylon.iter_new_merges(tree, version))
1410
direct = direct_merges (merges)
1411
out = "Patches applied:\n\n"
1413
out +=merge_log_recurse(log, merges, tree)
1416
def merge_log_recurse(log, interesting_merges, tree, indent = None):
1417
"""A variation on log-for-merge that scaled extremely badly"""
1420
out = "%s * %s\n%s %s\n\n" % (indent, log.revision, indent, log.summary)
1421
new_patch_logs = [f for f in interesting_merges if (f.revision in
1422
log.new_patches and f != log) ]
1423
direct = direct_merges(new_patch_logs)
1424
for merge in direct:
1425
out+=merge_log_recurse(merge, new_patch_logs, tree, indent+" ")
1429
colorize(arch.Chatter("* "+str))
1432
def user_hunk_confirm(hunk):
1433
"""Asks user to confirm each hunk
1435
:param hunk: a diff hunk
1437
:return: The user's choice
1440
for line in pylon.diff_classifier(hunk.split('\n')):
1442
return confirm("Revert this hunk?", False)
1444
def merge_completions(tree, arg, index):
1445
completions = list(pylon.iter_partners(tree, tree.tree_version))
1446
if len(completions) == 0:
1447
completions = list(tree.iter_log_versions())
1451
for completion in completions:
1452
alias = pylon.compact_alias(str(completion), tree)
1454
aliases.extend(alias)
1456
for completion in completions:
1457
if completion.archive == tree.tree_version.archive:
1458
aliases.append(completion.nonarch)
1460
except Exception, e:
1463
completions.extend(aliases)
1467
def iter_file_completions(arg, only_dirs = False):
1468
"""Generate an iterator that iterates through filename completions.
1470
:param arg: The filename fragment to match
1472
:param only_dirs: If true, match only directories
1473
:type only_dirs: bool
1477
extras = [".", ".."]
1480
(dir, file) = os.path.split(arg)
1482
listingdir = os.path.expanduser(dir)
1485
for file in iter_combine([os.listdir(listingdir), extras]):
1487
userfile = dir+'/'+file
1490
if userfile.startswith(arg):
1491
if os.path.isdir(listingdir+'/'+file):
1498
def iter_dir_completions(arg):
1499
"""Generate an iterator that iterates through directory name completions.
1501
:param arg: The directory name fragment to match
1504
return iter_file_completions(arg, True)
1507
def iter_munged_completions(iter, arg, text):
1508
for completion in iter:
1509
completion = str(completion)
1510
if completion.startswith(arg):
1511
yield completion[len(arg)-len(text):]
1513
# arch-tag: 7d28710a-0b02-4dcb-86a7-e6b3b5486da4