1
# Copyright (C) 2007-2011 Canonical Ltd
1
# Copyright (C) 2005, 2006, 2007, 2008, 2009 Canonical Ltd
3
3
# This program is free software; you can redistribute it and/or modify
4
4
# it under the terms of the GNU General Public License as published by
29
29
from bzrlib.lazy_import import lazy_import
30
30
lazy_import(globals(), """
31
from bisect import bisect_left
33
from copy import deepcopy
34
42
from bzrlib import (
37
45
conflicts as _mod_conflicts,
41
filters as _mod_filters,
44
55
revision as _mod_revision,
66
from bzrlib.transport import get_transport
70
from bzrlib import symbol_versioning
52
71
from bzrlib.decorators import needs_read_lock, needs_write_lock
53
from bzrlib.inventory import Inventory, ROOT_ID, entry_factory
54
from bzrlib.lock import LogicalLockResult
55
from bzrlib.lockable_files import LockableFiles
56
from bzrlib.lockdir import LockDir
72
from bzrlib.filters import filtered_input_file, internal_size_sha_file_byname
73
from bzrlib.inventory import InventoryEntry, Inventory, ROOT_ID, entry_factory
74
import bzrlib.mutabletree
57
75
from bzrlib.mutabletree import needs_tree_write_lock
58
76
from bzrlib.osutils import (
86
from bzrlib.trace import mutter, note
65
87
from bzrlib.transport.local import LocalTransport
66
from bzrlib.tree import (
70
from bzrlib.workingtree import (
77
class DirStateWorkingTree(InventoryWorkingTree):
79
_DEFAULT_WORTH_SAVING_LIMIT = 10
88
from bzrlib.tree import InterTree
89
from bzrlib.progress import DummyProgress, ProgressPhase
90
from bzrlib.revision import NULL_REVISION, CURRENT_REVISION
91
from bzrlib.rio import RioReader, rio_file, Stanza
92
from bzrlib.symbol_versioning import (deprecated_passed,
97
from bzrlib.tree import Tree
98
from bzrlib.workingtree import WorkingTree, WorkingTree3, WorkingTreeFormat3
101
class DirStateWorkingTree(WorkingTree3):
81
102
def __init__(self, basedir,
83
104
_control_files=None,
93
114
self._format = _format
94
115
self.bzrdir = _bzrdir
95
116
basedir = safe_unicode(basedir)
96
trace.mutter("opening working tree %r", basedir)
117
mutter("opening working tree %r", basedir)
97
118
self._branch = branch
98
119
self.basedir = realpath(basedir)
99
120
# if branch is at our basedir and is a format 6 or less
133
154
state.add(f, file_id, kind, None, '')
134
155
self._make_dirty(reset_inventory=True)
136
def _get_check_refs(self):
137
"""Return the references needed to perform a check of this tree."""
138
return [('trees', self.last_revision())]
140
157
def _make_dirty(self, reset_inventory):
141
158
"""Make the tree state dirty.
195
212
def _comparison_data(self, entry, path):
196
213
kind, executable, stat_value = \
197
WorkingTree._comparison_data(self, entry, path)
214
WorkingTree3._comparison_data(self, entry, path)
198
215
# it looks like a plain directory, but it's really a reference -- see
200
217
if (self._repo_supports_tree_reference and kind == 'directory'
206
223
def commit(self, message=None, revprops=None, *args, **kwargs):
207
224
# mark the tree as dirty post commit - commit
208
225
# can change the current versioned list by doing deletes.
209
result = WorkingTree.commit(self, message, revprops, *args, **kwargs)
226
result = WorkingTree3.commit(self, message, revprops, *args, **kwargs)
210
227
self._make_dirty(reset_inventory=True)
231
248
local_path = self.bzrdir.get_workingtree_transport(None
232
249
).local_abspath('dirstate')
233
250
self._dirstate = dirstate.DirState.on_file(local_path,
234
self._sha1_provider(), self._worth_saving_limit())
251
self._sha1_provider())
235
252
return self._dirstate
237
254
def _sha1_provider(self):
249
def _worth_saving_limit(self):
250
"""How many hash changes are ok before we must save the dirstate.
252
:return: an integer. -1 means never save.
254
config = self.branch.get_config()
255
val = config.get_user_option('bzr.workingtree.worth_saving_limit')
257
val = self._DEFAULT_WORTH_SAVING_LIMIT
261
except ValueError, e:
262
trace.warning('Invalid config value for'
263
' "bzr.workingtree.worth_saving_limit"'
264
' value %r is not an integer.'
266
val = self._DEFAULT_WORTH_SAVING_LIMIT
269
266
def filter_unversioned_files(self, paths):
270
267
"""Filter out paths that are versioned.
467
464
return osutils.lexists(pathjoin(
468
465
self.basedir, row[0].decode('utf8'), row[1].decode('utf8')))
470
def has_or_had_id(self, file_id):
471
state = self.current_dirstate()
472
row, parents = self._get_entry(file_id=file_id)
473
return row is not None
476
468
def id2path(self, file_id):
477
469
"Convert a file-id to a path."
600
592
return _mod_revision.NULL_REVISION
602
594
def lock_read(self):
603
"""See Branch.lock_read, and WorkingTree.unlock.
605
:return: A bzrlib.lock.LogicalLockResult.
595
"""See Branch.lock_read, and WorkingTree.unlock."""
607
596
self.branch.lock_read()
609
598
self._control_files.lock_read()
644
632
self.branch.unlock()
646
return LogicalLockResult(self.unlock)
648
635
def lock_tree_write(self):
649
"""See MutableTree.lock_tree_write, and WorkingTree.unlock.
651
:return: A bzrlib.lock.LogicalLockResult.
636
"""See MutableTree.lock_tree_write, and WorkingTree.unlock."""
653
637
self.branch.lock_read()
654
return self._lock_self_write()
638
self._lock_self_write()
656
640
def lock_write(self):
657
"""See MutableTree.lock_write, and WorkingTree.unlock.
659
:return: A bzrlib.lock.LogicalLockResult.
641
"""See MutableTree.lock_write, and WorkingTree.unlock."""
661
642
self.branch.lock_write()
662
return self._lock_self_write()
643
self._lock_self_write()
664
645
@needs_tree_write_lock
665
646
def move(self, from_paths, to_dir, after=False):
735
716
from_entry = self._get_entry(path=from_rel)
736
717
if from_entry == (None, None):
737
718
raise errors.BzrMoveFailedError(from_rel,to_dir,
738
errors.NotVersionedError(path=from_rel))
719
errors.NotVersionedError(path=str(from_rel)))
740
721
from_id = from_entry[0][2]
741
722
to_rel = pathjoin(to_dir, from_tail)
1070
1051
def set_last_revision(self, new_revision):
1071
1052
"""Change the last revision in the working tree."""
1072
1053
parents = self.get_parent_ids()
1073
if new_revision in (_mod_revision.NULL_REVISION, None):
1054
if new_revision in (NULL_REVISION, None):
1074
1055
if len(parents) >= 2:
1075
1056
raise AssertionError(
1076
1057
"setting the last parent to none with a pending merge is "
1145
1126
_mod_revision.NULL_REVISION)))
1146
1127
ghosts.append(rev_id)
1147
1128
accepted_revisions.add(rev_id)
1149
if (len(real_trees) == 1
1151
and self.branch.repository._format.fast_deltas
1152
and isinstance(real_trees[0][1],
1153
revisiontree.InventoryRevisionTree)
1154
and self.get_parent_ids()):
1155
rev_id, rev_tree = real_trees[0]
1156
basis_id = self.get_parent_ids()[0]
1157
# There are times when basis_tree won't be in
1158
# self.branch.repository, (switch, for example)
1160
basis_tree = self.branch.repository.revision_tree(basis_id)
1161
except errors.NoSuchRevision:
1162
# Fall back to the set_parent_trees(), since we can't use
1163
# _make_delta if we can't get the RevisionTree
1166
delta = rev_tree.inventory._make_delta(basis_tree.inventory)
1167
dirstate.update_basis_by_delta(delta, rev_id)
1170
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1129
dirstate.set_parent_trees(real_trees, ghosts=ghosts)
1171
1130
self._make_dirty(reset_inventory=False)
1173
1132
def _set_root_id(self, file_id):
1194
1153
def unlock(self):
1195
1154
"""Unlock in format 4 trees needs to write the entire dirstate."""
1155
# do non-implementation specific cleanup
1196
1158
if self._control_files._lock_count == 1:
1197
# do non-implementation specific cleanup
1200
1159
# eventually we should do signature checking during read locks for
1201
1160
# dirstate updates.
1202
1161
if self._control_files._lock_mode == 'w':
1265
1224
# just forget the whole block.
1266
1225
entry_index = 0
1267
1226
while entry_index < len(block[1]):
1227
# Mark this file id as having been removed
1268
1228
entry = block[1][entry_index]
1269
if entry[1][0][0] in 'ar':
1270
# don't remove absent or renamed entries
1229
ids_to_unversion.discard(entry[0][2])
1230
if (entry[1][0][0] in 'ar' # don't remove absent or renamed
1232
or not state._make_absent(entry)):
1271
1233
entry_index += 1
1273
# Mark this file id as having been removed
1274
ids_to_unversion.discard(entry[0][2])
1275
if not state._make_absent(entry):
1276
# The block has not shrunk.
1278
1234
# go to the next block. (At the moment we dont delete empty
1280
1236
block_index += 1
1301
1257
# have to change the legacy inventory too.
1302
1258
if self._inventory is not None:
1303
1259
for file_id in file_ids:
1304
if self._inventory.has_id(file_id):
1305
self._inventory.remove_recursive_id(file_id)
1260
self._inventory.remove_recursive_id(file_id)
1307
1262
@needs_tree_write_lock
1308
1263
def rename_one(self, from_rel, to_rel, after=False):
1309
1264
"""See WorkingTree.rename_one"""
1311
super(DirStateWorkingTree, self).rename_one(from_rel, to_rel, after)
1266
WorkingTree.rename_one(self, from_rel, to_rel, after)
1313
1268
@needs_tree_write_lock
1314
1269
def apply_inventory_delta(self, changes):
1333
1288
if self._dirty:
1334
1289
raise AssertionError("attempting to write an inventory when the "
1335
1290
"dirstate is dirty will lose pending changes")
1336
had_inventory = self._inventory is not None
1337
# Setting self._inventory = None forces the dirstate to regenerate the
1338
# working inventory. We do this because self.inventory may be inv, or
1339
# may have been modified, and either case would prevent a clean delta
1341
self._inventory = None
1343
delta = inv._make_delta(self.inventory)
1345
self.apply_inventory_delta(delta)
1291
self.current_dirstate().set_state_from_inventory(inv)
1292
self._make_dirty(reset_inventory=False)
1293
if self._inventory is not None:
1347
1294
self._inventory = inv
1350
@needs_tree_write_lock
1351
def reset_state(self, revision_ids=None):
1352
"""Reset the state of the working tree.
1354
This does a hard-reset to a last-known-good state. This is a way to
1355
fix if something got corrupted (like the .bzr/checkout/dirstate file)
1357
if revision_ids is None:
1358
revision_ids = self.get_parent_ids()
1359
if not revision_ids:
1360
base_tree = self.branch.repository.revision_tree(
1361
_mod_revision.NULL_REVISION)
1364
trees = zip(revision_ids,
1365
self.branch.repository.revision_trees(revision_ids))
1366
base_tree = trees[0][1]
1367
state = self.current_dirstate()
1368
# We don't support ghosts yet
1369
state.set_state_from_scratch(base_tree.inventory, trees, [])
1372
1298
class ContentFilterAwareSHA1Provider(dirstate.SHA1Provider):
1378
1304
"""See dirstate.SHA1Provider.sha1()."""
1379
1305
filters = self.tree._content_filter_stack(
1380
1306
self.tree.relpath(osutils.safe_unicode(abspath)))
1381
return _mod_filters.internal_size_sha_file_byname(abspath, filters)[1]
1307
return internal_size_sha_file_byname(abspath, filters)[1]
1383
1309
def stat_and_sha1(self, abspath):
1384
1310
"""See dirstate.SHA1Provider.stat_and_sha1()."""
1389
1315
statvalue = os.fstat(file_obj.fileno())
1391
file_obj = _mod_filters.filtered_input_file(file_obj, filters)
1317
file_obj = filtered_input_file(file_obj, filters)
1392
1318
sha1 = osutils.size_sha_file(file_obj)[1]
1394
1320
file_obj.close()
1395
1321
return statvalue, sha1
1398
class ContentFilteringDirStateWorkingTree(DirStateWorkingTree):
1399
"""Dirstate working tree that supports content filtering.
1401
The dirstate holds the hash and size of the canonical form of the file,
1402
and most methods must return that.
1405
def _file_content_summary(self, path, stat_result):
1406
# This is to support the somewhat obsolete path_content_summary method
1407
# with content filtering: see
1408
# <https://bugs.launchpad.net/bzr/+bug/415508>.
1410
# If the dirstate cache is up to date and knows the hash and size,
1412
# Otherwise if there are no content filters, return the on-disk size
1413
# and leave the hash blank.
1414
# Otherwise, read and filter the on-disk file and use its size and
1417
# The dirstate doesn't store the size of the canonical form so we
1418
# can't trust it for content-filtered trees. We just return None.
1419
dirstate_sha1 = self._dirstate.sha1_from_stat(path, stat_result)
1420
executable = self._is_executable_from_path_and_stat(path, stat_result)
1421
return ('file', None, executable, dirstate_sha1)
1424
1324
class WorkingTree4(DirStateWorkingTree):
1425
1325
"""This is the Format 4 working tree.
1427
This differs from WorkingTree by:
1327
This differs from WorkingTree3 by:
1428
1328
- Having a consolidated internal dirstate, stored in a
1429
1329
randomly-accessible sorted file on disk.
1430
1330
- Not having a regular inventory attribute. One can be synthesized
1458
1358
return views.PathBasedViews(self)
1461
class DirStateWorkingTreeFormat(WorkingTreeFormat):
1463
missing_parent_conflicts = True
1465
supports_versioned_directories = True
1467
_lock_class = LockDir
1468
_lock_file_name = 'lock'
1470
def _open_control_files(self, a_bzrdir):
1471
transport = a_bzrdir.get_workingtree_transport(None)
1472
return LockableFiles(transport, self._lock_file_name,
1361
class DirStateWorkingTreeFormat(WorkingTreeFormat3):
1475
1362
def initialize(self, a_bzrdir, revision_id=None, from_branch=None,
1476
1363
accelerator_tree=None, hardlink=False):
1477
1364
"""See WorkingTreeFormat.initialize().
1479
1366
:param revision_id: allows creating a working tree at a different
1480
revision than the branch is at.
1367
revision than the branch is at.
1481
1368
:param accelerator_tree: A tree which can be used for retrieving file
1482
1369
contents more quickly than the revision tree, i.e. a workingtree.
1483
1370
The revision tree will be used for cases where accelerator_tree's
1516
1403
wt.lock_tree_write()
1518
1405
self._init_custom_control_files(wt)
1519
if revision_id in (None, _mod_revision.NULL_REVISION):
1406
if revision_id in (None, NULL_REVISION):
1520
1407
if branch.repository.supports_rich_root():
1521
1408
wt._set_root_id(generate_ids.gen_root_id())
1547
1434
if basis_root_id is not None:
1548
1435
wt._set_root_id(basis_root_id)
1550
if wt.supports_content_filtering():
1551
# The original tree may not have the same content filters
1552
# applied so we can't safely build the inventory delta from
1554
delta_from_tree = False
1556
delta_from_tree = True
1557
1437
# delta_from_tree is safe even for DirStateRevisionTrees,
1558
1438
# because wt4.apply_inventory_delta does not mutate the input
1559
1439
# inventory entries.
1560
1440
transform.build_tree(basis, wt, accelerator_tree,
1562
delta_from_tree=delta_from_tree)
1441
hardlink=hardlink, delta_from_tree=True)
1576
1455
:param wt: the WorkingTree object
1579
def open(self, a_bzrdir, _found=False):
1580
"""Return the WorkingTree object for a_bzrdir
1582
_found is a private parameter, do not use it. It is used to indicate
1583
if format probing has already been done.
1586
# we are being called directly and must probe.
1587
raise NotImplementedError
1588
if not isinstance(a_bzrdir.transport, LocalTransport):
1589
raise errors.NotLocalUrl(a_bzrdir.transport.base)
1590
wt = self._open(a_bzrdir, self._open_control_files(a_bzrdir))
1593
1458
def _open(self, a_bzrdir, control_files):
1594
1459
"""Open the tree itself.
1689
class DirStateRevisionTree(InventoryTree):
1690
"""A revision tree pulling the inventory from a dirstate.
1692
Note that this is one of the historical (ie revision) trees cached in the
1693
dirstate for easy access, not the workingtree.
1554
class DirStateRevisionTree(Tree):
1555
"""A revision tree pulling the inventory from a dirstate."""
1696
1557
def __init__(self, dirstate, revision_id, repository):
1697
1558
self._dirstate = dirstate
1711
1572
def annotate_iter(self, file_id,
1712
1573
default_revision=_mod_revision.CURRENT_REVISION):
1713
1574
"""See Tree.annotate_iter"""
1714
text_key = (file_id, self.get_file_revision(file_id))
1575
text_key = (file_id, self.inventory[file_id].revision)
1715
1576
annotations = self._repository.texts.annotate(text_key)
1716
1577
return [(key[-1], line) for (key, line) in annotations]
1579
def _get_ancestors(self, default_revision):
1580
return set(self._repository.get_ancestry(self._revision_id,
1718
1582
def _comparison_data(self, entry, path):
1719
1583
"""See Tree._comparison_data."""
1720
1584
if entry is None:
1836
1700
elif kind == 'directory':
1837
1701
parent_ies[(dirname + '/' + name).strip('/')] = inv_entry
1838
1702
elif kind == 'symlink':
1703
inv_entry.executable = False
1704
inv_entry.text_size = None
1839
1705
inv_entry.symlink_target = utf8_decode(fingerprint)[0]
1840
1706
elif kind == 'tree-reference':
1841
1707
inv_entry.reference_revision = fingerprint or None
1865
1731
parent_index = self._get_parent_index()
1866
1732
last_changed_revision = entry[1][parent_index][4]
1868
rev = self._repository.get_revision(last_changed_revision)
1869
except errors.NoSuchRevision:
1870
raise errors.FileTimestampUnavailable(self.id2path(file_id))
1871
return rev.timestamp
1733
return self._repository.get_revision(last_changed_revision).timestamp
1873
1735
def get_file_sha1(self, file_id, path=None, stat_value=None):
1874
1736
entry = self._get_entry(file_id=file_id, path=path)
1878
1740
return parent_details[1]
1882
def get_file_revision(self, file_id):
1883
return self.inventory[file_id].revision
1885
1743
def get_file(self, file_id, path=None):
1886
1744
return StringIO(self.get_file_text(file_id))
1911
1769
return self._repository.iter_files_bytes(repo_desired_files)
1913
def get_symlink_target(self, file_id, path=None):
1771
def get_symlink_target(self, file_id):
1914
1772
entry = self._get_entry(file_id=file_id)
1915
1773
parent_index = self._get_parent_index()
1916
1774
if entry[1][parent_index][0] != 'l':
1945
1803
entry = self._get_entry(file_id=file_id)[1]
1946
1804
if entry is None:
1947
1805
raise errors.NoSuchId(tree=self, file_id=file_id)
1948
parent_index = self._get_parent_index()
1949
return dirstate.DirState._minikind_to_kind[entry[parent_index][0]]
1806
return dirstate.DirState._minikind_to_kind[entry[1][0]]
1951
1808
def stored_kind(self, file_id):
1952
1809
"""See Tree.stored_kind"""
1969
1826
def is_executable(self, file_id, path=None):
1970
1827
ie = self.inventory[file_id]
1971
1828
if ie.kind != "file":
1973
1830
return ie.executable
1975
def is_locked(self):
1978
def list_files(self, include_root=False, from_dir=None, recursive=True):
1832
def list_files(self, include_root=False):
1979
1833
# We use a standard implementation, because DirStateRevisionTree is
1980
1834
# dealing with one of the parents of the current state
1981
1835
inv = self._get_inventory()
1982
if from_dir is None:
1985
from_dir_id = inv.path2id(from_dir)
1986
if from_dir_id is None:
1987
# Directory not versioned
1989
entries = inv.iter_entries(from_dir=from_dir_id, recursive=recursive)
1990
if inv.root is not None and not include_root and from_dir is None:
1836
entries = inv.iter_entries()
1837
if self.inventory.root is not None and not include_root:
1992
1839
for path, entry in entries:
1993
1840
yield path, 'V', entry.kind, entry.file_id, entry
1995
1842
def lock_read(self):
1996
"""Lock the tree for a set of operations.
1998
:return: A bzrlib.lock.LogicalLockResult.
1843
"""Lock the tree for a set of operations."""
2000
1844
if not self._locked:
2001
1845
self._repository.lock_read()
2002
1846
if self._dirstate._lock_token is None:
2003
1847
self._dirstate.lock_read()
2004
1848
self._dirstate_locked = True
2005
1849
self._locked += 1
2006
return LogicalLockResult(self.unlock)
2008
1851
def _must_be_locked(self):
2009
1852
if not self._locked:
2088
1931
def make_source_parent_tree(source, target):
2089
1932
"""Change the source tree into a parent of the target."""
2090
1933
revid = source.commit('record tree')
2091
target.branch.fetch(source.branch, revid)
1934
target.branch.repository.fetch(source.branch.repository, revid)
2092
1935
target.set_parent_ids([revid])
2093
1936
return target.basis_tree(), target
2102
def make_source_parent_tree_compiled_dirstate(klass, test_case, source,
1945
def make_source_parent_tree_compiled_dirstate(klass, test_case, source, target):
2104
1946
from bzrlib.tests.test__dirstate_helpers import \
2105
compiled_dirstate_helpers_feature
2106
test_case.requireFeature(compiled_dirstate_helpers_feature)
2107
from bzrlib._dirstate_helpers_pyx import ProcessEntryC
1947
CompiledDirstateHelpersFeature
1948
if not CompiledDirstateHelpersFeature.available():
1949
from bzrlib.tests import UnavailableFeature
1950
raise UnavailableFeature(CompiledDirstateHelpersFeature)
1951
from bzrlib._dirstate_helpers_c import ProcessEntryC
2108
1952
result = klass.make_source_parent_tree(source, target)
2109
1953
result[1]._iter_changes = ProcessEntryC
2140
1984
output. An unversioned file is defined as one with (False, False)
2141
1985
for the versioned pair.
1987
# NB: show_status depends on being able to pass in non-versioned files
1988
# and report them as unknown
2143
1989
# TODO: handle extra trees in the dirstate.
2144
1990
if (extra_trees or specific_files == []):
2145
1991
# we can't fast-path these cases (yet)
2148
1994
require_versioned, want_unversioned=want_unversioned)
2149
1995
parent_ids = self.target.get_parent_ids()
2150
1996
if not (self.source._revision_id in parent_ids
2151
or self.source._revision_id == _mod_revision.NULL_REVISION):
1997
or self.source._revision_id == NULL_REVISION):
2152
1998
raise AssertionError(
2153
1999
"revision {%s} is not stored in {%s}, but %s "
2154
2000
"can only be used for trees stored in the dirstate"
2155
2001
% (self.source._revision_id, self.target, self.iter_changes))
2156
2002
target_index = 0
2157
if self.source._revision_id == _mod_revision.NULL_REVISION:
2003
if self.source._revision_id == NULL_REVISION:
2158
2004
source_index = None
2159
2005
indices = (target_index,)
2181
2027
state._read_dirblocks_if_needed()
2182
2028
if require_versioned:
2183
2029
# -- check all supplied paths are versioned in a search tree. --
2030
all_versioned = True
2185
2031
for path in specific_files:
2186
2032
path_entries = state._entries_for_path(path)
2187
2033
if not path_entries:
2188
2034
# this specified path is not present at all: error
2189
not_versioned.append(path)
2035
all_versioned = False
2191
2037
found_versioned = False
2192
2038
# for each id at this path
2193
2039
for entry in path_entries:
2200
2046
if not found_versioned:
2201
2047
# none of the indexes was not 'absent' at all ids for this
2203
not_versioned.append(path)
2204
if len(not_versioned) > 0:
2205
raise errors.PathsNotVersionedError(not_versioned)
2049
all_versioned = False
2051
if not all_versioned:
2052
raise errors.PathsNotVersionedError(specific_files)
2206
2053
# -- remove redundancy in supplied specific_files to prevent over-scanning --
2207
2054
search_specific_files = osutils.minimum_path_selection(specific_files)
2222
2069
(revisiontree.RevisionTree, DirStateRevisionTree)):
2224
2071
# the source revid must be in the target dirstate
2225
if not (source._revision_id == _mod_revision.NULL_REVISION or
2072
if not (source._revision_id == NULL_REVISION or
2226
2073
source._revision_id in target.get_parent_ids()):
2227
2074
# TODO: what about ghosts? it may well need to
2228
2075
# check for them explicitly.