2080
1885
if not found_versioned:
2081
1886
# none of the indexes was not 'absent' at all ids for this
2083
not_versioned.append(path)
2084
if len(not_versioned) > 0:
2085
raise errors.PathsNotVersionedError(not_versioned)
1888
all_versioned = False
1890
if not all_versioned:
1891
raise errors.PathsNotVersionedError(specific_files)
2086
1892
# -- remove redundancy in supplied specific_files to prevent over-scanning --
2087
search_specific_files = osutils.minimum_path_selection(specific_files)
1893
search_specific_files = set()
1894
for path in specific_files:
1895
other_specific_files = specific_files.difference(set([path]))
1896
if not osutils.is_inside_any(other_specific_files, path):
1897
# this is a top level path, we must check it.
1898
search_specific_files.add(path)
1900
# compare source_index and target_index at or under each element of search_specific_files.
1901
# follow the following comparison table. Note that we only want to do diff operations when
1902
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1906
# Source | Target | disk | action
1907
# r | fdlt | | add source to search, add id path move and perform
1908
# | | | diff check on source-target
1909
# r | fdlt | a | dangling file that was present in the basis.
1911
# r | a | | add source to search
1913
# r | r | | this path is present in a non-examined tree, skip.
1914
# r | r | a | this path is present in a non-examined tree, skip.
1915
# a | fdlt | | add new id
1916
# a | fdlt | a | dangling locally added file, skip
1917
# a | a | | not present in either tree, skip
1918
# a | a | a | not present in any tree, skip
1919
# a | r | | not present in either tree at this path, skip as it
1920
# | | | may not be selected by the users list of paths.
1921
# a | r | a | not present in either tree at this path, skip as it
1922
# | | | may not be selected by the users list of paths.
1923
# fdlt | fdlt | | content in both: diff them
1924
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1925
# fdlt | a | | unversioned: output deleted id for now
1926
# fdlt | a | a | unversioned and deleted: output deleted id
1927
# fdlt | r | | relocated in this tree, so add target to search.
1928
# | | | Dont diff, we will see an r,fd; pair when we reach
1929
# | | | this id at the other path.
1930
# fdlt | r | a | relocated in this tree, so add target to search.
1931
# | | | Dont diff, we will see an r,fd; pair when we reach
1932
# | | | this id at the other path.
1934
# for all search_indexs in each path at or under each element of
1935
# search_specific_files, if the detail is relocated: add the id, and add the
1936
# relocated path as one to search if its not searched already. If the
1937
# detail is not relocated, add the id.
1938
searched_specific_files = set()
1939
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1940
# Using a list so that we can access the values and change them in
1941
# nested scope. Each one is [path, file_id, entry]
1942
last_source_parent = [None, None]
1943
last_target_parent = [None, None]
2089
1945
use_filesystem_for_exec = (sys.platform != 'win32')
2090
iter_changes = self.target._iter_changes(include_unchanged,
2091
use_filesystem_for_exec, search_specific_files, state,
2092
source_index, target_index, want_unversioned, self.target)
2093
return iter_changes.iter_changes()
1947
# Just a sentry, so that _process_entry can say that this
1948
# record is handled, but isn't interesting to process (unchanged)
1949
uninteresting = object()
1952
old_dirname_to_file_id = {}
1953
new_dirname_to_file_id = {}
1954
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1955
# keeping a cache of directories that we have seen.
1957
def _process_entry(entry, path_info):
1958
"""Compare an entry and real disk to generate delta information.
1960
:param path_info: top_relpath, basename, kind, lstat, abspath for
1961
the path of entry. If None, then the path is considered absent.
1962
(Perhaps we should pass in a concrete entry for this ?)
1963
Basename is returned as a utf8 string because we expect this
1964
tuple will be ignored, and don't want to take the time to
1966
:return: None if these don't match
1967
A tuple of information about the change, or
1968
the object 'uninteresting' if these match, but are
1969
basically identical.
1971
if source_index is None:
1972
source_details = NULL_PARENT_DETAILS
1974
source_details = entry[1][source_index]
1975
target_details = entry[1][target_index]
1976
target_minikind = target_details[0]
1977
if path_info is not None and target_minikind in 'fdlt':
1978
if not (target_index == 0):
1979
raise AssertionError()
1980
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
1981
stat_value=path_info[3])
1982
# The entry may have been modified by update_entry
1983
target_details = entry[1][target_index]
1984
target_minikind = target_details[0]
1987
file_id = entry[0][2]
1988
source_minikind = source_details[0]
1989
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
1990
# claimed content in both: diff
1991
# r | fdlt | | add source to search, add id path move and perform
1992
# | | | diff check on source-target
1993
# r | fdlt | a | dangling file that was present in the basis.
1995
if source_minikind in 'r':
1996
# add the source to the search path to find any children it
1997
# has. TODO ? : only add if it is a container ?
1998
if not osutils.is_inside_any(searched_specific_files,
2000
search_specific_files.add(source_details[1])
2001
# generate the old path; this is needed for stating later
2003
old_path = source_details[1]
2004
old_dirname, old_basename = os.path.split(old_path)
2005
path = pathjoin(entry[0][0], entry[0][1])
2006
old_entry = state._get_entry(source_index,
2008
# update the source details variable to be the real
2010
if old_entry == (None, None):
2011
raise errors.CorruptDirstate(state._filename,
2012
"entry '%s/%s' is considered renamed from %r"
2013
" but source does not exist\n"
2014
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
2015
source_details = old_entry[1][source_index]
2016
source_minikind = source_details[0]
2018
old_dirname = entry[0][0]
2019
old_basename = entry[0][1]
2020
old_path = path = None
2021
if path_info is None:
2022
# the file is missing on disk, show as removed.
2023
content_change = True
2027
# source and target are both versioned and disk file is present.
2028
target_kind = path_info[2]
2029
if target_kind == 'directory':
2031
old_path = path = pathjoin(old_dirname, old_basename)
2032
new_dirname_to_file_id[path] = file_id
2033
if source_minikind != 'd':
2034
content_change = True
2036
# directories have no fingerprint
2037
content_change = False
2039
elif target_kind == 'file':
2040
if source_minikind != 'f':
2041
content_change = True
2043
# We could check the size, but we already have the
2045
content_change = (link_or_sha1 != source_details[1])
2046
# Target details is updated at update_entry time
2047
if use_filesystem_for_exec:
2048
# We don't need S_ISREG here, because we are sure
2049
# we are dealing with a file.
2050
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
2052
target_exec = target_details[3]
2053
elif target_kind == 'symlink':
2054
if source_minikind != 'l':
2055
content_change = True
2057
content_change = (link_or_sha1 != source_details[1])
2059
elif target_kind == 'tree-reference':
2060
if source_minikind != 't':
2061
content_change = True
2063
content_change = False
2066
raise Exception, "unknown kind %s" % path_info[2]
2067
if source_minikind == 'd':
2069
old_path = path = pathjoin(old_dirname, old_basename)
2070
old_dirname_to_file_id[old_path] = file_id
2071
# parent id is the entry for the path in the target tree
2072
if old_dirname == last_source_parent[0]:
2073
source_parent_id = last_source_parent[1]
2076
source_parent_id = old_dirname_to_file_id[old_dirname]
2078
source_parent_entry = state._get_entry(source_index,
2079
path_utf8=old_dirname)
2080
source_parent_id = source_parent_entry[0][2]
2081
if source_parent_id == entry[0][2]:
2082
# This is the root, so the parent is None
2083
source_parent_id = None
2085
last_source_parent[0] = old_dirname
2086
last_source_parent[1] = source_parent_id
2087
new_dirname = entry[0][0]
2088
if new_dirname == last_target_parent[0]:
2089
target_parent_id = last_target_parent[1]
2092
target_parent_id = new_dirname_to_file_id[new_dirname]
2094
# TODO: We don't always need to do the lookup, because the
2095
# parent entry will be the same as the source entry.
2096
target_parent_entry = state._get_entry(target_index,
2097
path_utf8=new_dirname)
2098
if target_parent_entry == (None, None):
2099
raise AssertionError(
2100
"Could not find target parent in wt: %s\nparent of: %s"
2101
% (new_dirname, entry))
2102
target_parent_id = target_parent_entry[0][2]
2103
if target_parent_id == entry[0][2]:
2104
# This is the root, so the parent is None
2105
target_parent_id = None
2107
last_target_parent[0] = new_dirname
2108
last_target_parent[1] = target_parent_id
2110
source_exec = source_details[3]
2111
if (include_unchanged
2113
or source_parent_id != target_parent_id
2114
or old_basename != entry[0][1]
2115
or source_exec != target_exec
2117
if old_path is None:
2118
old_path = path = pathjoin(old_dirname, old_basename)
2119
old_path_u = utf8_decode(old_path)[0]
2122
old_path_u = utf8_decode(old_path)[0]
2123
if old_path == path:
2126
path_u = utf8_decode(path)[0]
2127
source_kind = _minikind_to_kind[source_minikind]
2128
return (entry[0][2],
2129
(old_path_u, path_u),
2132
(source_parent_id, target_parent_id),
2133
(utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
2134
(source_kind, target_kind),
2135
(source_exec, target_exec))
2137
return uninteresting
2138
elif source_minikind in 'a' and target_minikind in 'fdlt':
2139
# looks like a new file
2140
if path_info is not None:
2141
path = pathjoin(entry[0][0], entry[0][1])
2142
# parent id is the entry for the path in the target tree
2143
# TODO: these are the same for an entire directory: cache em.
2144
parent_id = state._get_entry(target_index,
2145
path_utf8=entry[0][0])[0][2]
2146
if parent_id == entry[0][2]:
2148
if use_filesystem_for_exec:
2149
# We need S_ISREG here, because we aren't sure if this
2152
stat.S_ISREG(path_info[3].st_mode)
2153
and stat.S_IEXEC & path_info[3].st_mode)
2155
target_exec = target_details[3]
2156
return (entry[0][2],
2157
(None, utf8_decode(path)[0]),
2161
(None, utf8_decode(entry[0][1])[0]),
2162
(None, path_info[2]),
2163
(None, target_exec))
2165
# but its not on disk: we deliberately treat this as just
2166
# never-present. (Why ?! - RBC 20070224)
2168
elif source_minikind in 'fdlt' and target_minikind in 'a':
2169
# unversioned, possibly, or possibly not deleted: we dont care.
2170
# if its still on disk, *and* theres no other entry at this
2171
# path [we dont know this in this routine at the moment -
2172
# perhaps we should change this - then it would be an unknown.
2173
old_path = pathjoin(entry[0][0], entry[0][1])
2174
# parent id is the entry for the path in the target tree
2175
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
2176
if parent_id == entry[0][2]:
2178
return (entry[0][2],
2179
(utf8_decode(old_path)[0], None),
2183
(utf8_decode(entry[0][1])[0], None),
2184
(_minikind_to_kind[source_minikind], None),
2185
(source_details[3], None))
2186
elif source_minikind in 'fdlt' and target_minikind in 'r':
2187
# a rename; could be a true rename, or a rename inherited from
2188
# a renamed parent. TODO: handle this efficiently. Its not
2189
# common case to rename dirs though, so a correct but slow
2190
# implementation will do.
2191
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
2192
search_specific_files.add(target_details[1])
2193
elif source_minikind in 'ra' and target_minikind in 'ra':
2194
# neither of the selected trees contain this file,
2195
# so skip over it. This is not currently directly tested, but
2196
# is indirectly via test_too_much.TestCommands.test_conflicts.
2199
raise AssertionError("don't know how to compare "
2200
"source_minikind=%r, target_minikind=%r"
2201
% (source_minikind, target_minikind))
2202
## import pdb;pdb.set_trace()
2205
while search_specific_files:
2206
# TODO: the pending list should be lexically sorted? the
2207
# interface doesn't require it.
2208
current_root = search_specific_files.pop()
2209
current_root_unicode = current_root.decode('utf8')
2210
searched_specific_files.add(current_root)
2211
# process the entries for this containing directory: the rest will be
2212
# found by their parents recursively.
2213
root_entries = _entries_for_path(current_root)
2214
root_abspath = self.target.abspath(current_root_unicode)
2216
root_stat = os.lstat(root_abspath)
2218
if e.errno == errno.ENOENT:
2219
# the path does not exist: let _process_entry know that.
2220
root_dir_info = None
2222
# some other random error: hand it up.
2225
root_dir_info = ('', current_root,
2226
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
2228
if root_dir_info[2] == 'directory':
2229
if self.target._directory_is_tree_reference(
2230
current_root.decode('utf8')):
2231
root_dir_info = root_dir_info[:2] + \
2232
('tree-reference',) + root_dir_info[3:]
2234
if not root_entries and not root_dir_info:
2235
# this specified path is not present at all, skip it.
2237
path_handled = False
2238
for entry in root_entries:
2239
result = _process_entry(entry, root_dir_info)
2240
if result is not None:
2242
if result is not uninteresting:
2244
if want_unversioned and not path_handled and root_dir_info:
2245
new_executable = bool(
2246
stat.S_ISREG(root_dir_info[3].st_mode)
2247
and stat.S_IEXEC & root_dir_info[3].st_mode)
2249
(None, current_root_unicode),
2253
(None, splitpath(current_root_unicode)[-1]),
2254
(None, root_dir_info[2]),
2255
(None, new_executable)
2257
initial_key = (current_root, '', '')
2258
block_index, _ = state._find_block_index_from_key(initial_key)
2259
if block_index == 0:
2260
# we have processed the total root already, but because the
2261
# initial key matched it we should skip it here.
2263
if root_dir_info and root_dir_info[2] == 'tree-reference':
2264
current_dir_info = None
2266
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2268
current_dir_info = dir_iterator.next()
2270
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
2271
# python 2.5 has e.errno == EINVAL,
2272
# and e.winerror == ERROR_DIRECTORY
2273
e_winerror = getattr(e, 'winerror', None)
2274
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
2275
# there may be directories in the inventory even though
2276
# this path is not a file on disk: so mark it as end of
2278
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
2279
current_dir_info = None
2280
elif (sys.platform == 'win32'
2281
and (e.errno in win_errors
2282
or e_winerror in win_errors)):
2283
current_dir_info = None
2287
if current_dir_info[0][0] == '':
2288
# remove .bzr from iteration
2289
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2290
if current_dir_info[1][bzr_index][0] != '.bzr':
2291
raise AssertionError()
2292
del current_dir_info[1][bzr_index]
2293
# walk until both the directory listing and the versioned metadata
2295
if (block_index < len(state._dirblocks) and
2296
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2297
current_block = state._dirblocks[block_index]
2299
current_block = None
2300
while (current_dir_info is not None or
2301
current_block is not None):
2302
if (current_dir_info and current_block
2303
and current_dir_info[0][0] != current_block[0]):
2304
if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2305
# filesystem data refers to paths not covered by the dirblock.
2306
# this has two possibilities:
2307
# A) it is versioned but empty, so there is no block for it
2308
# B) it is not versioned.
2310
# if (A) then we need to recurse into it to check for
2311
# new unknown files or directories.
2312
# if (B) then we should ignore it, because we don't
2313
# recurse into unknown directories.
2315
while path_index < len(current_dir_info[1]):
2316
current_path_info = current_dir_info[1][path_index]
2317
if want_unversioned:
2318
if current_path_info[2] == 'directory':
2319
if self.target._directory_is_tree_reference(
2320
current_path_info[0].decode('utf8')):
2321
current_path_info = current_path_info[:2] + \
2322
('tree-reference',) + current_path_info[3:]
2323
new_executable = bool(
2324
stat.S_ISREG(current_path_info[3].st_mode)
2325
and stat.S_IEXEC & current_path_info[3].st_mode)
2327
(None, utf8_decode(current_path_info[0])[0]),
2331
(None, utf8_decode(current_path_info[1])[0]),
2332
(None, current_path_info[2]),
2333
(None, new_executable))
2334
# dont descend into this unversioned path if it is
2336
if current_path_info[2] in ('directory',
2338
del current_dir_info[1][path_index]
2342
# This dir info has been handled, go to the next
2344
current_dir_info = dir_iterator.next()
2345
except StopIteration:
2346
current_dir_info = None
2348
# We have a dirblock entry for this location, but there
2349
# is no filesystem path for this. This is most likely
2350
# because a directory was removed from the disk.
2351
# We don't have to report the missing directory,
2352
# because that should have already been handled, but we
2353
# need to handle all of the files that are contained
2355
for current_entry in current_block[1]:
2356
# entry referring to file not present on disk.
2357
# advance the entry only, after processing.
2358
result = _process_entry(current_entry, None)
2359
if result is not None:
2360
if result is not uninteresting:
2363
if (block_index < len(state._dirblocks) and
2364
osutils.is_inside(current_root,
2365
state._dirblocks[block_index][0])):
2366
current_block = state._dirblocks[block_index]
2368
current_block = None
2371
if current_block and entry_index < len(current_block[1]):
2372
current_entry = current_block[1][entry_index]
2374
current_entry = None
2375
advance_entry = True
2377
if current_dir_info and path_index < len(current_dir_info[1]):
2378
current_path_info = current_dir_info[1][path_index]
2379
if current_path_info[2] == 'directory':
2380
if self.target._directory_is_tree_reference(
2381
current_path_info[0].decode('utf8')):
2382
current_path_info = current_path_info[:2] + \
2383
('tree-reference',) + current_path_info[3:]
2385
current_path_info = None
2387
path_handled = False
2388
while (current_entry is not None or
2389
current_path_info is not None):
2390
if current_entry is None:
2391
# the check for path_handled when the path is adnvaced
2392
# will yield this path if needed.
2394
elif current_path_info is None:
2395
# no path is fine: the per entry code will handle it.
2396
result = _process_entry(current_entry, current_path_info)
2397
if result is not None:
2398
if result is not uninteresting:
2400
elif (current_entry[0][1] != current_path_info[1]
2401
or current_entry[1][target_index][0] in 'ar'):
2402
# The current path on disk doesn't match the dirblock
2403
# record. Either the dirblock is marked as absent, or
2404
# the file on disk is not present at all in the
2405
# dirblock. Either way, report about the dirblock
2406
# entry, and let other code handle the filesystem one.
2408
# Compare the basename for these files to determine
2410
if current_path_info[1] < current_entry[0][1]:
2411
# extra file on disk: pass for now, but only
2412
# increment the path, not the entry
2413
advance_entry = False
2415
# entry referring to file not present on disk.
2416
# advance the entry only, after processing.
2417
result = _process_entry(current_entry, None)
2418
if result is not None:
2419
if result is not uninteresting:
2421
advance_path = False
2423
result = _process_entry(current_entry, current_path_info)
2424
if result is not None:
2426
if result is not uninteresting:
2428
if advance_entry and current_entry is not None:
2430
if entry_index < len(current_block[1]):
2431
current_entry = current_block[1][entry_index]
2433
current_entry = None
2435
advance_entry = True # reset the advance flaga
2436
if advance_path and current_path_info is not None:
2437
if not path_handled:
2438
# unversioned in all regards
2439
if want_unversioned:
2440
new_executable = bool(
2441
stat.S_ISREG(current_path_info[3].st_mode)
2442
and stat.S_IEXEC & current_path_info[3].st_mode)
2444
(None, utf8_decode(current_path_info[0])[0]),
2448
(None, utf8_decode(current_path_info[1])[0]),
2449
(None, current_path_info[2]),
2450
(None, new_executable))
2451
# dont descend into this unversioned path if it is
2453
if current_path_info[2] in ('directory'):
2454
del current_dir_info[1][path_index]
2456
# dont descend the disk iterator into any tree
2458
if current_path_info[2] == 'tree-reference':
2459
del current_dir_info[1][path_index]
2462
if path_index < len(current_dir_info[1]):
2463
current_path_info = current_dir_info[1][path_index]
2464
if current_path_info[2] == 'directory':
2465
if self.target._directory_is_tree_reference(
2466
current_path_info[0].decode('utf8')):
2467
current_path_info = current_path_info[:2] + \
2468
('tree-reference',) + current_path_info[3:]
2470
current_path_info = None
2471
path_handled = False
2473
advance_path = True # reset the advance flagg.
2474
if current_block is not None:
2476
if (block_index < len(state._dirblocks) and
2477
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2478
current_block = state._dirblocks[block_index]
2480
current_block = None
2481
if current_dir_info is not None:
2483
current_dir_info = dir_iterator.next()
2484
except StopIteration:
2485
current_dir_info = None
2096
2489
def is_compatible(source, target):
2097
2490
# the target must be a dirstate working tree
2098
if not isinstance(target, DirStateWorkingTree):
2491
if not isinstance(target, WorkingTree4):
2100
# the source must be a revtree or dirstate rev tree.
2493
# the source must be a revtreee or dirstate rev tree.
2101
2494
if not isinstance(source,
2102
2495
(revisiontree.RevisionTree, DirStateRevisionTree)):
2104
2497
# the source revid must be in the target dirstate
2105
if not (source._revision_id == _mod_revision.NULL_REVISION or
2498
if not (source._revision_id == NULL_REVISION or
2106
2499
source._revision_id in target.get_parent_ids()):
2107
# TODO: what about ghosts? it may well need to
2500
# TODO: what about ghosts? it may well need to
2108
2501
# check for them explicitly.