~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/cmdutil.py

  • Committer: Robert Collins
  • Date: 2005-09-13 11:20:11 UTC
  • mto: (147.2.6) (364.1.3 bzrtools)
  • mto: This revision was merged to the branch mainline in revision 324.
  • Revision ID: robertc@robertcollins.net-20050913112010-ffd55901ae7f0791
determine version-0 ancestors from a tree when possible

Show diffs side-by-side

added added

removed removed

Lines of Context:
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
17
17
 
18
 
import pybaz
19
 
from pybaz.backends.baz import sequence_cmd
 
18
import options
 
19
import os
 
20
import sys
 
21
import pybaz as arch
 
22
 
 
23
import pylon
 
24
from pylon.errors import *
 
25
from pylon import errors
 
26
from pylon import paths
 
27
from pylon import util
 
28
 
 
29
import shutil
 
30
import datetime
 
31
import dateutil.parser
 
32
import optparse 
20
33
import re
 
34
from optparse import OptionGroup
 
35
import add_tagline
 
36
from add_tagline import AlreadyTagged, NoCommentSyntax
 
37
import terminal
21
38
 
22
39
__docformat__ = "restructuredtext"
23
40
__doc__ = "Utility functions to be used by commands"
24
 
 
25
 
def direct_merges(merges, excludes=[]):
 
41
    
 
42
 
 
43
def alias_endpoint(spectype, spec):
 
44
    """Determines the endpoint for iteration.  Assumes input is of the form \
 
45
    "tprev" or "tprev:5".
 
46
 
 
47
    :param spectype: The leading portion of the specification, e.g. "tprev"
 
48
    :type spectype: string
 
49
    :param spec: The actual spec
 
50
    :type spec: string
 
51
    :return: The endpoint to use
 
52
    :rtype: integer
 
53
    """
 
54
    if len(spec)>len(spectype+":"):
 
55
        try:
 
56
            endpoint=int(spec[len(spectype+":"):])
 
57
        except ValueError:
 
58
            raise errors.CantDetermineRevision(spec, 
 
59
                                               "\""+spec[len(spectype+":"):]\
 
60
                                               +"\" is not an integer.")
 
61
        if endpoint < 0:
 
62
            raise errors.CantDetermineRevision(spec, "\""+str(endpoint)+\
 
63
                                               "\" is negative.")
 
64
 
 
65
    else:
 
66
        endpoint=1
 
67
    return endpoint
 
68
 
 
69
 
 
70
def determine_version_arch(arg, tree):
 
71
    """
 
72
    Returns an `arch.Version`, using the default archive if necessary.
 
73
 
 
74
    :param arg: version name
 
75
    :type arg: str
 
76
    :rtype: `arch.Version`
 
77
    """
 
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():
 
82
        return arch.Version(
 
83
            str(arch.default_archive())+"/"+ name.get_package_version())
 
84
    else:
 
85
        return arch.Version(name)
 
86
 
 
87
def determine_version_tree(arg, tree):
 
88
    """
 
89
    Returns an `arch.Version`, using the tree-version archive if necessary.
 
90
 
 
91
    :param arg: version name
 
92
    :type arg: str
 
93
    :param tree: The tree to use for getting the archive (if needed).
 
94
    :type tree: `arch.ArchSourceTree`
 
95
    :rtype: `arch.Version`
 
96
    """
 
97
    if arg is None:
 
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():
 
103
        return arch.Version(
 
104
            str(tree.tree_version.archive)+"/"+ 
 
105
                name.get_package_version())
 
106
    else:
 
107
        return arch.Version(name)
 
108
 
 
109
 
 
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)
 
114
    else:
 
115
        return determine_revision_tree(tree, name)
 
116
    
 
117
 
 
118
def expand_prefix_alias(args, tree, prefix="^"):
 
119
    """
 
120
    Treats all arguments that have the prefix as aliases, and expands them.
 
121
 
 
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
 
127
    :type prefix: str
 
128
    :rtype: list of str
 
129
    """
 
130
    expanded=[]
 
131
    for segment in args:
 
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))
 
137
        else:
 
138
            expanded.append(segment)
 
139
    return expanded
 
140
 
 
141
 
 
142
def tag_cur(tree):
 
143
    """Determine the latest revision of the version of the tag source.
 
144
 
 
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`
 
149
    """
 
150
    if tree==None:
 
151
        raise CantDetermineRevision("tagcur", "No source tree available.")
 
152
    spec = pylon.tag_source(tree)
 
153
    if spec is None:
 
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()
 
158
    return spec
 
159
 
 
160
 
 
161
def expand_alias(spec, tree):
 
162
    """
 
163
    Attempts to perform alias expansion on a given spec.  Will expand both \
 
164
    automatic aliases and user-specified aliases
 
165
 
 
166
    :param spec: The specification to expand.
 
167
    :type spec: string
 
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
 
172
 
 
173
    """
 
174
    name = arch.NameParser(spec)
 
175
    if name.is_version() or name.has_patchlevel():
 
176
        return spec
 
177
    if spec.startswith("^"):
 
178
        spec=spec[1:]
 
179
    if pylon.paths.is_url_path(spec):
 
180
        try:
 
181
            spec, dummy=pylon.paths.full_path_decode(spec)
 
182
        except pylon.paths.CantDecode, e:
 
183
            pass
 
184
        
 
185
    elif spec.startswith("tprev"):
 
186
        if tree==None:
 
187
            raise CantDetermineRevision(spec, "No source tree available.")
 
188
 
 
189
        iter = tree.iter_logs(reverse=True)
 
190
        endpoint=alias_endpoint("tprev", spec)
 
191
        try:
 
192
            for q in range(endpoint):
 
193
                iter.next()
 
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"):
 
199
        if tree==None:
 
200
            raise CantDetermineRevision(spec, "No source tree available.")
 
201
        version = None
 
202
        try:
 
203
            if len(spec)>4:
 
204
                version=determine_version_tree(expand_alias(spec[5:], tree),
 
205
                    tree)
 
206
            spec = tree.iter_logs(version, reverse=True).next().revision
 
207
        except StopIteration:
 
208
            if version is None:
 
209
                version = "default version (%s)" % str(tree.tree_version)
 
210
            raise CantDetermineRevision(spec, 
 
211
                "Tree contains no logs for %s." % str(version))
 
212
    elif spec == "ttag":
 
213
        if tree==None:
 
214
            raise CantDetermineRevision(spec, "No source tree available.")
 
215
        spec = pylon.tag_source(tree)
 
216
        if spec is None:
 
217
            raise CantDetermineRevision("ttag", 
 
218
                "This revision has no ancestor version.")
 
219
 
 
220
    elif spec == "tagcur":
 
221
        spec = tag_cur(tree)
 
222
 
 
223
    elif spec.startswith("tanc"):
 
224
        iter = pylon.iter_ancestry(tree)
 
225
        endpoint=alias_endpoint("tanc", spec)
 
226
        try:
 
227
            for q in range(endpoint):
 
228
                iter.next()
 
229
            spec = iter.next()
 
230
        except StopIteration:
 
231
            raise CantDetermineRevision(spec, 
 
232
                "Value \"%d\" is out of range." % endpoint)
 
233
 
 
234
    elif spec.startswith("tmod:"):
 
235
        try:
 
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:])
 
240
 
 
241
 
 
242
    elif spec.startswith("tdate:"):
 
243
        default=datetime.datetime.now().replace(hour=23, minute=59, second=59, 
 
244
                                                microsecond=999999)
 
245
        try:
 
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.")
 
251
        revision = None
 
252
        for log in pylon.iter_any_ancestry(tree, tree.tree_revision):
 
253
            if not isinstance(log, arch.Patchlog):
 
254
                break
 
255
            if log.date <= date:
 
256
                revision = log.revision
 
257
                break
 
258
        if revision is None:
 
259
            raise CantDetermineRevision(spec, "No logs found for date \"%s\"."\
 
260
                                        % spec[6:])
 
261
        spec = revision        
 
262
 
 
263
    elif spec.startswith("acur"):
 
264
        if len(spec)>4:
 
265
            version=determine_version_arch(expand_alias(spec[5:], tree), tree)
 
266
        elif tree:
 
267
            version=tree.tree_version
 
268
        else:
 
269
            raise CantDetermineRevision(spec, "No source tree available.")
 
270
        ensure_archive_registered(version.archive)
 
271
        spec=version.iter_revisions(True).next()
 
272
 
 
273
    elif spec.startswith("mergeanc"):
 
274
        if tree is None:
 
275
            raise CantDetermineRevision(spec, "No source tree available.")
 
276
 
 
277
        if spec == "mergeanc":
 
278
            try:
 
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)
 
283
        else:
 
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, 
 
288
                                                  other_revision)
 
289
        if merge_anc is None:
 
290
            raise CantDetermineRevision(spec, "Can't find a common ancestor.")
 
291
        return merge_anc
 
292
 
 
293
    elif spec.startswith("micro"):
 
294
        specsection = spec.split(':')
 
295
        if len(specsection) > 1:
 
296
            version = determine_version_arch(specsection[1], tree)
 
297
        else:
 
298
            version = tree.tree_version
 
299
        try:
 
300
            return pylon.iter_micro(tree, version).next().revision
 
301
        except StopIteration, e:
 
302
            raise CantDetermineRevision(spec, "No next microbranch revision")
 
303
        
 
304
    else:
 
305
        expansion=None
 
306
        if '@' in spec:
 
307
            return spec
 
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:
 
312
            spec = expansion
 
313
    return spec
 
314
 
 
315
 
 
316
def determine_revision_arch(tree, spec=None, check_existence=True, allow_package=False):
 
317
    """
 
318
    Determines a revision from a revision, patchlevel, package-version, or package.
 
319
    Uses the archive to supply missing elements.
 
320
 
 
321
    :param tree: The working tree, used to interpret patchlevels
 
322
    :type tree: `arch.WorkingTree`
 
323
    :param spec: The name specification
 
324
    :type spec: string
 
325
    """
 
326
    name = None
 
327
    if spec!=None:
 
328
        spec=expand_alias(spec, tree)
 
329
        name = arch.NameParser(spec)
 
330
    elif tree:
 
331
        name = arch.NameParser(tree.tree_version)
 
332
    else:
 
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()
 
337
            if archive is None:
 
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)
 
342
 
 
343
        if name.is_version():
 
344
            version = arch.Version(name)
 
345
            ensure_archive_registered(version.archive)
 
346
            try:
 
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")
 
351
                else:
 
352
                    raise CantDetermineRevision(spec, "The version \""+str(name)+"\" has no revisions")
 
353
 
 
354
            except Exception, e:
 
355
                print e
 
356
                raise CantDetermineRevision(spec, 
 
357
                    "Error listing revisions for \""+str(name)+"\".")
 
358
 
 
359
        elif (allow_package and name.is_package()):
 
360
            branch = arch.Branch(name)
 
361
            ensure_archive_registered(branch.archive)
 
362
            try:
 
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")
 
367
                else:
 
368
                    raise CantDetermineRevision(spec, "The package \""+str(name)+"\" has no revisions")
 
369
 
 
370
            except Exception, e:
 
371
                print e
 
372
                raise CantDetermineRevision(spec, 
 
373
                    "Error listing revisions for \""+str(name)+"\".")
 
374
 
 
375
    else:
 
376
        if tree is None:
 
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)
 
383
 
 
384
def determine_revision_tree(tree, spec=None):
 
385
    """
 
386
    Determines a revision from a revision, patchlevel, or package-version.
 
387
 
 
388
    Uses the tree to supply missing elements
 
389
 
 
390
    :param tree: The tree to use by default
 
391
    :type tree: `arch.ArchSourceTree`
 
392
    :param spec: The revision specification
 
393
    :type spec: string
 
394
    :rtype: `arch.Revision`
 
395
    """
 
396
    name = None
 
397
    if spec!=None:
 
398
        spec=expand_alias(spec, tree)
 
399
        name = arch.NameParser(spec)
 
400
    else:
 
401
        name = arch.NameParser(str(tree.tree_version))
 
402
 
 
403
    revision = None
 
404
    if name.is_version():
 
405
        try:
 
406
            version = name.get_package_version()
 
407
            if name.has_archive():
 
408
                version = name.get_archive()+"/"+version
 
409
            else:
 
410
                version = arch.Version(str(tree.tree_version.archive)
 
411
                                       +"/"+version)
 
412
            revision = tree.iter_logs(version, True).next().revision
 
413
        except StopIteration:
 
414
            raise CantDetermineRevision(spec, 
 
415
                                      "No revisions present for this version.")
 
416
    elif name !=None:
 
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
 
424
    if revision == None:
 
425
        raise CantDetermineRevision(spec, 
 
426
                                "Not a revision, package-version, or patchlevel.")
 
427
    return arch.Revision(revision)
 
428
 
 
429
def determine_version_revision_tree(spec, tree):
 
430
    """Get a version or revision from tree and input.  Version preferred.
 
431
 
 
432
    :param spec: A revision, version, patchlevel or alias
 
433
    :type spec: str
 
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`
 
438
    """
 
439
    try:
 
440
        return determine_version_tree(spec, tree)
 
441
    except errors.CantDetermineVersion, e:
 
442
        return determine_revision_tree(tree, spec)
 
443
 
 
444
 
 
445
def show_diffs(changeset):
 
446
    """
 
447
    Print diff output for a changeset.
 
448
 
 
449
    :param changeset: the name of the changeset
 
450
    :type changeset: string
 
451
    """
 
452
 
 
453
    for line in pylon.diff_iter2(changeset):
 
454
        colorize(line)
 
455
 
 
456
 
 
457
def show_custom_diffs(changeset, diffargs, orig, mod):
 
458
    """
 
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
 
465
    :type orig: str
 
466
    :param mod: The path to the MOD directory
 
467
    :type mod: str
 
468
    """
 
469
    try:
 
470
        for entry in changeset.get_entries().itervalues():
 
471
            if entry.diff is None:
 
472
                continue
 
473
            try:
 
474
                print pylon.invoke_diff(orig, entry.orig_name, mod,
 
475
                                              entry.mod_name,
 
476
                                              diffargs).rstrip("\n")
 
477
            except pylon.BinaryFiles:
 
478
                pass
 
479
    except arch.util.ExecProblem, e: 
 
480
        print e.proc.error
 
481
 
 
482
 
 
483
def colorize (item, suppress_chatter=False):
 
484
    """
 
485
    Colorizes and prints tla-style output, according to class type.
 
486
 
 
487
    Colorizing can be disabled by setting options.colorize to false.
 
488
 
 
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
 
493
    """
 
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():
 
498
            colorize = 'yes'
 
499
        else:
 
500
            colorize = 'no'
 
501
 
 
502
    if colorize == 'no':
 
503
        print item
 
504
        return
 
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):
 
531
        print item
 
532
    else:
 
533
        print item
 
534
    sys.stdout.flush()
 
535
 
 
536
 
 
537
def confirm(question, default=False):
 
538
    """
 
539
    Prompts a user to confirm or deny an action.
 
540
 
 
541
    :param question: The question to ask
 
542
    :type question: string
 
543
    :param default: The default choice
 
544
    :type default: boolean
 
545
    """
 
546
 
 
547
    while True:
 
548
        sys.stdout.write(question)
 
549
        if default:
 
550
            sys.stdout.write(" [Y/n] ")
 
551
        else:
 
552
            sys.stdout.write(" [y/N] ")
 
553
        answer=sys.stdin.readline().strip().lower()
 
554
        if answer=="y":
 
555
            return True
 
556
        elif answer=="n":
 
557
            return False
 
558
        elif answer=="":
 
559
            return default
 
560
 
 
561
 
 
562
def prompt(type):
 
563
    """
 
564
    Prompts the user to decide whether to perform an action
 
565
 
 
566
    Prompts can be overridden by setting their option to "always" or "never"
 
567
    :param type: the prompt type, as defined in options
 
568
    :type type: string
 
569
    """
 
570
    if options.prompts[type]=="always":
 
571
        return True
 
572
    elif options.prompts[type]=="never":
 
573
        return False
 
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)
 
578
    else:
 
579
        raise BadOption
 
580
 
 
581
def get_mirror_source(archive):
 
582
    """
 
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.
 
586
 
 
587
    :param archive: The archive to find the source for
 
588
    :type archive: `arch.Archive`
 
589
    """
 
590
 
 
591
    sourcename=str(archive)+"-SOURCE"
 
592
    for arch_entry in arch.iter_archives():
 
593
        if str(arch_entry) == sourcename and archive.is_mirror:
 
594
            return sourcename 
 
595
    return None
 
596
 
 
597
def get_best_branch_or_version(branch_version):
 
598
    """
 
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.
 
601
 
 
602
    :param branch_version: The `arch.Version` to retrieve the version of
 
603
    :type branch_version: `arch.Version` or `arch.Branch`
 
604
 
 
605
    """
 
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
 
609
        
 
610
    source=get_mirror_source(branch_version.archive)
 
611
    if source is None:
 
612
        return branch_version
 
613
    elif arch.NameParser(branch_version).is_package():
 
614
        return arch.Branch(source+"/"+branch_version.nonarch)
 
615
    else:
 
616
        return arch.Version(source+"/"+branch_version.nonarch)
 
617
 
 
618
def get_latest_revision_anywhere(branch_version):
 
619
    """
 
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
 
623
 
 
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`
 
627
    """
 
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():
 
631
        raise StopIteration
 
632
    source_revision = source_branch_version.iter_revisions(True).next()
 
633
    revision = arch.Revision(str(branch_version.archive) + "/"+ 
 
634
        source_revision.nonarch)
 
635
    return revision
 
636
 
 
637
 
 
638
def update(revision, tree, patch_forward=False):
 
639
    """
 
640
    Puts the given revision in a tree, preserving uncommitted changes
 
641
 
 
642
    :param revision: The revision to get
 
643
    :param tree: The tree to update
 
644
    """
 
645
    command=['update', '--dir', str(tree)]
 
646
    if patch_forward:
 
647
        command.append('--forward')
 
648
    command.append(str(revision))
 
649
    return arch.util.exec_safe_iter_stdout('tla', command, expected=(0, 1, 2))
 
650
 
 
651
 
 
652
def apply_delta(a_spec, b_spec, tree, ignore_present=False):
 
653
    """Apply the difference between two things to tree.
 
654
 
 
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 
 
662
    """
 
663
    tmp=pylon.util.tmpdir(tree)
 
664
    changeset=tmp+"/changeset"
 
665
    delt=arch.iter_delta(a_spec, b_spec, changeset)
 
666
    for line in delt:
 
667
        yield line
 
668
    yield arch.Chatter("* applying changeset")
 
669
    for line in delt.changeset.iter_apply(tree, ignore_present):
 
670
        yield line
 
671
    shutil.rmtree(tmp)
 
672
 
 
673
 
 
674
def iter_apply_delta_filter(iter):
 
675
    show_file_changes=False
 
676
    for line in iter:
 
677
        if pylon.chattermatch(line, "applying changeset"):
 
678
            show_file_changes=True
 
679
        if pylon.chattermatch(line, "changeset:"):
 
680
            pass
 
681
        elif not show_file_changes and isinstance(line, arch.MergeOutcome):
 
682
            pass
 
683
        else:
 
684
            yield line
 
685
 
 
686
 
 
687
def find_editor():
 
688
    """
 
689
    Finds an editor to use.  Uses the EDITOR environment variable.
 
690
 
 
691
    :rtype: string
 
692
    """
 
693
    if os.environ.has_key("EDITOR") and os.environ["EDITOR"] != "":
 
694
        return os.environ["EDITOR"]
 
695
    else:
 
696
        raise NoEditorSpecified
 
697
 
 
698
 
 
699
def invoke_editor(filename):
 
700
    """
 
701
    Invokes user-specified editor.
 
702
 
 
703
    :param filename: The file to edit
 
704
    :type filename: string
 
705
    """
 
706
    os.system("%s %s" % (find_editor(), filename))
 
707
 
 
708
def iter_reverse(iter):
 
709
    """Produce brute-force reverse iterator.
 
710
 
 
711
    :param iter: The iterator to run through in reverse
 
712
    :type iter: Iterator of any
 
713
    :rtype: reversed list of any
 
714
    """
 
715
    reversed = list(iter)
 
716
    reversed.reverse()
 
717
    return reversed
 
718
 
 
719
def iter_cacherevs(version, reverse=False):
 
720
    """Iterates through cached revisions as revisions, not patchlevels"""
 
721
    iter =  version.iter_cachedrevs()
 
722
    if not reverse:
 
723
        return iter
 
724
    else:
 
725
        return iter_reverse(iter)
 
726
 
 
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
 
735
    :type reverse: bool
 
736
    """
 
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:
 
742
                break
 
743
            yield miss_revision
 
744
    for miss_revision in miss:
 
745
        yield miss_revision
 
746
 
 
747
 
 
748
def iter_partner_missing(tree, reverse=False, skip_present=False):
 
749
    """Generate an iterator of all missing partner revisions.
 
750
 
 
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
 
754
    :type reverse: bool
 
755
    :return: Iterator of missing partner revisions
 
756
    :rtype: Iterator of `arch.Revision`
 
757
    """
 
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, 
 
762
                                           skip_present):
 
763
            yield revision
 
764
 
 
765
def iter_skip(iter, skips=0):
 
766
    """Ajusts an iteration list by skipping items at beginning or end.  May
 
767
    modify original iterator.
 
768
 
 
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
 
772
        skip from the end.
 
773
    :type skips: int
 
774
    :return: an iterator that starts or stops at a different place
 
775
    :rtype: iterator of iter's iter type
 
776
    """
 
777
    if skips == 0:
 
778
        return iter;
 
779
    if skips > 0:
 
780
        for q in range(skips):
 
781
            iter.next()
 
782
        return iter
 
783
    if skips < 0:
 
784
        return iter_skip_end(iter, -skips)
 
785
 
 
786
def iter_skip_end(iter, skips):
 
787
    """Generates an iterator based on iter that skips the last skips items
 
788
 
 
789
    :param skips: number of items to skip from the end.
 
790
    :type skips: int
 
791
    :return: an iterator that stops at a different place
 
792
    :rtype: iterator of iter's iteration type
 
793
    """
 
794
    iter = iter_skip(iter, -skips + 1)
 
795
    item = None
 
796
    for it in iter:
 
797
        if item is not None:
 
798
            yield item
 
799
        item = it
 
800
 
 
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, 
 
808
                                                         sections[0], 
 
809
                                                         int(sections[1]))
 
810
    elif len(sections) == 2:
 
811
        return pylon.iter_changed_file_line(tree, revision, 
 
812
                                                    sections[0], 
 
813
                                                    int(sections[1]))
 
814
 
 
815
 
 
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
 
821
    """
 
822
    for iterator in iterators:
 
823
        for value in iterator:
 
824
            yield value
 
825
 
 
826
 
 
827
def is_tla_command(command):
 
828
    """Determines whether the specified string is a tla command.
 
829
    
 
830
    :param command: The string to check
 
831
    :type command: str
 
832
    :rtype: bool"""
 
833
    for line in pylon.iter_tla_commands():
 
834
        if (line == command):
 
835
            return True
 
836
    return False
 
837
 
 
838
 
 
839
 
 
840
class BadCommandOption:
 
841
    def __init__(self, message):
 
842
        self.text = message
 
843
 
 
844
    def __str__(self):
 
845
        return self.text
 
846
 
 
847
def raise_get_help(option, opt, value, parser):
 
848
    raise GetHelp
 
849
 
 
850
 
 
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")
 
857
 
 
858
    def error(self, message):
 
859
        raise BadCommandOption(message)
 
860
 
 
861
    def iter_options(self):
 
862
        return iter_combine([self._short_opt.iterkeys(), 
 
863
                            self._long_opt.iterkeys()])
 
864
 
 
865
 
 
866
def add_tagline_or_explicit_id(file, tltl=False, implicit=False):
 
867
    """This is basically a wrapper for add-tagline
 
868
 
 
869
    :param file: The file to add an id for
 
870
    :type file: str
 
871
    :param tltl: Use lord-style tagline
 
872
    :type tltl: bool
 
873
    """
 
874
    opt = optparse.Values()
 
875
    opt.__dict__["tltl"] = tltl
 
876
    opt.__dict__["id"] = None
 
877
    opt.__dict__["implicit"] = implicit
 
878
 
 
879
    if not os.path.isfile(file) or os.path.islink(file):
 
880
        pylon.add_id([file])
 
881
        return 
 
882
    try:
 
883
        add_tagline.tagFile(file, opt)
 
884
    except add_tagline.NoCommentSyntax:
 
885
        print "Can't use tagline for \"%s\" because no comment sytax known." % \
 
886
            file
 
887
        if prompt("Fallthrough to explicit"):
 
888
            pylon.add_id([file])
 
889
        else:
 
890
            raise
 
891
 
 
892
browse_memo = {}
 
893
 
 
894
def iter_browse_memo(search):
 
895
    """Memoized version of iter_browse.
 
896
    :param search: The string to find entries for
 
897
    :type search: str
 
898
    :return: Any categories, branches, versions that matched
 
899
    :rtype: iterator of `arch.Category`, `arch.Branch`, `arch.Version`, \
 
900
 `arch.Revision`
 
901
    """
 
902
    global browse_memo
 
903
    if arch.NameParser.is_archive_name(search):
 
904
        archive = search
 
905
    else:
 
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))
 
910
 
 
911
    for entry in browse_memo[str(archive)]:
 
912
        if str(entry).startswith(search):
 
913
            yield entry
 
914
 
 
915
 
 
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):
 
921
            yield completion[0]
 
922
 
 
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:]
 
931
 
 
932
    else:
 
933
        if arg.endswith('/'):
 
934
            arg = arg[:-1]
 
935
        try:
 
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):]
 
941
        except Exception, e:
 
942
            print e
 
943
 
 
944
 
 
945
def ensure_archive_registered(archive, location=None):
 
946
    if archive.is_registered():
 
947
        return
 
948
    user_supplied = False
 
949
    if location is None:
 
950
        colorize (arch.Chatter("* Looking up archive %s" % archive))
 
951
        home = None
 
952
        mirror = None
 
953
        
 
954
        try:
 
955
            colorize (arch.Chatter("* Checking bonehunter.rulez.org"))
 
956
            home = pylon.bh_lookup(archive)
 
957
        except ArchiveLookupError, e:
 
958
            print e
 
959
 
 
960
        if home is not None:
 
961
            print "Found location %s" % home
 
962
            if prompt("Use lookup location"):
 
963
                location = home
 
964
        if location is None:
 
965
            try:
 
966
                colorize (arch.Chatter("* Checking sourcecontrol.net"))
 
967
                (home, mirror) = pylon.sc_lookup(archive)
 
968
                if home is not None:
 
969
                    print "Found location %s" % home
 
970
                    if prompt("Use lookup location"):
 
971
                        location = home
 
972
                if location is None and mirror is not None:
 
973
                    print "Found mirror location %s" % mirror 
 
974
                    if prompt("Use lookup mirror location"):
 
975
                        location = mirror
 
976
            except ArchiveLookupError, e:
 
977
                print e
 
978
        if location is None:
 
979
            print "If you know the location for %s, please enter it here." % \
 
980
                archive
 
981
            location = raw_input("Fai> ")
 
982
            if location == "":
 
983
                location = None
 
984
            else:
 
985
                user_supplied = True
 
986
                
 
987
        if location is None:
 
988
            raise arch.errors.ArchiveNotRegistered(archive)
 
989
 
 
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))
 
1000
 
 
1001
    else:
 
1002
        ar = register_archive(str(archive), location, str(archive))
 
1003
    if user_supplied and prompt("Submit user-supplied"):
 
1004
        pylon.bh_submit(archive, location)
 
1005
 
 
1006
pylon.ensure_archive_registered = ensure_archive_registered
 
1007
 
 
1008
def location_cacheable(location):
 
1009
    return not location.startswith('/') and not location.startswith('cached:')\
 
1010
        and pylon.ArchParams().exists('=arch-cache')    
 
1011
 
 
1012
def ensure_revision_exists(revision):
 
1013
    ensure_archive_registered(revision.archive)
 
1014
    if revision.exists():
 
1015
        return
 
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):
 
1022
                colorize(line)
 
1023
            return
 
1024
    raise NoSuchRevision(revision)
 
1025
 
 
1026
pylon.ensure_revision_exists = ensure_revision_exists
 
1027
 
 
1028
 
 
1029
def merge_ancestor(mine, other, other_start):
 
1030
    """Determines an ancestor suitable for merging, according to the tree logs.
 
1031
 
 
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`
 
1040
    """
 
1041
    my_ancestors = list (pylon.iter_ancestry_logs(mine))
 
1042
    other_ancestors = list (pylon.iter_ancestry_logs(other, 
 
1043
                                                             other_start))
 
1044
    
 
1045
    other_ancestor = None
 
1046
    mine_ancestor = None
 
1047
    
 
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
 
1053
                    break
 
1054
            elif oth_log.revision in log.new_patches:
 
1055
                mine_ancestor = oth_log
 
1056
                break
 
1057
        if mine_ancestor is not None:
 
1058
            break        
 
1059
 
 
1060
    for log in other_ancestors:
 
1061
        if log == mine_ancestor:
 
1062
            break
 
1063
        for my_log in my_ancestors:
 
1064
            if log.continuation_of:
 
1065
                if my_log.revision == my_log:
 
1066
                    other_ancestor = my_log
 
1067
                    break
 
1068
            elif my_log.revision in log.new_patches:
 
1069
                other_ancestor = my_log 
 
1070
                break
 
1071
        if other_ancestor is not None:
 
1072
            break
 
1073
 
 
1074
    if other_ancestor is not None:
 
1075
        return other_ancestor
 
1076
    else:
 
1077
        return mine_ancestor
 
1078
 
 
1079
 
 
1080
 
 
1081
 
 
1082
def iter_supported_switches(cmd):
 
1083
    for line in pylon.iter_tla_cmd_help(cmd):
 
1084
        if not line.startswith("  -"):
 
1085
            continue
 
1086
        chunks = line[2:].split()
 
1087
        if chunks[0][-1] == ",":
 
1088
            yield chunks[0][:-1]
 
1089
            yield chunks[1]
 
1090
        else:
 
1091
            yield chunks[0]
 
1092
 
 
1093
def supports_switch(cmd, switch):
 
1094
    return switch in list(iter_supported_switches(cmd))
 
1095
 
 
1096
        
 
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
 
1102
    """
 
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" %\
 
1108
                arname
 
1109
            if not prompt("Register wrong name"):
 
1110
                ar.unregister()
 
1111
                raise WrongArchName(location, expected_name, arname)
 
1112
    return ar
 
1113
 
 
1114
       
 
1115
def direct_merges(merges):
26
1116
    """Get a list of direct merges, from a list of direct and indirect
27
1117
    
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`
32
1122
    """
33
 
    indirect = set()
 
1123
    indirect = []
 
1124
    direct = []
34
1125
    logs = list(merges)
35
1126
    if not logs:
36
1127
        return []
37
1128
    for log in logs:
38
 
        try:
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:
43
 
            print
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
52
1132
            continue
71
1151
                        found = True
72
1152
                print "ancestor of %s is %s" % (log.revision, ancestor)
73
1153
        if ancestor is not None:
74
 
            indirect.add(str(ancestor))
75
 
    return [log.revision for log in logs if not str(log.revision) in indirect
76
 
            and log.revision not in excludes]
77
 
 
 
1154
            indirect.append(ancestor)
 
1155
    return [log for log in logs if not log.revision in indirect]
78
1156
 
79
1157
def namespace_previous(revision):
80
1158
    if revision.patchlevel == 'base-0':
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
96
1174
 
97
 
def iter_new_merges(tree, version, reverse=False):
98
 
    """List patchlogs that are new in this tree since the last commit.
99
 
 
100
 
    :param tree: The working tree to calculate new revisions in
101
 
    :type tree: str
102
 
    :param version: The version to use when determining new logs
103
 
    :type version: str
104
 
    :param reverse: If true, list backwards, from newest to oldest
105
 
    :type reverse: bool
106
 
    :return: An iterator for new revision logs in this tree
107
 
    :rtype: Iterator of `pybaz.Patchlog`
108
 
    """
109
 
    assert (isinstance(version, pybaz.Version))
110
 
    for line in _iter_new_merges(tree, version.fullname, reverse):
111
 
        yield pybaz.Patchlog(line, tree)
112
 
 
113
 
def _iter_new_merges(directory, version, reverse):
114
 
    """List patchlogs that are new in this tree since the last commit.
115
 
 
116
 
    :param directory: The working tree to calculate new revisions in
117
 
    :type directory: str
118
 
    :param version: The version to use when determining new logs
119
 
    :type version: str
120
 
    :param reverse: If true, list backwards, from newest to oldest
121
 
    :type reverse: bool
122
 
    :return: An iterator for names of revisions new in this tree
123
 
    :rtype: Iterator of str
124
 
    """
125
 
    args = [ 'new-merges', '--dir', directory ]
126
 
    if reverse: 
127
 
        args.append('--reverse')
128
 
    args.append(version)
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." \
 
1179
                                          % version)
 
1180
 
 
1181
 
 
1182
def revision_iterator(tree, type, args, reverse, modified, shallow):
 
1183
    """Produce an iterator of revisions
 
1184
 
 
1185
    :param tree: The project tree or None
 
1186
    :type tree: `arch.ArchSourceTree`
 
1187
    :param type: The type of iterator to produce
 
1188
    :type type: str
 
1189
    :param args: The commandline positional arguments
 
1190
    :type args: list of str
 
1191
    :param reverse: If true, iterate in reverse
 
1192
    :type reverse: bool
 
1193
    :param modified: if non-null, use to create a file modification iterator
 
1194
    :type modified: str
 
1195
    """
 
1196
    if len(args) > 0:
 
1197
        spec = args[0]
 
1198
    else:
 
1199
        spec = None
 
1200
    if modified is not None:
 
1201
        iter = modified_iter(modified, tree, shallow != True)
 
1202
        if reverse:
 
1203
            return iter
 
1204
        else:
 
1205
            return iter_reverse(iter)
 
1206
    elif type == "archive":
 
1207
        if spec is None:
 
1208
            if tree is None:
 
1209
                raise CantDetermineRevision("", "Not in a project tree")
 
1210
            version = determine_version_tree(spec, tree)
 
1211
        else:
 
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":
 
1217
        if spec is None:
 
1218
            if tree is None:
 
1219
                raise CantDetermineRevision("", "Not in a project tree")
 
1220
            version = determine_version_tree(spec, tree)
 
1221
        else:
 
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":
 
1227
        if spec is None:
 
1228
            if tree is None:
 
1229
                raise CantDetermineRevision("", 
 
1230
                                                    "Not in a project tree")
 
1231
            version = determine_version_tree(spec, tree)
 
1232
        else:
 
1233
            version = determine_version_arch(spec, tree)
 
1234
        return version.iter_library_revisions(reverse)
 
1235
    elif type == "logs":
 
1236
        if tree is None:
 
1237
            raise CantDetermineRevision("", "Not in a project tree")
 
1238
        return tree.iter_logs(determine_version_tree(spec, \
 
1239
                              tree), reverse)
 
1240
    elif type == "missing" or type == "skip-present":
 
1241
        if tree is None:
 
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)
 
1248
 
 
1249
    elif type == "present":
 
1250
        if tree is None:
 
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)
 
1256
 
 
1257
    elif type == "new-merges" or type == "direct-merges":
 
1258
        if tree is None:
 
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":
 
1265
            return iter
 
1266
        elif type == "direct-merges":
 
1267
            return pylon.direct_merges(iter)
 
1268
 
 
1269
    elif type == "missing-from":
 
1270
        if tree is None:
 
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)
 
1275
 
 
1276
    elif type == "partner-missing":
 
1277
        return iter_partner_missing(tree, reverse)
 
1278
 
 
1279
    elif type == "ancestry":
 
1280
        revision = determine_revision_tree(tree, spec)
 
1281
        iter = pylon.iter_any_ancestry(tree, revision)
 
1282
        if reverse:
 
1283
            return iter
 
1284
        else:
 
1285
            return iter_reverse(iter)
 
1286
 
 
1287
    elif type == "archive-ancestry":
 
1288
        revision = determine_revision_tree(tree, spec)
 
1289
        iter = revision.iter_ancestors(metoo=True)
 
1290
        if reverse:
 
1291
            return iter
 
1292
        else:
 
1293
            return iter_reverse(iter)
 
1294
 
 
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)))
 
1305
        if reverse:
 
1306
            return iter
 
1307
        else:
 
1308
            return iter_reverse(iter)
 
1309
 
 
1310
    elif type == "micro":
 
1311
        return pylon.iter_micro(tree)
 
1312
 
 
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)
 
1317
 
 
1318
    elif type == "skip-conflicts":
 
1319
        #this only works for replay
 
1320
        if reverse:
 
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)
 
1324
 
 
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 "
 
1332
                          "complete copies")
 
1333
        select.add_option("", "--logs", action="store_const", 
 
1334
                          const="logs", dest="type",
 
1335
                          help="List revisions that have a patchlog in the "
 
1336
                          "tree")
 
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"
 
1356
                          " missing")
 
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")
 
1376
 
 
1377
        select.add_option("", "--dependencies", action="store_const", 
 
1378
                          const="dependencies", dest="type",
 
1379
                          help="List revisions that the given revision "
 
1380
                          "depends on")
 
1381
 
 
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")
 
1386
 
 
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")
 
1391
 
 
1392
        select.add_option("--micro", action="store_const", 
 
1393
                          const="micro", dest="type",
 
1394
                          help="List partner revisions aimed for this "
 
1395
                          "micro-branch")
 
1396
 
 
1397
        select.add_option("", "--modified", dest="modified", 
 
1398
                          help="List tree ancestor revisions that modified a "
 
1399
                          "given file", metavar="FILE[:LINE]")
 
1400
 
 
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")
 
1407
 
 
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"
 
1412
    for log in direct:
 
1413
        out +=merge_log_recurse(log, merges, tree)
 
1414
    return out
 
1415
 
 
1416
def merge_log_recurse(log, interesting_merges, tree, indent = None):
 
1417
    """A variation on log-for-merge that scaled extremely badly"""
 
1418
    if indent is None:
 
1419
        indent = ""
 
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+"  ")
 
1426
    return out
 
1427
 
 
1428
def chatter(str):
 
1429
    colorize(arch.Chatter("* "+str))
 
1430
 
 
1431
 
 
1432
def user_hunk_confirm(hunk):
 
1433
    """Asks user to confirm each hunk
 
1434
    
 
1435
    :param hunk: a diff hunk
 
1436
    :type hunk: str
 
1437
    :return: The user's choice
 
1438
    :rtype: bool
 
1439
    """
 
1440
    for line in pylon.diff_classifier(hunk.split('\n')):
 
1441
        colorize(line)
 
1442
    return confirm("Revert this hunk?", False)
 
1443
 
 
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())
 
1448
 
 
1449
    aliases = []
 
1450
    try:
 
1451
        for completion in completions:
 
1452
            alias = pylon.compact_alias(str(completion), tree)
 
1453
            if alias:
 
1454
                aliases.extend(alias)
 
1455
 
 
1456
        for completion in completions:
 
1457
            if completion.archive == tree.tree_version.archive:
 
1458
                aliases.append(completion.nonarch)
 
1459
 
 
1460
    except Exception, e:
 
1461
        print e
 
1462
        
 
1463
    completions.extend(aliases)
 
1464
    return completions
 
1465
 
 
1466
 
 
1467
def iter_file_completions(arg, only_dirs = False):
 
1468
    """Generate an iterator that iterates through filename completions.
 
1469
 
 
1470
    :param arg: The filename fragment to match
 
1471
    :type arg: str
 
1472
    :param only_dirs: If true, match only directories
 
1473
    :type only_dirs: bool
 
1474
    """
 
1475
    cwd = os.getcwd()
 
1476
    if cwd != "/":
 
1477
        extras = [".", ".."]
 
1478
    else:
 
1479
        extras = []
 
1480
    (dir, file) = os.path.split(arg)
 
1481
    if dir != "":
 
1482
        listingdir = os.path.expanduser(dir)
 
1483
    else:
 
1484
        listingdir = cwd
 
1485
    for file in iter_combine([os.listdir(listingdir), extras]):
 
1486
        if dir != "":
 
1487
            userfile = dir+'/'+file
 
1488
        else:
 
1489
            userfile = file
 
1490
        if userfile.startswith(arg):
 
1491
            if os.path.isdir(listingdir+'/'+file):
 
1492
                userfile+='/'
 
1493
                yield userfile
 
1494
            elif not only_dirs:
 
1495
                yield userfile
 
1496
 
 
1497
 
 
1498
def iter_dir_completions(arg):
 
1499
    """Generate an iterator that iterates through directory name completions.
 
1500
 
 
1501
    :param arg: The directory name fragment to match
 
1502
    :type arg: str
 
1503
    """
 
1504
    return iter_file_completions(arg, True)
 
1505
 
 
1506
 
 
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):]
 
1512
 
 
1513
# arch-tag: 7d28710a-0b02-4dcb-86a7-e6b3b5486da4