~bzr-pqm/bzr/bzr.dev

70 by mbp at sourcefrog
Prepare for smart recursive add.
1
# Copyright (C) 2005 Canonical Ltd
1 by mbp at sourcefrog
import from baz patch-364
2
3
# This program is free software; you can redistribute it and/or modify
4
# it under the terms of the GNU General Public License as published by
5
# the Free Software Foundation; either version 2 of the License, or
6
# (at your option) any later version.
7
8
# This program is distributed in the hope that it will be useful,
9
# but WITHOUT ANY WARRANTY; without even the implied warranty of
10
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
11
# GNU General Public License for more details.
12
13
# You should have received a copy of the GNU General Public License
14
# along with this program; if not, write to the Free Software
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
16
17
"""Tree classes, representing directory at point in time.
18
"""
19
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
20
import os
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
21
from cStringIO import StringIO
800 by Martin Pool
Merge John's import-speedup branch:
22
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
23
import bzrlib
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
24
from bzrlib.trace import mutter, note
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
25
from bzrlib.errors import BzrError, BzrCheckError
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
26
from bzrlib.inventory import Inventory
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
27
from bzrlib.osutils import appendpath, fingerprint_file
1 by mbp at sourcefrog
import from baz patch-364
28
29
678 by Martin Pool
- export to tarballs
30
exporters = {}
31
558 by Martin Pool
- All top-level classes inherit from object
32
class Tree(object):
1 by mbp at sourcefrog
import from baz patch-364
33
    """Abstract file tree.
34
35
    There are several subclasses:
36
    
37
    * `WorkingTree` exists as files on disk editable by the user.
38
39
    * `RevisionTree` is a tree as recorded at some point in the past.
40
41
    * `EmptyTree`
42
43
    Trees contain an `Inventory` object, and also know how to retrieve
44
    file texts mentioned in the inventory, either from a working
45
    directory or from a store.
46
47
    It is possible for trees to contain files that are not described
48
    in their inventory or vice versa; for this use `filenames()`.
49
50
    Trees can be compared, etc, regardless of whether they are working
51
    trees or versioned trees.
52
    """
53
    
54
    def has_filename(self, filename):
55
        """True if the tree has given filename."""
56
        raise NotImplementedError()
57
1185.12.39 by abentley
Propogated has_or_had_id to Tree
58
    def has_id(self, file_id):
59
        return self.inventory.has_id(file_id)
60
61
    def has_or_had_id(self, file_id):
62
        if file_id == self.inventory.root.file_id:
1185.12.38 by abentley
semi-broke merge
63
            return True
1 by mbp at sourcefrog
import from baz patch-364
64
        return self.inventory.has_id(file_id)
65
462 by Martin Pool
- New form 'file_id in tree' to check if the file is present
66
    __contains__ = has_id
67
68
    def __iter__(self):
69
        return iter(self.inventory)
70
1 by mbp at sourcefrog
import from baz patch-364
71
    def id2path(self, file_id):
72
        return self.inventory.id2path(file_id)
73
1465 by Robert Collins
Bugfix the new pull --clobber to not generate spurious conflicts.
74
    def kind(self, file_id):
75
        raise NotImplementedError("subclasses must implement kind")
76
1 by mbp at sourcefrog
import from baz patch-364
77
    def _get_inventory(self):
78
        return self._inventory
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
79
    
80
    def get_file_by_path(self, path):
81
        return self.get_file(self._inventory.path2id(path))
1 by mbp at sourcefrog
import from baz patch-364
82
83
    inventory = property(_get_inventory,
84
                         doc="Inventory of this Tree")
85
86
    def _check_retrieved(self, ie, f):
1364 by Martin Pool
- remove extra verification of files retrieved from tree
87
        if not __debug__:
88
            return  
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
89
        fp = fingerprint_file(f)
90
        f.seek(0)
91
        
184 by mbp at sourcefrog
pychecker fixups
92
        if ie.text_size != None:
131 by mbp at sourcefrog
check size and sha1 of files retrieved from the tree
93
            if ie.text_size != fp['size']:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
94
                raise BzrError("mismatched size for file %r in %r" % (ie.file_id, self._store),
1 by mbp at sourcefrog
import from baz patch-364
95
                        ["inventory expects %d bytes" % ie.text_size,
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
96
                         "file is actually %d bytes" % fp['size'],
1 by mbp at sourcefrog
import from baz patch-364
97
                         "store is probably damaged/corrupt"])
98
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
99
        if ie.text_sha1 != fp['sha1']:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
100
            raise BzrError("wrong SHA-1 for file %r in %r" % (ie.file_id, self._store),
1 by mbp at sourcefrog
import from baz patch-364
101
                    ["inventory expects %s" % ie.text_sha1,
130 by mbp at sourcefrog
- fixup checks on retrieved files to cope with compression,
102
                     "file is actually %s" % fp['sha1'],
1 by mbp at sourcefrog
import from baz patch-364
103
                     "store is probably damaged/corrupt"])
104
105
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
106
    def print_file(self, file_id):
107
        """Print file with id `file_id` to stdout."""
176 by mbp at sourcefrog
New cat command contributed by janmar.
108
        import sys
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
109
        sys.stdout.write(self.get_file_text(file_id))
176 by mbp at sourcefrog
New cat command contributed by janmar.
110
        
111
        
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
112
    def export(self, dest, format='dir', root=None):
678 by Martin Pool
- export to tarballs
113
        """Export this tree."""
114
        try:
115
            exporter = exporters[format]
116
        except KeyError:
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
117
            from bzrlib.errors import BzrCommandError
678 by Martin Pool
- export to tarballs
118
            raise BzrCommandError("export format %r not supported" % format)
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
119
        exporter(self, dest, root)
1 by mbp at sourcefrog
import from baz patch-364
120
121
122
123
class RevisionTree(Tree):
124
    """Tree viewing a previous revision.
125
126
    File text can be retrieved from the text store.
127
254 by Martin Pool
- Doc cleanups from Magnus Therning
128
    TODO: Some kind of `__repr__` method, but a good one
1 by mbp at sourcefrog
import from baz patch-364
129
           probably means knowing the branch and revision number,
130
           or at least passing a description to the constructor.
131
    """
132
    
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
133
    def __init__(self, weave_store, inv, revision_id):
134
        self._weave_store = weave_store
1 by mbp at sourcefrog
import from baz patch-364
135
        self._inventory = inv
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
136
        self._revision_id = revision_id
137
1210 by Martin Pool
- get correct old file version in RevisionTree
138
    def get_weave(self, file_id):
1417.1.8 by Robert Collins
use transactions in the weave store interface, which enables caching for log
139
        # FIXME: RevisionTree should be given a branch
140
        # not a store, or the store should know the branch.
141
        import bzrlib.transactions as transactions
142
        return self._weave_store.get_weave(file_id,
143
            transactions.PassThroughTransaction())
1369 by Martin Pool
- try to avoid redundant conversion of strings when retrieving from weaves
144
145
146
    def get_file_lines(self, file_id):
147
        ie = self._inventory[file_id]
148
        weave = self.get_weave(file_id)
1092.2.22 by Robert Collins
text_version and name_version unification looking reasonable
149
        return weave.get(ie.revision)
1210 by Martin Pool
- get correct old file version in RevisionTree
150
        
151
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
152
    def get_file_text(self, file_id):
1369 by Martin Pool
- try to avoid redundant conversion of strings when retrieving from weaves
153
        return ''.join(self.get_file_lines(file_id))
154
1 by mbp at sourcefrog
import from baz patch-364
155
156
    def get_file(self, file_id):
1196 by Martin Pool
- [WIP] retrieve historical texts from weaves
157
        return StringIO(self.get_file_text(file_id))
1 by mbp at sourcefrog
import from baz patch-364
158
159
    def get_file_size(self, file_id):
160
        return self._inventory[file_id].text_size
161
162
    def get_file_sha1(self, file_id):
163
        ie = self._inventory[file_id]
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
164
        if ie.kind == "file":
165
            return ie.text_sha1
1 by mbp at sourcefrog
import from baz patch-364
166
1398 by Robert Collins
integrate in Gustavos x-bit patch
167
    def is_executable(self, file_id):
1185.12.28 by Aaron Bentley
Removed use of readonly path for executability test
168
        ie = self._inventory[file_id]
169
        if ie.kind != "file":
170
            return None 
1398 by Robert Collins
integrate in Gustavos x-bit patch
171
        return self._inventory[file_id].executable
172
1 by mbp at sourcefrog
import from baz patch-364
173
    def has_filename(self, filename):
174
        return bool(self.inventory.path2id(filename))
175
176
    def list_files(self):
177
        # The only files returned by this are those from the version
178
        for path, entry in self.inventory.iter_entries():
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
179
            yield path, 'V', entry.kind, entry.file_id, entry
1 by mbp at sourcefrog
import from baz patch-364
180
1092.2.6 by Robert Collins
symlink support updated to work
181
    def get_symlink_target(self, file_id):
182
        ie = self._inventory[file_id]
183
        return ie.symlink_target;
1 by mbp at sourcefrog
import from baz patch-364
184
1185.12.28 by Aaron Bentley
Removed use of readonly path for executability test
185
    def kind(self, file_id):
186
        return self._inventory[file_id].kind
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
187
1465 by Robert Collins
Bugfix the new pull --clobber to not generate spurious conflicts.
188
1 by mbp at sourcefrog
import from baz patch-364
189
class EmptyTree(Tree):
974.1.26 by aaron.bentley at utoronto
merged mbp@sourcefrog.net-20050817233101-0939da1cf91f2472
190
    def __init__(self):
191
        self._inventory = Inventory()
1 by mbp at sourcefrog
import from baz patch-364
192
1092.2.6 by Robert Collins
symlink support updated to work
193
    def get_symlink_target(self, file_id):
194
        return None
195
1 by mbp at sourcefrog
import from baz patch-364
196
    def has_filename(self, filename):
197
        return False
198
1465 by Robert Collins
Bugfix the new pull --clobber to not generate spurious conflicts.
199
    def kind(self, file_id):
200
        assert self._inventory[file_id].kind == "root_directory"
201
        return "root_directory"
202
1 by mbp at sourcefrog
import from baz patch-364
203
    def list_files(self):
1399.1.2 by Robert Collins
push kind character creation into InventoryEntry and TreeEntry
204
        return iter([])
1 by mbp at sourcefrog
import from baz patch-364
205
    
974.1.12 by aaron.bentley at utoronto
Switched from text-id to hashcache for merge optimization
206
    def __contains__(self, file_id):
207
        return file_id in self._inventory
208
974.1.14 by aaron.bentley at utoronto
Fixed bugs in merge optimization
209
    def get_file_sha1(self, file_id):
210
        assert self._inventory[file_id].kind == "root_directory"
211
        return None
212
213
1 by mbp at sourcefrog
import from baz patch-364
214
######################################################################
215
# diff
216
217
# TODO: Merge these two functions into a single one that can operate
218
# on either a whole tree or a set of files.
219
220
# TODO: Return the diff in order by filename, not by category or in
221
# random order.  Can probably be done by lock-stepping through the
222
# filenames from both trees.
223
224
225
def file_status(filename, old_tree, new_tree):
226
    """Return single-letter status, old and new names for a file.
227
228
    The complexity here is in deciding how to represent renames;
229
    many complex cases are possible.
230
    """
231
    old_inv = old_tree.inventory
232
    new_inv = new_tree.inventory
233
    new_id = new_inv.path2id(filename)
234
    old_id = old_inv.path2id(filename)
235
236
    if not new_id and not old_id:
237
        # easy: doesn't exist in either; not versioned at all
238
        if new_tree.is_ignored(filename):
239
            return 'I', None, None
240
        else:
241
            return '?', None, None
242
    elif new_id:
243
        # There is now a file of this name, great.
244
        pass
245
    else:
246
        # There is no longer a file of this name, but we can describe
247
        # what happened to the file that used to have
248
        # this name.  There are two possibilities: either it was
249
        # deleted entirely, or renamed.
250
        assert old_id
251
        if new_inv.has_id(old_id):
252
            return 'X', old_inv.id2path(old_id), new_inv.id2path(old_id)
253
        else:
254
            return 'D', old_inv.id2path(old_id), None
255
256
    # if the file_id is new in this revision, it is added
257
    if new_id and not old_inv.has_id(new_id):
258
        return 'A'
259
260
    # if there used to be a file of this name, but that ID has now
261
    # disappeared, it is deleted
262
    if old_id and not new_inv.has_id(old_id):
263
        return 'D'
264
265
    return 'wtf?'
266
267
    
268
164 by mbp at sourcefrog
new 'renames' command
269
def find_renames(old_inv, new_inv):
270
    for file_id in old_inv:
271
        if file_id not in new_inv:
272
            continue
273
        old_name = old_inv.id2path(file_id)
274
        new_name = new_inv.id2path(file_id)
275
        if old_name != new_name:
276
            yield (old_name, new_name)
277
            
678 by Martin Pool
- export to tarballs
278
279
280
######################################################################
281
# export
282
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
283
def dir_exporter(tree, dest, root):
678 by Martin Pool
- export to tarballs
284
    """Export this tree to a new directory.
285
286
    `dest` should not exist, and will be created holding the
287
    contents of this tree.
288
289
    TODO: To handle subdirectories we need to create the
290
           directories first.
291
292
    :note: If the export fails, the destination directory will be
293
           left in a half-assed state.
294
    """
800 by Martin Pool
Merge John's import-speedup branch:
295
    import os
678 by Martin Pool
- export to tarballs
296
    os.mkdir(dest)
1185.31.4 by John Arbash Meinel
Fixing mutter() calls to not have to do string processing.
297
    mutter('export version %r', tree)
678 by Martin Pool
- export to tarballs
298
    inv = tree.inventory
299
    for dp, ie in inv.iter_entries():
1185.37.1 by Jamie Wilkinson
Exclude '.bzrignore' from exports.
300
        if dp != ".bzrignore":
301
            ie.put_on_disk(dest, dp, tree)
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
302
678 by Martin Pool
- export to tarballs
303
exporters['dir'] = dir_exporter
304
305
try:
306
    import tarfile
307
except ImportError:
308
    pass
309
else:
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
310
    def get_root_name(dest):
311
        """Get just the root name for a tarball.
312
313
        >>> get_root_name('mytar.tar')
314
        'mytar'
315
        >>> get_root_name('mytar.tar.bz2')
316
        'mytar'
317
        >>> get_root_name('tar.tar.tar.tgz')
318
        'tar.tar.tar'
319
        >>> get_root_name('bzr-0.0.5.tar.gz')
320
        'bzr-0.0.5'
321
        >>> get_root_name('a/long/path/mytar.tgz')
322
        'mytar'
323
        >>> get_root_name('../parent/../dir/other.tbz2')
324
        'other'
325
        """
326
        endings = ['.tar', '.tar.gz', '.tgz', '.tar.bz2', '.tbz2']
327
        dest = os.path.basename(dest)
328
        for end in endings:
329
            if dest.endswith(end):
330
                return dest[:-len(end)]
331
332
    def tar_exporter(tree, dest, root, compression=None):
678 by Martin Pool
- export to tarballs
333
        """Export this tree to a new tar file.
334
335
        `dest` will be created holding the contents of this tree; if it
336
        already exists, it will be clobbered, like with "tar -c".
337
        """
800 by Martin Pool
Merge John's import-speedup branch:
338
        from time import time
339
        now = time()
678 by Martin Pool
- export to tarballs
340
        compression = str(compression or '')
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
341
        if root is None:
342
            root = get_root_name(dest)
678 by Martin Pool
- export to tarballs
343
        try:
344
            ball = tarfile.open(dest, 'w:' + compression)
345
        except tarfile.CompressionError, e:
694 by Martin Pool
- weed out all remaining calls to bailout() and remove the function
346
            raise BzrError(str(e))
1185.31.4 by John Arbash Meinel
Fixing mutter() calls to not have to do string processing.
347
        mutter('export version %r', tree)
678 by Martin Pool
- export to tarballs
348
        inv = tree.inventory
349
        for dp, ie in inv.iter_entries():
1185.37.1 by Jamie Wilkinson
Exclude '.bzrignore' from exports.
350
            if dp != ".bzrignore":
351
                mutter("  export {%s} kind %s to %s" % (ie.file_id, ie.kind, dest))
352
                item, fileobj = ie.get_tar_item(root, dp, now, tree)
353
                ball.addfile(item, fileobj)
678 by Martin Pool
- export to tarballs
354
        ball.close()
1399.1.6 by Robert Collins
move exporting functionality into inventory.py - uncovers bug in symlink support
355
678 by Martin Pool
- export to tarballs
356
    exporters['tar'] = tar_exporter
357
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
358
    def tgz_exporter(tree, dest, root):
359
        tar_exporter(tree, dest, root, compression='gz')
678 by Martin Pool
- export to tarballs
360
    exporters['tgz'] = tgz_exporter
361
849 by Martin Pool
- Put files inside an exported tarball into a top-level directory rather than
362
    def tbz_exporter(tree, dest, root):
363
        tar_exporter(tree, dest, root, compression='bz2')
678 by Martin Pool
- export to tarballs
364
    exporters['tbz2'] = tbz_exporter