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
20
from bzrlib.trace import mutter
22
Represent and apply a changeset
37
24
__docformat__ = "restructuredtext"
48
35
newdict[value] = key
52
class ChangeExecFlag(object):
39
class PatchApply(object):
40
"""Patch application as a kind of content change"""
41
def __init__(self, contents):
44
:param contents: The text of the patch to apply
45
:type contents: str"""
46
self.contents = contents
48
def __eq__(self, other):
49
if not isinstance(other, PatchApply):
51
elif self.contents != other.contents:
56
def __ne__(self, other):
57
return not (self == other)
59
def apply(self, filename, conflict_handler, reverse=False):
60
"""Applies the patch to the specified file.
62
:param filename: the file to apply the patch to
64
:param reverse: If true, apply the patch in reverse
67
input_name = filename+".orig"
69
os.rename(filename, input_name)
71
if e.errno != errno.ENOENT:
73
if conflict_handler.patch_target_missing(filename, self.contents)\
76
os.rename(filename, input_name)
79
status = patch.patch(self.contents, input_name, filename,
81
os.chmod(filename, os.stat(input_name).st_mode)
85
conflict_handler.failed_hunks(filename)
88
class ChangeUnixPermissions(object):
53
89
"""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
91
def __init__(self, old_mode, new_mode):
92
self.old_mode = old_mode
93
self.new_mode = new_mode
59
95
def apply(self, filename, conflict_handler, reverse=False):
61
from_exec_flag = self.old_exec_flag
62
to_exec_flag = self.new_exec_flag
97
from_mode = self.old_mode
98
to_mode = self.new_mode
64
from_exec_flag = self.new_exec_flag
65
to_exec_flag = self.old_exec_flag
100
from_mode = self.new_mode
101
to_mode = self.old_mode
67
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
103
current_mode = os.stat(filename).st_mode &0777
68
104
except OSError, e:
69
105
if e.errno == errno.ENOENT:
70
if conflict_handler.missing_for_exec_flag(filename) == "skip":
106
if conflict_handler.missing_for_chmod(filename) == "skip":
73
current_exec_flag = from_exec_flag
109
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":
111
if from_mode is not None and current_mode != from_mode:
112
if conflict_handler.wrong_old_perms(filename, from_mode,
113
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
116
if to_mode is not None:
94
118
os.chmod(filename, to_mode)
95
119
except IOError, e:
96
120
if e.errno == errno.ENOENT:
97
conflict_handler.missing_for_exec_flag(filename)
121
conflict_handler.missing_for_chmod(filename)
99
123
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)
124
if not isinstance(other, ChangeUnixPermissions):
126
elif self.old_mode != other.old_mode:
128
elif self.new_mode != other.new_mode:
104
133
def __ne__(self, other):
105
134
return not (self == other)
108
136
def dir_create(filename, conflict_handler, reverse):
109
137
"""Creates the directory, or deletes it if reverse is true. Intended to be
110
138
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
267
def reversed(sequence):
307
268
max = len(sequence) - 1
308
269
for i in range(len(sequence)):
414
369
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):
370
def __init__(self, base_file, other_file):
371
self.base_file = base_file
372
self.other_file = other_file
427
374
def __eq__(self, other):
428
375
if not isinstance(other, Diff3Merge):
430
return (self.base == other.base and
431
self.other == other.other and self.file_id == other.file_id)
377
return (self.base_file == other.base_file and
378
self.other_file == other.other_file)
433
380
def __ne__(self, other):
434
381
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
383
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,
384
new_file = filename+".new"
386
base = self.base_file
387
other = self.other_file
389
base = self.other_file
390
other = self.base_file
391
status = patch.diff3(new_file, filename, base, other)
393
os.chmod(new_file, os.stat(filename).st_mode)
394
os.rename(new_file, filename)
398
conflict_handler.merge_conflict(new_file, filename, base, other)
938
856
entry.apply(path, conflict_handler, reverse)
939
857
temp_name[entry.id] = None
941
elif entry.needs_rename():
942
if entry.is_creation(reverse):
944
860
to_name = os.path.join(temp_dir, str(i))
945
861
src_path = inventory.get(entry.id)
946
862
if src_path is not None:
947
863
src_path = os.path.join(dir, src_path)
949
rename(src_path, to_name)
865
os.rename(src_path, to_name)
950
866
temp_name[entry.id] = to_name
951
867
except OSError, e:
952
868
if e.errno != errno.ENOENT:
954
if conflict_handler.missing_for_rename(src_path, to_name) \
870
if conflict_handler.missing_for_rename(src_path) == "skip":
980
895
new_path = os.path.join(dir, new_tree_path)
981
896
old_path = changed_inventory.get(entry.id)
982
if bzrlib.osutils.lexists(new_path):
897
if os.path.exists(new_path):
983
898
if conflict_handler.target_exists(entry, new_path, old_path) == \
986
901
if entry.is_creation(reverse):
987
902
entry.apply(new_path, conflict_handler, reverse)
988
903
changed_inventory[entry.id] = new_tree_path
989
elif entry.needs_rename():
990
if entry.is_deletion(reverse):
992
905
if old_path is None:
995
mutter('rename %s to final name %s', old_path, new_path)
996
rename(old_path, new_path)
908
os.rename(old_path, new_path)
997
909
changed_inventory[entry.id] = new_tree_path
998
910
except OSError, e:
999
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
1000
% (old_path, new_path, entry, e))
911
raise Exception ("%s is missing" % new_path)
1002
913
class TargetExists(Exception):
1003
914
def __init__(self, entry, target):
1035
946
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
1036
947
self.this_path = this_path
949
class MergePermissionConflict(Exception):
950
def __init__(self, this_path, base_path, other_path):
951
this_perms = os.stat(this_path).st_mode & 0755
952
base_perms = os.stat(base_path).st_mode & 0755
953
other_perms = os.stat(other_path).st_mode & 0755
954
msg = """Conflicting permission for %s
958
""" % (this_path, this_perms, base_perms, other_perms)
959
self.this_path = this_path
960
self.base_path = base_path
961
self.other_path = other_path
962
Exception.__init__(self, msg)
1038
964
class WrongOldContents(Exception):
1039
965
def __init__(self, filename):
1040
966
msg = "Contents mismatch deleting %s" % filename
1041
967
self.filename = filename
1042
968
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)
970
class WrongOldPermissions(Exception):
971
def __init__(self, filename, old_perms, new_perms):
972
msg = "Permission missmatch on %s:\n" \
973
"Expected 0%o, got 0%o." % (filename, old_perms, new_perms)
1048
974
self.filename = filename
1049
975
Exception.__init__(self, msg)
1134
1047
def rename_conflict(self, id, this_name, base_name, other_name):
1135
1048
raise RenameConflict(id, this_name, base_name, other_name)
1137
def move_conflict(self, id, this_dir, base_dir, other_dir):
1050
def move_conflict(self, id, inventory):
1051
this_dir = inventory.this.get_dir(id)
1052
base_dir = inventory.base.get_dir(id)
1053
other_dir = inventory.other.get_dir(id)
1138
1054
raise MoveConflict(id, this_dir, base_dir, other_dir)
1140
def merge_conflict(self, new_file, this_path, base_lines, other_lines):
1056
def merge_conflict(self, new_file, this_path, base_path, other_path):
1141
1057
os.unlink(new_file)
1142
1058
raise MergeConflict(this_path)
1060
def permission_conflict(self, this_path, base_path, other_path):
1061
raise MergePermissionConflict(this_path, base_path, other_path)
1144
1063
def wrong_old_contents(self, filename, expected_contents):
1145
1064
raise WrongOldContents(filename)
1147
1066
def rem_contents_conflict(self, filename, this_contents, base_contents):
1148
1067
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)
1069
def wrong_old_perms(self, filename, old_perms, new_perms):
1070
raise WrongOldPermissions(filename, old_perms, new_perms)
1153
1072
def rmdir_non_empty(self, filename):
1154
1073
raise DeletingNonEmptyDirectory(filename)
1159
1078
def patch_target_missing(self, filename, contents):
1160
1079
raise PatchTargetMissing(filename)
1162
def missing_for_exec_flag(self, filename):
1163
raise MissingForExecFlag(filename)
1081
def missing_for_chmod(self, filename):
1082
raise MissingPermsFile(filename)
1165
1084
def missing_for_rm(self, filename, change):
1166
1085
raise MissingForRm(filename)
1168
def missing_for_rename(self, filename, to_path):
1169
raise MissingForRename(filename, to_path)
1087
def missing_for_rename(self, filename):
1088
raise MissingForRename(filename)
1171
def missing_for_merge(self, file_id, other_path):
1172
raise MissingForMerge(other_path)
1090
def missing_for_merge(self, file_id, inventory):
1091
raise MissingForMerge(inventory.other.get_path(file_id))
1174
1093
def new_contents_conflict(self, filename, other_contents):
1175
1094
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
1099
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." \
1311
class UnsuppportedFiletype(Exception):
1312
def __init__(self, full_path, stat_result):
1313
msg = "The file \"%s\" is not a supported filetype." % full_path
1407
1314
Exception.__init__(self, msg)
1408
1315
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)()
1316
self.stat_result = stat_result
1318
def generate_changeset(tree_a, tree_b, inventory_a=None, inventory_b=None):
1319
return ChangesetGenerator(tree_a, tree_b, inventory_a, inventory_b)()
1415
1321
class ChangesetGenerator(object):
1416
def __init__(self, tree_a, tree_b, interesting_ids=None):
1322
def __init__(self, tree_a, tree_b, inventory_a=None, inventory_b=None):
1417
1323
object.__init__(self)
1418
1324
self.tree_a = tree_a
1419
1325
self.tree_b = tree_b
1420
self._interesting_ids = interesting_ids
1326
if inventory_a is not None:
1327
self.inventory_a = inventory_a
1329
self.inventory_a = tree_a.inventory()
1330
if inventory_b is not None:
1331
self.inventory_b = inventory_b
1333
self.inventory_b = tree_b.inventory()
1334
self.r_inventory_a = self.reverse_inventory(self.inventory_a)
1335
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:
1337
def reverse_inventory(self, inventory):
1339
for entry in inventory.itervalues():
1340
if entry.id is None:
1342
r_inventory[entry.id] = entry
1429
1345
def __call__(self):
1430
1346
cset = Changeset()
1431
for file_id in self.iter_both_tree_ids():
1432
cs_entry = self.make_entry(file_id)
1347
for entry in self.inventory_a.itervalues():
1348
if entry.id is None:
1350
cs_entry = self.make_entry(entry.id)
1433
1351
if cs_entry is not None and not cs_entry.is_boring():
1434
1352
cset.add_entry(cs_entry)
1354
for entry in self.inventory_b.itervalues():
1355
if entry.id is None:
1357
if not self.r_inventory_a.has_key(entry.id):
1358
cs_entry = self.make_entry(entry.id)
1359
if cs_entry is not None and not cs_entry.is_boring():
1360
cset.add_entry(cs_entry)
1436
1361
for entry in list(cset.entries.itervalues()):
1437
1362
if entry.parent != entry.new_parent:
1438
1363
if not cset.entries.has_key(entry.parent) and\
1446
1371
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)
1374
def get_entry_parent(self, entry, inventory):
1377
if entry.path == "./.":
1379
dirname = os.path.dirname(entry.path)
1382
parent = inventory[dirname]
1385
def get_paths(self, entry, tree):
1388
full_path = tree.readonly_path(entry.id)
1389
if entry.path == ".":
1390
return ("", full_path)
1391
return (entry.path, full_path)
1393
def make_basic_entry(self, id, only_interesting):
1394
entry_a = self.r_inventory_a.get(id)
1395
entry_b = self.r_inventory_b.get(id)
1475
1396
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)
1397
return (None, None, None)
1398
parent = self.get_entry_parent(entry_a, self.inventory_a)
1399
(path, full_path_a) = self.get_paths(entry_a, self.tree_a)
1400
cs_entry = ChangesetEntry(id, parent, path)
1401
new_parent = self.get_entry_parent(entry_b, self.inventory_b)
1404
(new_path, full_path_b) = self.get_paths(entry_b, self.tree_b)
1484
1406
cs_entry.new_path = new_path
1485
1407
cs_entry.new_parent = new_parent
1408
return (cs_entry, full_path_a, full_path_b)
1488
1410
def is_interesting(self, entry_a, entry_b):
1489
if self._interesting_ids is None:
1491
1411
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
1412
if entry_a.interesting:
1414
if entry_b is not None:
1415
if entry_b.interesting:
1499
1419
def make_boring_entry(self, id):
1500
cs_entry = self.make_basic_entry(id, only_interesting=False)
1420
(cs_entry, full_path_a, full_path_b) = \
1421
self.make_basic_entry(id, only_interesting=False)
1501
1422
if cs_entry.is_creation_or_deletion():
1502
1423
return self.make_entry(id, only_interesting=False)
1507
1428
def make_entry(self, id, only_interesting=True):
1508
cs_entry = self.make_basic_entry(id, only_interesting)
1429
(cs_entry, full_path_a, full_path_b) = \
1430
self.make_basic_entry(id, only_interesting)
1510
1432
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)
1435
stat_a = self.lstat(full_path_a)
1436
stat_b = self.lstat(full_path_b)
1438
cs_entry.new_parent = None
1439
cs_entry.new_path = None
1441
cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
1442
cs_entry.contents_change = self.make_contents_change(full_path_a,
1522
1446
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)
1448
def make_mode_change(self, stat_a, stat_b):
1450
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1451
mode_a = stat_a.st_mode & 0777
1453
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1454
mode_b = stat_b.st_mode & 0777
1455
if mode_a == mode_b:
1457
return ChangeUnixPermissions(mode_a, mode_b)
1459
def make_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1460
if stat_a is None and stat_b is None:
1462
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1463
stat.S_ISDIR(stat_b.st_mode):
1465
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1466
stat.S_ISREG(stat_b.st_mode):
1467
if stat_a.st_ino == stat_b.st_ino and \
1468
stat_a.st_dev == stat_b.st_dev:
1470
if file(full_path_a, "rb").read() == \
1471
file(full_path_b, "rb").read():
1474
patch_contents = patch.diff(full_path_a,
1475
file(full_path_b, "rb").read())
1476
if patch_contents is None:
1478
return PatchApply(patch_contents)
1480
a_contents = self.get_contents(stat_a, full_path_a)
1481
b_contents = self.get_contents(stat_b, full_path_b)
1539
1482
if a_contents == b_contents:
1541
1484
return ReplaceContents(a_contents, b_contents)
1486
def get_contents(self, stat_result, full_path):
1487
if stat_result is None:
1489
elif stat.S_ISREG(stat_result.st_mode):
1490
return FileCreate(file(full_path, "rb").read())
1491
elif stat.S_ISDIR(stat_result.st_mode):
1493
elif stat.S_ISLNK(stat_result.st_mode):
1494
return SymlinkCreate(os.readlink(full_path))
1496
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))
1498
def lstat(self, full_path):
1500
if full_path is not None:
1502
stat_result = os.lstat(full_path)
1504
if e.errno != errno.ENOENT:
1559
1509
def full_path(entry, tree):
1560
return os.path.join(tree.basedir, entry.path)
1510
return os.path.join(tree.root, entry.path)
1562
1512
def new_delete_entry(entry, tree, inventory, delete):
1563
1513
if entry.path == "":