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,
796
683
if parent == NULL_ID or parent is None:
798
685
raise SourceRootHasName(self, to_name)
801
parent_entry = changeset.entries.get(parent)
802
if parent_entry is None:
688
if from_dir == to_dir:
803
689
dir = os.path.dirname(id_map[self.id])
805
mutter("path, new_path: %r %r", self.path, self.new_path)
691
mutter("path, new_path: %r %r" % (self.path, self.new_path))
692
parent_entry = changeset.entries[parent]
806
693
dir = parent_entry.get_new_path(id_map, changeset, reverse)
807
694
if from_name == to_name:
808
695
name = os.path.basename(id_map[self.id])
938
825
entry.apply(path, conflict_handler, reverse)
939
826
temp_name[entry.id] = None
941
elif entry.needs_rename():
942
if entry.is_creation(reverse):
944
829
to_name = os.path.join(temp_dir, str(i))
945
830
src_path = inventory.get(entry.id)
946
831
if src_path is not None:
947
832
src_path = os.path.join(dir, src_path)
949
rename(src_path, to_name)
834
os.rename(src_path, to_name)
950
835
temp_name[entry.id] = to_name
951
836
except OSError, e:
952
837
if e.errno != errno.ENOENT:
954
if conflict_handler.missing_for_rename(src_path, to_name) \
839
if conflict_handler.missing_for_rename(src_path) == "skip":
980
864
new_path = os.path.join(dir, new_tree_path)
981
865
old_path = changed_inventory.get(entry.id)
982
if bzrlib.osutils.lexists(new_path):
866
if os.path.exists(new_path):
983
867
if conflict_handler.target_exists(entry, new_path, old_path) == \
986
870
if entry.is_creation(reverse):
987
871
entry.apply(new_path, conflict_handler, reverse)
988
872
changed_inventory[entry.id] = new_tree_path
989
elif entry.needs_rename():
990
if entry.is_deletion(reverse):
992
874
if old_path is None:
995
mutter('rename %s to final name %s', old_path, new_path)
996
rename(old_path, new_path)
877
os.rename(old_path, new_path)
997
878
changed_inventory[entry.id] = new_tree_path
998
879
except OSError, e:
999
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
1000
% (old_path, new_path, entry, e))
880
raise Exception ("%s is missing" % new_path)
1002
882
class TargetExists(Exception):
1003
883
def __init__(self, entry, target):
1035
915
Exception.__init__(self, "Conflict applying changes to %s" % this_path)
1036
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)
1038
933
class WrongOldContents(Exception):
1039
934
def __init__(self, filename):
1040
935
msg = "Contents mismatch deleting %s" % filename
1041
936
self.filename = filename
1042
937
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)
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)
1048
943
self.filename = filename
1049
944
Exception.__init__(self, msg)
1093
988
msg = "Conflicting contents for new file %s" % (filename)
1094
989
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)
1107
992
class MissingForMerge(Exception):
1108
993
def __init__(self, filename):
1141
1029
os.unlink(new_file)
1142
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)
1144
1035
def wrong_old_contents(self, filename, expected_contents):
1145
1036
raise WrongOldContents(filename)
1147
1038
def rem_contents_conflict(self, filename, this_contents, base_contents):
1148
1039
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)
1041
def wrong_old_perms(self, filename, old_perms, new_perms):
1042
raise WrongOldPermissions(filename, old_perms, new_perms)
1153
1044
def rmdir_non_empty(self, filename):
1154
1045
raise DeletingNonEmptyDirectory(filename)
1159
1050
def patch_target_missing(self, filename, contents):
1160
1051
raise PatchTargetMissing(filename)
1162
def missing_for_exec_flag(self, filename):
1163
raise MissingForExecFlag(filename)
1053
def missing_for_chmod(self, filename):
1054
raise MissingPermsFile(filename)
1165
1056
def missing_for_rm(self, filename, change):
1166
1057
raise MissingForRm(filename)
1168
def missing_for_rename(self, filename, to_path):
1169
raise MissingForRename(filename, to_path)
1059
def missing_for_rename(self, filename):
1060
raise MissingForRename(filename)
1171
1062
def missing_for_merge(self, file_id, other_path):
1172
1063
raise MissingForMerge(other_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." \
1283
class UnsuppportedFiletype(Exception):
1284
def __init__(self, full_path, stat_result):
1285
msg = "The file \"%s\" is not a supported filetype." % full_path
1407
1286
Exception.__init__(self, msg)
1408
1287
self.full_path = full_path
1288
self.stat_result = stat_result
1411
1290
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1412
1291
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1415
1293
class ChangesetGenerator(object):
1416
1294
def __init__(self, tree_a, tree_b, interesting_ids=None):
1417
1295
object.__init__(self)
1510
1388
if cs_entry is None:
1513
cs_entry.metadata_change = self.make_exec_flag_change(id)
1515
1390
if id in self.tree_a and id in self.tree_b:
1516
1391
a_sha1 = self.tree_a.get_file_sha1(id)
1517
1392
b_sha1 = self.tree_b.get_file_sha1(id)
1518
1393
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1519
1394
return cs_entry
1521
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,
1522
1406
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)
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)
1539
1433
if a_contents == b_contents:
1541
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)
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))
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:
1559
1460
def full_path(entry, tree):
1560
return os.path.join(tree.basedir, entry.path)
1461
return os.path.join(tree.root, entry.path)
1562
1463
def new_delete_entry(entry, tree, inventory, delete):
1563
1464
if entry.path == "":