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
35
from bzrlib.errors import BzrCheckError
21
Represent and apply a changeset
37
23
__docformat__ = "restructuredtext"
48
34
newdict[value] = key
52
class ChangeExecFlag(object):
38
class PatchApply(object):
39
"""Patch application as a kind of content change"""
40
def __init__(self, contents):
43
:param contents: The text of the patch to apply
44
:type contents: str"""
45
self.contents = contents
47
def __eq__(self, other):
48
if not isinstance(other, PatchApply):
50
elif self.contents != other.contents:
55
def __ne__(self, other):
56
return not (self == other)
58
def apply(self, filename, conflict_handler, reverse=False):
59
"""Applies the patch to the specified file.
61
:param filename: the file to apply the patch to
63
:param reverse: If true, apply the patch in reverse
66
input_name = filename+".orig"
68
os.rename(filename, input_name)
70
if e.errno != errno.ENOENT:
72
if conflict_handler.patch_target_missing(filename, self.contents)\
75
os.rename(filename, input_name)
78
status = patch.patch(self.contents, input_name, filename,
80
os.chmod(filename, os.stat(input_name).st_mode)
84
conflict_handler.failed_hunks(filename)
87
class ChangeUnixPermissions(object):
53
88
"""This is two-way change, suitable for file modification, creation,
55
def __init__(self, old_exec_flag, new_exec_flag):
56
self.old_exec_flag = old_exec_flag
57
self.new_exec_flag = new_exec_flag
90
def __init__(self, old_mode, new_mode):
91
self.old_mode = old_mode
92
self.new_mode = new_mode
59
94
def apply(self, filename, conflict_handler, reverse=False):
61
from_exec_flag = self.old_exec_flag
62
to_exec_flag = self.new_exec_flag
96
from_mode = self.old_mode
97
to_mode = self.new_mode
64
from_exec_flag = self.new_exec_flag
65
to_exec_flag = self.old_exec_flag
99
from_mode = self.new_mode
100
to_mode = self.old_mode
67
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
102
current_mode = os.stat(filename).st_mode &0777
68
103
except OSError, e:
69
104
if e.errno == errno.ENOENT:
70
if conflict_handler.missing_for_exec_flag(filename) == "skip":
105
if conflict_handler.missing_for_chmod(filename) == "skip":
73
current_exec_flag = from_exec_flag
108
current_mode = from_mode
75
if from_exec_flag is not None and current_exec_flag != from_exec_flag:
76
if conflict_handler.wrong_old_exec_flag(filename,
77
from_exec_flag, current_exec_flag) != "continue":
110
if from_mode is not None and current_mode != from_mode:
111
if conflict_handler.wrong_old_perms(filename, from_mode,
112
current_mode) != "continue":
80
if to_exec_flag is not None:
81
current_mode = os.stat(filename).st_mode
85
to_mode = current_mode | (0100 & ~umask)
86
# Enable x-bit for others only if they can read it.
87
if current_mode & 0004:
88
to_mode |= 0001 & ~umask
89
if current_mode & 0040:
90
to_mode |= 0010 & ~umask
92
to_mode = current_mode & ~0111
115
if to_mode is not None:
94
117
os.chmod(filename, to_mode)
95
118
except IOError, e:
96
119
if e.errno == errno.ENOENT:
97
conflict_handler.missing_for_exec_flag(filename)
120
conflict_handler.missing_for_chmod(filename)
99
122
def __eq__(self, other):
100
return (isinstance(other, ChangeExecFlag) and
101
self.old_exec_flag == other.old_exec_flag and
102
self.new_exec_flag == other.new_exec_flag)
123
if not isinstance(other, ChangeUnixPermissions):
125
elif self.old_mode != other.old_mode:
127
elif self.new_mode != other.new_mode:
104
132
def __ne__(self, other):
105
133
return not (self == other)
108
135
def dir_create(filename, conflict_handler, reverse):
109
136
"""Creates the directory, or deletes it if reverse is true. Intended to be
110
137
used with ReplaceContents.
240
class TreeFileCreate(object):
241
"""Create or delete a file (for use with ReplaceContents)"""
242
def __init__(self, tree, file_id):
245
:param contents: The contents of the file to write
249
self.file_id = file_id
252
return "TreeFileCreate(%s)" % self.file_id
254
def __eq__(self, other):
255
if not isinstance(other, TreeFileCreate):
257
return self.tree.get_file_sha1(self.file_id) == \
258
other.tree.get_file_sha1(other.file_id)
260
def __ne__(self, other):
261
return not (self == other)
263
def write_file(self, filename):
264
outfile = file(filename, "wb")
265
for line in self.tree.get_file(self.file_id):
268
def same_text(self, filename):
269
in_file = file(filename, "rb")
270
return sha_file(in_file) == self.tree.get_file_sha1(self.file_id)
272
def __call__(self, filename, conflict_handler, reverse):
273
"""Create or delete a file
275
:param filename: The name of the file to create
277
:param reverse: Delete the file instead of creating it
282
self.write_file(filename)
284
if e.errno == errno.ENOENT:
285
if conflict_handler.missing_parent(filename)=="continue":
286
self.write_file(filename)
292
if not self.same_text(filename):
293
direction = conflict_handler.wrong_old_contents(filename,
294
self.tree.get_file(self.file_id).read())
295
if direction != "continue":
299
if e.errno != errno.ENOENT:
301
if conflict_handler.missing_for_rm(filename, undo) == "skip":
306
266
def reversed(sequence):
307
267
max = len(sequence) - 1
308
268
for i in range(len(sequence)):
414
368
class Diff3Merge(object):
415
history_based = False
416
def __init__(self, file_id, base, other):
417
self.file_id = file_id
421
def is_creation(self):
424
def is_deletion(self):
369
def __init__(self, base_file, other_file):
370
self.base_file = base_file
371
self.other_file = other_file
427
373
def __eq__(self, other):
428
374
if not isinstance(other, Diff3Merge):
430
return (self.base == other.base and
431
self.other == other.other and self.file_id == other.file_id)
376
return (self.base_file == other.base_file and
377
self.other_file == other.other_file)
433
379
def __ne__(self, other):
434
380
return not (self == other)
436
def dump_file(self, temp_dir, name, tree):
437
out_path = os.path.join(temp_dir, name)
438
out_file = file(out_path, "wb")
439
in_file = tree.get_file(self.file_id)
444
382
def apply(self, filename, conflict_handler, reverse=False):
446
temp_dir = mkdtemp(prefix="bzr-")
448
new_file = filename+".new"
449
base_file = self.dump_file(temp_dir, "base", self.base)
450
other_file = self.dump_file(temp_dir, "other", self.other)
457
status = bzrlib.patch.diff3(new_file, filename, base, other)
459
os.chmod(new_file, os.stat(filename).st_mode)
460
rename(new_file, filename)
464
def get_lines(filename):
465
my_file = file(filename, "rb")
466
lines = my_file.readlines()
469
base_lines = get_lines(base)
470
other_lines = get_lines(other)
471
conflict_handler.merge_conflict(new_file, filename, base_lines,
383
new_file = filename+".new"
385
base = self.base_file
386
other = self.other_file
388
base = self.other_file
389
other = self.base_file
390
status = patch.diff3(new_file, filename, base, other)
392
os.chmod(new_file, os.stat(filename).st_mode)
393
os.rename(new_file, filename)
397
conflict_handler.merge_conflict(new_file, filename, base, other)
938
853
entry.apply(path, conflict_handler, reverse)
939
854
temp_name[entry.id] = None
941
elif entry.needs_rename():
942
if entry.is_creation(reverse):
944
857
to_name = os.path.join(temp_dir, str(i))
945
858
src_path = inventory.get(entry.id)
946
859
if src_path is not None:
947
860
src_path = os.path.join(dir, src_path)
949
rename(src_path, to_name)
862
os.rename(src_path, to_name)
950
863
temp_name[entry.id] = to_name
951
864
except OSError, e:
952
865
if e.errno != errno.ENOENT:
954
if conflict_handler.missing_for_rename(src_path, to_name) \
867
if conflict_handler.missing_for_rename(src_path) == "skip":
980
892
new_path = os.path.join(dir, new_tree_path)
981
893
old_path = changed_inventory.get(entry.id)
982
if bzrlib.osutils.lexists(new_path):
894
if os.path.exists(new_path):
983
895
if conflict_handler.target_exists(entry, new_path, old_path) == \
986
898
if entry.is_creation(reverse):
987
899
entry.apply(new_path, conflict_handler, reverse)
988
900
changed_inventory[entry.id] = new_tree_path
989
elif entry.needs_rename():
990
if entry.is_deletion(reverse):
992
902
if old_path is None:
995
mutter('rename %s to final name %s', old_path, new_path)
996
rename(old_path, new_path)
905
os.rename(old_path, new_path)
997
906
changed_inventory[entry.id] = new_tree_path
998
907
except OSError, e:
999
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
1000
% (old_path, new_path, entry, e))
908
raise Exception ("%s is missing" % new_path)
1002
910
class TargetExists(Exception):
1003
911
def __init__(self, entry, target):
1035
943
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
1036
944
self.this_path = this_path
946
class MergePermissionConflict(Exception):
947
def __init__(self, this_path, base_path, other_path):
948
this_perms = os.stat(this_path).st_mode & 0755
949
base_perms = os.stat(base_path).st_mode & 0755
950
other_perms = os.stat(other_path).st_mode & 0755
951
msg = """Conflicting permission for %s
955
""" % (this_path, this_perms, base_perms, other_perms)
956
self.this_path = this_path
957
self.base_path = base_path
958
self.other_path = other_path
959
Exception.__init__(self, msg)
1038
961
class WrongOldContents(Exception):
1039
962
def __init__(self, filename):
1040
963
msg = "Contents mismatch deleting %s" % filename
1041
964
self.filename = filename
1042
965
Exception.__init__(self, msg)
1044
class WrongOldExecFlag(Exception):
1045
def __init__(self, filename, old_exec_flag, new_exec_flag):
1046
msg = "Executable flag missmatch on %s:\n" \
1047
"Expected %s, got %s." % (filename, old_exec_flag, new_exec_flag)
967
class WrongOldPermissions(Exception):
968
def __init__(self, filename, old_perms, new_perms):
969
msg = "Permission missmatch on %s:\n" \
970
"Expected 0%o, got 0%o." % (filename, old_perms, new_perms)
1048
971
self.filename = filename
1049
972
Exception.__init__(self, msg)
1134
1044
def rename_conflict(self, id, this_name, base_name, other_name):
1135
1045
raise RenameConflict(id, this_name, base_name, other_name)
1137
def move_conflict(self, id, this_dir, base_dir, other_dir):
1047
def move_conflict(self, id, inventory):
1048
this_dir = inventory.this.get_dir(id)
1049
base_dir = inventory.base.get_dir(id)
1050
other_dir = inventory.other.get_dir(id)
1138
1051
raise MoveConflict(id, this_dir, base_dir, other_dir)
1140
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
1053
def merge_conflict(self, new_file, this_path, base_path, other_path):
1141
1054
os.unlink(new_file)
1142
1055
raise MergeConflict(this_path)
1057
def permission_conflict(self, this_path, base_path, other_path):
1058
raise MergePermissionConflict(this_path, base_path, other_path)
1144
1060
def wrong_old_contents(self, filename, expected_contents):
1145
1061
raise WrongOldContents(filename)
1147
1063
def rem_contents_conflict(self, filename, this_contents, base_contents):
1148
1064
raise RemoveContentsConflict(filename)
1150
def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
1151
raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
1066
def wrong_old_perms(self, filename, old_perms, new_perms):
1067
raise WrongOldPermissions(filename, old_perms, new_perms)
1153
1069
def rmdir_non_empty(self, filename):
1154
1070
raise DeletingNonEmptyDirectory(filename)
1159
1075
def patch_target_missing(self, filename, contents):
1160
1076
raise PatchTargetMissing(filename)
1162
def missing_for_exec_flag(self, filename):
1163
raise MissingForExecFlag(filename)
1078
def missing_for_chmod(self, filename):
1079
raise MissingPermsFile(filename)
1165
1081
def missing_for_rm(self, filename, change):
1166
1082
raise MissingForRm(filename)
1168
def missing_for_rename(self, filename, to_path):
1169
raise MissingForRename(filename, to_path)
1084
def missing_for_rename(self, filename):
1085
raise MissingForRename(filename)
1171
def missing_for_merge(self, file_id, other_path):
1172
raise MissingForMerge(other_path)
1087
def missing_for_merge(self, file_id, inventory):
1088
raise MissingForMerge(inventory.other.get_path(file_id))
1174
1090
def new_contents_conflict(self, filename, other_contents):
1175
1091
raise NewContentsConflict(filename)
1177
def weave_merge_conflict(self, filename, weave, other_i, out_file):
1178
raise WeaveMergeConflict(filename)
1180
def threeway_contents_conflict(self, filename, this_contents,
1181
base_contents, other_contents):
1182
raise ThreewayContentsConflict(filename)
1187
1096
def apply_changeset(changeset, inventory, dir, conflict_handler=None,
1403
class UnsupportedFiletype(Exception):
1404
def __init__(self, kind, full_path):
1405
msg = "The file \"%s\" is a %s, which is not a supported filetype." \
1308
class UnsuppportedFiletype(Exception):
1309
def __init__(self, full_path, stat_result):
1310
msg = "The file \"%s\" is not a supported filetype." % full_path
1407
1311
Exception.__init__(self, msg)
1408
1312
self.full_path = full_path
1411
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1412
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1313
self.stat_result = stat_result
1315
def generate_changeset(tree_a, tree_b, inventory_a=None, inventory_b=None):
1316
return ChangesetGenerator(tree_a, tree_b, inventory_a, inventory_b)()
1415
1318
class ChangesetGenerator(object):
1416
def __init__(self, tree_a, tree_b, interesting_ids=None):
1319
def __init__(self, tree_a, tree_b, inventory_a=None, inventory_b=None):
1417
1320
object.__init__(self)
1418
1321
self.tree_a = tree_a
1419
1322
self.tree_b = tree_b
1420
self._interesting_ids = interesting_ids
1323
if inventory_a is not None:
1324
self.inventory_a = inventory_a
1326
self.inventory_a = tree_a.inventory()
1327
if inventory_b is not None:
1328
self.inventory_b = inventory_b
1330
self.inventory_b = tree_b.inventory()
1331
self.r_inventory_a = self.reverse_inventory(self.inventory_a)
1332
self.r_inventory_b = self.reverse_inventory(self.inventory_b)
1422
def iter_both_tree_ids(self):
1423
for file_id in self.tree_a:
1425
for file_id in self.tree_b:
1426
if file_id not in self.tree_a:
1334
def reverse_inventory(self, inventory):
1336
for entry in inventory.itervalues():
1337
if entry.id is None:
1339
r_inventory[entry.id] = entry
1429
1342
def __call__(self):
1430
1343
cset = Changeset()
1431
for file_id in self.iter_both_tree_ids():
1432
cs_entry = self.make_entry(file_id)
1344
for entry in self.inventory_a.itervalues():
1345
if entry.id is None:
1347
cs_entry = self.make_entry(entry.id)
1433
1348
if cs_entry is not None and not cs_entry.is_boring():
1434
1349
cset.add_entry(cs_entry)
1351
for entry in self.inventory_b.itervalues():
1352
if entry.id is None:
1354
if not self.r_inventory_a.has_key(entry.id):
1355
cs_entry = self.make_entry(entry.id)
1356
if cs_entry is not None and not cs_entry.is_boring():
1357
cset.add_entry(cs_entry)
1436
1358
for entry in list(cset.entries.itervalues()):
1437
1359
if entry.parent != entry.new_parent:
1438
1360
if not cset.entries.has_key(entry.parent) and\
1446
1368
cset.add_entry(parent_entry)
1449
def iter_inventory(self, tree):
1450
for file_id in tree:
1451
yield self.get_entry(file_id, tree)
1453
def get_entry(self, file_id, tree):
1454
if not tree.has_or_had_id(file_id):
1456
return tree.inventory[file_id]
1458
def get_entry_parent(self, entry):
1461
return entry.parent_id
1463
def get_path(self, file_id, tree):
1464
if not tree.has_or_had_id(file_id):
1466
path = tree.id2path(file_id)
1472
def make_basic_entry(self, file_id, only_interesting):
1473
entry_a = self.get_entry(file_id, self.tree_a)
1474
entry_b = self.get_entry(file_id, self.tree_b)
1371
def get_entry_parent(self, entry, inventory):
1374
if entry.path == "./.":
1376
dirname = os.path.dirname(entry.path)
1379
parent = inventory[dirname]
1382
def get_paths(self, entry, tree):
1385
full_path = tree.readonly_path(entry.id)
1386
if entry.path == ".":
1387
return ("", full_path)
1388
return (entry.path, full_path)
1390
def make_basic_entry(self, id, only_interesting):
1391
entry_a = self.r_inventory_a.get(id)
1392
entry_b = self.r_inventory_b.get(id)
1475
1393
if only_interesting and not self.is_interesting(entry_a, entry_b):
1477
parent = self.get_entry_parent(entry_a)
1478
path = self.get_path(file_id, self.tree_a)
1479
cs_entry = ChangesetEntry(file_id, parent, path)
1480
new_parent = self.get_entry_parent(entry_b)
1482
new_path = self.get_path(file_id, self.tree_b)
1394
return (None, None, None)
1395
parent = self.get_entry_parent(entry_a, self.inventory_a)
1396
(path, full_path_a) = self.get_paths(entry_a, self.tree_a)
1397
cs_entry = ChangesetEntry(id, parent, path)
1398
new_parent = self.get_entry_parent(entry_b, self.inventory_b)
1401
(new_path, full_path_b) = self.get_paths(entry_b, self.tree_b)
1484
1403
cs_entry.new_path = new_path
1485
1404
cs_entry.new_parent = new_parent
1405
return (cs_entry, full_path_a, full_path_b)
1488
1407
def is_interesting(self, entry_a, entry_b):
1489
if self._interesting_ids is None:
1491
1408
if entry_a is not None:
1492
file_id = entry_a.file_id
1493
elif entry_b is not None:
1494
file_id = entry_b.file_id
1497
return file_id in self._interesting_ids
1409
if entry_a.interesting:
1411
if entry_b is not None:
1412
if entry_b.interesting:
1499
1416
def make_boring_entry(self, id):
1500
cs_entry = self.make_basic_entry(id, only_interesting=False)
1417
(cs_entry, full_path_a, full_path_b) = \
1418
self.make_basic_entry(id, only_interesting=False)
1501
1419
if cs_entry.is_creation_or_deletion():
1502
1420
return self.make_entry(id, only_interesting=False)
1507
1425
def make_entry(self, id, only_interesting=True):
1508
cs_entry = self.make_basic_entry(id, only_interesting)
1426
(cs_entry, full_path_a, full_path_b) = \
1427
self.make_basic_entry(id, only_interesting)
1510
1429
if cs_entry is None:
1513
cs_entry.metadata_change = self.make_exec_flag_change(id)
1515
if id in self.tree_a and id in self.tree_b:
1516
a_sha1 = self.tree_a.get_file_sha1(id)
1517
b_sha1 = self.tree_b.get_file_sha1(id)
1518
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1521
cs_entry.contents_change = self.make_contents_change(id)
1432
stat_a = self.lstat(full_path_a)
1433
stat_b = self.lstat(full_path_b)
1435
cs_entry.new_parent = None
1436
cs_entry.new_path = None
1438
cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
1439
cs_entry.contents_change = self.make_contents_change(full_path_a,
1522
1443
return cs_entry
1524
def make_exec_flag_change(self, file_id):
1525
exec_flag_a = exec_flag_b = None
1526
if file_id in self.tree_a and self.tree_a.kind(file_id) == "file":
1527
exec_flag_a = self.tree_a.is_executable(file_id)
1529
if file_id in self.tree_b and self.tree_b.kind(file_id) == "file":
1530
exec_flag_b = self.tree_b.is_executable(file_id)
1532
if exec_flag_a == exec_flag_b:
1534
return ChangeExecFlag(exec_flag_a, exec_flag_b)
1536
def make_contents_change(self, file_id):
1537
a_contents = get_contents(self.tree_a, file_id)
1538
b_contents = get_contents(self.tree_b, file_id)
1445
def make_mode_change(self, stat_a, stat_b):
1447
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1448
mode_a = stat_a.st_mode & 0777
1450
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1451
mode_b = stat_b.st_mode & 0777
1452
if mode_a == mode_b:
1454
return ChangeUnixPermissions(mode_a, mode_b)
1456
def make_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1457
if stat_a is None and stat_b is None:
1459
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1460
stat.S_ISDIR(stat_b.st_mode):
1462
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1463
stat.S_ISREG(stat_b.st_mode):
1464
if stat_a.st_ino == stat_b.st_ino and \
1465
stat_a.st_dev == stat_b.st_dev:
1467
if file(full_path_a, "rb").read() == \
1468
file(full_path_b, "rb").read():
1471
patch_contents = patch.diff(full_path_a,
1472
file(full_path_b, "rb").read())
1473
if patch_contents is None:
1475
return PatchApply(patch_contents)
1477
a_contents = self.get_contents(stat_a, full_path_a)
1478
b_contents = self.get_contents(stat_b, full_path_b)
1539
1479
if a_contents == b_contents:
1541
1481
return ReplaceContents(a_contents, b_contents)
1483
def get_contents(self, stat_result, full_path):
1484
if stat_result is None:
1486
elif stat.S_ISREG(stat_result.st_mode):
1487
return FileCreate(file(full_path, "rb").read())
1488
elif stat.S_ISDIR(stat_result.st_mode):
1490
elif stat.S_ISLNK(stat_result.st_mode):
1491
return SymlinkCreate(os.readlink(full_path))
1493
raise UnsupportedFiletype(full_path, stat_result)
1544
def get_contents(tree, file_id):
1545
"""Return the appropriate contents to create a copy of file_id from tree"""
1546
if file_id not in tree:
1548
kind = tree.kind(file_id)
1550
return TreeFileCreate(tree, file_id)
1551
elif kind in ("directory", "root_directory"):
1553
elif kind == "symlink":
1554
return SymlinkCreate(tree.get_symlink_target(file_id))
1556
raise UnsupportedFiletype(kind, tree.id2path(file_id))
1495
def lstat(self, full_path):
1497
if full_path is not None:
1499
stat_result = os.lstat(full_path)
1501
if e.errno != errno.ENOENT:
1559
1506
def full_path(entry, tree):
1560
return os.path.join(tree.basedir, entry.path)
1507
return os.path.join(tree.root, entry.path)
1562
1509
def new_delete_entry(entry, tree, inventory, delete):
1563
1510
if entry.path == "":