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
20
from bzrlib.trace import mutter
21
from bzrlib.osutils import rename
35
from bzrlib.errors import BzrCheckError
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.
37
33
__docformat__ = "restructuredtext"
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
233
def reversed(sequence):
307
234
max = len(sequence) - 1
308
235
for i in range(len(sequence)):
433
347
def __ne__(self, other):
434
348
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
350
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,
351
new_file = filename+".new"
352
base_file = self.base.readonly_path(self.file_id)
353
other_file = self.other.readonly_path(self.file_id)
360
status = patch.diff3(new_file, filename, base, other)
362
os.chmod(new_file, os.stat(filename).st_mode)
363
rename(new_file, filename)
367
def get_lines(filename):
368
my_file = file(filename, "rb")
369
lines = my_file.readlines()
372
base_lines = get_lines(base)
373
other_lines = get_lines(other)
374
conflict_handler.merge_conflict(new_file, filename, base_lines,
798
693
raise SourceRootHasName(self, to_name)
801
parent_entry = changeset.entries.get(parent)
802
if parent_entry is None:
696
if from_dir == to_dir:
803
697
dir = os.path.dirname(id_map[self.id])
805
mutter("path, new_path: %r %r", self.path, self.new_path)
699
mutter("path, new_path: %r %r" % (self.path, self.new_path))
700
parent_entry = changeset.entries[parent]
806
701
dir = parent_entry.get_new_path(id_map, changeset, reverse)
807
702
if from_name == to_name:
808
703
name = os.path.basename(id_map[self.id])
986
878
if entry.is_creation(reverse):
987
879
entry.apply(new_path, conflict_handler, reverse)
988
880
changed_inventory[entry.id] = new_tree_path
989
elif entry.needs_rename():
990
if entry.is_deletion(reverse):
992
882
if old_path is None:
995
mutter('rename %s to final name %s', old_path, new_path)
996
885
rename(old_path, new_path)
997
886
changed_inventory[entry.id] = new_tree_path
998
887
except OSError, e:
999
raise BzrCheckError('failed to rename %s to %s for changeset entry %s: %s'
1000
% (old_path, new_path, entry, e))
888
raise Exception ("%s is missing" % new_path)
1002
890
class TargetExists(Exception):
1003
891
def __init__(self, entry, target):
1093
981
msg = "Conflicting contents for new file %s" % (filename)
1094
982
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
985
class MissingForMerge(Exception):
1108
986
def __init__(self, filename):
1165
1043
def missing_for_rm(self, filename, change):
1166
1044
raise MissingForRm(filename)
1168
def missing_for_rename(self, filename, to_path):
1169
raise MissingForRename(filename, to_path)
1046
def missing_for_rename(self, filename):
1047
raise MissingForRename(filename)
1171
1049
def missing_for_merge(self, file_id, other_path):
1172
1050
raise MissingForMerge(other_path)
1174
1052
def new_contents_conflict(self, filename, other_contents):
1175
1053
raise NewContentsConflict(filename)
1177
def weave_merge_conflict(self, filename, weave, other_i, out_file):
1178
raise WeaveMergeConflict(filename)
1180
def threeway_contents_conflict(self, filename, this_contents,
1181
base_contents, other_contents):
1182
raise ThreewayContentsConflict(filename)
1184
1055
def finalize(self):
1218
1089
#apply changes that don't affect filenames
1219
1090
for entry in changeset.entries.itervalues():
1220
if not entry.is_creation_or_deletion() and not entry.is_boring():
1221
if entry.id not in inventory:
1222
warning("entry {%s} no longer present, can't be updated",
1091
if not entry.is_creation_or_deletion():
1225
1092
path = os.path.join(dir, inventory[entry.id])
1226
1093
entry.apply(path, conflict_handler, reverse)
1246
1113
r_inventory = {}
1247
1114
for entry in tree.source_inventory().itervalues():
1248
1115
inventory[entry.id] = entry.path
1249
new_inventory = apply_changeset(cset, r_inventory, tree.basedir,
1116
new_inventory = apply_changeset(cset, r_inventory, tree.root,
1250
1117
reverse=reverse)
1251
1118
new_entries, remove_entries = \
1252
1119
get_inventory_change(inventory, new_inventory, cset, reverse)
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." \
1270
class UnsuppportedFiletype(Exception):
1271
def __init__(self, full_path, stat_result):
1272
msg = "The file \"%s\" is not a supported filetype." % full_path
1407
1273
Exception.__init__(self, msg)
1408
1274
self.full_path = full_path
1275
self.stat_result = stat_result
1411
1277
def generate_changeset(tree_a, tree_b, interesting_ids=None):
1412
1278
return ChangesetGenerator(tree_a, tree_b, interesting_ids)()
1518
1389
if None not in (a_sha1, b_sha1) and a_sha1 == b_sha1:
1519
1390
return cs_entry
1521
cs_entry.contents_change = self.make_contents_change(id)
1392
cs_entry.contents_change = self.make_contents_change(full_path_a,
1522
1396
return cs_entry
1524
def make_exec_flag_change(self, file_id):
1398
def make_exec_flag_change(self, stat_a, stat_b):
1525
1399
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)
1400
if stat_a is not None and not stat.S_ISLNK(stat_a.st_mode):
1401
exec_flag_a = bool(stat_a.st_mode & 0111)
1402
if stat_b is not None and not stat.S_ISLNK(stat_b.st_mode):
1403
exec_flag_b = bool(stat_b.st_mode & 0111)
1532
1404
if exec_flag_a == exec_flag_b:
1534
1406
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_contents_change(self, full_path_a, stat_a, full_path_b, stat_b):
1409
if stat_a is None and stat_b is None:
1411
if None not in (stat_a, stat_b) and stat.S_ISDIR(stat_a.st_mode) and\
1412
stat.S_ISDIR(stat_b.st_mode):
1414
if None not in (stat_a, stat_b) and stat.S_ISREG(stat_a.st_mode) and\
1415
stat.S_ISREG(stat_b.st_mode):
1416
if stat_a.st_ino == stat_b.st_ino and \
1417
stat_a.st_dev == stat_b.st_dev:
1420
a_contents = self.get_contents(stat_a, full_path_a)
1421
b_contents = self.get_contents(stat_b, full_path_b)
1539
1422
if a_contents == b_contents:
1541
1424
return ReplaceContents(a_contents, b_contents)
1426
def get_contents(self, stat_result, full_path):
1427
if stat_result is None:
1429
elif stat.S_ISREG(stat_result.st_mode):
1430
return FileCreate(file(full_path, "rb").read())
1431
elif stat.S_ISDIR(stat_result.st_mode):
1433
elif stat.S_ISLNK(stat_result.st_mode):
1434
return SymlinkCreate(os.readlink(full_path))
1436
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))
1438
def lstat(self, full_path):
1440
if full_path is not None:
1442
stat_result = os.lstat(full_path)
1444
if e.errno != errno.ENOENT:
1559
1449
def full_path(entry, tree):
1560
return os.path.join(tree.basedir, entry.path)
1450
return os.path.join(tree.root, entry.path)
1562
1452
def new_delete_entry(entry, tree, inventory, delete):
1563
1453
if entry.path == "":