13
13
# You should have received a copy of the GNU General Public License
14
14
# along with this program; if not, write to the Free Software
15
15
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17
"""Represent and apply a changeset.
19
Conflicts in applying a changeset are represented as exceptions.
21
This only handles the in-memory objects representing changesets, which are
22
primarily used by the merge code.
28
from tempfile import mkdtemp
29
from shutil import rmtree
30
from itertools import izip
32
from bzrlib.trace import mutter, warning
33
from bzrlib.osutils import rename, sha_file
21
Represent and apply a changeset
36
23
__docformat__ = "restructuredtext"
40
class OldFailedTreeOp(Exception):
42
Exception.__init__(self, "bzr-tree-change contains files from a"
43
" previous failed merge operation.")
44
28
def invert_dict(dict):
46
30
for (key,value) in dict.iteritems():
47
31
newdict[value] = key
51
class ChangeExecFlag(object):
35
class PatchApply(object):
36
"""Patch application as a kind of content change"""
37
def __init__(self, contents):
40
:param contents: The text of the patch to apply
41
:type contents: str"""
42
self.contents = contents
44
def __eq__(self, other):
45
if not isinstance(other, PatchApply):
47
elif self.contents != other.contents:
52
def __ne__(self, other):
53
return not (self == other)
55
def apply(self, filename, conflict_handler, reverse=False):
56
"""Applies the patch to the specified file.
58
:param filename: the file to apply the patch to
60
:param reverse: If true, apply the patch in reverse
63
input_name = filename+".orig"
65
os.rename(filename, input_name)
67
if e.errno != errno.ENOENT:
69
if conflict_handler.patch_target_missing(filename, self.contents)\
72
os.rename(filename, input_name)
75
status = patch.patch(self.contents, input_name, filename,
77
os.chmod(filename, os.stat(input_name).st_mode)
81
conflict_handler.failed_hunks(filename)
84
class ChangeUnixPermissions(object):
52
85
"""This is two-way change, suitable for file modification, creation,
54
def __init__(self, old_exec_flag, new_exec_flag):
55
self.old_exec_flag = old_exec_flag
56
self.new_exec_flag = new_exec_flag
87
def __init__(self, old_mode, new_mode):
88
self.old_mode = old_mode
89
self.new_mode = new_mode
58
91
def apply(self, filename, conflict_handler, reverse=False):
60
from_exec_flag = self.old_exec_flag
61
to_exec_flag = self.new_exec_flag
93
from_mode = self.old_mode
94
to_mode = self.new_mode
63
from_exec_flag = self.new_exec_flag
64
to_exec_flag = self.old_exec_flag
96
from_mode = self.new_mode
97
to_mode = self.old_mode
66
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
99
current_mode = os.stat(filename).st_mode &0777
67
100
except OSError, e:
68
101
if e.errno == errno.ENOENT:
69
if conflict_handler.missing_for_exec_flag(filename) == "skip":
102
if conflict_handler.missing_for_chmod(filename) == "skip":
72
current_exec_flag = from_exec_flag
105
current_mode = from_mode
74
if from_exec_flag is not None and current_exec_flag != from_exec_flag:
75
if conflict_handler.wrong_old_exec_flag(filename,
76
from_exec_flag, current_exec_flag) != "continue":
107
if from_mode is not None and current_mode != from_mode:
108
if conflict_handler.wrong_old_perms(filename, from_mode,
109
current_mode) != "continue":
79
if to_exec_flag is not None:
80
current_mode = os.stat(filename).st_mode
84
to_mode = current_mode | (0100 & ~umask)
85
# Enable x-bit for others only if they can read it.
86
if current_mode & 0004:
87
to_mode |= 0001 & ~umask
88
if current_mode & 0040:
89
to_mode |= 0010 & ~umask
91
to_mode = current_mode & ~0111
112
if to_mode is not None:
93
114
os.chmod(filename, to_mode)
94
115
except IOError, e:
95
116
if e.errno == errno.ENOENT:
96
conflict_handler.missing_for_exec_flag(filename)
117
conflict_handler.missing_for_chmod(filename)
98
119
def __eq__(self, other):
99
return (isinstance(other, ChangeExecFlag) and
100
self.old_exec_flag == other.old_exec_flag and
101
self.new_exec_flag == other.new_exec_flag)
120
if not isinstance(other, ChangeUnixPermissions):
122
elif self.old_mode != other.old_mode:
124
elif self.new_mode != other.new_mode:
103
129
def __ne__(self, other):
104
130
return not (self == other)
107
132
def dir_create(filename, conflict_handler, reverse):
108
133
"""Creates the directory, or deletes it if reverse is true. Intended to be
109
134
used with ReplaceContents.
239
class TreeFileCreate(object):
240
"""Create or delete a file (for use with ReplaceContents)"""
241
def __init__(self, tree, file_id):
244
:param contents: The contents of the file to write
248
self.file_id = file_id
251
return "TreeFileCreate(%s)" % self.file_id
253
def __eq__(self, other):
254
if not isinstance(other, TreeFileCreate):
256
return self.tree.get_file_sha1(self.file_id) == \
257
other.tree.get_file_sha1(other.file_id)
259
def __ne__(self, other):
260
return not (self == other)
262
def write_file(self, filename):
263
outfile = file(filename, "wb")
264
for line in self.tree.get_file(self.file_id):
267
def same_text(self, filename):
268
in_file = file(filename, "rb")
269
return sha_file(in_file) == self.tree.get_file_sha1(self.file_id)
271
def __call__(self, filename, conflict_handler, reverse):
272
"""Create or delete a file
274
:param filename: The name of the file to create
276
:param reverse: Delete the file instead of creating it
281
self.write_file(filename)
283
if e.errno == errno.ENOENT:
284
if conflict_handler.missing_parent(filename)=="continue":
285
self.write_file(filename)
291
if not self.same_text(filename):
292
direction = conflict_handler.wrong_old_contents(filename,
293
self.tree.get_file(self.file_id).read())
294
if direction != "continue":
298
if e.errno != errno.ENOENT:
300
if conflict_handler.missing_for_rm(filename, undo) == "skip":
305
263
def reversed(sequence):
306
264
max = len(sequence) - 1
307
265
for i in range(len(sequence)):
413
365
class Diff3Merge(object):
414
history_based = False
415
def __init__(self, file_id, base, other):
416
self.file_id = file_id
420
def is_creation(self):
423
def is_deletion(self):
366
def __init__(self, base_file, other_file):
367
self.base_file = base_file
368
self.other_file = other_file
426
370
def __eq__(self, other):
427
371
if not isinstance(other, Diff3Merge):
429
return (self.base == other.base and
430
self.other == other.other and self.file_id == other.file_id)
373
return (self.base_file == other.base_file and
374
self.other_file == other.other_file)
432
376
def __ne__(self, other):
433
377
return not (self == other)
435
def dump_file(self, temp_dir, name, tree):
436
out_path = os.path.join(temp_dir, name)
437
out_file = file(out_path, "wb")
438
in_file = tree.get_file(self.file_id)
443
379
def apply(self, filename, conflict_handler, reverse=False):
445
temp_dir = mkdtemp(prefix="bzr-")
447
new_file = filename+".new"
448
base_file = self.dump_file(temp_dir, "base", self.base)
449
other_file = self.dump_file(temp_dir, "other", self.other)
456
status = bzrlib.patch.diff3(new_file, filename, base, other)
458
os.chmod(new_file, os.stat(filename).st_mode)
459
rename(new_file, filename)
463
def get_lines(filename):
464
my_file = file(filename, "rb")
465
lines = my_file.readlines()
468
base_lines = get_lines(base)
469
other_lines = get_lines(other)
470
conflict_handler.merge_conflict(new_file, filename, base_lines,
380
new_file = filename+".new"
382
base = self.base_file
383
other = self.other_file
385
base = self.other_file
386
other = self.base_file
387
status = patch.diff3(new_file, filename, base, other)
389
os.chmod(new_file, os.stat(filename).st_mode)
390
os.rename(new_file, filename)
394
conflict_handler.merge_conflict(new_file, filename, base, other)
927
842
:return: a mapping of id to temporary name
928
843
:rtype: Dictionary
845
temp_dir = os.path.join(dir, "temp")
931
847
for i in range(len(source_entries)):
932
848
entry = source_entries[i]
933
849
if entry.is_deletion(reverse):
934
850
path = os.path.join(dir, inventory[entry.id])
935
851
entry.apply(path, conflict_handler, reverse)
936
temp_name[entry.id] = None
938
elif entry.needs_rename():
939
to_name = os.path.join(temp_dir, str(i))
854
to_name = temp_dir+"/"+str(i)
940
855
src_path = inventory.get(entry.id)
941
856
if src_path is not None:
942
857
src_path = os.path.join(dir, src_path)
944
rename(src_path, to_name)
859
os.rename(src_path, to_name)
945
860
temp_name[entry.id] = to_name
946
861
except OSError, e:
947
862
if e.errno != errno.ENOENT:
949
if conflict_handler.missing_for_rename(src_path, to_name) \
864
if conflict_handler.missing_for_rename(src_path) == "skip":
956
def rename_to_new_create(changed_inventory, target_entries, inventory,
957
changeset, dir, conflict_handler, reverse):
870
def rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
871
conflict_handler, reverse):
958
872
"""Rename entries with temp names to their final names, create new files.
960
:param changed_inventory: A mapping of id to temporary name
961
:type changed_inventory: Dictionary
874
:param temp_name: A mapping of id to temporary name
875
:type temp_name: Dictionary
962
876
:param target_entries: The entries to apply changes to
963
877
:type target_entries: List of `ChangesetEntry`
964
878
:param changeset: The changeset to apply
969
883
:type reverse: bool
971
885
for entry in target_entries:
972
new_tree_path = entry.get_new_path(inventory, changeset, reverse)
973
if new_tree_path is None:
886
new_path = entry.get_new_path(inventory, changeset, reverse)
975
new_path = os.path.join(dir, new_tree_path)
976
old_path = changed_inventory.get(entry.id)
977
if bzrlib.osutils.lexists(new_path):
889
new_path = os.path.join(dir, new_path)
890
old_path = temp_name.get(entry.id)
891
if os.path.exists(new_path):
978
892
if conflict_handler.target_exists(entry, new_path, old_path) == \
981
895
if entry.is_creation(reverse):
982
896
entry.apply(new_path, conflict_handler, reverse)
983
changed_inventory[entry.id] = new_tree_path
984
elif entry.needs_rename():
985
898
if old_path is None:
988
rename(old_path, new_path)
989
changed_inventory[entry.id] = new_tree_path
901
os.rename(old_path, new_path)
990
902
except OSError, e:
991
903
raise Exception ("%s is missing" % new_path)
1026
938
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
1027
939
self.this_path = this_path
941
class MergePermissionConflict(Exception):
942
def __init__(self, this_path, base_path, other_path):
943
this_perms = os.stat(this_path).st_mode & 0755
944
base_perms = os.stat(base_path).st_mode & 0755
945
other_perms = os.stat(other_path).st_mode & 0755
946
msg = """Conflicting permission for %s
950
""" % (this_path, this_perms, base_perms, other_perms)
951
self.this_path = this_path
952
self.base_path = base_path
953
self.other_path = other_path
954
Exception.__init__(self, msg)
1029
956
class WrongOldContents(Exception):
1030
957
def __init__(self, filename):
1031
958
msg = "Contents mismatch deleting %s" % filename
1032
959
self.filename = filename
1033
960
Exception.__init__(self, msg)
1035
class WrongOldExecFlag(Exception):
1036
def __init__(self, filename, old_exec_flag, new_exec_flag):
1037
msg = "Executable flag missmatch on %s:\n" \
1038
"Expected %s, got %s." % (filename, old_exec_flag, new_exec_flag)
962
class WrongOldPermissions(Exception):
963
def __init__(self, filename, old_perms, new_perms):
964
msg = "Permission missmatch on %s:\n" \
965
"Expected 0%o, got 0%o." % (filename, old_perms, new_perms)
1039
966
self.filename = filename
1040
967
Exception.__init__(self, msg)
1076
1003
class MissingForRename(Exception):
1077
def __init__(self, filename, to_path):
1078
msg = "Attempt to move missing path %s to %s" % (filename, to_path)
1004
def __init__(self, filename):
1005
msg = "Attempt to move missing path %s" % (filename)
1079
1006
Exception.__init__(self, msg)
1080
1007
self.filename = filename
1082
class NewContentsConflict(Exception):
1083
def __init__(self, filename):
1084
msg = "Conflicting contents for new file %s" % (filename)
1085
Exception.__init__(self, msg)
1087
class WeaveMergeConflict(Exception):
1088
def __init__(self, filename):
1089
msg = "Conflicting contents for file %s" % (filename)
1090
Exception.__init__(self, msg)
1092
class ThreewayContentsConflict(Exception):
1093
def __init__(self, filename):
1094
msg = "Conflicting contents for file %s" % (filename)
1095
Exception.__init__(self, msg)
1098
class MissingForMerge(Exception):
1099
def __init__(self, filename):
1100
msg = "The file %s was modified, but does not exist in this tree"\
1102
Exception.__init__(self, msg)
1105
1009
class ExceptionConflictHandler(object):
1106
"""Default handler for merge exceptions.
1108
This throws an error on any kind of conflict. Conflict handlers can
1109
descend from this class if they have a better way to handle some or
1110
all types of conflict.
1010
def __init__(self, dir):
1112
1013
def missing_parent(self, pathname):
1113
1014
parent = os.path.dirname(pathname)
1114
1015
raise Exception("Parent directory missing for %s" % pathname)
1125
1026
def rename_conflict(self, id, this_name, base_name, other_name):
1126
1027
raise RenameConflict(id, this_name, base_name, other_name)
1128
def move_conflict(self, id, this_dir, base_dir, other_dir):
1029
def move_conflict(self, id, inventory):
1030
this_dir = inventory.this.get_dir(id)
1031
base_dir = inventory.base.get_dir(id)
1032
other_dir = inventory.other.get_dir(id)
1129
1033
raise MoveConflict(id, this_dir, base_dir, other_dir)
1131
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
1035
def merge_conflict(self, new_file, this_path, base_path, other_path):
1132
1036
os.unlink(new_file)
1133
1037
raise MergeConflict(this_path)
1039
def permission_conflict(self, this_path, base_path, other_path):
1040
raise MergePermissionConflict(this_path, base_path, other_path)
1135
1042
def wrong_old_contents(self, filename, expected_contents):
1136
1043
raise WrongOldContents(filename)
1138
1045
def rem_contents_conflict(self, filename, this_contents, base_contents):
1139
1046
raise RemoveContentsConflict(filename)
1141
def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
1142
raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
1048
def wrong_old_perms(self, filename, old_perms, new_perms):
1049
raise WrongOldPermissions(filename, old_perms, new_perms)
1144
1051
def rmdir_non_empty(self, filename):
1145
1052
raise DeletingNonEmptyDirectory(filename)
1150
1057
def patch_target_missing(self, filename, contents):
1151
1058
raise PatchTargetMissing(filename)
1153
def missing_for_exec_flag(self, filename):
1154
raise MissingForExecFlag(filename)
1060
def missing_for_chmod(self, filename):
1061
raise MissingPermsFile(filename)
1156
1063
def missing_for_rm(self, filename, change):
1157
1064
raise MissingForRm(filename)
1159
def missing_for_rename(self, filename, to_path):
1160
raise MissingForRename(filename, to_path)
1162
def missing_for_merge(self, file_id, other_path):
1163
raise MissingForMerge(other_path)
1165
def new_contents_conflict(self, filename, other_contents):
1166
raise NewContentsConflict(filename)
1168
def weave_merge_conflict(self, filename, weave, other_i, out_file):
1169
raise WeaveMergeConflict(filename)
1171
def threeway_contents_conflict(self, filename, this_contents,
1172
base_contents, other_contents):
1173
raise ThreewayContentsConflict(filename)
1066
def missing_for_rename(self, filename):
1067
raise MissingForRename(filename)
1178
1072
def apply_changeset(changeset, inventory, dir, conflict_handler=None,
1223
1102
(source_entries, target_entries) = get_rename_entries(changeset, inventory,
1226
changed_inventory = rename_to_temp_delete(source_entries, inventory, dir,
1227
temp_dir, conflict_handler,
1105
temp_name = rename_to_temp_delete(source_entries, inventory, dir,
1106
conflict_handler, reverse)
1230
rename_to_new_create(changed_inventory, target_entries, inventory,
1231
changeset, dir, conflict_handler, reverse)
1108
rename_to_new_create(temp_name, target_entries, inventory, changeset, dir,
1109
conflict_handler, reverse)
1232
1110
os.rmdir(temp_dir)
1233
return changed_inventory
1111
r_inventory = invert_dict(inventory)
1112
new_entries, removed_entries = get_inventory_change(inventory,
1113
r_inventory, changeset, reverse)
1115
for path, file_id in new_entries.iteritems():
1116
new_inventory[file_id] = path
1117
for file_id in removed_entries:
1118
new_inventory[file_id] = None
1119
return new_inventory
1236
1122
def apply_changeset_tree(cset, tree, reverse=False):
1237
1123
r_inventory = {}
1238
1124
for entry in tree.source_inventory().itervalues():
1239
1125
inventory[entry.id] = entry.path
1240
new_inventory = apply_changeset(cset, r_inventory, tree.basedir,
1126
new_inventory = apply_changeset(cset, r_inventory, tree.root,
1241
1127
reverse=reverse)
1242
1128
new_entries, remove_entries = \
1243
1129
get_inventory_change(inventory, new_inventory, cset, reverse)
1394
class UnsupportedFiletype(Exception):
1395
def __init__(self, kind, full_path):
1396
msg = "The file \"%s\" is a %s, which is not a supported filetype." \
1284
class UnsuppportedFiletype(Exception):
1285
def __init__(self, full_path, stat_result):
1286
msg = "The file \"%s\" is not a supported filetype." % full_path
1398
1287
Exception.__init__(self, msg)
1399
1288
self.full_path = full_path
1402
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1403
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1289
self.stat_result = stat_result
1291
def generate_changeset(tree_a, tree_b, inventory_a=None, inventory_b=None):
1292
return ChangesetGenerator(tree_a, tree_b, inventory_a, inventory_b)()
1406
1294
class ChangesetGenerator(object):
1407
def __init__(self, tree_a, tree_b, interesting_ids=None):
1295
def __init__(self, tree_a, tree_b, inventory_a=None, inventory_b=None):
1408
1296
object.__init__(self)
1409
1297
self.tree_a = tree_a
1410
1298
self.tree_b = tree_b
1411
self._interesting_ids = interesting_ids
1299
if inventory_a is not None:
1300
self.inventory_a = inventory_a
1302
self.inventory_a = tree_a.inventory()
1303
if inventory_b is not None:
1304
self.inventory_b = inventory_b
1306
self.inventory_b = tree_b.inventory()
1307
self.r_inventory_a = self.reverse_inventory(self.inventory_a)
1308
self.r_inventory_b = self.reverse_inventory(self.inventory_b)
1413
def iter_both_tree_ids(self):
1414
for file_id in self.tree_a:
1416
for file_id in self.tree_b:
1417
if file_id not in self.tree_a:
1310
def reverse_inventory(self, inventory):
1312
for entry in inventory.itervalues():
1313
if entry.id is None:
1315
r_inventory[entry.id] = entry
1420
1318
def __call__(self):
1421
1319
cset = Changeset()
1422
for file_id in self.iter_both_tree_ids():
1423
cs_entry = self.make_entry(file_id)
1320
for entry in self.inventory_a.itervalues():
1321
if entry.id is None:
1323
cs_entry = self.make_entry(entry.id)
1424
1324
if cs_entry is not None and not cs_entry.is_boring():
1425
1325
cset.add_entry(cs_entry)
1327
for entry in self.inventory_b.itervalues():
1328
if entry.id is None:
1330
if not self.r_inventory_a.has_key(entry.id):
1331
cs_entry = self.make_entry(entry.id)
1332
if cs_entry is not None and not cs_entry.is_boring():
1333
cset.add_entry(cs_entry)
1427
1334
for entry in list(cset.entries.itervalues()):
1428
1335
if entry.parent != entry.new_parent:
1429
1336
if not cset.entries.has_key(entry.parent) and\
1437
1344
cset.add_entry(parent_entry)
1440
def iter_inventory(self, tree):
1441
for file_id in tree:
1442
yield self.get_entry(file_id, tree)
1444
def get_entry(self, file_id, tree):
1445
if not tree.has_or_had_id(file_id):
1447
return tree.inventory[file_id]
1449
def get_entry_parent(self, entry):
1452
return entry.parent_id
1454
def get_path(self, file_id, tree):
1455
if not tree.has_or_had_id(file_id):
1457
path = tree.id2path(file_id)
1463
def make_basic_entry(self, file_id, only_interesting):
1464
entry_a = self.get_entry(file_id, self.tree_a)
1465
entry_b = self.get_entry(file_id, self.tree_b)
1347
def get_entry_parent(self, entry, inventory):
1350
if entry.path == "./.":
1352
dirname = os.path.dirname(entry.path)
1355
parent = inventory[dirname]
1358
def get_paths(self, entry, tree):
1361
full_path = tree.readonly_path(entry.id)
1362
if entry.path == ".":
1363
return ("", full_path)
1364
return (entry.path, full_path)
1366
def make_basic_entry(self, id, only_interesting):
1367
entry_a = self.r_inventory_a.get(id)
1368
entry_b = self.r_inventory_b.get(id)
1466
1369
if only_interesting and not self.is_interesting(entry_a, entry_b):
1468
parent = self.get_entry_parent(entry_a)
1469
path = self.get_path(file_id, self.tree_a)
1470
cs_entry = ChangesetEntry(file_id, parent, path)
1471
new_parent = self.get_entry_parent(entry_b)
1473
new_path = self.get_path(file_id, self.tree_b)
1370
return (None, None, None)
1371
parent = self.get_entry_parent(entry_a, self.inventory_a)
1372
(path, full_path_a) = self.get_paths(entry_a, self.tree_a)
1373
cs_entry = ChangesetEntry(id, parent, path)
1374
new_parent = self.get_entry_parent(entry_b, self.inventory_b)
1377
(new_path, full_path_b) = self.get_paths(entry_b, self.tree_b)
1475
1379
cs_entry.new_path = new_path
1476
1380
cs_entry.new_parent = new_parent
1381
return (cs_entry, full_path_a, full_path_b)
1479
1383
def is_interesting(self, entry_a, entry_b):
1480
if self._interesting_ids is None:
1482
1384
if entry_a is not None:
1483
file_id = entry_a.file_id
1484
elif entry_b is not None:
1485
file_id = entry_b.file_id
1488
return file_id in self._interesting_ids
1385
if entry_a.interesting:
1387
if entry_b is not None:
1388
if entry_b.interesting:
1490
1392
def make_boring_entry(self, id):
1491
cs_entry = self.make_basic_entry(id, only_interesting=False)
1393
(cs_entry, full_path_a, full_path_b) = \
1394
self.make_basic_entry(id, only_interesting=False)
1492
1395
if cs_entry.is_creation_or_deletion():
1493
1396
return self.make_entry(id, only_interesting=False)
1498
1401
def make_entry(self, id, only_interesting=True):
1499
cs_entry = self.make_basic_entry(id, only_interesting)
1402
(cs_entry, full_path_a, full_path_b) = \
1403
self.make_basic_entry(id, only_interesting)
1501
1405
if cs_entry is None:
1504
cs_entry.metadata_change = self.make_exec_flag_change(id)
1506
if id in self.tree_a and id in self.tree_b:
1507
a_sha1 = self.tree_a.get_file_sha1(id)
1508
b_sha1 = self.tree_b.get_file_sha1(id)
1509
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1512
cs_entry.contents_change = self.make_contents_change(id)
1408
stat_a = self.lstat(full_path_a)
1409
stat_b = self.lstat(full_path_b)
1411
cs_entry.new_parent = None
1412
cs_entry.new_path = None
1414
cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
1415
cs_entry.contents_change = self.make_contents_change(full_path_a,
1513
1419
return cs_entry
1515
def make_exec_flag_change(self, file_id):
1516
exec_flag_a = exec_flag_b = None
1517
if file_id in self.tree_a and self.tree_a.kind(file_id) == "file":
1518
exec_flag_a = self.tree_a.is_executable(file_id)
1520
if file_id in self.tree_b and self.tree_b.kind(file_id) == "file":
1521
exec_flag_b = self.tree_b.is_executable(file_id)
1523
if exec_flag_a == exec_flag_b:
1525
return ChangeExecFlag(exec_flag_a, exec_flag_b)
1527
def make_contents_change(self, file_id):
1528
a_contents = get_contents(self.tree_a, file_id)
1529
b_contents = get_contents(self.tree_b, file_id)
1421
def make_mode_change(self, stat_a, stat_b):
1423
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1424
mode_a = stat_a.st_mode & 0777
1426
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1427
mode_b = stat_b.st_mode & 0777
1428
if mode_a == mode_b:
1430
return ChangeUnixPermissions(mode_a, mode_b)
1432
def make_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1433
if stat_a is None and stat_b is None:
1435
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1436
stat.S_ISDIR(stat_b.st_mode):
1438
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1439
stat.S_ISREG(stat_b.st_mode):
1440
if stat_a.st_ino == stat_b.st_ino and \
1441
stat_a.st_dev == stat_b.st_dev:
1443
if file(full_path_a, "rb").read() == \
1444
file(full_path_b, "rb").read():
1447
patch_contents = patch.diff(full_path_a,
1448
file(full_path_b, "rb").read())
1449
if patch_contents is None:
1451
return PatchApply(patch_contents)
1453
a_contents = self.get_contents(stat_a, full_path_a)
1454
b_contents = self.get_contents(stat_b, full_path_b)
1530
1455
if a_contents == b_contents:
1532
1457
return ReplaceContents(a_contents, b_contents)
1459
def get_contents(self, stat_result, full_path):
1460
if stat_result is None:
1462
elif stat.S_ISREG(stat_result.st_mode):
1463
return FileCreate(file(full_path, "rb").read())
1464
elif stat.S_ISDIR(stat_result.st_mode):
1466
elif stat.S_ISLNK(stat_result.st_mode):
1467
return SymlinkCreate(os.readlink(full_path))
1469
raise UnsupportedFiletype(full_path, stat_result)
1535
def get_contents(tree, file_id):
1536
"""Return the appropriate contents to create a copy of file_id from tree"""
1537
if file_id not in tree:
1539
kind = tree.kind(file_id)
1541
return TreeFileCreate(tree, file_id)
1542
elif kind in ("directory", "root_directory"):
1544
elif kind == "symlink":
1545
return SymlinkCreate(tree.get_symlink_target(file_id))
1547
raise UnsupportedFiletype(kind, tree.id2path(file_id))
1471
def lstat(self, full_path):
1473
if full_path is not None:
1475
stat_result = os.lstat(full_path)
1477
if e.errno != errno.ENOENT:
1550
1482
def full_path(entry, tree):
1551
return os.path.join(tree.basedir, entry.path)
1483
return os.path.join(tree.root, entry.path)
1553
1485
def new_delete_entry(entry, tree, inventory, delete):
1554
1486
if entry.path == "":