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
22
# XXX: mbp: I'm not totally convinced that we should handle conflicts
23
# as part of changeset application, rather than only in the merge
26
"""Represent and apply a changeset
28
Conflicts in applying a changeset are represented as exceptions.
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
31
36
__docformat__ = "restructuredtext"
42
47
newdict[value] = key
47
class ChangeUnixPermissions(object):
51
class ChangeExecFlag(object):
48
52
"""This is two-way change, suitable for file modification, creation,
50
def __init__(self, old_mode, new_mode):
51
self.old_mode = old_mode
52
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
54
58
def apply(self, filename, conflict_handler, reverse=False):
56
from_mode = self.old_mode
57
to_mode = self.new_mode
60
from_exec_flag = self.old_exec_flag
61
to_exec_flag = self.new_exec_flag
59
from_mode = self.new_mode
60
to_mode = self.old_mode
63
from_exec_flag = self.new_exec_flag
64
to_exec_flag = self.old_exec_flag
62
current_mode = os.stat(filename).st_mode &0777
66
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
64
68
if e.errno == errno.ENOENT:
65
if conflict_handler.missing_for_chmod(filename) == "skip":
69
if conflict_handler.missing_for_exec_flag(filename) == "skip":
68
current_mode = from_mode
72
current_exec_flag = from_exec_flag
70
if from_mode is not None and current_mode != from_mode:
71
if conflict_handler.wrong_old_perms(filename, from_mode,
72
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":
75
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
77
93
os.chmod(filename, to_mode)
79
95
if e.errno == errno.ENOENT:
80
conflict_handler.missing_for_chmod(filename)
96
conflict_handler.missing_for_exec_flag(filename)
82
98
def __eq__(self, other):
83
if not isinstance(other, ChangeUnixPermissions):
85
elif self.old_mode != other.old_mode:
87
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)
92
103
def __ne__(self, other):
93
104
return not (self == other)
95
107
def dir_create(filename, conflict_handler, reverse):
96
108
"""Creates the directory, or deletes it if reverse is true. Intended to be
97
109
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":
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
os.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,
825
935
entry.apply(path, conflict_handler, reverse)
826
936
temp_name[entry.id] = None
938
elif entry.needs_rename():
829
939
to_name = os.path.join(temp_dir, str(i))
830
940
src_path = inventory.get(entry.id)
831
941
if src_path is not None:
832
942
src_path = os.path.join(dir, src_path)
834
os.rename(src_path, to_name)
944
rename(src_path, to_name)
835
945
temp_name[entry.id] = to_name
836
946
except OSError, e:
837
947
if e.errno != errno.ENOENT:
839
if conflict_handler.missing_for_rename(src_path) == "skip":
949
if conflict_handler.missing_for_rename(src_path, to_name) \
864
975
new_path = os.path.join(dir, new_tree_path)
865
976
old_path = changed_inventory.get(entry.id)
866
if os.path.exists(new_path):
977
if bzrlib.osutils.lexists(new_path):
867
978
if conflict_handler.target_exists(entry, new_path, old_path) == \
870
981
if entry.is_creation(reverse):
871
982
entry.apply(new_path, conflict_handler, reverse)
872
983
changed_inventory[entry.id] = new_tree_path
984
elif entry.needs_rename():
874
985
if old_path is None:
877
os.rename(old_path, new_path)
988
rename(old_path, new_path)
878
989
changed_inventory[entry.id] = new_tree_path
879
990
except OSError, e:
880
991
raise Exception ("%s is missing" % new_path)
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):
1029
1132
os.unlink(new_file)
1030
1133
raise MergeConflict(this_path)
1032
def permission_conflict(self, this_path, base_path, other_path):
1033
raise MergePermissionConflict(this_path, base_path, other_path)
1035
1135
def wrong_old_contents(self, filename, expected_contents):
1036
1136
raise WrongOldContents(filename)
1038
1138
def rem_contents_conflict(self, filename, this_contents, base_contents):
1039
1139
raise RemoveContentsConflict(filename)
1041
def wrong_old_perms(self, filename, old_perms, new_perms):
1042
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)
1044
1144
def rmdir_non_empty(self, filename):
1045
1145
raise DeletingNonEmptyDirectory(filename)
1050
1150
def patch_target_missing(self, filename, contents):
1051
1151
raise PatchTargetMissing(filename)
1053
def missing_for_chmod(self, filename):
1054
raise MissingPermsFile(filename)
1153
def missing_for_exec_flag(self, filename):
1154
raise MissingForExecFlag(filename)
1056
1156
def missing_for_rm(self, filename, change):
1057
1157
raise MissingForRm(filename)
1059
def missing_for_rename(self, filename):
1060
raise MissingForRename(filename)
1159
def missing_for_rename(self, filename, to_path):
1160
raise MissingForRename(filename, to_path)
1062
1162
def missing_for_merge(self, file_id, other_path):
1063
1163
raise MissingForMerge(other_path)
1283
class UnsuppportedFiletype(Exception):
1284
def __init__(self, full_path, stat_result):
1285
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." \
1286
1398
Exception.__init__(self, msg)
1287
1399
self.full_path = full_path
1288
self.stat_result = stat_result
1290
1402
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1291
1403
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1293
1406
class ChangesetGenerator(object):
1294
1407
def __init__(self, tree_a, tree_b, interesting_ids=None):
1295
1408
object.__init__(self)
1388
1501
if cs_entry is None:
1504
cs_entry.metadata_change = self.make_exec_flag_change(id)
1390
1506
if id in self.tree_a and id in self.tree_b:
1391
1507
a_sha1 = self.tree_a.get_file_sha1(id)
1392
1508
b_sha1 = self.tree_b.get_file_sha1(id)
1393
1509
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1394
1510
return cs_entry
1396
full_path_a = self.tree_a.readonly_path(id)
1397
full_path_b = self.tree_b.readonly_path(id)
1398
stat_a = self.lstat(full_path_a)
1399
stat_b = self.lstat(full_path_b)
1401
cs_entry.metadata_change = self.make_mode_change(stat_a, stat_b)
1402
cs_entry.contents_change = self.make_contents_change(full_path_a,
1512
cs_entry.contents_change = self.make_contents_change(id)
1406
1513
return cs_entry
1408
def make_mode_change(self, stat_a, stat_b):
1410
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1411
mode_a = stat_a.st_mode & 0777
1413
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1414
mode_b = stat_b.st_mode & 0777
1415
if mode_a == mode_b:
1417
return ChangeUnixPermissions(mode_a, mode_b)
1419
def make_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1420
if stat_a is None and stat_b is None:
1422
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1423
stat.S_ISDIR(stat_b.st_mode):
1425
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1426
stat.S_ISREG(stat_b.st_mode):
1427
if stat_a.st_ino == stat_b.st_ino and \
1428
stat_a.st_dev == stat_b.st_dev:
1431
a_contents = self.get_contents(stat_a, full_path_a)
1432
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)
1433
1530
if a_contents == b_contents:
1435
1532
return ReplaceContents(a_contents, b_contents)
1437
def get_contents(self, stat_result, full_path):
1438
if stat_result is None:
1440
elif stat.S_ISREG(stat_result.st_mode):
1441
return FileCreate(file(full_path, "rb").read())
1442
elif stat.S_ISDIR(stat_result.st_mode):
1444
elif stat.S_ISLNK(stat_result.st_mode):
1445
return SymlinkCreate(os.readlink(full_path))
1447
raise UnsupportedFiletype(full_path, stat_result)
1449
def lstat(self, full_path):
1451
if full_path is not None:
1453
stat_result = os.lstat(full_path)
1455
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))
1460
1550
def full_path(entry, tree):
1461
return os.path.join(tree.root, entry.path)
1551
return os.path.join(tree.basedir, entry.path)
1463
1553
def new_delete_entry(entry, tree, inventory, delete):
1464
1554
if entry.path == "":