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
35
from bzrlib.errors import BzrCheckError
31
37
__docformat__ = "restructuredtext"
42
48
newdict[value] = key
47
class ChangeUnixPermissions(object):
52
class ChangeExecFlag(object):
48
53
"""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
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
54
59
def apply(self, filename, conflict_handler, reverse=False):
56
from_mode = self.old_mode
57
to_mode = self.new_mode
61
from_exec_flag = self.old_exec_flag
62
to_exec_flag = self.new_exec_flag
59
from_mode = self.new_mode
60
to_mode = self.old_mode
64
from_exec_flag = self.new_exec_flag
65
to_exec_flag = self.old_exec_flag
62
current_mode = os.stat(filename).st_mode &0777
67
current_exec_flag = bool(os.stat(filename).st_mode & 0111)
64
69
if e.errno == errno.ENOENT:
65
if conflict_handler.missing_for_chmod(filename) == "skip":
70
if conflict_handler.missing_for_exec_flag(filename) == "skip":
68
current_mode = from_mode
73
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":
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":
75
if to_mode is not None:
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
77
94
os.chmod(filename, to_mode)
79
96
if e.errno == errno.ENOENT:
80
conflict_handler.missing_for_chmod(filename)
97
conflict_handler.missing_for_exec_flag(filename)
82
99
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:
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)
92
104
def __ne__(self, other):
93
105
return not (self == other)
95
108
def dir_create(filename, conflict_handler, reverse):
96
109
"""Creates the directory, or deletes it if reverse is true. Intended to be
97
110
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":
226
306
def reversed(sequence):
227
307
max = len(sequence) - 1
228
308
for i in range(len(sequence)):
340
433
def __ne__(self, other):
341
434
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)
343
444
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,
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,
825
938
entry.apply(path, conflict_handler, reverse)
826
939
temp_name[entry.id] = None
941
elif entry.needs_rename():
942
if entry.is_creation(reverse):
829
944
to_name = os.path.join(temp_dir, str(i))
830
945
src_path = inventory.get(entry.id)
831
946
if src_path is not None:
832
947
src_path = os.path.join(dir, src_path)
834
os.rename(src_path, to_name)
949
rename(src_path, to_name)
835
950
temp_name[entry.id] = to_name
836
951
except OSError, e:
837
952
if e.errno != errno.ENOENT:
839
if conflict_handler.missing_for_rename(src_path) == "skip":
954
if conflict_handler.missing_for_rename(src_path, to_name) \
864
980
new_path = os.path.join(dir, new_tree_path)
865
981
old_path = changed_inventory.get(entry.id)
866
if os.path.exists(new_path):
982
if bzrlib.osutils.lexists(new_path):
867
983
if conflict_handler.target_exists(entry, new_path, old_path) == \
870
986
if entry.is_creation(reverse):
871
987
entry.apply(new_path, conflict_handler, reverse)
872
988
changed_inventory[entry.id] = new_tree_path
989
elif entry.needs_rename():
990
if entry.is_deletion(reverse):
874
992
if old_path is None:
877
os.rename(old_path, new_path)
995
mutter('rename %s to final name %s', old_path, new_path)
996
rename(old_path, new_path)
878
997
changed_inventory[entry.id] = new_tree_path
879
998
except OSError, e:
880
raise Exception ("%s is missing" % new_path)
999
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
1000
% (old_path, new_path, entry, e))
882
1002
class TargetExists(Exception):
883
1003
def __init__(self, entry, target):
915
1035
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
916
1036
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
1038
class WrongOldContents(Exception):
934
1039
def __init__(self, filename):
935
1040
msg = "Contents mismatch deleting %s" % filename
936
1041
self.filename = filename
937
1042
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)
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)
943
1048
self.filename = filename
944
1049
Exception.__init__(self, msg)
988
1093
msg = "Conflicting contents for new file %s" % (filename)
989
1094
Exception.__init__(self, msg)
1096
class WeaveMergeConflict(Exception):
1097
def __init__(self, filename):
1098
msg = "Conflicting contents for file %s" % (filename)
1099
Exception.__init__(self, msg)
1101
class ThreewayContentsConflict(Exception):
1102
def __init__(self, filename):
1103
msg = "Conflicting contents for file %s" % (filename)
1104
Exception.__init__(self, msg)
992
1107
class MissingForMerge(Exception):
993
1108
def __init__(self, filename):
1029
1141
os.unlink(new_file)
1030
1142
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
1144
def wrong_old_contents(self, filename, expected_contents):
1036
1145
raise WrongOldContents(filename)
1038
1147
def rem_contents_conflict(self, filename, this_contents, base_contents):
1039
1148
raise RemoveContentsConflict(filename)
1041
def wrong_old_perms(self, filename, old_perms, new_perms):
1042
raise WrongOldPermissions(filename, old_perms, new_perms)
1150
def wrong_old_exec_flag(self, filename, old_exec_flag, new_exec_flag):
1151
raise WrongOldExecFlag(filename, old_exec_flag, new_exec_flag)
1044
1153
def rmdir_non_empty(self, filename):
1045
1154
raise DeletingNonEmptyDirectory(filename)
1050
1159
def patch_target_missing(self, filename, contents):
1051
1160
raise PatchTargetMissing(filename)
1053
def missing_for_chmod(self, filename):
1054
raise MissingPermsFile(filename)
1162
def missing_for_exec_flag(self, filename):
1163
raise MissingForExecFlag(filename)
1056
1165
def missing_for_rm(self, filename, change):
1057
1166
raise MissingForRm(filename)
1059
def missing_for_rename(self, filename):
1060
raise MissingForRename(filename)
1168
def missing_for_rename(self, filename, to_path):
1169
raise MissingForRename(filename, to_path)
1062
1171
def missing_for_merge(self, file_id, other_path):
1063
1172
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
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." \
1286
1407
Exception.__init__(self, msg)
1287
1408
self.full_path = full_path
1288
self.stat_result = stat_result
1290
1411
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1291
1412
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1293
1415
class ChangesetGenerator(object):
1294
1416
def __init__(self, tree_a, tree_b, interesting_ids=None):
1295
1417
object.__init__(self)
1388
1510
if cs_entry is None:
1513
cs_entry.metadata_change = self.make_exec_flag_change(id)
1390
1515
if id in self.tree_a and id in self.tree_b:
1391
1516
a_sha1 = self.tree_a.get_file_sha1(id)
1392
1517
b_sha1 = self.tree_b.get_file_sha1(id)
1393
1518
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1394
1519
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,
1521
cs_entry.contents_change = self.make_contents_change(id)
1406
1522
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)
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)
1433
1539
if a_contents == b_contents:
1435
1541
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:
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))
1460
1559
def full_path(entry, tree):
1461
return os.path.join(tree.root, entry.path)
1560
return os.path.join(tree.basedir, entry.path)
1463
1562
def new_delete_entry(entry, tree, inventory, delete):
1464
1563
if entry.path == "":