1926
1921
if not all_versioned:
1927
1922
raise errors.PathsNotVersionedError(specific_files)
1928
1923
# -- remove redundancy in supplied specific_files to prevent over-scanning --
1929
search_specific_files = set()
1930
1924
for path in specific_files:
1931
1925
other_specific_files = specific_files.difference(set([path]))
1932
1926
if not osutils.is_inside_any(other_specific_files, path):
1933
1927
# this is a top level path, we must check it.
1934
1928
search_specific_files.add(path)
1936
# compare source_index and target_index at or under each element of search_specific_files.
1937
# follow the following comparison table. Note that we only want to do diff operations when
1938
# the target is fdl because thats when the walkdirs logic will have exposed the pathinfo
1942
# Source | Target | disk | action
1943
# r | fdlt | | add source to search, add id path move and perform
1944
# | | | diff check on source-target
1945
# r | fdlt | a | dangling file that was present in the basis.
1947
# r | a | | add source to search
1949
# r | r | | this path is present in a non-examined tree, skip.
1950
# r | r | a | this path is present in a non-examined tree, skip.
1951
# a | fdlt | | add new id
1952
# a | fdlt | a | dangling locally added file, skip
1953
# a | a | | not present in either tree, skip
1954
# a | a | a | not present in any tree, skip
1955
# a | r | | not present in either tree at this path, skip as it
1956
# | | | may not be selected by the users list of paths.
1957
# a | r | a | not present in either tree at this path, skip as it
1958
# | | | may not be selected by the users list of paths.
1959
# fdlt | fdlt | | content in both: diff them
1960
# fdlt | fdlt | a | deleted locally, but not unversioned - show as deleted ?
1961
# fdlt | a | | unversioned: output deleted id for now
1962
# fdlt | a | a | unversioned and deleted: output deleted id
1963
# fdlt | r | | relocated in this tree, so add target to search.
1964
# | | | Dont diff, we will see an r,fd; pair when we reach
1965
# | | | this id at the other path.
1966
# fdlt | r | a | relocated in this tree, so add target to search.
1967
# | | | Dont diff, we will see an r,fd; pair when we reach
1968
# | | | this id at the other path.
1970
# for all search_indexs in each path at or under each element of
1971
# search_specific_files, if the detail is relocated: add the id, and add the
1972
# relocated path as one to search if its not searched already. If the
1973
# detail is not relocated, add the id.
1974
searched_specific_files = set()
1975
NULL_PARENT_DETAILS = dirstate.DirState.NULL_PARENT_DETAILS
1976
# Using a list so that we can access the values and change them in
1977
# nested scope. Each one is [path, file_id, entry]
1978
last_source_parent = [None, None]
1979
last_target_parent = [None, None]
1981
1930
use_filesystem_for_exec = (sys.platform != 'win32')
1983
# Just a sentry, so that _process_entry can say that this
1984
# record is handled, but isn't interesting to process (unchanged)
1985
uninteresting = object()
1987
old_dirname_to_file_id = {}
1988
new_dirname_to_file_id = {}
1989
# TODO: jam 20070516 - Avoid the _get_entry lookup overhead by
1990
# keeping a cache of directories that we have seen.
1992
def _process_entry(entry, path_info):
1993
"""Compare an entry and real disk to generate delta information.
1995
:param path_info: top_relpath, basename, kind, lstat, abspath for
1996
the path of entry. If None, then the path is considered absent.
1997
(Perhaps we should pass in a concrete entry for this ?)
1998
Basename is returned as a utf8 string because we expect this
1999
tuple will be ignored, and don't want to take the time to
2001
:return: None if these don't match
2002
A tuple of information about the change, or
2003
the object 'uninteresting' if these match, but are
2004
basically identical.
2006
if source_index is None:
2007
source_details = NULL_PARENT_DETAILS
2009
source_details = entry[1][source_index]
2010
target_details = entry[1][target_index]
2011
target_minikind = target_details[0]
2012
if path_info is not None and target_minikind in 'fdlt':
2013
if not (target_index == 0):
2014
raise AssertionError()
2015
link_or_sha1 = state.update_entry(entry, abspath=path_info[4],
2016
stat_value=path_info[3])
2017
# The entry may have been modified by update_entry
2018
target_details = entry[1][target_index]
2019
target_minikind = target_details[0]
2022
file_id = entry[0][2]
2023
source_minikind = source_details[0]
2024
if source_minikind in 'fdltr' and target_minikind in 'fdlt':
2025
# claimed content in both: diff
2026
# r | fdlt | | add source to search, add id path move and perform
2027
# | | | diff check on source-target
2028
# r | fdlt | a | dangling file that was present in the basis.
2030
if source_minikind in 'r':
2031
# add the source to the search path to find any children it
2032
# has. TODO ? : only add if it is a container ?
2033
if not osutils.is_inside_any(searched_specific_files,
2035
search_specific_files.add(source_details[1])
2036
# generate the old path; this is needed for stating later
2038
old_path = source_details[1]
2039
old_dirname, old_basename = os.path.split(old_path)
2040
path = pathjoin(entry[0][0], entry[0][1])
2041
old_entry = state._get_entry(source_index,
2043
# update the source details variable to be the real
2045
if old_entry == (None, None):
2046
raise errors.CorruptDirstate(state._filename,
2047
"entry '%s/%s' is considered renamed from %r"
2048
" but source does not exist\n"
2049
"entry: %s" % (entry[0][0], entry[0][1], old_path, entry))
2050
source_details = old_entry[1][source_index]
2051
source_minikind = source_details[0]
2053
old_dirname = entry[0][0]
2054
old_basename = entry[0][1]
2055
old_path = path = None
2056
if path_info is None:
2057
# the file is missing on disk, show as removed.
2058
content_change = True
2062
# source and target are both versioned and disk file is present.
2063
target_kind = path_info[2]
2064
if target_kind == 'directory':
2066
old_path = path = pathjoin(old_dirname, old_basename)
2067
new_dirname_to_file_id[path] = file_id
2068
if source_minikind != 'd':
2069
content_change = True
2071
# directories have no fingerprint
2072
content_change = False
2074
elif target_kind == 'file':
2075
if source_minikind != 'f':
2076
content_change = True
2078
# We could check the size, but we already have the
2080
content_change = (link_or_sha1 != source_details[1])
2081
# Target details is updated at update_entry time
2082
if use_filesystem_for_exec:
2083
# We don't need S_ISREG here, because we are sure
2084
# we are dealing with a file.
2085
target_exec = bool(stat.S_IEXEC & path_info[3].st_mode)
2087
target_exec = target_details[3]
2088
elif target_kind == 'symlink':
2089
if source_minikind != 'l':
2090
content_change = True
2092
content_change = (link_or_sha1 != source_details[1])
2094
elif target_kind == 'tree-reference':
2095
if source_minikind != 't':
2096
content_change = True
2098
content_change = False
2101
raise Exception, "unknown kind %s" % path_info[2]
2102
if source_minikind == 'd':
2104
old_path = path = pathjoin(old_dirname, old_basename)
2105
old_dirname_to_file_id[old_path] = file_id
2106
# parent id is the entry for the path in the target tree
2107
if old_dirname == last_source_parent[0]:
2108
source_parent_id = last_source_parent[1]
2111
source_parent_id = old_dirname_to_file_id[old_dirname]
2113
source_parent_entry = state._get_entry(source_index,
2114
path_utf8=old_dirname)
2115
source_parent_id = source_parent_entry[0][2]
2116
if source_parent_id == entry[0][2]:
2117
# This is the root, so the parent is None
2118
source_parent_id = None
2120
last_source_parent[0] = old_dirname
2121
last_source_parent[1] = source_parent_id
2122
new_dirname = entry[0][0]
2123
if new_dirname == last_target_parent[0]:
2124
target_parent_id = last_target_parent[1]
2127
target_parent_id = new_dirname_to_file_id[new_dirname]
2129
# TODO: We don't always need to do the lookup, because the
2130
# parent entry will be the same as the source entry.
2131
target_parent_entry = state._get_entry(target_index,
2132
path_utf8=new_dirname)
2133
if target_parent_entry == (None, None):
2134
raise AssertionError(
2135
"Could not find target parent in wt: %s\nparent of: %s"
2136
% (new_dirname, entry))
2137
target_parent_id = target_parent_entry[0][2]
2138
if target_parent_id == entry[0][2]:
2139
# This is the root, so the parent is None
2140
target_parent_id = None
2142
last_target_parent[0] = new_dirname
2143
last_target_parent[1] = target_parent_id
2145
source_exec = source_details[3]
2146
if (include_unchanged
2148
or source_parent_id != target_parent_id
2149
or old_basename != entry[0][1]
2150
or source_exec != target_exec
2152
if old_path is None:
2153
old_path = path = pathjoin(old_dirname, old_basename)
2154
old_path_u = utf8_decode(old_path)[0]
2157
old_path_u = utf8_decode(old_path)[0]
2158
if old_path == path:
2161
path_u = utf8_decode(path)[0]
2162
source_kind = _minikind_to_kind[source_minikind]
2163
return (entry[0][2],
2164
(old_path_u, path_u),
2167
(source_parent_id, target_parent_id),
2168
(utf8_decode(old_basename)[0], utf8_decode(entry[0][1])[0]),
2169
(source_kind, target_kind),
2170
(source_exec, target_exec))
2172
return uninteresting
2173
elif source_minikind in 'a' and target_minikind in 'fdlt':
2174
# looks like a new file
2175
path = pathjoin(entry[0][0], entry[0][1])
2176
# parent id is the entry for the path in the target tree
2177
# TODO: these are the same for an entire directory: cache em.
2178
parent_id = state._get_entry(target_index,
2179
path_utf8=entry[0][0])[0][2]
2180
if parent_id == entry[0][2]:
2182
if path_info is not None:
2184
if use_filesystem_for_exec:
2185
# We need S_ISREG here, because we aren't sure if this
2188
stat.S_ISREG(path_info[3].st_mode)
2189
and stat.S_IEXEC & path_info[3].st_mode)
2191
target_exec = target_details[3]
2192
return (entry[0][2],
2193
(None, utf8_decode(path)[0]),
2197
(None, utf8_decode(entry[0][1])[0]),
2198
(None, path_info[2]),
2199
(None, target_exec))
2201
# Its a missing file, report it as such.
2202
return (entry[0][2],
2203
(None, utf8_decode(path)[0]),
2207
(None, utf8_decode(entry[0][1])[0]),
2210
elif source_minikind in 'fdlt' and target_minikind in 'a':
2211
# unversioned, possibly, or possibly not deleted: we dont care.
2212
# if its still on disk, *and* theres no other entry at this
2213
# path [we dont know this in this routine at the moment -
2214
# perhaps we should change this - then it would be an unknown.
2215
old_path = pathjoin(entry[0][0], entry[0][1])
2216
# parent id is the entry for the path in the target tree
2217
parent_id = state._get_entry(source_index, path_utf8=entry[0][0])[0][2]
2218
if parent_id == entry[0][2]:
2220
return (entry[0][2],
2221
(utf8_decode(old_path)[0], None),
2225
(utf8_decode(entry[0][1])[0], None),
2226
(_minikind_to_kind[source_minikind], None),
2227
(source_details[3], None))
2228
elif source_minikind in 'fdlt' and target_minikind in 'r':
2229
# a rename; could be a true rename, or a rename inherited from
2230
# a renamed parent. TODO: handle this efficiently. Its not
2231
# common case to rename dirs though, so a correct but slow
2232
# implementation will do.
2233
if not osutils.is_inside_any(searched_specific_files, target_details[1]):
2234
search_specific_files.add(target_details[1])
2235
elif source_minikind in 'ra' and target_minikind in 'ra':
2236
# neither of the selected trees contain this file,
2237
# so skip over it. This is not currently directly tested, but
2238
# is indirectly via test_too_much.TestCommands.test_conflicts.
2241
raise AssertionError("don't know how to compare "
2242
"source_minikind=%r, target_minikind=%r"
2243
% (source_minikind, target_minikind))
2244
## import pdb;pdb.set_trace()
2247
while search_specific_files:
2248
# TODO: the pending list should be lexically sorted? the
2249
# interface doesn't require it.
2250
current_root = search_specific_files.pop()
2251
current_root_unicode = current_root.decode('utf8')
2252
searched_specific_files.add(current_root)
2253
# process the entries for this containing directory: the rest will be
2254
# found by their parents recursively.
2255
root_entries = _entries_for_path(current_root)
2256
root_abspath = self.target.abspath(current_root_unicode)
2258
root_stat = os.lstat(root_abspath)
2260
if e.errno == errno.ENOENT:
2261
# the path does not exist: let _process_entry know that.
2262
root_dir_info = None
2264
# some other random error: hand it up.
2267
root_dir_info = ('', current_root,
2268
osutils.file_kind_from_stat_mode(root_stat.st_mode), root_stat,
2270
if root_dir_info[2] == 'directory':
2271
if self.target._directory_is_tree_reference(
2272
current_root.decode('utf8')):
2273
root_dir_info = root_dir_info[:2] + \
2274
('tree-reference',) + root_dir_info[3:]
2276
if not root_entries and not root_dir_info:
2277
# this specified path is not present at all, skip it.
2279
path_handled = False
2280
for entry in root_entries:
2281
result = _process_entry(entry, root_dir_info)
2282
if result is not None:
2284
if result is not uninteresting:
2286
if want_unversioned and not path_handled and root_dir_info:
2287
new_executable = bool(
2288
stat.S_ISREG(root_dir_info[3].st_mode)
2289
and stat.S_IEXEC & root_dir_info[3].st_mode)
2291
(None, current_root_unicode),
2295
(None, splitpath(current_root_unicode)[-1]),
2296
(None, root_dir_info[2]),
2297
(None, new_executable)
2299
initial_key = (current_root, '', '')
2300
block_index, _ = state._find_block_index_from_key(initial_key)
2301
if block_index == 0:
2302
# we have processed the total root already, but because the
2303
# initial key matched it we should skip it here.
2305
if root_dir_info and root_dir_info[2] == 'tree-reference':
2306
current_dir_info = None
2308
dir_iterator = osutils._walkdirs_utf8(root_abspath, prefix=current_root)
2310
current_dir_info = dir_iterator.next()
2312
# on win32, python2.4 has e.errno == ERROR_DIRECTORY, but
2313
# python 2.5 has e.errno == EINVAL,
2314
# and e.winerror == ERROR_DIRECTORY
2315
e_winerror = getattr(e, 'winerror', None)
2316
win_errors = (ERROR_DIRECTORY, ERROR_PATH_NOT_FOUND)
2317
# there may be directories in the inventory even though
2318
# this path is not a file on disk: so mark it as end of
2320
if e.errno in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
2321
current_dir_info = None
2322
elif (sys.platform == 'win32'
2323
and (e.errno in win_errors
2324
or e_winerror in win_errors)):
2325
current_dir_info = None
2329
if current_dir_info[0][0] == '':
2330
# remove .bzr from iteration
2331
bzr_index = bisect_left(current_dir_info[1], ('.bzr',))
2332
if current_dir_info[1][bzr_index][0] != '.bzr':
2333
raise AssertionError()
2334
del current_dir_info[1][bzr_index]
2335
# walk until both the directory listing and the versioned metadata
2337
if (block_index < len(state._dirblocks) and
2338
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2339
current_block = state._dirblocks[block_index]
2341
current_block = None
2342
while (current_dir_info is not None or
2343
current_block is not None):
2344
if (current_dir_info and current_block
2345
and current_dir_info[0][0] != current_block[0]):
2346
if cmp_by_dirs(current_dir_info[0][0], current_block[0]) < 0:
2347
# filesystem data refers to paths not covered by the dirblock.
2348
# this has two possibilities:
2349
# A) it is versioned but empty, so there is no block for it
2350
# B) it is not versioned.
2352
# if (A) then we need to recurse into it to check for
2353
# new unknown files or directories.
2354
# if (B) then we should ignore it, because we don't
2355
# recurse into unknown directories.
2357
while path_index < len(current_dir_info[1]):
2358
current_path_info = current_dir_info[1][path_index]
2359
if want_unversioned:
2360
if current_path_info[2] == 'directory':
2361
if self.target._directory_is_tree_reference(
2362
current_path_info[0].decode('utf8')):
2363
current_path_info = current_path_info[:2] + \
2364
('tree-reference',) + current_path_info[3:]
2365
new_executable = bool(
2366
stat.S_ISREG(current_path_info[3].st_mode)
2367
and stat.S_IEXEC & current_path_info[3].st_mode)
2369
(None, utf8_decode(current_path_info[0])[0]),
2373
(None, utf8_decode(current_path_info[1])[0]),
2374
(None, current_path_info[2]),
2375
(None, new_executable))
2376
# dont descend into this unversioned path if it is
2378
if current_path_info[2] in ('directory',
2380
del current_dir_info[1][path_index]
2384
# This dir info has been handled, go to the next
2386
current_dir_info = dir_iterator.next()
2387
except StopIteration:
2388
current_dir_info = None
2390
# We have a dirblock entry for this location, but there
2391
# is no filesystem path for this. This is most likely
2392
# because a directory was removed from the disk.
2393
# We don't have to report the missing directory,
2394
# because that should have already been handled, but we
2395
# need to handle all of the files that are contained
2397
for current_entry in current_block[1]:
2398
# entry referring to file not present on disk.
2399
# advance the entry only, after processing.
2400
result = _process_entry(current_entry, None)
2401
if result is not None:
2402
if result is not uninteresting:
2405
if (block_index < len(state._dirblocks) and
2406
osutils.is_inside(current_root,
2407
state._dirblocks[block_index][0])):
2408
current_block = state._dirblocks[block_index]
2410
current_block = None
2413
if current_block and entry_index < len(current_block[1]):
2414
current_entry = current_block[1][entry_index]
2416
current_entry = None
2417
advance_entry = True
2419
if current_dir_info and path_index < len(current_dir_info[1]):
2420
current_path_info = current_dir_info[1][path_index]
2421
if current_path_info[2] == 'directory':
2422
if self.target._directory_is_tree_reference(
2423
current_path_info[0].decode('utf8')):
2424
current_path_info = current_path_info[:2] + \
2425
('tree-reference',) + current_path_info[3:]
2427
current_path_info = None
2429
path_handled = False
2430
while (current_entry is not None or
2431
current_path_info is not None):
2432
if current_entry is None:
2433
# the check for path_handled when the path is adnvaced
2434
# will yield this path if needed.
2436
elif current_path_info is None:
2437
# no path is fine: the per entry code will handle it.
2438
result = _process_entry(current_entry, current_path_info)
2439
if result is not None:
2440
if result is not uninteresting:
2442
elif (current_entry[0][1] != current_path_info[1]
2443
or current_entry[1][target_index][0] in 'ar'):
2444
# The current path on disk doesn't match the dirblock
2445
# record. Either the dirblock is marked as absent, or
2446
# the file on disk is not present at all in the
2447
# dirblock. Either way, report about the dirblock
2448
# entry, and let other code handle the filesystem one.
2450
# Compare the basename for these files to determine
2452
if current_path_info[1] < current_entry[0][1]:
2453
# extra file on disk: pass for now, but only
2454
# increment the path, not the entry
2455
advance_entry = False
2457
# entry referring to file not present on disk.
2458
# advance the entry only, after processing.
2459
result = _process_entry(current_entry, None)
2460
if result is not None:
2461
if result is not uninteresting:
2463
advance_path = False
2465
result = _process_entry(current_entry, current_path_info)
2466
if result is not None:
2468
if result is not uninteresting:
2470
if advance_entry and current_entry is not None:
2472
if entry_index < len(current_block[1]):
2473
current_entry = current_block[1][entry_index]
2475
current_entry = None
2477
advance_entry = True # reset the advance flaga
2478
if advance_path and current_path_info is not None:
2479
if not path_handled:
2480
# unversioned in all regards
2481
if want_unversioned:
2482
new_executable = bool(
2483
stat.S_ISREG(current_path_info[3].st_mode)
2484
and stat.S_IEXEC & current_path_info[3].st_mode)
2486
relpath_unicode = utf8_decode(current_path_info[0])[0]
2487
except UnicodeDecodeError:
2488
raise errors.BadFilenameEncoding(
2489
current_path_info[0], osutils._fs_enc)
2491
(None, relpath_unicode),
2495
(None, utf8_decode(current_path_info[1])[0]),
2496
(None, current_path_info[2]),
2497
(None, new_executable))
2498
# dont descend into this unversioned path if it is
2500
if current_path_info[2] in ('directory'):
2501
del current_dir_info[1][path_index]
2503
# dont descend the disk iterator into any tree
2505
if current_path_info[2] == 'tree-reference':
2506
del current_dir_info[1][path_index]
2509
if path_index < len(current_dir_info[1]):
2510
current_path_info = current_dir_info[1][path_index]
2511
if current_path_info[2] == 'directory':
2512
if self.target._directory_is_tree_reference(
2513
current_path_info[0].decode('utf8')):
2514
current_path_info = current_path_info[:2] + \
2515
('tree-reference',) + current_path_info[3:]
2517
current_path_info = None
2518
path_handled = False
2520
advance_path = True # reset the advance flagg.
2521
if current_block is not None:
2523
if (block_index < len(state._dirblocks) and
2524
osutils.is_inside(current_root, state._dirblocks[block_index][0])):
2525
current_block = state._dirblocks[block_index]
2527
current_block = None
2528
if current_dir_info is not None:
2530
current_dir_info = dir_iterator.next()
2531
except StopIteration:
2532
current_dir_info = None
1931
iter_changes = self.target._iter_changes(include_unchanged,
1932
use_filesystem_for_exec, search_specific_files, state,
1933
source_index, target_index, want_unversioned, self.target)
1934
return iter_changes.iter_changes()
2535
1937
def is_compatible(source, target):