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
# 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.
37
31
__docformat__ = "restructuredtext"
48
42
newdict[value] = key
52
class ChangeExecFlag(object):
47
class ChangeUnixPermissions(object):
53
48
"""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
50
def __init__(self, old_mode, new_mode):
51
self.old_mode = old_mode
52
self.new_mode = new_mode
59
54
def apply(self, filename, conflict_handler, reverse=False):
61
from_exec_flag = self.old_exec_flag
62
to_exec_flag = self.new_exec_flag
56
from_mode = self.old_mode
57
to_mode = self.new_mode
64
from_exec_flag = self.new_exec_flag
65
to_exec_flag = self.old_exec_flag
59
from_mode = self.new_mode
60
to_mode = self.old_mode
67
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
62
current_mode = os.stat(filename).st_mode &0777
69
64
if e.errno == errno.ENOENT:
70
if conflict_handler.missing_for_exec_flag(filename) == "skip":
65
if conflict_handler.missing_for_chmod(filename) == "skip":
73
current_exec_flag = from_exec_flag
68
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":
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":
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
75
if to_mode is not None:
94
77
os.chmod(filename, to_mode)
96
79
if e.errno == errno.ENOENT:
97
conflict_handler.missing_for_exec_flag(filename)
80
conflict_handler.missing_for_chmod(filename)
99
82
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)
83
if not isinstance(other, ChangeUnixPermissions):
85
elif self.old_mode != other.old_mode:
87
elif self.new_mode != other.new_mode:
104
92
def __ne__(self, other):
105
93
return not (self == other)
108
95
def dir_create(filename, conflict_handler, reverse):
109
96
"""Creates the directory, or deletes it if reverse is true. Intended to be
110
97
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
226
def reversed(sequence):
307
227
max = len(sequence) - 1
308
228
for i in range(len(sequence)):
433
340
def __ne__(self, other):
434
341
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
343
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,
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,
938
825
entry.apply(path, conflict_handler, reverse)
939
826
temp_name[entry.id] = None
941
elif entry.needs_rename():
942
829
to_name = os.path.join(temp_dir, str(i))
943
830
src_path = inventory.get(entry.id)
944
831
if src_path is not None:
945
832
src_path = os.path.join(dir, src_path)
947
rename(src_path, to_name)
834
os.rename(src_path, to_name)
948
835
temp_name[entry.id] = to_name
949
836
except OSError, e:
950
837
if e.errno != errno.ENOENT:
952
if conflict_handler.missing_for_rename(src_path, to_name) \
839
if conflict_handler.missing_for_rename(src_path) == "skip":
978
864
new_path = os.path.join(dir, new_tree_path)
979
865
old_path = changed_inventory.get(entry.id)
980
if bzrlib.osutils.lexists(new_path):
866
if os.path.exists(new_path):
981
867
if conflict_handler.target_exists(entry, new_path, old_path) == \
984
870
if entry.is_creation(reverse):
985
871
entry.apply(new_path, conflict_handler, reverse)
986
872
changed_inventory[entry.id] = new_tree_path
987
elif entry.needs_rename():
988
874
if old_path is None:
991
mutter('rename %s to final name %s', old_path, new_path)
992
rename(old_path, new_path)
877
os.rename(old_path, new_path)
993
878
changed_inventory[entry.id] = new_tree_path
994
879
except OSError, e:
995
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
996
% (old_path, new_path, entry, e))
880
raise Exception ("%s is missing" % new_path)
998
882
class TargetExists(Exception):
999
883
def __init__(self, entry, target):
1031
915
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
1032
916
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)
1034
933
class WrongOldContents(Exception):
1035
934
def __init__(self, filename):
1036
935
msg = "Contents mismatch deleting %s" % filename
1037
936
self.filename = filename
1038
937
Exception.__init__(self, msg)
1040
class WrongOldExecFlag(Exception):
1041
def __init__(self, filename, old_exec_flag, new_exec_flag):
1042
msg = "Executable flag missmatch on %s:\n" \
1043
"Expected %s, got %s." % (filename, old_exec_flag, new_exec_flag)
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)
1044
943
self.filename = filename
1045
944
Exception.__init__(self, msg)
1089
988
msg = "Conflicting contents for new file %s" % (filename)
1090
989
Exception.__init__(self, msg)
1092
class WeaveMergeConflict(Exception):
1093
def __init__(self, filename):
1094
msg = "Conflicting contents for file %s" % (filename)
1095
Exception.__init__(self, msg)
1097
class ThreewayContentsConflict(Exception):
1098
def __init__(self, filename):
1099
msg = "Conflicting contents for file %s" % (filename)
1100
Exception.__init__(self, msg)
1103
992
class MissingForMerge(Exception):
1104
993
def __init__(self, filename):
1137
1029
os.unlink(new_file)
1138
1030
raise MergeConflict(this_path)
1032
def permission_conflict(self, this_path, base_path, other_path):
1033
raise MergePermissionConflict(this_path, base_path, other_path)
1140
1035
def wrong_old_contents(self, filename, expected_contents):
1141
1036
raise WrongOldContents(filename)
1143
1038
def rem_contents_conflict(self, filename, this_contents, base_contents):
1144
1039
raise RemoveContentsConflict(filename)
1146
def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
1147
raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
1041
def wrong_old_perms(self, filename, old_perms, new_perms):
1042
raise WrongOldPermissions(filename, old_perms, new_perms)
1149
1044
def rmdir_non_empty(self, filename):
1150
1045
raise DeletingNonEmptyDirectory(filename)
1155
1050
def patch_target_missing(self, filename, contents):
1156
1051
raise PatchTargetMissing(filename)
1158
def missing_for_exec_flag(self, filename):
1159
raise MissingForExecFlag(filename)
1053
def missing_for_chmod(self, filename):
1054
raise MissingPermsFile(filename)
1161
1056
def missing_for_rm(self, filename, change):
1162
1057
raise MissingForRm(filename)
1164
def missing_for_rename(self, filename, to_path):
1165
raise MissingForRename(filename, to_path)
1059
def missing_for_rename(self, filename):
1060
raise MissingForRename(filename)
1167
1062
def missing_for_merge(self, file_id, other_path):
1168
1063
raise MissingForMerge(other_path)
1399
class UnsupportedFiletype(Exception):
1400
def __init__(self, kind, full_path):
1401
msg = "The file \"%s\" is a %s, which is not a supported filetype." \
1283
class UnsuppportedFiletype(Exception):
1284
def __init__(self, full_path, stat_result):
1285
msg = "The file \"%s\" is not a supported filetype." % full_path
1403
1286
Exception.__init__(self, msg)
1404
1287
self.full_path = full_path
1288
self.stat_result = stat_result
1407
1290
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1408
1291
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1411
1293
class ChangesetGenerator(object):
1412
1294
def __init__(self, tree_a, tree_b, interesting_ids=None):
1413
1295
object.__init__(self)
1506
1388
if cs_entry is None:
1509
cs_entry.metadata_change = self.make_exec_flag_change(id)
1511
1390
if id in self.tree_a and id in self.tree_b:
1512
1391
a_sha1 = self.tree_a.get_file_sha1(id)
1513
1392
b_sha1 = self.tree_b.get_file_sha1(id)
1514
1393
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1515
1394
return cs_entry
1517
cs_entry.contents_change = self.make_contents_change(id)
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,
1518
1406
return cs_entry
1520
def make_exec_flag_change(self, file_id):
1521
exec_flag_a = exec_flag_b = None
1522
if file_id in self.tree_a and self.tree_a.kind(file_id) == "file":
1523
exec_flag_a = self.tree_a.is_executable(file_id)
1525
if file_id in self.tree_b and self.tree_b.kind(file_id) == "file":
1526
exec_flag_b = self.tree_b.is_executable(file_id)
1528
if exec_flag_a == exec_flag_b:
1530
return ChangeExecFlag(exec_flag_a, exec_flag_b)
1532
def make_contents_change(self, file_id):
1533
a_contents = get_contents(self.tree_a, file_id)
1534
b_contents = get_contents(self.tree_b, file_id)
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)
1535
1433
if a_contents == b_contents:
1537
1435
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)
1540
def get_contents(tree, file_id):
1541
"""Return the appropriate contents to create a copy of file_id from tree"""
1542
if file_id not in tree:
1544
kind = tree.kind(file_id)
1546
return TreeFileCreate(tree, file_id)
1547
elif kind in ("directory", "root_directory"):
1549
elif kind == "symlink":
1550
return SymlinkCreate(tree.get_symlink_target(file_id))
1552
raise UnsupportedFiletype(kind, tree.id2path(file_id))
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:
1555
1460
def full_path(entry, tree):
1556
return os.path.join(tree.basedir, entry.path)
1461
return os.path.join(tree.root, entry.path)
1558
1463
def new_delete_entry(entry, tree, inventory, delete):
1559
1464
if entry.path == "":