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.
20
from bzrlib.trace import mutter
21
from bzrlib.osutils import rename
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
24
# XXX: mbp: I'm not totally convinced that we should handle conflicts
25
# as part of changeset application, rather than only in the merge
28
"""Represent and apply a changeset
30
Conflicts in applying a changeset are represented as exceptions.
33
36
__docformat__ = "restructuredtext"
48
class ChangeUnixPermissions(object):
51
class ChangeExecFlag(object):
49
52
"""This is two-way change, suitable for file modification, creation,
51
def __init__(self, old_mode, new_mode):
52
self.old_mode = old_mode
53
self.new_mode = new_mode
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
55
58
def apply(self, filename, conflict_handler, reverse=False):
57
from_mode = self.old_mode
58
to_mode = self.new_mode
60
from_exec_flag = self.old_exec_flag
61
to_exec_flag = self.new_exec_flag
60
from_mode = self.new_mode
61
to_mode = self.old_mode
63
from_exec_flag = self.new_exec_flag
64
to_exec_flag = self.old_exec_flag
63
current_mode = os.stat(filename).st_mode &0777
66
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
65
68
if e.errno == errno.ENOENT:
66
if conflict_handler.missing_for_chmod(filename) == "skip":
69
if conflict_handler.missing_for_exec_flag(filename) == "skip":
69
current_mode = from_mode
72
current_exec_flag = from_exec_flag
71
if from_mode is not None and current_mode != from_mode:
72
if conflict_handler.wrong_old_perms(filename, from_mode,
73
current_mode) != "continue":
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":
76
if to_mode is not None:
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
78
93
os.chmod(filename, to_mode)
80
95
if e.errno == errno.ENOENT:
81
conflict_handler.missing_for_chmod(filename)
96
conflict_handler.missing_for_exec_flag(filename)
83
98
def __eq__(self, other):
84
if not isinstance(other, ChangeUnixPermissions):
86
elif self.old_mode != other.old_mode:
88
elif self.new_mode != other.new_mode:
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)
93
103
def __ne__(self, other):
94
104
return not (self == other)
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":
226
305
def reversed(sequence):
227
306
max = len(sequence) - 1
228
307
for i in range(len(sequence)):
340
432
def __ne__(self, other):
341
433
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)
343
443
def apply(self, filename, conflict_handler, reverse=False):
344
new_file = filename+".new"
345
base_file = self.base.readonly_path(self.file_id)
346
other_file = self.other.readonly_path(self.file_id)
353
status = patch.diff3(new_file, filename, base, other)
355
os.chmod(new_file, os.stat(filename).st_mode)
356
rename(new_file, filename)
360
def get_lines(filename):
361
my_file = file(base, "rb")
362
lines = my_file.readlines()
364
base_lines = get_lines(base)
365
other_lines = get_lines(other)
366
conflict_handler.merge_conflict(new_file, filename, base_lines,
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,
915
1026
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
916
1027
self.this_path = this_path
918
class MergePermissionConflict(Exception):
919
def __init__(self, this_path, base_path, other_path):
920
this_perms = os.stat(this_path).st_mode & 0755
921
base_perms = os.stat(base_path).st_mode & 0755
922
other_perms = os.stat(other_path).st_mode & 0755
923
msg = """Conflicting permission for %s
927
""" % (this_path, this_perms, base_perms, other_perms)
928
self.this_path = this_path
929
self.base_path = base_path
930
self.other_path = other_path
931
Exception.__init__(self, msg)
933
1029
class WrongOldContents(Exception):
934
1030
def __init__(self, filename):
935
1031
msg = "Contents mismatch deleting %s" % filename
936
1032
self.filename = filename
937
1033
Exception.__init__(self, msg)
939
class WrongOldPermissions(Exception):
940
def __init__(self, filename, old_perms, new_perms):
941
msg = "Permission missmatch on %s:\n" \
942
"Expected 0%o, got 0%o." % (filename, old_perms, new_perms)
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)
943
1039
self.filename = filename
944
1040
Exception.__init__(self, msg)
988
1084
msg = "Conflicting contents for new file %s" % (filename)
989
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)
992
1098
class MissingForMerge(Exception):
993
1099
def __init__(self, filename):
1026
1132
os.unlink(new_file)
1027
1133
raise MergeConflict(this_path)
1029
def permission_conflict(self, this_path, base_path, other_path):
1030
raise MergePermissionConflict(this_path, base_path, other_path)
1032
1135
def wrong_old_contents(self, filename, expected_contents):
1033
1136
raise WrongOldContents(filename)
1035
1138
def rem_contents_conflict(self, filename, this_contents, base_contents):
1036
1139
raise RemoveContentsConflict(filename)
1038
def wrong_old_perms(self, filename, old_perms, new_perms):
1039
raise WrongOldPermissions(filename, old_perms, new_perms)
1141
def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
1142
raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
1041
1144
def rmdir_non_empty(self, filename):
1042
1145
raise DeletingNonEmptyDirectory(filename)
1047
1150
def patch_target_missing(self, filename, contents):
1048
1151
raise PatchTargetMissing(filename)
1050
def missing_for_chmod(self, filename):
1051
raise MissingPermsFile(filename)
1153
def missing_for_exec_flag(self, filename):
1154
raise MissingForExecFlag(filename)
1053
1156
def missing_for_rm(self, filename, change):
1054
1157
raise MissingForRm(filename)
1056
def missing_for_rename(self, filename):
1057
raise MissingForRename(filename)
1159
def missing_for_rename(self, filename, to_path):
1160
raise MissingForRename(filename, to_path)
1059
1162
def missing_for_merge(self, file_id, other_path):
1060
1163
raise MissingForMerge(other_path)
1062
1165
def new_contents_conflict(self, filename, other_contents):
1063
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)
1065
1175
def finalize(self):
1280
class UnsuppportedFiletype(Exception):
1281
def __init__(self, full_path, stat_result):
1282
msg = "The file \"%s\" is not a supported filetype." % full_path
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." \
1283
1398
Exception.__init__(self, msg)
1284
1399
self.full_path = full_path
1285
self.stat_result = stat_result
1287
1402
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1288
1403
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1290
1406
class ChangesetGenerator(object):
1291
1407
def __init__(self, tree_a, tree_b, interesting_ids=None):
1292
1408
object.__init__(self)
1398
1509
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1399
1510
return cs_entry
1401
cs_entry.contents_change = self.make_contents_change(full_path_a,
1512
cs_entry.contents_change = self.make_contents_change(id)
1405
1513
return cs_entry
1407
def make_mode_change(self, stat_a, stat_b):
1409
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1410
mode_a = stat_a.st_mode & 0777
1412
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1413
mode_b = stat_b.st_mode & 0777
1414
if mode_a == mode_b:
1416
return ChangeUnixPermissions(mode_a, mode_b)
1418
def make_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1419
if stat_a is None and stat_b is None:
1421
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1422
stat.S_ISDIR(stat_b.st_mode):
1424
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1425
stat.S_ISREG(stat_b.st_mode):
1426
if stat_a.st_ino == stat_b.st_ino and \
1427
stat_a.st_dev == stat_b.st_dev:
1430
a_contents = self.get_contents(stat_a, full_path_a)
1431
b_contents = self.get_contents(stat_b, full_path_b)
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)
1432
1530
if a_contents == b_contents:
1434
1532
return ReplaceContents(a_contents, b_contents)
1436
def get_contents(self, stat_result, full_path):
1437
if stat_result is None:
1439
elif stat.S_ISREG(stat_result.st_mode):
1440
return FileCreate(file(full_path, "rb").read())
1441
elif stat.S_ISDIR(stat_result.st_mode):
1443
elif stat.S_ISLNK(stat_result.st_mode):
1444
return SymlinkCreate(os.readlink(full_path))
1446
raise UnsupportedFiletype(full_path, stat_result)
1448
def lstat(self, full_path):
1450
if full_path is not None:
1452
stat_result = os.lstat(full_path)
1454
if e.errno != errno.ENOENT:
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))
1459
1550
def full_path(entry, tree):
1460
return os.path.join(tree.root, entry.path)
1551
return os.path.join(tree.basedir, entry.path)
1462
1553
def new_delete_entry(entry, tree, inventory, delete):
1463
1554
if entry.path == "":