80
79
MutableGitIndexTree,
82
81
from .mapping import (
88
CONFLICT_SUFFIXES = ['.BASE', '.OTHER', '.THIS']
91
# TODO: There should be a base revid attribute to better inform the user about
92
# how the conflicts were generated.
93
class TextConflict(_mod_conflicts.Conflict):
94
"""The merge algorithm could not resolve all differences encountered."""
98
typestring = 'text conflict'
100
_conflict_re = re.compile(b'^(<{7}|={7}|>{7})')
102
def associated_filenames(self):
103
return [self.path + suffix for suffix in CONFLICT_SUFFIXES]
105
def _resolve(self, tt, winner_suffix):
106
"""Resolve the conflict by copying one of .THIS or .OTHER into file.
108
:param tt: The TreeTransform where the conflict is resolved.
109
:param winner_suffix: Either 'THIS' or 'OTHER'
111
The resolution is symmetric, when taking THIS, item.THIS is renamed
112
into item and vice-versa. This takes one of the files as a whole
113
ignoring every difference that could have been merged cleanly.
115
# To avoid useless copies, we switch item and item.winner_suffix, only
116
# item will exist after the conflict has been resolved anyway.
117
item_tid = tt.trans_id_tree_path(self.path)
118
item_parent_tid = tt.get_tree_parent(item_tid)
119
winner_path = self.path + '.' + winner_suffix
120
winner_tid = tt.trans_id_tree_path(winner_path)
121
winner_parent_tid = tt.get_tree_parent(winner_tid)
122
# Switch the paths to preserve the content
123
tt.adjust_path(osutils.basename(self.path),
124
winner_parent_tid, winner_tid)
125
tt.adjust_path(osutils.basename(winner_path),
126
item_parent_tid, item_tid)
127
tt.unversion_file(item_tid)
128
tt.version_file(winner_tid)
131
def action_auto(self, tree):
132
# GZ 2012-07-27: Using NotImplementedError to signal that a conflict
133
# can't be auto resolved does not seem ideal.
135
kind = tree.kind(self.path)
136
except errors.NoSuchFile:
139
raise NotImplementedError("Conflict is not a file")
140
conflict_markers_in_line = self._conflict_re.search
141
with tree.get_file(self.path) as f:
143
if conflict_markers_in_line(line):
144
raise NotImplementedError("Conflict markers present")
146
def _resolve_with_cleanups(self, tree, *args, **kwargs):
147
with tree.transform() as tt:
148
self._resolve(tt, *args, **kwargs)
150
def action_take_this(self, tree):
151
self._resolve_with_cleanups(tree, 'THIS')
153
def action_take_other(self, tree):
154
self._resolve_with_cleanups(tree, 'OTHER')
156
def do(self, action, tree):
157
"""Apply the specified action to the conflict.
159
:param action: The method name to call.
161
:param tree: The tree passed as a parameter to the method.
163
meth = getattr(self, 'action_%s' % action, None)
165
raise NotImplementedError(self.__class__.__name__ + '.' + action)
168
def action_done(self, tree):
169
"""Mark the conflict as solved once it has been handled."""
170
# This method does nothing but simplifies the design of upper levels.
174
return 'Text conflict in %(path)s' % self.__dict__
177
return self.describe()
180
return "%s(%r)" % (type(self).__name__, self.path)
87
183
class GitWorkingTree(MutableGitIndexTree, workingtree.WorkingTree):
88
184
"""A Git working tree."""
973
1068
- (current_inv[0][0] < cur_disk_dir_relpath))
974
1069
if direction > 0:
975
1070
# disk is before inventory - unknown
976
dirblock = [(relpath, basename, kind, stat, None, None) for
1071
dirblock = [(relpath, basename, kind, stat, None) for
977
1072
relpath, basename, kind, stat, top_path in
978
1073
cur_disk_dir_content]
979
yield (cur_disk_dir_relpath, None), dirblock
1074
yield cur_disk_dir_relpath, dirblock
981
1076
current_disk = next(disk_iterator)
982
1077
except StopIteration:
983
1078
disk_finished = True
984
1079
elif direction < 0:
985
1080
# inventory is before disk - missing.
986
dirblock = [(relpath, basename, 'unknown', None, fileid, kind)
1081
dirblock = [(relpath, basename, 'unknown', None, kind)
987
1082
for relpath, basename, dkind, stat, fileid, kind in
989
yield (current_inv[0][0], current_inv[0][1]), dirblock
1084
yield current_inv[0][0], dirblock
991
1086
current_inv = next(inventory_iterator)
992
1087
except StopIteration:
1004
1099
# versioned, present file
1005
1100
dirblock.append((inv_row[0],
1006
1101
inv_row[1], disk_row[2],
1007
disk_row[3], inv_row[4],
1102
disk_row[3], inv_row[5]))
1009
1103
elif len(path_elements[0]) == 5:
1010
1104
# unknown disk file
1011
1105
dirblock.append(
1012
1106
(path_elements[0][0], path_elements[0][1],
1013
1107
path_elements[0][2], path_elements[0][3],
1015
1109
elif len(path_elements[0]) == 6:
1016
1110
# versioned, absent file.
1017
1111
dirblock.append(
1018
1112
(path_elements[0][0], path_elements[0][1],
1019
'unknown', None, path_elements[0][4],
1020
1114
path_elements[0][5]))
1022
1116
raise NotImplementedError('unreachable code')
1023
yield current_inv[0], dirblock
1117
yield current_inv[0][0], dirblock
1025
1119
current_inv = next(inventory_iterator)
1026
1120
except StopIteration:
1067
1161
def store_uncommitted(self):
1068
1162
raise errors.StoringUncommittedNotSupported(self)
1070
def apply_inventory_delta(self, changes):
1071
for (old_path, new_path, file_id, ie) in changes:
1072
if old_path is not None:
1073
(index, old_subpath) = self._lookup_index(
1074
old_path.encode('utf-8'))
1076
self._index_del_entry(index, old_subpath)
1080
self._versioned_dirs = None
1081
if new_path is not None and ie.kind != 'directory':
1082
if ie.kind == 'tree-reference':
1083
self._index_add_entry(
1085
reference_revision=ie.reference_revision)
1087
self._index_add_entry(new_path, ie.kind)
1090
1164
def annotate_iter(self, path,
1091
1165
default_revision=_mod_revision.CURRENT_REVISION):
1092
1166
"""See Tree.annotate_iter
1211
1287
def pull(self, source, overwrite=False, stop_revision=None,
1212
1288
change_reporter=None, possible_transports=None, local=False,
1289
show_base=False, tag_selector=None):
1214
1290
with self.lock_write(), source.lock_read():
1215
1291
old_revision = self.branch.last_revision()
1216
1292
count = self.branch.pull(source, overwrite, stop_revision,
1217
1293
possible_transports=possible_transports,
1294
local=local, tag_selector=tag_selector)
1219
1295
self._update_git_tree(
1220
1296
old_revision=old_revision,
1221
1297
new_revision=self.branch.last_revision(),
1360
1441
branch_location)
1363
b'path', tree_path.encode('utf-8'))
1444
b'path', encode_git_path(tree_path))
1366
1447
b'url', branch_location.encode('utf-8'))
1367
1448
config.write_to_path(path)
1368
1449
self.add('.gitmodules')
1453
def update(self, change_reporter=None, possible_transports=None,
1454
revision=None, old_tip=_marker, show_base=False):
1455
"""Update a working tree along its branch.
1457
This will update the branch if its bound too, which means we have
1458
multiple trees involved:
1460
- The new basis tree of the master.
1461
- The old basis tree of the branch.
1462
- The old basis tree of the working tree.
1463
- The current working tree state.
1465
Pathologically, all three may be different, and non-ancestors of each
1466
other. Conceptually we want to:
1468
- Preserve the wt.basis->wt.state changes
1469
- Transform the wt.basis to the new master basis.
1470
- Apply a merge of the old branch basis to get any 'local' changes from
1472
- Restore the wt.basis->wt.state changes.
1474
There isn't a single operation at the moment to do that, so we:
1476
- Merge current state -> basis tree of the master w.r.t. the old tree
1478
- Do a 'normal' merge of the old branch basis if it is relevant.
1480
:param revision: The target revision to update to. Must be in the
1482
:param old_tip: If branch.update() has already been run, the value it
1483
returned (old tip of the branch or None). _marker is used
1486
if self.branch.get_bound_location() is not None:
1488
update_branch = (old_tip is self._marker)
1490
self.lock_tree_write()
1491
update_branch = False
1494
old_tip = self.branch.update(possible_transports)
1496
if old_tip is self._marker:
1498
return self._update_tree(old_tip, change_reporter, revision, show_base)
1502
def _update_tree(self, old_tip=None, change_reporter=None, revision=None,
1504
"""Update a tree to the master branch.
1506
:param old_tip: if supplied, the previous tip revision the branch,
1507
before it was changed to the master branch's tip.
1509
# here if old_tip is not None, it is the old tip of the branch before
1510
# it was updated from the master branch. This should become a pending
1511
# merge in the working tree to preserve the user existing work. we
1512
# cant set that until we update the working trees last revision to be
1513
# one from the new branch, because it will just get absorbed by the
1514
# parent de-duplication logic.
1516
# We MUST save it even if an error occurs, because otherwise the users
1517
# local work is unreferenced and will appear to have been lost.
1519
with self.lock_tree_write():
1520
from .. import merge
1523
last_rev = self.get_parent_ids()[0]
1525
last_rev = _mod_revision.NULL_REVISION
1526
if revision is None:
1527
revision = self.branch.last_revision()
1529
old_tip = old_tip or _mod_revision.NULL_REVISION
1531
if not _mod_revision.is_null(old_tip) and old_tip != last_rev:
1532
# the branch we are bound to was updated
1533
# merge those changes in first
1534
base_tree = self.basis_tree()
1535
other_tree = self.branch.repository.revision_tree(old_tip)
1536
nb_conflicts = merge.merge_inner(self.branch, other_tree,
1537
base_tree, this_tree=self,
1538
change_reporter=change_reporter,
1539
show_base=show_base)
1541
self.add_parent_tree((old_tip, other_tree))
1544
if last_rev != _mod_revision.ensure_null(revision):
1545
to_tree = self.branch.repository.revision_tree(revision)
1547
# determine the branch point
1548
graph = self.branch.repository.get_graph()
1549
base_rev_id = graph.find_unique_lca(self.branch.last_revision(),
1551
base_tree = self.branch.repository.revision_tree(base_rev_id)
1553
nb_conflicts = merge.merge_inner(self.branch, to_tree, base_tree,
1555
change_reporter=change_reporter,
1556
show_base=show_base)
1557
self.set_last_revision(revision)
1558
# TODO - dedup parents list with things merged by pull ?
1559
# reuse the tree we've updated to to set the basis:
1560
parent_trees = [(revision, to_tree)]
1561
merges = self.get_parent_ids()[1:]
1562
# Ideally we ask the tree for the trees here, that way the working
1563
# tree can decide whether to give us the entire tree or give us a
1564
# lazy initialised tree. dirstate for instance will have the trees
1565
# in ram already, whereas a last-revision + basis-inventory tree
1566
# will not, but also does not need them when setting parents.
1567
for parent in merges:
1568
parent_trees.append(
1569
(parent, self.branch.repository.revision_tree(parent)))
1570
if not _mod_revision.is_null(old_tip):
1571
parent_trees.append(
1572
(old_tip, self.branch.repository.revision_tree(old_tip)))
1573
self.set_parent_trees(parent_trees)
1574
last_rev = parent_trees[0][0]
1371
1578
class GitWorkingTreeFormat(workingtree.WorkingTreeFormat):