~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/cmdutil.py

  • Committer: Robert Collins
  • Date: 2005-09-13 09:54:12 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-20050913095412-5c033118008f53fe
import symlinks (needs symlink enabled bzr)

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
# Copyright (C) 2004 Aaron Bentley
 
2
# <aaron.bentley@utoronto.ca>
 
3
#
 
4
#    This program is free software; you can redistribute it and/or modify
 
5
#    it under the terms of the GNU General Public License as published by
 
6
#    the Free Software Foundation; either version 2 of the License, or
 
7
#    (at your option) any later version.
 
8
#
 
9
#    This program is distributed in the hope that it will be useful,
 
10
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
 
11
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 
12
#    GNU General Public License for more details.
 
13
#
 
14
#    You should have received a copy of the GNU General Public License
 
15
#    along with this program; if not, write to the Free Software
 
16
#    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 
17
 
 
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 
 
33
import re
 
34
from optparse import OptionGroup
 
35
import add_tagline
 
36
from add_tagline import AlreadyTagged, NoCommentSyntax
 
37
import terminal
 
38
 
 
39
__docformat__ = "restructuredtext"
 
40
__doc__ = "Utility functions to be used by commands"
 
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):
 
1116
    """Get a list of direct merges, from a list of direct and indirect
 
1117
    
 
1118
    :param merges: Iterator of merge patchlogs
 
1119
    :type merges: iter of `arch.Patchlog`
 
1120
    :return: The direct merges
 
1121
    :rtype: list of `arch.Patchlog`
 
1122
    """
 
1123
    indirect = []
 
1124
    direct = []
 
1125
    logs = list(merges)
 
1126
    if not logs:
 
1127
        return []
 
1128
    for log in logs:
 
1129
        indirect.extend([f for f in log.new_patches if f != log.revision])
 
1130
        ancestor = log.revision.ancestor
 
1131
        if ancestor is not None:
 
1132
            indirect.append(log.revision.ancestor)
 
1133
    return [log for log in logs if not log.revision in indirect]
 
1134
 
 
1135
 
 
1136
def require_version_exists(version, spec):
 
1137
    if not version.exists():
 
1138
        raise errors.CantDetermineVersion(spec, 
 
1139
                                          "The version %s does not exist." \
 
1140
                                          % version)
 
1141
 
 
1142
 
 
1143
def revision_iterator(tree, type, args, reverse, modified, shallow):
 
1144
    """Produce an iterator of revisions
 
1145
 
 
1146
    :param tree: The project tree or None
 
1147
    :type tree: `arch.ArchSourceTree`
 
1148
    :param type: The type of iterator to produce
 
1149
    :type type: str
 
1150
    :param args: The commandline positional arguments
 
1151
    :type args: list of str
 
1152
    :param reverse: If true, iterate in reverse
 
1153
    :type reverse: bool
 
1154
    :param modified: if non-null, use to create a file modification iterator
 
1155
    :type modified: str
 
1156
    """
 
1157
    if len(args) > 0:
 
1158
        spec = args[0]
 
1159
    else:
 
1160
        spec = None
 
1161
    if modified is not None:
 
1162
        iter = modified_iter(modified, tree, shallow != True)
 
1163
        if reverse:
 
1164
            return iter
 
1165
        else:
 
1166
            return iter_reverse(iter)
 
1167
    elif type == "archive":
 
1168
        if spec is None:
 
1169
            if tree is None:
 
1170
                raise CantDetermineRevision("", "Not in a project tree")
 
1171
            version = determine_version_tree(spec, tree)
 
1172
        else:
 
1173
            version = determine_version_arch(spec, tree)
 
1174
            ensure_archive_registered(version.archive)
 
1175
            require_version_exists(version, spec)
 
1176
        return version.iter_revisions(reverse)
 
1177
    elif type == "cacherevs":
 
1178
        if spec is None:
 
1179
            if tree is None:
 
1180
                raise CantDetermineRevision("", "Not in a project tree")
 
1181
            version = determine_version_tree(spec, tree)
 
1182
        else:
 
1183
            version = determine_version_arch(spec, tree)
 
1184
            ensure_archive_registered(version.archive)
 
1185
            require_version_exists(version, spec)
 
1186
        return iter_cacherevs(version, reverse)
 
1187
    elif type == "library":
 
1188
        if spec is None:
 
1189
            if tree is None:
 
1190
                raise CantDetermineRevision("", 
 
1191
                                                    "Not in a project tree")
 
1192
            version = determine_version_tree(spec, tree)
 
1193
        else:
 
1194
            version = determine_version_arch(spec, tree)
 
1195
        return version.iter_library_revisions(reverse)
 
1196
    elif type == "logs":
 
1197
        if tree is None:
 
1198
            raise CantDetermineRevision("", "Not in a project tree")
 
1199
        return tree.iter_logs(determine_version_tree(spec, \
 
1200
                              tree), reverse)
 
1201
    elif type == "missing" or type == "skip-present":
 
1202
        if tree is None:
 
1203
            raise CantDetermineRevision("", "Not in a project tree")
 
1204
        skip = (type == "skip-present")
 
1205
        version = determine_version_tree(spec, tree)
 
1206
        ensure_archive_registered(version.archive)
 
1207
        require_version_exists(version, spec)
 
1208
        return pylon.iter_missing(tree, version, reverse, skip_present=skip)
 
1209
 
 
1210
    elif type == "present":
 
1211
        if tree is None:
 
1212
            raise CantDetermineRevision("", "Not in a project tree")
 
1213
        version = determine_version_tree(spec, tree)
 
1214
        ensure_archive_registered(version.archive)
 
1215
        require_version_exists(version, spec)
 
1216
        return iter_present(tree, version, reverse)
 
1217
 
 
1218
    elif type == "new-merges" or type == "direct-merges":
 
1219
        if tree is None:
 
1220
            raise CantDetermineRevision("", "Not in a project tree")
 
1221
        version = determine_version_tree(spec, tree)
 
1222
        ensure_archive_registered(version.archive)
 
1223
        require_version_exists(version, spec)
 
1224
        iter = pylon.iter_new_merges(tree, version, reverse)
 
1225
        if type == "new-merges":
 
1226
            return iter
 
1227
        elif type == "direct-merges":
 
1228
            return pylon.direct_merges(iter)
 
1229
 
 
1230
    elif type == "missing-from":
 
1231
        if tree is None:
 
1232
            raise CantDetermineRevision("", "Not in a project tree")
 
1233
        revision = determine_revision_tree(tree, spec)
 
1234
        libtree = pylon.find_or_make_local_revision(revision)
 
1235
        return pylon.iter_missing(libtree, tree.tree_version, reverse)
 
1236
 
 
1237
    elif type == "partner-missing":
 
1238
        return iter_partner_missing(tree, reverse)
 
1239
 
 
1240
    elif type == "ancestry":
 
1241
        revision = determine_revision_tree(tree, spec)
 
1242
        iter = pylon.iter_any_ancestry(tree, revision)
 
1243
        if reverse:
 
1244
            return iter
 
1245
        else:
 
1246
            return iter_reverse(iter)
 
1247
 
 
1248
    elif type == "archive-ancestry":
 
1249
        revision = determine_revision_tree(tree, spec)
 
1250
        iter = revision.iter_ancestors(metoo=True)
 
1251
        if reverse:
 
1252
            return iter
 
1253
        else:
 
1254
            return iter_reverse(iter)
 
1255
 
 
1256
    elif type == "dependencies" or type == "non-dependencies" or \
 
1257
        type == "with-deps":
 
1258
        nondeps = (type == "non-dependencies")
 
1259
        revision = determine_revision_tree(tree, spec)
 
1260
        anc_tree = pylon.find_or_make_local_revision(revision)
 
1261
        anc_iter = pylon.iter_any_ancestry(anc_tree, revision)
 
1262
        iter = pylon.iter_depends(anc_iter, nondeps)
 
1263
        if type == "with-deps":
 
1264
            iter = iter_combine(((revision,),
 
1265
                                 pylon.iter_to_present(iter, tree)))
 
1266
        if reverse:
 
1267
            return iter
 
1268
        else:
 
1269
            return iter_reverse(iter)
 
1270
 
 
1271
    elif type == "micro":
 
1272
        return pylon.iter_micro(tree)
 
1273
 
 
1274
    elif type == "added-log":
 
1275
        revision = determine_revision_tree(tree, spec)
 
1276
        iter = pylon.iter_ancestry_logs(tree)
 
1277
        return pylon.iter_added_log(iter, revision)
 
1278
 
 
1279
    elif type == "skip-conflicts":
 
1280
        #this only works for replay
 
1281
        if reverse:
 
1282
            raise Exception("Skip-conflicts doesn't work in reverse.")
 
1283
        version = determine_version_tree(spec, tree)
 
1284
        return pylon.iter_skip_replay_conflicts(tree, version)
 
1285
 
 
1286
def add_revision_iter_options(select):
 
1287
        select.add_option("", "--archive", action="store_const", 
 
1288
                          const="archive", dest="type", default="default",
 
1289
                          help="List all revisions in the archive")
 
1290
        select.add_option("", "--cacherevs", action="store_const", 
 
1291
                          const="cacherevs", dest="type",
 
1292
                          help="List all revisions stored in the archive as "
 
1293
                          "complete copies")
 
1294
        select.add_option("", "--logs", action="store_const", 
 
1295
                          const="logs", dest="type",
 
1296
                          help="List revisions that have a patchlog in the "
 
1297
                          "tree")
 
1298
        select.add_option("", "--missing", action="store_const", 
 
1299
                          const="missing", dest="type",
 
1300
                          help="List revisions from the specified version that"
 
1301
                          " have no patchlog in the tree")
 
1302
        select.add_option("", "--skip-present", action="store_const", 
 
1303
                          const="skip-present", dest="type",
 
1304
                          help="List revisions from the specified version that"
 
1305
                          " have no patchlogs at all in the tree")
 
1306
        select.add_option("", "--present", action="store_const", 
 
1307
                          const="present", dest="type",
 
1308
                          help="List revisions from the specified version that"
 
1309
                          " have no patchlog in the tree, but can't be merged")
 
1310
        select.add_option("", "--missing-from", action="store_const", 
 
1311
                          const="missing-from", dest="type",
 
1312
                          help="List revisions from the specified revision "
 
1313
                          "that have no patchlog for the tree version")
 
1314
        select.add_option("", "--partner-missing", action="store_const", 
 
1315
                          const="partner-missing", dest="type",
 
1316
                          help="List revisions in partner versions that are"
 
1317
                          " missing")
 
1318
        select.add_option("", "--new-merges", action="store_const", 
 
1319
                          const="new-merges", dest="type",
 
1320
                          help="List revisions that have had patchlogs added"
 
1321
                          " to the tree since the last commit")
 
1322
        select.add_option("", "--direct-merges", action="store_const", 
 
1323
                          const="direct-merges", dest="type",
 
1324
                          help="List revisions that have been directly added"
 
1325
                          " to tree since the last commit ")
 
1326
        select.add_option("", "--library", action="store_const", 
 
1327
                          const="library", dest="type",
 
1328
                          help="List revisions in the revision library")
 
1329
        select.add_option("", "--archive-ancestry", action="store_const", 
 
1330
                          const="archive-ancestry", dest="type",
 
1331
                          help="List revisions that are ancestors of the "
 
1332
                          "current tree version")
 
1333
        select.add_option("", "--ancestry", action="store_const", 
 
1334
                          const="ancestry", dest="type",
 
1335
                          help="List revisions that are ancestors of the "
 
1336
                          "current tree version")
 
1337
 
 
1338
        select.add_option("", "--dependencies", action="store_const", 
 
1339
                          const="dependencies", dest="type",
 
1340
                          help="List revisions that the given revision "
 
1341
                          "depends on")
 
1342
 
 
1343
        select.add_option("", "--non-dependencies", action="store_const", 
 
1344
                          const="non-dependencies", dest="type",
 
1345
                          help="List revisions that the given revision "
 
1346
                          "does not depend on")
 
1347
 
 
1348
        select.add_option("", "--with-deps", action="store_const", 
 
1349
                          const="with-deps", dest="type",
 
1350
                          help="The specified revision, plus its dependencies"
 
1351
                          "since the last present dependency")
 
1352
 
 
1353
        select.add_option("--micro", action="store_const", 
 
1354
                          const="micro", dest="type",
 
1355
                          help="List partner revisions aimed for this "
 
1356
                          "micro-branch")
 
1357
 
 
1358
        select.add_option("", "--modified", dest="modified", 
 
1359
                          help="List tree ancestor revisions that modified a "
 
1360
                          "given file", metavar="FILE[:LINE]")
 
1361
 
 
1362
        select.add_option("", "--shallow", dest="shallow", action="store_true",
 
1363
                          help="Don't scan merges when looking for revisions"
 
1364
                          " that modified a line")
 
1365
        select.add_option("--added-log", action="store_const", 
 
1366
                          const="added-log", dest="type",
 
1367
                          help="List revisions that added this log")
 
1368
 
 
1369
def log_for_merge2(tree, version):
 
1370
    merges = list(pylon.iter_new_merges(tree, version))
 
1371
    direct = direct_merges (merges)
 
1372
    out = "Patches applied:\n\n"
 
1373
    for log in direct:
 
1374
        out +=merge_log_recurse(log, merges, tree)
 
1375
    return out
 
1376
 
 
1377
def merge_log_recurse(log, interesting_merges, tree, indent = None):
 
1378
    """A variation on log-for-merge that scaled extremely badly"""
 
1379
    if indent is None:
 
1380
        indent = ""
 
1381
    out = "%s * %s\n%s   %s\n\n" % (indent, log.revision, indent, log.summary)
 
1382
    new_patch_logs = [f for f in interesting_merges if (f.revision in
 
1383
                      log.new_patches and f != log) ]
 
1384
    direct = direct_merges(new_patch_logs)
 
1385
    for merge in direct:
 
1386
        out+=merge_log_recurse(merge, new_patch_logs, tree, indent+"  ")
 
1387
    return out
 
1388
 
 
1389
def chatter(str):
 
1390
    colorize(arch.Chatter("* "+str))
 
1391
 
 
1392
 
 
1393
def user_hunk_confirm(hunk):
 
1394
    """Asks user to confirm each hunk
 
1395
    
 
1396
    :param hunk: a diff hunk
 
1397
    :type hunk: str
 
1398
    :return: The user's choice
 
1399
    :rtype: bool
 
1400
    """
 
1401
    for line in pylon.diff_classifier(hunk.split('\n')):
 
1402
        colorize(line)
 
1403
    return confirm("Revert this hunk?", False)
 
1404
 
 
1405
def merge_completions(tree, arg, index):
 
1406
    completions = list(pylon.iter_partners(tree, tree.tree_version))
 
1407
    if len(completions) == 0:
 
1408
        completions = list(tree.iter_log_versions())
 
1409
 
 
1410
    aliases = []
 
1411
    try:
 
1412
        for completion in completions:
 
1413
            alias = pylon.compact_alias(str(completion), tree)
 
1414
            if alias:
 
1415
                aliases.extend(alias)
 
1416
 
 
1417
        for completion in completions:
 
1418
            if completion.archive == tree.tree_version.archive:
 
1419
                aliases.append(completion.nonarch)
 
1420
 
 
1421
    except Exception, e:
 
1422
        print e
 
1423
        
 
1424
    completions.extend(aliases)
 
1425
    return completions
 
1426
 
 
1427
 
 
1428
def iter_file_completions(arg, only_dirs = False):
 
1429
    """Generate an iterator that iterates through filename completions.
 
1430
 
 
1431
    :param arg: The filename fragment to match
 
1432
    :type arg: str
 
1433
    :param only_dirs: If true, match only directories
 
1434
    :type only_dirs: bool
 
1435
    """
 
1436
    cwd = os.getcwd()
 
1437
    if cwd != "/":
 
1438
        extras = [".", ".."]
 
1439
    else:
 
1440
        extras = []
 
1441
    (dir, file) = os.path.split(arg)
 
1442
    if dir != "":
 
1443
        listingdir = os.path.expanduser(dir)
 
1444
    else:
 
1445
        listingdir = cwd
 
1446
    for file in iter_combine([os.listdir(listingdir), extras]):
 
1447
        if dir != "":
 
1448
            userfile = dir+'/'+file
 
1449
        else:
 
1450
            userfile = file
 
1451
        if userfile.startswith(arg):
 
1452
            if os.path.isdir(listingdir+'/'+file):
 
1453
                userfile+='/'
 
1454
                yield userfile
 
1455
            elif not only_dirs:
 
1456
                yield userfile
 
1457
 
 
1458
 
 
1459
def iter_dir_completions(arg):
 
1460
    """Generate an iterator that iterates through directory name completions.
 
1461
 
 
1462
    :param arg: The directory name fragment to match
 
1463
    :type arg: str
 
1464
    """
 
1465
    return iter_file_completions(arg, True)
 
1466
 
 
1467
 
 
1468
def iter_munged_completions(iter, arg, text):
 
1469
    for completion in iter:
 
1470
        completion = str(completion)
 
1471
        if completion.startswith(arg):
 
1472
            yield completion[len(arg)-len(text):]
 
1473
 
 
1474
# arch-tag: 7d28710a-0b02-4dcb-86a7-e6b3b5486da4