~abentley/bzrtools/bzrtools.dev

« back to all changes in this revision

Viewing changes to fai/pylon/arch_compound.py

  • Committer: Robert Collins
  • Date: 2005-09-14 11:27:20 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-20050914112720-c66a21de86eafa6e
trim fai cribbage

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 os
19
 
import shutil
20
 
import re
21
 
import tempfile
22
 
 
23
 
import util
24
 
import pybaz
25
 
import arch_core
26
 
import native
27
 
import errors
28
 
from arch_core import chattermatch
29
 
import misc
30
 
import patches
31
 
 
32
 
__docformat__ = "restructuredtext"
33
 
__doc__ = "Compound functionality built on lower-level commands"
34
 
 
35
 
def iter_any_ancestry(tree, startrev):
36
 
    return _iter_ancestry(tree, startrev)
37
 
 
38
 
def _iter_ancestry(tree, startrev):
39
 
    """Iterate over ancestry according to tree's patchlogs.
40
 
 
41
 
    Generator that yields `arch.Patchlog` or `arch.Revision` instances
42
 
    according to `tree` ancestry. Each item returned is the direct ancestor of
43
 
    the previous `arch.Revision`. If `startrev` is supplied, it is the first
44
 
    item yielded. The iteration stops after the first ancestor named in a
45
 
    patchlog that does not, itself, have a patchlog in `tree`, or at the
46
 
    beginning.
47
 
 
48
 
    :param tree: tree to determine the ancestry of
49
 
    :type tree: `arch.ArchSourceTree`
50
 
    :param startrev: revision to start at. Must be in the ancestry of `tree`.
51
 
    :type startrev: `arch.Revision`
52
 
    :rtype: iterator of `arch.Patchlog` or `arch.Revision`
53
 
    """
54
 
    log = None
55
 
    while startrev is not None:
56
 
        nextrev = None
57
 
        skip = True
58
 
        for log in tree.iter_logs(startrev.version, reverse=True):
59
 
            if skip and log.revision == startrev:
60
 
                skip = False
61
 
            if skip:
62
 
                continue
63
 
            yield log
64
 
            if log.continuation_of:
65
 
                nextrev = log.continuation_of # sure it should not be startrev?
66
 
# pretty sure.  if we did that, startrev would never be None
67
 
                break
68
 
        startrev = nextrev
69
 
    if log and log.continuation_of:
70
 
        yield log.continuation_of
71
 
 
72
 
 
73
 
def iter_ancestry(tree, startrev=None):
74
 
    """Iterate over ancestry according to tree's patchlogs.
75
 
 
76
 
    Generator that yields `arch.Revision` instances according to `tree`
77
 
    ancestry.  Each `arch.Revision` returned is the direct ancestor of the
78
 
    previous `arch.Revision`.  If `startrev` is supplied, it is the first item
79
 
    yielded. The iteration stops after the first ancestor named in a patchlog
80
 
    that does not, itself, have a patchlog in `tree`, or at the beginning.
81
 
 
82
 
    :param tree: tree to determine the ancestry of.
83
 
    :type tree: `arch.ArchSourceTree`
84
 
    :param startrev: optional revision to start at. Must be in the ancestry of
85
 
        `tree`. The latest revision is used if not supplied.
86
 
    :type startrev: `arch.Revision`
87
 
    :rtype: iterator of `arch.Revision`
88
 
    """
89
 
    if startrev is None:
90
 
        startrev = tree.tree_revision
91
 
    for r in _iter_ancestry(tree, startrev):
92
 
        if not isinstance(r, pybaz.Revision):
93
 
            r = r.revision
94
 
        yield r
95
 
 
96
 
 
97
 
def iter_ancestry_logs(tree, startrev=None):
98
 
    """Iterate over ancestry according to tree's patchlogs.
99
 
 
100
 
    Generator that yields `arch.Patchlog` instances according to `tree`
101
 
    ancestry.  Each `arch.Patchlog` returned is from the direct ancestor of the
102
 
    revision of the previous `arch.Patchlog`.  If `startrev` is supplied, it is
103
 
    the first item yielded. The iteration stops after the first ancestor named
104
 
    in a patchlog that does not, itself, have a patchlog in `tree`, or at the
105
 
    beginning.
106
 
 
107
 
    :param tree: tree to determine the ancestry of.
108
 
    :type tree: `arch.ArchSourceTree`
109
 
    :param startrev: optional revision to start at. Must be in the ancestry of
110
 
        `tree`. The latest revision is used if not supplied.
111
 
    :type startrev: `arch.Revision`
112
 
    :rtype: iterator of `arch.Patchlog`
113
 
    """
114
 
    if startrev is None:
115
 
        startrev = tree.tree_revision
116
 
    for r in _iter_ancestry(tree, startrev):
117
 
        if not isinstance(r, pybaz.Patchlog):
118
 
            r = pybaz.Patchlog(r)
119
 
        yield r
120
 
 
121
 
 
122
 
def get_entry_name(entry):
123
 
    """Returns a filename for the entry, preferring the MOD name
124
 
 
125
 
    :param entry: The entry to get a filename for
126
 
    :type entry: `ChangesetEntry`
127
 
    :return: The found file name
128
 
    :rtype: str
129
 
    """
130
 
    if entry.mod_name is not None:
131
 
        return entry.mod_name
132
 
    else:
133
 
        return entry.orig_name
134
 
 
135
 
def iter_changes(changeset_munger):
136
 
    entries = changeset_munger.get_entries()
137
 
    sorted_entries = list(entries.itervalues())
138
 
    sorted_entries.sort(util.cmp_func(get_entry_name))
139
 
 
140
 
    for entry in sorted_entries:
141
 
        if entry.orig_name and not entry.mod_name:
142
 
            if entry.orig_type == "dir":
143
 
                suf="/ "
144
 
            else:
145
 
                suf="  "
146
 
            yield pybaz.FileDeletion("D%s%s" % (suf, entry.orig_name[2:]), ' ')
147
 
 
148
 
    for entry in sorted_entries:
149
 
        if entry.mod_name and not entry.orig_name:
150
 
            if entry.mod_type == "dir":
151
 
                suf="/ "
152
 
            else:
153
 
                suf="  "
154
 
            yield pybaz.FileAddition("A%s%s" % (suf, entry.mod_name[2:]), ' ')
155
 
 
156
 
    for entry in sorted_entries:
157
 
        if entry.orig_name != entry.mod_name and entry.orig_name and \
158
 
            entry.mod_name:
159
 
            yield pybaz.FileRename("=> %s\t%s" % (entry.orig_name[2:], 
160
 
                                                 entry.mod_name[2:]), ' ')
161
 
        if entry.diff:
162
 
            yield pybaz.FileModification("M  %s" % entry.mod_name[2:], ' ')
163
 
 
164
 
        if entry.original or entry.modified:
165
 
            yield pybaz.FileModification("Mb %s" % entry.mod_name[2:], ' ')
166
 
 
167
 
        if entry.orig_perms or entry.mod_perms:
168
 
            if entry.mod_type == "file":
169
 
                yield pybaz.FilePermissionsChange("-- %s" % entry.mod_name[2:],
170
 
                                                 ' ')
171
 
            elif entry.mod_type == "dir":
172
 
                yield pybaz.FilePermissionsChange("-/ %s" % entry.mod_name[2:],
173
 
                                                 ' ')
174
 
 
175
 
def diff_iter2(changeset, 
176
 
               exclude_str='(^\./{arch\}|/(\.arch-ids|\.arch-inventory))'):
177
 
    """Native diff iterator
178
 
 
179
 
    :param changeset: The changeset to iterate through the diffs of
180
 
    :type changeset: str
181
 
    """
182
 
    munger = native.ChangesetMunger(changeset)
183
 
    munger.read_indices()
184
 
    sorted_entries = list(munger.get_entries().itervalues())
185
 
    sorted_entries.sort(util.cmp_func(get_entry_name))
186
 
    if exclude_str is not None:
187
 
        exclude = re.compile(exclude_str)
188
 
    else:
189
 
        exclude = None
190
 
    for entry in sorted_entries:
191
 
        if exclude is not None and munger.entry_matches_pattern(entry, exclude):
192
 
            continue
193
 
        if entry.diff is not None:
194
 
            lines = [f.rstrip('\n') for f in open(entry.diff)]
195
 
        elif entry.mod_name is None or entry.orig_name is None:
196
 
            if entry.removed_file is None and entry.new_file is None:
197
 
                continue
198
 
            try:
199
 
                diff = native.invoke_diff(changeset+"/removed-files-archive",
200
 
                                          entry.orig_name,
201
 
                                          changeset+"/new-files-archive",
202
 
                                          entry.mod_name)
203
 
                lines = diff.split('\n')
204
 
            except native.BinaryFiles:
205
 
                continue
206
 
        else:
207
 
            continue
208
 
        for line in native.diff_classifier(lines):
209
 
            yield line
210
 
 
211
 
 
212
 
def replay(tree, revision, linehandler=None, reverse=False, munge_opts=None):
213
 
    """Applies the changeset for a revision to the tree
214
 
 
215
 
    :param tree: The tree to apply changes for
216
 
    :type tree: `arch.WorkingTree`
217
 
    :param revision: The revision to get the changes for
218
 
    :type revision: `arch.Revision`
219
 
    :param reverse: Apply the changeset in reverse
220
 
    :type reverse: bool
221
 
    :param munge_opts: options for munging the changeset before applying
222
 
    :type munge_opts: `MungeOpts`
223
 
    """
224
 
    #revision = NativeRevision(str(revision))
225
 
    my_dir = util.tmpdir(os.getcwd())
226
 
    try:
227
 
        changeset = revision.get_patch(my_dir)
228
 
        munger = native.ChangesetMunger(changeset)
229
 
        munger.read_indices()
230
 
        if munge_opts is not None:
231
 
            munger.munge(munge_opts)
232
 
#        entry_handler = PatchEntryHandler(tree)
233
 
        entry_handler = None
234
 
        iterator = iter_custom_apply_changeset(tree, munger, entry_handler, reverse=reverse)
235
 
        for line in iterator:
236
 
            if linehandler is not None:
237
 
                linehandler(line)
238
 
    finally:
239
 
        if os.access(my_dir, os.R_OK):
240
 
            shutil.rmtree(my_dir)
241
 
    try:
242
 
        if iterator.conflicting:
243
 
            return 1
244
 
        else:
245
 
            return 0
246
 
    except:
247
 
        return 2
248
 
 
249
 
 
250
 
def revert(tree, revision, munge_opts=None, keep=True):
251
 
    """Apply the difference between two things to tree.
252
 
 
253
 
    :param tree: The tree to apply changes to
254
 
    :type tree: `arch.ArchSourceTree`
255
 
    :param revision: The revision to apply the changes to
256
 
    :type revision: `arch.Revision`
257
 
    :param munge_opts: If supplied, a set of parameters for munging the changeset
258
 
    :type munge_opts: `MungeOpts`
259
 
    :param keep: If true, keep the generated changeset
260
 
    :type keep: bool
261
 
    :rtype: Iterator of changeset application output 
262
 
    """
263
 
    changeset = pybaz.util.new_numbered_name(tree, ',,undo-')
264
 
    delt=pybaz.iter_delta(revision, tree, changeset)
265
 
    for line in delt:
266
 
        if not isinstance(line, pybaz.TreeChange) and not chattermatch(line, "changeset:"):
267
 
            yield line
268
 
    if munge_opts is not None:
269
 
        yield pybaz.Chatter("* munging changeset")
270
 
        munger = native.ChangesetMunger(delt.changeset)
271
 
        entries = munger.munge(munge_opts)
272
 
        if entries == 0 and munge_opts.keep_pattern is not None:
273
 
            print "No entries matched pattern %s" % munge_opts.keep_pattern
274
 
    yield pybaz.Chatter("* applying changeset")
275
 
    for line in delt.changeset.iter_apply(tree, reverse=True):
276
 
        yield line
277
 
    if not keep:
278
 
        shutil.rmtree(changeset)
279
 
 
280
 
 
281
 
def iter_added_log(logs, revision):
282
 
    """Generate an iterator of revisions that added a certain revision log.
283
 
 
284
 
    :param logs: An iterator of patchlogs to filter
285
 
    :type logs: iter of `arch.Patchlog`
286
 
    :param revision: The revision of the log to look for
287
 
    :type revision: `arch.Revision`
288
 
    :rtype: iter of `arch.Revision`
289
 
    """
290
 
    for log in logs:
291
 
        if revision in log.new_patches:
292
 
            yield log
293
 
 
294
 
 
295
 
def merge_ancestor2(mine, other, other_revision):
296
 
    """Determines an ancestor suitable for merging, according to the tree logs.
297
 
 
298
 
    :param mine: Tree to find an ancestor for (usually the working tree)
299
 
    :type mine: `arch.WorkingTree`
300
 
    :param other: Tree that merged or was merged by mine
301
 
    :type other: `arch.ArchSourceTree`
302
 
    :param other_revision: Override for broken library revisions
303
 
    :type other_revision: `arch.Revision`
304
 
    :return: Log of the merged revision
305
 
    :rtype: `arch.Patchlog`
306
 
    """
307
 
    # find latest ancestor of opposite tree in each tree
308
 
    my_revision=tree_latest(mine)
309
 
    my_merged=last_merge_of(other, iter_ancestry(mine, my_revision))
310
 
    other_merged=last_merge_of(mine, iter_ancestry(other, other_revision))
311
 
    if my_merged is None:
312
 
        if other_merged is None:
313
 
            raise errors.UnrelatedTrees(mine, other)
314
 
        return other_merged
315
 
    if other_merged is None:
316
 
        return my_merged
317
 
 
318
 
    # determine which merge happend later in sequence for each tree
319
 
    other_latest = latest_merge(other, other_revision, other_merged, my_merged)
320
 
    my_latest = latest_merge(mine, my_revision, my_merged, other_merged)
321
 
    if other_latest != my_latest:
322
 
        #probably paralel merge
323
 
        raise errors.NoLatestRevision(my_latest, other_latest)
324
 
 
325
 
    return my_latest
326
 
 
327
 
 
328
 
def last_merge_of(into, ancestry):
329
 
    """Determine last log from the ancestry of "merged" in "into".
330
 
 
331
 
    :param into: The tree that a log was merged into
332
 
    :type into: `arch.ArchSourceTree`
333
 
    :param ancestry: An interator of the tree's ancestry
334
 
    :type ancestry: iter of `arch.Patchlog`
335
 
    :return: The last merged log, or None if none found
336
 
    :rtype: `arch.Revision`
337
 
    """
338
 
    log_list = None
339
 
    log_version = None
340
 
 
341
 
    for ancestor in ancestry:
342
 
        if ancestor.version != log_version:
343
 
            log_version = ancestor.version
344
 
            log_list = list([f.revision for f in into.iter_logs(log_version)])
345
 
        if ancestor in log_list:
346
 
            return ancestor
347
 
    return None
348
 
 
349
 
 
350
 
def latest_merge(tree, tree_revision, tree_merged, other_merged):
351
 
    """Determines which of two revisions was merged most recently into tree
352
 
    :param tree: The tree the revisions were merged into
353
 
    :type tree: `arch.ArchSourceTree`
354
 
    :param tree_merged: The revision from tree that was merged
355
 
    :type tree_merged: `arch.Revision`
356
 
    :param other_merged: The revision from OTHER that was merged
357
 
    :type other_merged: `arch.Revision`
358
 
    :return: The revision merged most recently, according to this tree
359
 
    :rtype: `arch.Revision`
360
 
    """
361
 
 
362
 
    for log in iter_ancestry_logs(tree, tree_revision):
363
 
        if log.revision == tree_merged:
364
 
            return tree_merged
365
 
        # skip logs that are continuations, because of bogus new-patches
366
 
        if log.continuation_of:
367
 
            continue
368
 
        if other_merged in log.new_patches:
369
 
            return other_merged
370
 
    return None
371
 
 
372
 
 
373
 
def iter_vchange(iterator):
374
 
    """First revision of each sequence from a different package-version.
375
 
 
376
 
    Generator that yields only those revisions in different
377
 
    package-versions than the previous revision.
378
 
 
379
 
    :type iterator: iterator of `arch.Revision`
380
 
    :rtype: iterator of `arch.Revision`
381
 
    """
382
 
    oldversion = None
383
 
    for item in iterator:
384
 
        if oldversion is not None and item.version != oldversion:
385
 
            yield item
386
 
        oldversion = item.version
387
 
 
388
 
 
389
 
def tag_source(tree):
390
 
    """Return the revision that the current tree thinks it was tagged from.
391
 
 
392
 
    :type tree: `arch.ArchSourceTree`
393
 
    :rtype: `arch.Revision`
394
 
    """
395
 
    try:
396
 
        return iter_vchange(iter_ancestry(tree, None)).next()
397
 
    except StopIteration:
398
 
        return None
399
 
 
400
 
 
401
 
def ensure_revision_exists(revision):
402
 
    pass
403
 
 
404
 
 
405
 
def ensure_archive_registered(archive, location=None):
406
 
    """Throw an exception if the archive isn't registered.
407
 
    This function is intended to be overridden.
408
 
 
409
 
    :param archive: The archive to check
410
 
    :type archive: `arch.Archive`
411
 
    :param location: A possible location of the archive
412
 
    :type location: str
413
 
    """
414
 
    if archive.is_registered():
415
 
        return
416
 
    else:
417
 
        raise pybaz.errors.ArchiveNotRegistered(archive)
418
 
 
419
 
 
420
 
def tree_file_path(tree, path):
421
 
    """Convert a relative or absolute path into a tree-relative path
422
 
 
423
 
    :param tree: The tree the path falls in
424
 
    :param path: The path the tree falls in
425
 
    """
426
 
    dir, file = os.path.split(path)
427
 
    if dir == "":
428
 
        dir = "."
429
 
    dir = os.path.realpath(dir)
430
 
    treedir = os.path.realpath(tree)
431
 
    if not dir.startswith(treedir):
432
 
        raise errors.TreeNotContain(tree, path)
433
 
    dir = dir[len(treedir)+1:]
434
 
    return os.path.join(dir, file)
435
 
 
436
 
def tree_cwd(tree):
437
 
    """Get the current directory expressed as a tree path.
438
 
    :param tree: The tree that the current directory is in
439
 
    :type tree: ArchSourceTree
440
 
    :return: The tree path
441
 
    :raise TreeNotContain: The path does not fall in the tree
442
 
    """
443
 
    cwd = os.getcwd()
444
 
    if not cwd.startswith(str(tree)):
445
 
        raise errors.TreeNotContain(tree, cwd)
446
 
    treepath = cwd[len(str(tree)):]
447
 
    treepath = treepath.lstrip('/')
448
 
    return treepath
449
 
 
450
 
 
451
 
def iter_changedfile(tree, revision, filename, yield_file=False):
452
 
    """Lists logs of ancestor revisions that changed a particular file.  
453
 
    Revisions that do not have patchlogs in the tree are ignored.
454
 
 
455
 
    :param tree: The tree to get logs from
456
 
    :type tree: `arch.ArchSourceTree`
457
 
    :param revision: The revision to trace ancestry from
458
 
    :type revision: `arch.Revision`
459
 
    :param filename: The filename to look for changes to
460
 
    :type filename: str
461
 
    :rtype: iterator of `arch.Patchlog`
462
 
    """
463
 
    cwd = tree_cwd(tree)
464
 
    if cwd != "":
465
 
        filename = cwd + "/" + filename
466
 
    for log in _iter_ancestry(tree, revision):
467
 
        if isinstance(log, pybaz.Patchlog):
468
 
            if filename in log.modified_files or filename in log.new_files or filename in log.removed_files:
469
 
                if yield_file:
470
 
                    yield (filename, log)
471
 
                else:
472
 
                    yield log
473
 
            if log.renamed_files:
474
 
                for (key, value) in log.renamed_files.iteritems():
475
 
                    if value == filename:
476
 
                        filename = key
477
 
                        break
478
 
 
479
 
def iter_changed_file_line(tree, revision, filename, line_num):
480
 
    """Lists logs of ancestor revisions that changed a particular file.  
481
 
    Revisions that do not have patchlogs in the tree are ignored.
482
 
 
483
 
    :param tree: The tree to get logs from
484
 
    :type tree: `arch.ArchSourceTree`
485
 
    :param revision: The revision to trace ancestry from
486
 
    :type revision: `arch.Revision`
487
 
    :param filename: The filename to look for changes to
488
 
    :type filename: str
489
 
    :rtype: iterator of `arch.Patchlog`
490
 
    """
491
 
    cwd = tree_cwd(tree)
492
 
    if cwd != "":
493
 
        cwd += ("/")
494
 
 
495
 
    patches = []
496
 
    for (filename, log) in iter_changedfile(tree, revision, filename, True):
497
 
        path = filename
498
 
        if log.revision.patchlevel == "base-0" and log.continuation_of is None:
499
 
            continue
500
 
        if filename in log.new_files:
501
 
            continue
502
 
        if filename in log.removed_files:
503
 
            continue
504
 
        patch = parse_changeset_patches(log.revision,[path])[0]
505
 
        if patch is None:
506
 
            continue
507
 
        new_iter = patch.iter_inserted()
508
 
        for (num, line) in new_iter:
509
 
            old_num = num
510
 
 
511
 
            for cur_patch in patches:
512
 
                num = cur_patch.pos_in_mod(num)
513
 
                if num == None: 
514
 
                    break
515
 
            if num == line_num - 1:
516
 
                yield log
517
 
        patches=[patch]+patches
518
 
 
519
 
 
520
 
class AnnotateLine:
521
 
    """A line associated with the log that produced it"""
522
 
    def __init__(self, text, log=None):
523
 
        self.text = text
524
 
        self.log = log
525
 
 
526
 
 
527
 
def annotate_file(tree, revision, filename):
528
 
    """Lists logs of ancestor revisions that changed a particular file.  
529
 
    Revisions that do not have patchlogs in the tree are ignored.
530
 
 
531
 
    :param tree: The tree to get logs from
532
 
    :type tree: `arch.ArchSourceTree`
533
 
    :param revision: The revision to trace ancestry from
534
 
    :type revision: `arch.Revision`
535
 
    :param filename: The filename to look for changes to
536
 
    :type filename: str
537
 
    :rtype: iterator of `arch.Patchlog`
538
 
    """
539
 
    lines = [AnnotateLine(f) for f in open(filename)]
540
 
 
541
 
    patches = []
542
 
    for (filename, log) in iter_changedfile(tree, revision, filename, True):
543
 
        patch = None
544
 
        path = filename
545
 
        if log.revision.patchlevel == "base-0" and log.continuation_of is None:
546
 
            continue
547
 
        if filename in log.new_files:
548
 
            new_iter = iter_new_file(log.revision, path)
549
 
        else:
550
 
            patch = parse_changeset_patches(log.revision,[path])[0]
551
 
            if patch is None: 
552
 
                continue
553
 
            new_iter = patch.iter_inserted()
554
 
        for (num, line) in new_iter:
555
 
            old_num = num
556
 
 
557
 
            for cur_patch in patches:
558
 
                num = cur_patch.pos_in_mod(num)
559
 
                if num == None: 
560
 
                    break
561
 
 
562
 
            if num is not None and lines[num].log is None:
563
 
                lines[num].log = log
564
 
        if patch is None:
565
 
            break
566
 
        patches=[patch]+patches
567
 
    return lines
568
 
 
569
 
 
570
 
def print_annotated(lines):
571
 
    """Unprettily-prints a set of annotated lines from annotate_file"""
572
 
    last = "NotNoneOrPatchlog"
573
 
    for line in lines:
574
 
        if line.log != last:
575
 
            if line.log == None:
576
 
                print "UNASSIGNED"
577
 
            else:
578
 
                print line.log.revision
579
 
            last=line.log
580
 
        print("  " + line.text.rstrip("\n"))
581
 
 
582
 
def iter_changed_file_line_orig(tree, revision, filename, line_num):
583
 
    """Lists logs of revisions that originally added a line to a particular
584
 
    file.  Revisions that do not have patchlogs in the tree are ignored.
585
 
 
586
 
    :param tree: The tree to get logs from
587
 
    :type tree: `arch.ArchSourceTree`
588
 
    :param revision: The revision to trace ancestry from
589
 
    :type revision: `arch.Revision`
590
 
    :param filename: The filename to look for changes to
591
 
    :type filename: str
592
 
    :rtype: iterator of `arch.Patchlog`
593
 
    """
594
 
    path = tree_cwd(tree)+"/"+filename
595
 
    my_file = open(filename)
596
 
    try:
597
 
        for i in range(line_num):
598
 
            line = my_file.next()
599
 
    except StopIteration:
600
 
        raise ValueError("line number out of range")
601
 
    for log in iter_changed_file_line(tree, revision, filename, line_num):
602
 
        yield merge_blame(log, filename, path, line)
603
 
 
604
 
 
605
 
def merge_blame(log, filename, path, line, diffs=None):
606
 
    """Return the log of the revision that added a line.
607
 
 
608
 
    :param log: The log of the revision that added a line
609
 
    :type log: `arch.Patchlog`
610
 
    :param filename: The name of the file to look for
611
 
    :type filename: str
612
 
    :param path: The path of the file to look for
613
 
    :type path: str
614
 
    :param line: The added line
615
 
    :type line: str
616
 
    """
617
 
    for revision in log.new_patches:
618
 
        if revision == log.revision:
619
 
            continue
620
 
        if filename in log.modified_files or filename in log.new_files:
621
 
            if patch_added_line(log.revision, filename, line, diffs):
622
 
                return merge_blame(revision.patchlog, filename, path, line,
623
 
                                   diffs)
624
 
    return log
625
 
 
626
 
def get_diff(revision, path, diffs):
627
 
    """Returns the diff for a given path, memoizing it in diffs.
628
 
    :param revision: The revision to find the diff for
629
 
    :type revision: `arch.Revision`
630
 
    :param path: The path of the diff to get
631
 
    :type path: str
632
 
    :param diffs: The diff will be placed in diffs if it's not already there
633
 
    :type diffs: map of str => str
634
 
    :return: lines of the diff
635
 
    """
636
 
    if diffs is not None:
637
 
        diff = diffs.get(str(revision))
638
 
    else:
639
 
        diff = None
640
 
 
641
 
    if diff is not None:
642
 
        return diff
643
 
    
644
 
    tree = util.tmpdir()
645
 
    try:
646
 
        ensure_archive_registered(revision.archive)
647
 
        changeset = revision.get_patch(tree+"/changeset")
648
 
        patch_path = "%s/patches/%s.patch" % (changeset, path)
649
 
        if not os.access(patch_path, os.R_OK):
650
 
            return None
651
 
        diff = list(open(patch_path))
652
 
        if diffs is not None:
653
 
            diffs[str(revision)] = diff
654
 
    finally:
655
 
        shutil.rmtree(tree)
656
 
    return diff
657
 
 
658
 
def patch_added_line(revision, path, line, diffs=None):
659
 
    """Determine whether a revision's changeset added a line.
660
 
 
661
 
    :param revision: The log of the revision that may have added a line
662
 
    :type revision: `arch.Revision`
663
 
    :param path: The path of the file to look for
664
 
    :type path: str
665
 
    :param line: The added line
666
 
    :type line: str
667
 
    """
668
 
    diff = get_diff(revision, path, diffs)
669
 
    if diff is None:
670
 
        return False
671
 
    for pline in diff:
672
 
        match = (pline.startswith('+') and pline[1:] == line)
673
 
        if match:
674
 
            break
675
 
    return match
676
 
 
677
 
 
678
 
def parse_changeset_patches(revision, paths):
679
 
    """Retrieves the requested patches from a revision changeset.
680
 
 
681
 
    :param revision: The revision of the changeset
682
 
    :type revision: `arch.Revision`
683
 
    :param paths: The path of the file to get patches for
684
 
    :type path: str
685
 
    :return: list of patches, in order of request
686
 
    :rtype: list of `patches.Patch`
687
 
    """
688
 
    tree = util.tmpdir()
689
 
    ensure_archive_registered(revision.archive)
690
 
    changeset = revision.get_patch(tree+"/changeset")
691
 
    patchen = []
692
 
    for path in paths:
693
 
        patch_path = "%s/patches/%s.patch" % (changeset, path)
694
 
        if os.path.exists(patch_path):
695
 
            patchen.append(patches.parse_patch(open(patch_path)))
696
 
        else:
697
 
            patchen.append(None)
698
 
    shutil.rmtree(tree)
699
 
    return patchen 
700
 
 
701
 
 
702
 
def iter_new_file(revision, path):
703
 
    """Retrieves the requested patches from a revision changeset.
704
 
 
705
 
    :param revision: The revision of the changeset
706
 
    :type revision: `arch.Revision`
707
 
    :param paths: The path of the file to get patches for
708
 
    :type path: str
709
 
    :return: list of patches, in order of request
710
 
    :rtype: list of `patches.Patch`
711
 
    """
712
 
    tree = util.tmpdir()
713
 
    ensure_archive_registered(revision.archive)
714
 
    changeset = revision.get_patch(tree+"/changeset")
715
 
    file_path = "%s/new-files-archive/%s" % (changeset, path)
716
 
    i = 0
717
 
    lines = []
718
 
    for line in open(file_path):
719
 
        lines.append((i, line))
720
 
        i += 1
721
 
    shutil.rmtree(tree)
722
 
    for line in lines:
723
 
        yield line
724
 
 
725
 
 
726
 
def iter_log_match(iter, key, value):
727
 
    for log in iter:
728
 
        if isinstance(log, pybaz.Revision):
729
 
            log = pybaz.Patchlog(log)
730
 
        if log[key] == value:
731
 
            yield log
732
 
 
733
 
 
734
 
def tree_latest(tree, version=None):
735
 
    """Return the latest Revision in the tree
736
 
 
737
 
    :param tree: The tree to return the revision for
738
 
    :type tree: `arch.ArchSourceTree`
739
 
    :param version: The version of the revision to find
740
 
    :type version: `arch.Version`
741
 
    """
742
 
    if version is None:
743
 
        version = tree.tree_version
744
 
    try:
745
 
        log = tree.iter_logs(version = version, reverse = True).next()
746
 
        return log.revision
747
 
    except StopIteration:
748
 
        raise errors.NoVersionRevisions(tree, version)
749
 
 
750
 
def iter_all_library_revisions():
751
 
    """Generates an iterator of all revisions in the library
752
 
    :return: iterator of all revisions in the revision library
753
 
    :rtype: iter of `arch.Revision`
754
 
    """
755
 
    for archive in pybaz.iter_library_archives():
756
 
        for category in archive.iter_library_categories():
757
 
            for branch in category.iter_library_branches():
758
 
                for version in branch.iter_library_versions():
759
 
                    for revision in version.iter_library_revisions():
760
 
                        yield revision
761
 
 
762
 
 
763
 
def is_library_dir(directory):
764
 
    directory = os.path.realpath(directory)
765
 
    for revlib in arch_core.iter_revision_libraries():
766
 
        if dir == revlib:
767
 
            return True
768
 
    return False
769
 
 
770
 
 
771
 
def iter_greedy_libraries():
772
 
    """Generate an iterator of greedy libraries.
773
 
    :rtype: iter of str
774
 
    """
775
 
    for path in arch_core.iter_revision_libraries():
776
 
        if os.access("%s/=greedy" % path, os.R_OK):
777
 
            yield path
778
 
 
779
 
 
780
 
def find_or_make_local_revision(revision):
781
 
    """Finds or makes a library copy of a revision.
782
 
    :param revision: The revision to look for
783
 
    :type revision: `arch.Revision`
784
 
    :return: The library copy
785
 
    :rtype: `arch.LibraryTree`
786
 
    """
787
 
    try:
788
 
        return revision.library_find()
789
 
    except:
790
 
        ensure_revision_exists(revision)
791
 
        for lib in iter_greedy_libraries():
792
 
            list(arch_core.iter_library_add(revision, lib))
793
 
            return revision.library_find()
794
 
    raise errors.NoGreedy(revision)
795
 
 
796
 
def list_mod_names(tree):
797
 
    tmpdir = util.tmpdir()+"/changeset"
798
 
    modified = []
799
 
    try:
800
 
        for line in pybaz.iter_delta(tree_latest(tree), tree, tmpdir):
801
 
            if isinstance(line, pybaz.TreeChange):
802
 
                modified.append(line.name[1:])
803
 
    finally:
804
 
        shutil.rmtree(tmpdir)
805
 
    return modified
806
 
 
807
 
def list_mod(tree, revision=None):
808
 
    tmpdir = util.tmpdir()+"/changeset"
809
 
    modified = []
810
 
    if revision is None:
811
 
        revision = tree_latest(tree)
812
 
    try:
813
 
        for line in pybaz.iter_delta(revision, tree, tmpdir):
814
 
            if isinstance(line, pybaz.TreeChange):
815
 
                if line.name[0]==" ":
816
 
                    line.name = line.name[1:]
817
 
                modified.append(line)
818
 
    finally:
819
 
        shutil.rmtree(tmpdir)
820
 
    return modified
821
 
 
822
 
def valid_tree_root(tree):
823
 
    if tree is None:
824
 
        raise errors.TreeRootNone()
825
 
 
826
 
 
827
 
def iter_no_conflicts(iter_rev, test_dir, good_dir, reverse=False):
828
 
    """Generate an iterator of revisions/changesets that don't conflict.
829
 
    revision,changeset tuples are returned, but the changeset is None if
830
 
    applying it would produce conflicts.
831
 
 
832
 
    :param iter_rev: the iterator of revisions to go through
833
 
    :type iter_rev: iterator of `arch.Revision`
834
 
    :param test_dir: The scratch directory to use
835
 
    :type test_dir: str
836
 
    :param good_dir: The good directory to copy from
837
 
    :type good_dir: str
838
 
    :param reverse: Apply revisions in reverse?
839
 
    :type reverse: bool
840
 
    :rtype: iter of (revision, changeset) 
841
 
    """
842
 
    for revision in iter_rev:
843
 
        if revision == None:
844
 
            continue
845
 
        if isinstance(revision, pybaz.Patchlog):
846
 
            revision = revision.revision
847
 
        directory = util.tmpdir()
848
 
        tree = pybaz.tree_root(test_dir)
849
 
        changeset = revision.get_patch(directory+"/changeset")
850
 
        result = arch_core.apply_changeset(tree, changeset, 
851
 
                                           reverse=reverse)
852
 
        if result:
853
 
            changeset = None
854
 
            shutil.rmtree(test_dir)
855
 
            util.linktree(good_dir, test_dir)
856
 
 
857
 
        yield (revision, changeset)
858
 
        shutil.rmtree(directory)
859
 
 
860
 
def iter_no_conflicts_rev(iter_rev, test_dir, good_dir, reverse=False):
861
 
    """Generate an iterator of revisions/changesets that don't conflict.
862
 
    
863
 
    :param iter_rev: the iterator of revisions to go through
864
 
    :type iter_rev: iterator of `arch.Revision`
865
 
    :param test_dir: The scratch directory to use
866
 
    :type test_dir: str
867
 
    :param good_dir: The good directory to copy from
868
 
    :type good_dir: str
869
 
    :param reverse: Apply revisions in reverse?
870
 
    :type reverse: bool
871
 
    :rtype: iter of revision 
872
 
    """
873
 
    for (revision, changeset) in \
874
 
        iter_no_conflicts(iter_rev, test_dir, good_dir, reverse):
875
 
        if changeset is not None:
876
 
            shutil.rmtree(changeset)
877
 
            yield revision
878
 
 
879
 
 
880
 
def iter_skip_replay_conflicts(tree, version):
881
 
    """Generate an iterator of revisions/changesets that won't conflict if
882
 
    replayed.  Assumes that all revisions returned are applied.
883
 
    
884
 
    :param tree: the tree that revisions might conflict with
885
 
    :type tree: `arch.WorkingTree`
886
 
    :param version: 
887
 
    :param reverse: Apply revisions in reverse?
888
 
    """
889
 
    iter_miss = arch_core.iter_missing(tree, version, False)
890
 
    test_dir = util.tmpdir(tree)
891
 
    try:
892
 
        util.linktree(tree, test_dir, top_exists = True)
893
 
        iter_no_conf = iter_no_conflicts_rev(iter_miss, test_dir, tree)
894
 
        return util.iter_delete_wrapper(iter_no_conf, test_dir)
895
 
    except:
896
 
        shutil.rmtree(test_dir)
897
 
        raise
898
 
 
899
 
 
900
 
class iter_depends:
901
 
    def __init__(self, anc_iter, nondeps=True):
902
 
        self.anc_iter = anc_iter
903
 
        self.nondeps=nondeps
904
 
        try:
905
 
            revision = anc_iter.next()
906
 
            if isinstance(revision, pybaz.Patchlog):
907
 
                revision = revision.revision
908
 
            self.treedir = util.tmpdir(os.getcwd())
909
 
            self.good = self.treedir+"/good"
910
 
            self.test = self.treedir+"/test"
911
 
            list(arch_core.iter_get(revision, self.good))
912
 
            util.linktree(self.good, self.test)
913
 
        except StopIteration:
914
 
            self.treedir = None
915
 
 
916
 
    def __del__(self):
917
 
        if self.treedir is not None:
918
 
            shutil.rmtree(self.treedir)
919
 
 
920
 
    def __iter__(self):
921
 
        if self.treedir is None:
922
 
            return
923
 
        revision = None
924
 
        for newrevision in self.anc_iter:
925
 
            if revision == None:
926
 
                revision = newrevision
927
 
                continue
928
 
            if isinstance(revision, pybaz.Patchlog):
929
 
                revision = revision.revision
930
 
            directory = util.tmpdir()
931
 
            try:
932
 
                changeset = revision.get_patch(directory+"/changeset")
933
 
                result = arch_core.apply_changeset(pybaz.tree_root(self.test),
934
 
                                                   changeset, reverse=True)
935
 
                if result:
936
 
                    arch_core.apply_changeset(pybaz.tree_root(self.good),
937
 
                                              changeset, reverse=True)
938
 
            except:
939
 
                shutil.rmtree(directory)
940
 
                raise
941
 
            
942
 
            shutil.rmtree(directory)
943
 
            if (result == 0) == self.nondeps:
944
 
                yield revision
945
 
            if result != 0:
946
 
                shutil.rmtree(self.test)
947
 
                shutil.copytree(self.good, self.test)
948
 
            revision = newrevision
949
 
 
950
 
 
951
 
class LogMemo:
952
 
    """Memoized access to tree logs"""
953
 
    def __init__(self, tree):
954
 
        self.tree = tree
955
 
        self.logs = {}
956
 
 
957
 
    def get_log(self, revision):
958
 
        """From tree, get the log for this revision.
959
 
        
960
 
        :param revision: The revision to get
961
 
        :type revision: `arch.Revision`
962
 
        """
963
 
        if not self.logs.has_key(revision.version):
964
 
            self.logs[str(revision.version)] = \
965
 
                list(self.tree.iter_logs(revision.version))
966
 
        for log in self.logs[str(revision.version)]:
967
 
            if log.revision == revision:
968
 
                return log
969
 
        return None
970
 
 
971
 
 
972
 
def iter_to_present(iter, tree):
973
 
    """Returns all logs until it encounters new-patch present in the tree
974
 
 
975
 
    :param iter: The iterator that produces logs/revisions
976
 
    :type iter: iter of `arch.Patchlog` or `arch.Revision`
977
 
    """
978
 
    log_memo = LogMemo(tree)
979
 
    for log in iter:
980
 
        if isinstance(log, pybaz.Revision):
981
 
            log = log.patchlog
982
 
        for revision in log.new_patches:
983
 
            if log_memo.get_log(revision) is not None:
984
 
                return
985
 
        yield log
986
 
 
987
 
 
988
 
def changelog_for_merge(logs, merges):
989
 
    """Produces a new-format log-for-merge that includes merged log bodies
990
 
 
991
 
    :param logs: The list or iter of logs to use.  Direct merges recommended.
992
 
    :type logs: iter of `arch.Patchlog`
993
 
    :return: A string containing log-for-merge text
994
 
    :rtype: str
995
 
    """
996
 
    changelog = ""
997
 
    for log in logs:
998
 
        if changelog != "":
999
 
            changelog += "\n"
1000
 
        changelog += " * %s\n" % log.revision
1001
 
        changelog += " * %s\n" % log.summary
1002
 
        if (log.description != ""):
1003
 
            has_bogus_merges = False
1004
 
            for merge in log.new_patches:
1005
 
                if merges is not None and not merge in [x.revision for x in 
1006
 
                                                        merges]:
1007
 
                    has_bogus_merges = True
1008
 
                    break
1009
 
            if not has_bogus_merges:
1010
 
                changelog += log.description
1011
 
    return changelog
1012
 
 
1013
 
 
1014
 
class EntryHandler:
1015
 
    def __init__(self, tree):
1016
 
        self.tree = tree
1017
 
        self.id_map = arch_core.get_id_filename_map(tree)
1018
 
 
1019
 
class PatchEntryHandler(EntryHandler):
1020
 
    def __init__(self, tree):
1021
 
        EntryHandler.__init__(self, tree)
1022
 
        self.removed_dir = None
1023
 
 
1024
 
    def __call__(self, entry, reverse):
1025
 
        if not entry.diff:
1026
 
            return None
1027
 
        status = self.invoke_patch(entry, reverse)
1028
 
        if status == 0:
1029
 
            result = pybaz.FileModification("M  %s" % entry.mod_name[2:], ' ')
1030
 
        elif status == 1:
1031
 
            result = pybaz.PatchConflict("C   %s" % entry.mod_name[2:])
1032
 
        else:
1033
 
            raise Exception("unexpected status %s" % str(status))
1034
 
        os.unlink(entry.diff)
1035
 
        return result
1036
 
 
1037
 
    def get_removed_dir(self):
1038
 
        if self.removed_dir is None:
1039
 
            self.removed_dir = tempfile.mkdtemp("", "+removed-conflict-files", 
1040
 
                                        self.tree)
1041
 
        return self.removed_dir
1042
 
 
1043
 
    def remove_conflict_files(self, file):
1044
 
        rej = file+".rej"
1045
 
        if not os.path.exists(rej):
1046
 
            rej = None
1047
 
        orig = file+".orig"
1048
 
        if not os.path.exists(orig):
1049
 
            orig = None
1050
 
        if orig is not None or rej is not None:
1051
 
            removed_dir = self.get_removed_dir()
1052
 
            new_parent_dir = os.path.dirname(os.path.join(removed_dir,
1053
 
                                             file[len(self.tree):]))
1054
 
            if not os.path.exists(new_parent_dir):
1055
 
                os.makedirs(new_parent_dir)
1056
 
 
1057
 
            if orig is not None:
1058
 
                os.rename(orig, os.path.join(new_parent_dir, 
1059
 
                          os.path.basename(orig)))
1060
 
            if rej is not None:
1061
 
                os.rename(rej, os.path.join(new_parent_dir, 
1062
 
                          os.path.basename(rej)))
1063
 
 
1064
 
    def invoke_patch(self, entry, reverse=False):
1065
 
        file = os.path.join(self.tree, self.id_map[entry.id])
1066
 
        self.remove_conflict_files(file)
1067
 
        new_version = util.NewFileVersion(file)
1068
 
        out_file = new_version.temp_filename
1069
 
        status = native.invoke_patch(file, entry.diff, out_file, reverse)
1070
 
        if status == 1:
1071
 
            os.link(file, file+".orig")
1072
 
            os.rename(out_file+".rej", file+".rej")
1073
 
        new_version.commit()
1074
 
        return status
1075
 
 
1076
 
 
1077
 
class Diff3Handler(PatchEntryHandler):
1078
 
    def __init__(self, tree, older_tree, yours_tree):
1079
 
        PatchEntryHandler.__init__(self, tree)
1080
 
        self.older_tree = older_tree
1081
 
        self.yours_tree = yours_tree
1082
 
        self.older_map = arch_core.get_id_filename_map(self.older_tree)
1083
 
        self.yours_map = arch_core.get_id_filename_map(self.yours_tree)
1084
 
 
1085
 
    def invoke_patch(self, entry, reverse=False):
1086
 
        mine_file = os.path.join(self.tree, self.id_map[entry.id])
1087
 
        older_path = os.path.join(self.older_tree, self.older_map[entry.id])
1088
 
        yours_path = os.path.join(self.yours_tree, self.yours_map[entry.id])
1089
 
        self.remove_conflict_files(mine_file)
1090
 
        new_version = util.NewFileVersion(mine_file)
1091
 
        out_file = new_version.temp_filename
1092
 
        if reverse:
1093
 
            status = native.invoke_diff3(new_version, mine_file, yours_path, 
1094
 
                                         older_path)
1095
 
        else:
1096
 
            status = native.invoke_diff3(new_version, mine_file, older_path,
1097
 
                                         yours_path)
1098
 
 
1099
 
        new_version.commit()
1100
 
        return status
1101
 
 
1102
 
 
1103
 
class iter_custom_apply_changeset:
1104
 
    def __init__(self, dir, munger, entry_handler, reverse=False):
1105
 
        self.dir = dir
1106
 
        self.munger = munger
1107
 
        self.entry_handler = entry_handler
1108
 
        self.reverse = reverse
1109
 
        self.conflicting = False
1110
 
        self.iterator = self.make_iterator()
1111
 
 
1112
 
    def __iter__(self):
1113
 
        """Destructively applies a changeset
1114
 
        """
1115
 
        return self.iterator
1116
 
 
1117
 
 
1118
 
    def next(self):
1119
 
        return self.iterator.next()
1120
 
 
1121
 
 
1122
 
    def make_iterator(self):
1123
 
        if self.entry_handler is not None:
1124
 
            for entry in self.munger.get_entries().itervalues():
1125
 
                result = self.entry_handler(entry, self.reverse)
1126
 
                if result is not None:
1127
 
                    if isinstance(result, pybaz.PatchConflict):
1128
 
                        self.conflicting = True
1129
 
                    yield result
1130
 
        changeset = pybaz.Changeset(self.munger.changeset)
1131
 
        ch_iter = changeset.iter_apply(self.dir, self.reverse)
1132
 
        for line in ch_iter:
1133
 
            yield line
1134
 
        if ch_iter.conflicting:
1135
 
            self.conflicting = True
1136
 
 
1137
 
def apply_delta_custom(a_spec, b_spec, tree, munge_opts=None, apply_type=None, 
1138
 
        reverse=False):
1139
 
    def spec_resolve(spec):
1140
 
        if isinstance(spec, pybaz.Revision):
1141
 
            return find_or_make_local_revision(spec)
1142
 
        else:
1143
 
            return spec
1144
 
 
1145
 
    from_path = spec_resolve(a_spec)
1146
 
    to_path = spec_resolve(b_spec)
1147
 
    
1148
 
    if apply_type == PatchEntryHandler:
1149
 
        entry_handler = PatchEntryHandler(tree, reverse=reverse)
1150
 
    elif apply_type == Diff3Handler:
1151
 
        entry_handler = Diff3Handler(tree, from_path, to_path)
1152
 
    else:
1153
 
        entry_handler = None
1154
 
 
1155
 
    tmp=util.tmpdir(tree)
1156
 
    changeset=tmp+"/changeset"
1157
 
    delt=pybaz.iter_delta(from_path, to_path, changeset)
1158
 
    for line in delt:
1159
 
        yield line
1160
 
    munger = native.ChangesetMunger(delt.changeset)
1161
 
    munger.read_indices()
1162
 
    if munge_opts is not None:
1163
 
        yield pybaz.Chatter("* munging changeset")
1164
 
        munger.munge(munge_opts)
1165
 
    yield pybaz.Chatter("* applying changeset")
1166
 
    for line in iter_custom_apply_changeset(tree, munger, entry_handler, reverse=reverse):
1167
 
        yield line
1168
 
    shutil.rmtree(tmp)
1169
 
 
1170
 
 
1171
 
def revision(name):
1172
 
    try:
1173
 
        return NativeRevision(name)
1174
 
    except errors.UnsupportedScheme, e:
1175
 
        return pybaz.Revision(name)
1176
 
 
1177
 
 
1178
 
class NativeRevision(pybaz.Revision):
1179
 
    def __init__ (self, name):
1180
 
        parsed = pybaz.NameParser(name)
1181
 
        self.arch_name = parsed.get_archive()
1182
 
        params = native.ArchParams()
1183
 
        if not params['=locations'].exists(self.arch_name):
1184
 
            raise pybaz.errors.ArchiveNotRegistered()
1185
 
        self.location = params['=locations'][self.arch_name].strip()
1186
 
        native.get_pfs_type(self.location)
1187
 
        pybaz.Revision.__init__(self, name)
1188
 
        
1189
 
    def narchive(self):
1190
 
        return native.get_pfs_archive(self.location, self.arch_name)
1191
 
 
1192
 
    def exists(self):
1193
 
        return self.narchive().exists(self)
1194
 
 
1195
 
    def get_patch(self, dir):
1196
 
        tempdir = tempfile.mkdtemp("", ",", 
1197
 
                                   os.path.dirname(os.path.realpath(dir)))
1198
 
        try:
1199
 
            patchfile = os.path.join(tempdir + "delta.tar.gz")
1200
 
            patch = native.cache_get_revision(self, "delta.tar.gz")
1201
 
            if patch is None:
1202
 
                patch = self.narchive().get_patch(self)
1203
 
            file(patchfile, "wb").write(patch.read())
1204
 
            parent_dir = util.untar_parent(patchfile, tempdir)
1205
 
            os.rename(os.path.join(tempdir, parent_dir), dir)
1206
 
 
1207
 
        finally:
1208
 
            shutil.rmtree(tempdir)
1209
 
        return pybaz.Changeset(dir)
1210
 
 
1211
 
# arch-tag: c73c601a-0050-4e6c-ac63-819d7a00921f