/brz/remove-bazaar

To get this branch, use:
bzr branch http://gegoxaren.bato24.eu/bzr/brz/remove-bazaar

« back to all changes in this revision

Viewing changes to bzrlib/workingtree.py

  • Committer: Rory Yorke
  • Date: 2010-10-20 14:38:53 UTC
  • mto: This revision was merged to the branch mainline in revision 5519.
  • Revision ID: rory.yorke@gmail.com-20101020143853-9kfd2ldcjfroh8jw
Show missing files in bzr status (bug 134168).

"bzr status" will now show missing files, that is, those added with "bzr
add" and then removed by non bzr means (e.g., rm).

Blackbox tests were added for this case, and tests were also added to
test_delta, since the implementation change is in bzrlib.delta.

Might also affect bug 189709.

Show diffs side-by-side

added added

removed removed

Lines of Context:
49
49
    branch,
50
50
    bzrdir,
51
51
    conflicts as _mod_conflicts,
 
52
    controldir,
52
53
    errors,
53
54
    generate_ids,
54
55
    globbing,
61
62
    revisiontree,
62
63
    trace,
63
64
    transform,
 
65
    transport,
64
66
    ui,
65
67
    views,
66
68
    xml5,
67
69
    xml7,
68
70
    )
69
 
import bzrlib.branch
70
 
from bzrlib.transport import get_transport
71
71
from bzrlib.workingtree_4 import (
72
72
    WorkingTreeFormat4,
73
73
    WorkingTreeFormat5,
77
77
 
78
78
from bzrlib import symbol_versioning
79
79
from bzrlib.decorators import needs_read_lock, needs_write_lock
 
80
from bzrlib.lock import LogicalLockResult
80
81
from bzrlib.lockable_files import LockableFiles
81
82
from bzrlib.lockdir import LockDir
82
83
import bzrlib.mutabletree
168
169
 
169
170
 
170
171
class WorkingTree(bzrlib.mutabletree.MutableTree,
171
 
    bzrdir.ControlComponent):
 
172
    controldir.ControlComponent):
172
173
    """Working copy tree.
173
174
 
174
175
    The inventory is held in the `Branch` working-inventory, and the
176
177
 
177
178
    It is possible for a `WorkingTree` to have a filename which is
178
179
    not listed in the Inventory and vice versa.
 
180
 
 
181
    :ivar basedir: The root of the tree on disk. This is a unicode path object
 
182
        (as opposed to a URL).
179
183
    """
180
184
 
181
185
    # override this to set the strategy for storing views
346
350
        if path is None:
347
351
            path = osutils.getcwd()
348
352
        control, relpath = bzrdir.BzrDir.open_containing(path)
349
 
 
350
353
        return control.open_workingtree(), relpath
351
354
 
352
355
    @staticmethod
 
356
    def open_containing_paths(file_list, default_directory='.',
 
357
        canonicalize=True, apply_view=True):
 
358
        """Open the WorkingTree that contains a set of paths.
 
359
 
 
360
        Fail if the paths given are not all in a single tree.
 
361
 
 
362
        This is used for the many command-line interfaces that take a list of
 
363
        any number of files and that require they all be in the same tree.
 
364
        """
 
365
        # recommended replacement for builtins.internal_tree_files
 
366
        if file_list is None or len(file_list) == 0:
 
367
            tree = WorkingTree.open_containing(default_directory)[0]
 
368
            # XXX: doesn't really belong here, and seems to have the strange
 
369
            # side effect of making it return a bunch of files, not the whole
 
370
            # tree -- mbp 20100716
 
371
            if tree.supports_views() and apply_view:
 
372
                view_files = tree.views.lookup_view()
 
373
                if view_files:
 
374
                    file_list = view_files
 
375
                    view_str = views.view_display_str(view_files)
 
376
                    note("Ignoring files outside view. View is %s" % view_str)
 
377
            return tree, file_list
 
378
        tree = WorkingTree.open_containing(file_list[0])[0]
 
379
        return tree, tree.safe_relpath_files(file_list, canonicalize,
 
380
            apply_view=apply_view)
 
381
 
 
382
    def safe_relpath_files(self, file_list, canonicalize=True, apply_view=True):
 
383
        """Convert file_list into a list of relpaths in tree.
 
384
 
 
385
        :param self: A tree to operate on.
 
386
        :param file_list: A list of user provided paths or None.
 
387
        :param apply_view: if True and a view is set, apply it or check that
 
388
            specified files are within it
 
389
        :return: A list of relative paths.
 
390
        :raises errors.PathNotChild: When a provided path is in a different self
 
391
            than self.
 
392
        """
 
393
        if file_list is None:
 
394
            return None
 
395
        if self.supports_views() and apply_view:
 
396
            view_files = self.views.lookup_view()
 
397
        else:
 
398
            view_files = []
 
399
        new_list = []
 
400
        # self.relpath exists as a "thunk" to osutils, but canonical_relpath
 
401
        # doesn't - fix that up here before we enter the loop.
 
402
        if canonicalize:
 
403
            fixer = lambda p: osutils.canonical_relpath(self.basedir, p)
 
404
        else:
 
405
            fixer = self.relpath
 
406
        for filename in file_list:
 
407
            relpath = fixer(osutils.dereference_path(filename))
 
408
            if view_files and not osutils.is_inside_any(view_files, relpath):
 
409
                raise errors.FileOutsideView(filename, view_files)
 
410
            new_list.append(relpath)
 
411
        return new_list
 
412
 
 
413
    @staticmethod
353
414
    def open_downlevel(path=None):
354
415
        """Open an unsupported working tree.
355
416
 
368
429
                return True, None
369
430
            else:
370
431
                return True, tree
371
 
        transport = get_transport(location)
372
 
        iterator = bzrdir.BzrDir.find_bzrdirs(transport, evaluate=evaluate,
 
432
        t = transport.get_transport(location)
 
433
        iterator = bzrdir.BzrDir.find_bzrdirs(t, evaluate=evaluate,
373
434
                                              list_current=list_current)
374
 
        return [t for t in iterator if t is not None]
 
435
        return [tr for tr in iterator if tr is not None]
375
436
 
376
437
    # should be deprecated - this is slow and in any case treating them as a
377
438
    # container is (we now know) bad style -- mbp 20070302
462
523
        return (file_obj, stat_value)
463
524
 
464
525
    def get_file_text(self, file_id, path=None, filtered=True):
465
 
        return self.get_file(file_id, path=path, filtered=filtered).read()
 
526
        my_file = self.get_file(file_id, path=path, filtered=filtered)
 
527
        try:
 
528
            return my_file.read()
 
529
        finally:
 
530
            my_file.close()
466
531
 
467
532
    def get_file_byname(self, filename, filtered=True):
468
533
        path = self.abspath(filename)
522
587
 
523
588
        # Now we have the parents of this content
524
589
        annotator = self.branch.repository.texts.get_annotator()
525
 
        text = self.get_file(file_id).read()
 
590
        text = self.get_file_text(file_id)
526
591
        this_key =(file_id, default_revision)
527
592
        annotator.add_special_text(this_key, file_parent_keys, text)
528
593
        annotations = [(key[-1], line)
1202
1267
                # absolute path
1203
1268
                fap = from_dir_abspath + '/' + f
1204
1269
 
1205
 
                f_ie = inv.get_child(from_dir_id, f)
 
1270
                dir_ie = inv[from_dir_id]
 
1271
                if dir_ie.kind == 'directory':
 
1272
                    f_ie = dir_ie.children.get(f)
 
1273
                else:
 
1274
                    f_ie = None
1206
1275
                if f_ie:
1207
1276
                    c = 'V'
1208
1277
                elif self.is_ignored(fp[1:]):
1209
1278
                    c = 'I'
1210
1279
                else:
1211
 
                    # we may not have found this file, because of a unicode issue
 
1280
                    # we may not have found this file, because of a unicode
 
1281
                    # issue, or because the directory was actually a symlink.
1212
1282
                    f_norm, can_access = osutils.normalized_filename(f)
1213
1283
                    if f == f_norm or not can_access:
1214
1284
                        # No change, so treat this file normally
1257
1327
                stack.pop()
1258
1328
 
1259
1329
    @needs_tree_write_lock
1260
 
    def move(self, from_paths, to_dir=None, after=False, **kwargs):
 
1330
    def move(self, from_paths, to_dir=None, after=False):
1261
1331
        """Rename files.
1262
1332
 
1263
1333
        to_dir must exist in the inventory.
1297
1367
 
1298
1368
        # check for deprecated use of signature
1299
1369
        if to_dir is None:
1300
 
            to_dir = kwargs.get('to_name', None)
1301
 
            if to_dir is None:
1302
 
                raise TypeError('You must supply a target directory')
1303
 
            else:
1304
 
                symbol_versioning.warn('The parameter to_name was deprecated'
1305
 
                                       ' in version 0.13. Use to_dir instead',
1306
 
                                       DeprecationWarning)
1307
 
 
 
1370
            raise TypeError('You must supply a target directory')
1308
1371
        # check destination directory
1309
1372
        if isinstance(from_paths, basestring):
1310
1373
            raise ValueError()
1600
1663
 
1601
1664
    @needs_write_lock
1602
1665
    def pull(self, source, overwrite=False, stop_revision=None,
1603
 
             change_reporter=None, possible_transports=None, local=False):
 
1666
             change_reporter=None, possible_transports=None, local=False,
 
1667
             show_base=False):
1604
1668
        source.lock_read()
1605
1669
        try:
1606
1670
            old_revision_info = self.branch.last_revision_info()
1620
1684
                                basis_tree,
1621
1685
                                this_tree=self,
1622
1686
                                pb=None,
1623
 
                                change_reporter=change_reporter)
 
1687
                                change_reporter=change_reporter,
 
1688
                                show_base=show_base)
1624
1689
                    basis_root_id = basis_tree.get_root_id()
1625
1690
                    new_root_id = new_basis_tree.get_root_id()
1626
1691
                    if basis_root_id != new_root_id:
1798
1863
            raise errors.ObjectNotLocked(self)
1799
1864
 
1800
1865
    def lock_read(self):
1801
 
        """See Branch.lock_read, and WorkingTree.unlock."""
 
1866
        """Lock the tree for reading.
 
1867
 
 
1868
        This also locks the branch, and can be unlocked via self.unlock().
 
1869
 
 
1870
        :return: A bzrlib.lock.LogicalLockResult.
 
1871
        """
1802
1872
        if not self.is_locked():
1803
1873
            self._reset_data()
1804
1874
        self.branch.lock_read()
1805
1875
        try:
1806
 
            return self._control_files.lock_read()
 
1876
            self._control_files.lock_read()
 
1877
            return LogicalLockResult(self.unlock)
1807
1878
        except:
1808
1879
            self.branch.unlock()
1809
1880
            raise
1810
1881
 
1811
1882
    def lock_tree_write(self):
1812
 
        """See MutableTree.lock_tree_write, and WorkingTree.unlock."""
 
1883
        """See MutableTree.lock_tree_write, and WorkingTree.unlock.
 
1884
 
 
1885
        :return: A bzrlib.lock.LogicalLockResult.
 
1886
        """
1813
1887
        if not self.is_locked():
1814
1888
            self._reset_data()
1815
1889
        self.branch.lock_read()
1816
1890
        try:
1817
 
            return self._control_files.lock_write()
 
1891
            self._control_files.lock_write()
 
1892
            return LogicalLockResult(self.unlock)
1818
1893
        except:
1819
1894
            self.branch.unlock()
1820
1895
            raise
1821
1896
 
1822
1897
    def lock_write(self):
1823
 
        """See MutableTree.lock_write, and WorkingTree.unlock."""
 
1898
        """See MutableTree.lock_write, and WorkingTree.unlock.
 
1899
 
 
1900
        :return: A bzrlib.lock.LogicalLockResult.
 
1901
        """
1824
1902
        if not self.is_locked():
1825
1903
            self._reset_data()
1826
1904
        self.branch.lock_write()
1827
1905
        try:
1828
 
            return self._control_files.lock_write()
 
1906
            self._control_files.lock_write()
 
1907
            return LogicalLockResult(self.unlock)
1829
1908
        except:
1830
1909
            self.branch.unlock()
1831
1910
            raise
1948
2027
 
1949
2028
        inv_delta = []
1950
2029
 
1951
 
        new_files=set()
 
2030
        all_files = set() # specified and nested files 
1952
2031
        unknown_nested_files=set()
1953
2032
        if to_file is None:
1954
2033
            to_file = sys.stdout
1955
2034
 
 
2035
        files_to_backup = []
 
2036
 
1956
2037
        def recurse_directory_to_add_files(directory):
1957
2038
            # Recurse directory and add all files
1958
2039
            # so we can check if they have changed.
1959
 
            for parent_info, file_infos in\
1960
 
                self.walkdirs(directory):
 
2040
            for parent_info, file_infos in self.walkdirs(directory):
1961
2041
                for relpath, basename, kind, lstat, fileid, kind in file_infos:
1962
2042
                    # Is it versioned or ignored?
1963
 
                    if self.path2id(relpath) or self.is_ignored(relpath):
 
2043
                    if self.path2id(relpath):
1964
2044
                        # Add nested content for deletion.
1965
 
                        new_files.add(relpath)
 
2045
                        all_files.add(relpath)
1966
2046
                    else:
1967
 
                        # Files which are not versioned and not ignored
 
2047
                        # Files which are not versioned
1968
2048
                        # should be treated as unknown.
1969
 
                        unknown_nested_files.add((relpath, None, kind))
 
2049
                        files_to_backup.append(relpath)
1970
2050
 
1971
2051
        for filename in files:
1972
2052
            # Get file name into canonical form.
1973
2053
            abspath = self.abspath(filename)
1974
2054
            filename = self.relpath(abspath)
1975
2055
            if len(filename) > 0:
1976
 
                new_files.add(filename)
 
2056
                all_files.add(filename)
1977
2057
                recurse_directory_to_add_files(filename)
1978
2058
 
1979
 
        files = list(new_files)
 
2059
        files = list(all_files)
1980
2060
 
1981
2061
        if len(files) == 0:
1982
2062
            return # nothing to do
1986
2066
 
1987
2067
        # Bail out if we are going to delete files we shouldn't
1988
2068
        if not keep_files and not force:
1989
 
            has_changed_files = len(unknown_nested_files) > 0
1990
 
            if not has_changed_files:
1991
 
                for (file_id, path, content_change, versioned, parent_id, name,
1992
 
                     kind, executable) in self.iter_changes(self.basis_tree(),
1993
 
                         include_unchanged=True, require_versioned=False,
1994
 
                         want_unversioned=True, specific_files=files):
1995
 
                    if versioned == (False, False):
1996
 
                        # The record is unknown ...
1997
 
                        if not self.is_ignored(path[1]):
1998
 
                            # ... but not ignored
1999
 
                            has_changed_files = True
2000
 
                            break
2001
 
                    elif content_change and (kind[1] is not None):
2002
 
                        # Versioned and changed, but not deleted
2003
 
                        has_changed_files = True
2004
 
                        break
 
2069
            for (file_id, path, content_change, versioned, parent_id, name,
 
2070
                 kind, executable) in self.iter_changes(self.basis_tree(),
 
2071
                     include_unchanged=True, require_versioned=False,
 
2072
                     want_unversioned=True, specific_files=files):
 
2073
                if versioned[0] == False:
 
2074
                    # The record is unknown or newly added
 
2075
                    files_to_backup.append(path[1])
 
2076
                elif (content_change and (kind[1] is not None) and
 
2077
                        osutils.is_inside_any(files, path[1])):
 
2078
                    # Versioned and changed, but not deleted, and still
 
2079
                    # in one of the dirs to be deleted.
 
2080
                    files_to_backup.append(path[1])
2005
2081
 
2006
 
            if has_changed_files:
2007
 
                # Make delta show ALL applicable changes in error message.
2008
 
                tree_delta = self.changes_from(self.basis_tree(),
2009
 
                    require_versioned=False, want_unversioned=True,
2010
 
                    specific_files=files)
2011
 
                for unknown_file in unknown_nested_files:
2012
 
                    if unknown_file not in tree_delta.unversioned:
2013
 
                        tree_delta.unversioned.extend((unknown_file,))
2014
 
                raise errors.BzrRemoveChangedFilesError(tree_delta)
 
2082
        def backup(file_to_backup):
 
2083
            backup_name = self.bzrdir._available_backup_name(file_to_backup)
 
2084
            osutils.rename(abs_path, self.abspath(backup_name))
 
2085
            return "removed %s (but kept a copy: %s)" % (file_to_backup,
 
2086
                                                         backup_name)
2015
2087
 
2016
2088
        # Build inv_delta and delete files where applicable,
2017
2089
        # do this before any modifications to inventory.
2041
2113
                        len(os.listdir(abs_path)) > 0):
2042
2114
                        if force:
2043
2115
                            osutils.rmtree(abs_path)
 
2116
                            message = "deleted %s" % (f,)
2044
2117
                        else:
2045
 
                            message = "%s is not an empty directory "\
2046
 
                                "and won't be deleted." % (f,)
 
2118
                            message = backup(f)
2047
2119
                    else:
2048
 
                        osutils.delete_any(abs_path)
2049
 
                        message = "deleted %s" % (f,)
 
2120
                        if f in files_to_backup:
 
2121
                            message = backup(f)
 
2122
                        else:
 
2123
                            osutils.delete_any(abs_path)
 
2124
                            message = "deleted %s" % (f,)
2050
2125
                elif message is not None:
2051
2126
                    # Only care if we haven't done anything yet.
2052
2127
                    message = "%s does not exist." % (f,)
2189
2264
    _marker = object()
2190
2265
 
2191
2266
    def update(self, change_reporter=None, possible_transports=None,
2192
 
               revision=None, old_tip=_marker):
 
2267
               revision=None, old_tip=_marker, show_base=False):
2193
2268
        """Update a working tree along its branch.
2194
2269
 
2195
2270
        This will update the branch if its bound too, which means we have
2232
2307
            else:
2233
2308
                if old_tip is self._marker:
2234
2309
                    old_tip = None
2235
 
            return self._update_tree(old_tip, change_reporter, revision)
 
2310
            return self._update_tree(old_tip, change_reporter, revision, show_base)
2236
2311
        finally:
2237
2312
            self.unlock()
2238
2313
 
2239
2314
    @needs_tree_write_lock
2240
 
    def _update_tree(self, old_tip=None, change_reporter=None, revision=None):
 
2315
    def _update_tree(self, old_tip=None, change_reporter=None, revision=None,
 
2316
                     show_base=False):
2241
2317
        """Update a tree to the master branch.
2242
2318
 
2243
2319
        :param old_tip: if supplied, the previous tip revision the branch,
2270
2346
            other_tree = self.branch.repository.revision_tree(old_tip)
2271
2347
            nb_conflicts = merge.merge_inner(self.branch, other_tree,
2272
2348
                                             base_tree, this_tree=self,
2273
 
                                             change_reporter=change_reporter)
 
2349
                                             change_reporter=change_reporter,
 
2350
                                             show_base=show_base)
2274
2351
            if nb_conflicts:
2275
2352
                self.add_parent_tree((old_tip, other_tree))
2276
2353
                trace.note('Rerun update after fixing the conflicts.')
2300
2377
 
2301
2378
            nb_conflicts = merge.merge_inner(self.branch, to_tree, base_tree,
2302
2379
                                             this_tree=self,
2303
 
                                             change_reporter=change_reporter)
 
2380
                                             change_reporter=change_reporter,
 
2381
                                             show_base=show_base)
2304
2382
            self.set_last_revision(revision)
2305
2383
            # TODO - dedup parents list with things merged by pull ?
2306
2384
            # reuse the tree we've updated to to set the basis:
2636
2714
 
2637
2715
        In Format2 WorkingTrees we have a single lock for the branch and tree
2638
2716
        so lock_tree_write() degrades to lock_write().
 
2717
 
 
2718
        :return: An object with an unlock method which will release the lock
 
2719
            obtained.
2639
2720
        """
2640
2721
        self.branch.lock_write()
2641
2722
        try:
2642
 
            return self._control_files.lock_write()
 
2723
            self._control_files.lock_write()
 
2724
            return self
2643
2725
        except:
2644
2726
            self.branch.unlock()
2645
2727
            raise