##// END OF EJS Templates
mergestate: move most of of reset() into start()...
Martin von Zweigbergk -
r46066:9ea4b52a default
parent child Browse files
Show More
@@ -1,898 +1,898 b''
1 from __future__ import absolute_import
1 from __future__ import absolute_import
2
2
3 import collections
3 import collections
4 import errno
4 import errno
5 import shutil
5 import shutil
6 import struct
6 import struct
7
7
8 from .i18n import _
8 from .i18n import _
9 from .node import (
9 from .node import (
10 bin,
10 bin,
11 hex,
11 hex,
12 nullhex,
12 nullhex,
13 nullid,
13 nullid,
14 )
14 )
15 from .pycompat import delattr
15 from .pycompat import delattr
16 from . import (
16 from . import (
17 error,
17 error,
18 filemerge,
18 filemerge,
19 pycompat,
19 pycompat,
20 util,
20 util,
21 )
21 )
22 from .utils import hashutil
22 from .utils import hashutil
23
23
24 _pack = struct.pack
24 _pack = struct.pack
25 _unpack = struct.unpack
25 _unpack = struct.unpack
26
26
27
27
28 def _droponode(data):
28 def _droponode(data):
29 # used for compatibility for v1
29 # used for compatibility for v1
30 bits = data.split(b'\0')
30 bits = data.split(b'\0')
31 bits = bits[:-2] + bits[-1:]
31 bits = bits[:-2] + bits[-1:]
32 return b'\0'.join(bits)
32 return b'\0'.join(bits)
33
33
34
34
35 def _filectxorabsent(hexnode, ctx, f):
35 def _filectxorabsent(hexnode, ctx, f):
36 if hexnode == nullhex:
36 if hexnode == nullhex:
37 return filemerge.absentfilectx(ctx, f)
37 return filemerge.absentfilectx(ctx, f)
38 else:
38 else:
39 return ctx[f]
39 return ctx[f]
40
40
41
41
42 # Merge state record types. See ``mergestate`` docs for more.
42 # Merge state record types. See ``mergestate`` docs for more.
43
43
44 ####
44 ####
45 # merge records which records metadata about a current merge
45 # merge records which records metadata about a current merge
46 # exists only once in a mergestate
46 # exists only once in a mergestate
47 #####
47 #####
48 RECORD_LOCAL = b'L'
48 RECORD_LOCAL = b'L'
49 RECORD_OTHER = b'O'
49 RECORD_OTHER = b'O'
50 # record merge labels
50 # record merge labels
51 RECORD_LABELS = b'l'
51 RECORD_LABELS = b'l'
52 # store info about merge driver used and it's state
52 # store info about merge driver used and it's state
53 RECORD_MERGE_DRIVER_STATE = b'm'
53 RECORD_MERGE_DRIVER_STATE = b'm'
54
54
55 #####
55 #####
56 # record extra information about files, with one entry containing info about one
56 # record extra information about files, with one entry containing info about one
57 # file. Hence, multiple of them can exists
57 # file. Hence, multiple of them can exists
58 #####
58 #####
59 RECORD_FILE_VALUES = b'f'
59 RECORD_FILE_VALUES = b'f'
60
60
61 #####
61 #####
62 # merge records which represents state of individual merges of files/folders
62 # merge records which represents state of individual merges of files/folders
63 # These are top level records for each entry containing merge related info.
63 # These are top level records for each entry containing merge related info.
64 # Each record of these has info about one file. Hence multiple of them can
64 # Each record of these has info about one file. Hence multiple of them can
65 # exists
65 # exists
66 #####
66 #####
67 RECORD_MERGED = b'F'
67 RECORD_MERGED = b'F'
68 RECORD_CHANGEDELETE_CONFLICT = b'C'
68 RECORD_CHANGEDELETE_CONFLICT = b'C'
69 RECORD_MERGE_DRIVER_MERGE = b'D'
69 RECORD_MERGE_DRIVER_MERGE = b'D'
70 # the path was dir on one side of merge and file on another
70 # the path was dir on one side of merge and file on another
71 RECORD_PATH_CONFLICT = b'P'
71 RECORD_PATH_CONFLICT = b'P'
72
72
73 #####
73 #####
74 # possible state which a merge entry can have. These are stored inside top-level
74 # possible state which a merge entry can have. These are stored inside top-level
75 # merge records mentioned just above.
75 # merge records mentioned just above.
76 #####
76 #####
77 MERGE_RECORD_UNRESOLVED = b'u'
77 MERGE_RECORD_UNRESOLVED = b'u'
78 MERGE_RECORD_RESOLVED = b'r'
78 MERGE_RECORD_RESOLVED = b'r'
79 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
79 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
80 MERGE_RECORD_RESOLVED_PATH = b'pr'
80 MERGE_RECORD_RESOLVED_PATH = b'pr'
81 MERGE_RECORD_DRIVER_RESOLVED = b'd'
81 MERGE_RECORD_DRIVER_RESOLVED = b'd'
82 # represents that the file was automatically merged in favor
82 # represents that the file was automatically merged in favor
83 # of other version. This info is used on commit.
83 # of other version. This info is used on commit.
84 # This is now deprecated and commit related information is now
84 # This is now deprecated and commit related information is now
85 # stored in RECORD_FILE_VALUES
85 # stored in RECORD_FILE_VALUES
86 MERGE_RECORD_MERGED_OTHER = b'o'
86 MERGE_RECORD_MERGED_OTHER = b'o'
87
87
88 #####
88 #####
89 # top level record which stores other unknown records. Multiple of these can
89 # top level record which stores other unknown records. Multiple of these can
90 # exists
90 # exists
91 #####
91 #####
92 RECORD_OVERRIDE = b't'
92 RECORD_OVERRIDE = b't'
93
93
94 #####
94 #####
95 # possible states which a merge driver can have. These are stored inside a
95 # possible states which a merge driver can have. These are stored inside a
96 # RECORD_MERGE_DRIVER_STATE entry
96 # RECORD_MERGE_DRIVER_STATE entry
97 #####
97 #####
98 MERGE_DRIVER_STATE_UNMARKED = b'u'
98 MERGE_DRIVER_STATE_UNMARKED = b'u'
99 MERGE_DRIVER_STATE_MARKED = b'm'
99 MERGE_DRIVER_STATE_MARKED = b'm'
100 MERGE_DRIVER_STATE_SUCCESS = b's'
100 MERGE_DRIVER_STATE_SUCCESS = b's'
101
101
102 #####
102 #####
103 # legacy records which are no longer used but kept to prevent breaking BC
103 # legacy records which are no longer used but kept to prevent breaking BC
104 #####
104 #####
105 # This record was release in 5.4 and usage was removed in 5.5
105 # This record was release in 5.4 and usage was removed in 5.5
106 LEGACY_RECORD_RESOLVED_OTHER = b'R'
106 LEGACY_RECORD_RESOLVED_OTHER = b'R'
107
107
108
108
109 ACTION_FORGET = b'f'
109 ACTION_FORGET = b'f'
110 ACTION_REMOVE = b'r'
110 ACTION_REMOVE = b'r'
111 ACTION_ADD = b'a'
111 ACTION_ADD = b'a'
112 ACTION_GET = b'g'
112 ACTION_GET = b'g'
113 ACTION_PATH_CONFLICT = b'p'
113 ACTION_PATH_CONFLICT = b'p'
114 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
114 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
115 ACTION_ADD_MODIFIED = b'am'
115 ACTION_ADD_MODIFIED = b'am'
116 ACTION_CREATED = b'c'
116 ACTION_CREATED = b'c'
117 ACTION_DELETED_CHANGED = b'dc'
117 ACTION_DELETED_CHANGED = b'dc'
118 ACTION_CHANGED_DELETED = b'cd'
118 ACTION_CHANGED_DELETED = b'cd'
119 ACTION_MERGE = b'm'
119 ACTION_MERGE = b'm'
120 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
120 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
121 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
121 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
122 ACTION_KEEP = b'k'
122 ACTION_KEEP = b'k'
123 # the file was absent on local side before merge and we should
123 # the file was absent on local side before merge and we should
124 # keep it absent (absent means file not present, it can be a result
124 # keep it absent (absent means file not present, it can be a result
125 # of file deletion, rename etc.)
125 # of file deletion, rename etc.)
126 ACTION_KEEP_ABSENT = b'ka'
126 ACTION_KEEP_ABSENT = b'ka'
127 ACTION_EXEC = b'e'
127 ACTION_EXEC = b'e'
128 ACTION_CREATED_MERGE = b'cm'
128 ACTION_CREATED_MERGE = b'cm'
129
129
130
130
131 class mergestate(object):
131 class mergestate(object):
132 '''track 3-way merge state of individual files
132 '''track 3-way merge state of individual files
133
133
134 The merge state is stored on disk when needed. Two files are used: one with
134 The merge state is stored on disk when needed. Two files are used: one with
135 an old format (version 1), and one with a new format (version 2). Version 2
135 an old format (version 1), and one with a new format (version 2). Version 2
136 stores a superset of the data in version 1, including new kinds of records
136 stores a superset of the data in version 1, including new kinds of records
137 in the future. For more about the new format, see the documentation for
137 in the future. For more about the new format, see the documentation for
138 `_readrecordsv2`.
138 `_readrecordsv2`.
139
139
140 Each record can contain arbitrary content, and has an associated type. This
140 Each record can contain arbitrary content, and has an associated type. This
141 `type` should be a letter. If `type` is uppercase, the record is mandatory:
141 `type` should be a letter. If `type` is uppercase, the record is mandatory:
142 versions of Mercurial that don't support it should abort. If `type` is
142 versions of Mercurial that don't support it should abort. If `type` is
143 lowercase, the record can be safely ignored.
143 lowercase, the record can be safely ignored.
144
144
145 Currently known records:
145 Currently known records:
146
146
147 L: the node of the "local" part of the merge (hexified version)
147 L: the node of the "local" part of the merge (hexified version)
148 O: the node of the "other" part of the merge (hexified version)
148 O: the node of the "other" part of the merge (hexified version)
149 F: a file to be merged entry
149 F: a file to be merged entry
150 C: a change/delete or delete/change conflict
150 C: a change/delete or delete/change conflict
151 D: a file that the external merge driver will merge internally
151 D: a file that the external merge driver will merge internally
152 (experimental)
152 (experimental)
153 P: a path conflict (file vs directory)
153 P: a path conflict (file vs directory)
154 m: the external merge driver defined for this merge plus its run state
154 m: the external merge driver defined for this merge plus its run state
155 (experimental)
155 (experimental)
156 f: a (filename, dictionary) tuple of optional values for a given file
156 f: a (filename, dictionary) tuple of optional values for a given file
157 l: the labels for the parts of the merge.
157 l: the labels for the parts of the merge.
158
158
159 Merge driver run states (experimental):
159 Merge driver run states (experimental):
160 u: driver-resolved files unmarked -- needs to be run next time we're about
160 u: driver-resolved files unmarked -- needs to be run next time we're about
161 to resolve or commit
161 to resolve or commit
162 m: driver-resolved files marked -- only needs to be run before commit
162 m: driver-resolved files marked -- only needs to be run before commit
163 s: success/skipped -- does not need to be run any more
163 s: success/skipped -- does not need to be run any more
164
164
165 Merge record states (stored in self._state, indexed by filename):
165 Merge record states (stored in self._state, indexed by filename):
166 u: unresolved conflict
166 u: unresolved conflict
167 r: resolved conflict
167 r: resolved conflict
168 pu: unresolved path conflict (file conflicts with directory)
168 pu: unresolved path conflict (file conflicts with directory)
169 pr: resolved path conflict
169 pr: resolved path conflict
170 d: driver-resolved conflict
170 d: driver-resolved conflict
171
171
172 The resolve command transitions between 'u' and 'r' for conflicts and
172 The resolve command transitions between 'u' and 'r' for conflicts and
173 'pu' and 'pr' for path conflicts.
173 'pu' and 'pr' for path conflicts.
174 '''
174 '''
175
175
176 statepathv1 = b'merge/state'
176 statepathv1 = b'merge/state'
177 statepathv2 = b'merge/state2'
177 statepathv2 = b'merge/state2'
178
178
179 @staticmethod
179 @staticmethod
180 def clean(repo):
180 def clean(repo):
181 """Initialize a brand new merge state, removing any existing state on
181 """Initialize a brand new merge state, removing any existing state on
182 disk."""
182 disk."""
183 ms = mergestate(repo)
183 ms = mergestate(repo)
184 ms.reset()
184 ms.reset()
185 return ms
185 return ms
186
186
187 @staticmethod
187 @staticmethod
188 def read(repo):
188 def read(repo):
189 """Initialize the merge state, reading it from disk."""
189 """Initialize the merge state, reading it from disk."""
190 ms = mergestate(repo)
190 ms = mergestate(repo)
191 ms._read()
191 ms._read()
192 return ms
192 return ms
193
193
194 def __init__(self, repo):
194 def __init__(self, repo):
195 """Initialize the merge state.
195 """Initialize the merge state.
196
196
197 Do not use this directly! Instead call read() or clean()."""
197 Do not use this directly! Instead call read() or clean()."""
198 self._repo = repo
198 self._repo = repo
199 self._dirty = False
199 self._dirty = False
200 self._labels = None
200 self._labels = None
201
201
202 def reset(self):
202 def reset(self):
203 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
204
205 def start(self, node, other, labels=None):
206 self._local = node
207 self._other = other
208 self._labels = labels
203 self._state = {}
209 self._state = {}
204 self._stateextras = collections.defaultdict(dict)
210 self._stateextras = collections.defaultdict(dict)
205 for var in ('localctx', 'otherctx'):
211 for var in ('localctx', 'otherctx'):
206 if var in vars(self):
212 if var in vars(self):
207 delattr(self, var)
213 delattr(self, var)
208 self._readmergedriver = None
214 self._readmergedriver = None
209 if self.mergedriver:
215 if self.mergedriver:
210 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
216 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
211 else:
217 else:
212 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
218 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
213 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
214 self._results = {}
219 self._results = {}
215 self._dirty = False
220 self._dirty = False
216
221
217 def start(self, node, other, labels=None):
218 self._local = node
219 self._other = other
220 self._labels = labels
221
222 def _read(self):
222 def _read(self):
223 """Analyse each record content to restore a serialized state from disk
223 """Analyse each record content to restore a serialized state from disk
224
224
225 This function process "record" entry produced by the de-serialization
225 This function process "record" entry produced by the de-serialization
226 of on disk file.
226 of on disk file.
227 """
227 """
228 self._state = {}
228 self._state = {}
229 self._stateextras = collections.defaultdict(dict)
229 self._stateextras = collections.defaultdict(dict)
230 self._local = None
230 self._local = None
231 self._other = None
231 self._other = None
232 for var in ('localctx', 'otherctx'):
232 for var in ('localctx', 'otherctx'):
233 if var in vars(self):
233 if var in vars(self):
234 delattr(self, var)
234 delattr(self, var)
235 self._readmergedriver = None
235 self._readmergedriver = None
236 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
236 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
237 unsupported = set()
237 unsupported = set()
238 records = self._readrecords()
238 records = self._readrecords()
239 for rtype, record in records:
239 for rtype, record in records:
240 if rtype == RECORD_LOCAL:
240 if rtype == RECORD_LOCAL:
241 self._local = bin(record)
241 self._local = bin(record)
242 elif rtype == RECORD_OTHER:
242 elif rtype == RECORD_OTHER:
243 self._other = bin(record)
243 self._other = bin(record)
244 elif rtype == RECORD_MERGE_DRIVER_STATE:
244 elif rtype == RECORD_MERGE_DRIVER_STATE:
245 bits = record.split(b'\0', 1)
245 bits = record.split(b'\0', 1)
246 mdstate = bits[1]
246 mdstate = bits[1]
247 if len(mdstate) != 1 or mdstate not in (
247 if len(mdstate) != 1 or mdstate not in (
248 MERGE_DRIVER_STATE_UNMARKED,
248 MERGE_DRIVER_STATE_UNMARKED,
249 MERGE_DRIVER_STATE_MARKED,
249 MERGE_DRIVER_STATE_MARKED,
250 MERGE_DRIVER_STATE_SUCCESS,
250 MERGE_DRIVER_STATE_SUCCESS,
251 ):
251 ):
252 # the merge driver should be idempotent, so just rerun it
252 # the merge driver should be idempotent, so just rerun it
253 mdstate = MERGE_DRIVER_STATE_UNMARKED
253 mdstate = MERGE_DRIVER_STATE_UNMARKED
254
254
255 self._readmergedriver = bits[0]
255 self._readmergedriver = bits[0]
256 self._mdstate = mdstate
256 self._mdstate = mdstate
257 elif rtype in (
257 elif rtype in (
258 RECORD_MERGED,
258 RECORD_MERGED,
259 RECORD_CHANGEDELETE_CONFLICT,
259 RECORD_CHANGEDELETE_CONFLICT,
260 RECORD_PATH_CONFLICT,
260 RECORD_PATH_CONFLICT,
261 RECORD_MERGE_DRIVER_MERGE,
261 RECORD_MERGE_DRIVER_MERGE,
262 LEGACY_RECORD_RESOLVED_OTHER,
262 LEGACY_RECORD_RESOLVED_OTHER,
263 ):
263 ):
264 bits = record.split(b'\0')
264 bits = record.split(b'\0')
265 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
265 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
266 # and we now store related information in _stateextras, so
266 # and we now store related information in _stateextras, so
267 # lets write to _stateextras directly
267 # lets write to _stateextras directly
268 if bits[1] == MERGE_RECORD_MERGED_OTHER:
268 if bits[1] == MERGE_RECORD_MERGED_OTHER:
269 self._stateextras[bits[0]][b'filenode-source'] = b'other'
269 self._stateextras[bits[0]][b'filenode-source'] = b'other'
270 else:
270 else:
271 self._state[bits[0]] = bits[1:]
271 self._state[bits[0]] = bits[1:]
272 elif rtype == RECORD_FILE_VALUES:
272 elif rtype == RECORD_FILE_VALUES:
273 filename, rawextras = record.split(b'\0', 1)
273 filename, rawextras = record.split(b'\0', 1)
274 extraparts = rawextras.split(b'\0')
274 extraparts = rawextras.split(b'\0')
275 extras = {}
275 extras = {}
276 i = 0
276 i = 0
277 while i < len(extraparts):
277 while i < len(extraparts):
278 extras[extraparts[i]] = extraparts[i + 1]
278 extras[extraparts[i]] = extraparts[i + 1]
279 i += 2
279 i += 2
280
280
281 self._stateextras[filename] = extras
281 self._stateextras[filename] = extras
282 elif rtype == RECORD_LABELS:
282 elif rtype == RECORD_LABELS:
283 labels = record.split(b'\0', 2)
283 labels = record.split(b'\0', 2)
284 self._labels = [l for l in labels if len(l) > 0]
284 self._labels = [l for l in labels if len(l) > 0]
285 elif not rtype.islower():
285 elif not rtype.islower():
286 unsupported.add(rtype)
286 unsupported.add(rtype)
287 # contains a mapping of form:
287 # contains a mapping of form:
288 # {filename : (merge_return_value, action_to_be_performed}
288 # {filename : (merge_return_value, action_to_be_performed}
289 # these are results of re-running merge process
289 # these are results of re-running merge process
290 # this dict is used to perform actions on dirstate caused by re-running
290 # this dict is used to perform actions on dirstate caused by re-running
291 # the merge
291 # the merge
292 self._results = {}
292 self._results = {}
293 self._dirty = False
293 self._dirty = False
294
294
295 if unsupported:
295 if unsupported:
296 raise error.UnsupportedMergeRecords(unsupported)
296 raise error.UnsupportedMergeRecords(unsupported)
297
297
298 def _readrecords(self):
298 def _readrecords(self):
299 """Read merge state from disk and return a list of record (TYPE, data)
299 """Read merge state from disk and return a list of record (TYPE, data)
300
300
301 We read data from both v1 and v2 files and decide which one to use.
301 We read data from both v1 and v2 files and decide which one to use.
302
302
303 V1 has been used by version prior to 2.9.1 and contains less data than
303 V1 has been used by version prior to 2.9.1 and contains less data than
304 v2. We read both versions and check if no data in v2 contradicts
304 v2. We read both versions and check if no data in v2 contradicts
305 v1. If there is not contradiction we can safely assume that both v1
305 v1. If there is not contradiction we can safely assume that both v1
306 and v2 were written at the same time and use the extract data in v2. If
306 and v2 were written at the same time and use the extract data in v2. If
307 there is contradiction we ignore v2 content as we assume an old version
307 there is contradiction we ignore v2 content as we assume an old version
308 of Mercurial has overwritten the mergestate file and left an old v2
308 of Mercurial has overwritten the mergestate file and left an old v2
309 file around.
309 file around.
310
310
311 returns list of record [(TYPE, data), ...]"""
311 returns list of record [(TYPE, data), ...]"""
312 v1records = self._readrecordsv1()
312 v1records = self._readrecordsv1()
313 v2records = self._readrecordsv2()
313 v2records = self._readrecordsv2()
314 if self._v1v2match(v1records, v2records):
314 if self._v1v2match(v1records, v2records):
315 return v2records
315 return v2records
316 else:
316 else:
317 # v1 file is newer than v2 file, use it
317 # v1 file is newer than v2 file, use it
318 # we have to infer the "other" changeset of the merge
318 # we have to infer the "other" changeset of the merge
319 # we cannot do better than that with v1 of the format
319 # we cannot do better than that with v1 of the format
320 mctx = self._repo[None].parents()[-1]
320 mctx = self._repo[None].parents()[-1]
321 v1records.append((RECORD_OTHER, mctx.hex()))
321 v1records.append((RECORD_OTHER, mctx.hex()))
322 # add place holder "other" file node information
322 # add place holder "other" file node information
323 # nobody is using it yet so we do no need to fetch the data
323 # nobody is using it yet so we do no need to fetch the data
324 # if mctx was wrong `mctx[bits[-2]]` may fails.
324 # if mctx was wrong `mctx[bits[-2]]` may fails.
325 for idx, r in enumerate(v1records):
325 for idx, r in enumerate(v1records):
326 if r[0] == RECORD_MERGED:
326 if r[0] == RECORD_MERGED:
327 bits = r[1].split(b'\0')
327 bits = r[1].split(b'\0')
328 bits.insert(-2, b'')
328 bits.insert(-2, b'')
329 v1records[idx] = (r[0], b'\0'.join(bits))
329 v1records[idx] = (r[0], b'\0'.join(bits))
330 return v1records
330 return v1records
331
331
332 def _v1v2match(self, v1records, v2records):
332 def _v1v2match(self, v1records, v2records):
333 oldv2 = set() # old format version of v2 record
333 oldv2 = set() # old format version of v2 record
334 for rec in v2records:
334 for rec in v2records:
335 if rec[0] == RECORD_LOCAL:
335 if rec[0] == RECORD_LOCAL:
336 oldv2.add(rec)
336 oldv2.add(rec)
337 elif rec[0] == RECORD_MERGED:
337 elif rec[0] == RECORD_MERGED:
338 # drop the onode data (not contained in v1)
338 # drop the onode data (not contained in v1)
339 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
339 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
340 for rec in v1records:
340 for rec in v1records:
341 if rec not in oldv2:
341 if rec not in oldv2:
342 return False
342 return False
343 else:
343 else:
344 return True
344 return True
345
345
346 def _readrecordsv1(self):
346 def _readrecordsv1(self):
347 """read on disk merge state for version 1 file
347 """read on disk merge state for version 1 file
348
348
349 returns list of record [(TYPE, data), ...]
349 returns list of record [(TYPE, data), ...]
350
350
351 Note: the "F" data from this file are one entry short
351 Note: the "F" data from this file are one entry short
352 (no "other file node" entry)
352 (no "other file node" entry)
353 """
353 """
354 records = []
354 records = []
355 try:
355 try:
356 f = self._repo.vfs(self.statepathv1)
356 f = self._repo.vfs(self.statepathv1)
357 for i, l in enumerate(f):
357 for i, l in enumerate(f):
358 if i == 0:
358 if i == 0:
359 records.append((RECORD_LOCAL, l[:-1]))
359 records.append((RECORD_LOCAL, l[:-1]))
360 else:
360 else:
361 records.append((RECORD_MERGED, l[:-1]))
361 records.append((RECORD_MERGED, l[:-1]))
362 f.close()
362 f.close()
363 except IOError as err:
363 except IOError as err:
364 if err.errno != errno.ENOENT:
364 if err.errno != errno.ENOENT:
365 raise
365 raise
366 return records
366 return records
367
367
368 def _readrecordsv2(self):
368 def _readrecordsv2(self):
369 """read on disk merge state for version 2 file
369 """read on disk merge state for version 2 file
370
370
371 This format is a list of arbitrary records of the form:
371 This format is a list of arbitrary records of the form:
372
372
373 [type][length][content]
373 [type][length][content]
374
374
375 `type` is a single character, `length` is a 4 byte integer, and
375 `type` is a single character, `length` is a 4 byte integer, and
376 `content` is an arbitrary byte sequence of length `length`.
376 `content` is an arbitrary byte sequence of length `length`.
377
377
378 Mercurial versions prior to 3.7 have a bug where if there are
378 Mercurial versions prior to 3.7 have a bug where if there are
379 unsupported mandatory merge records, attempting to clear out the merge
379 unsupported mandatory merge records, attempting to clear out the merge
380 state with hg update --clean or similar aborts. The 't' record type
380 state with hg update --clean or similar aborts. The 't' record type
381 works around that by writing out what those versions treat as an
381 works around that by writing out what those versions treat as an
382 advisory record, but later versions interpret as special: the first
382 advisory record, but later versions interpret as special: the first
383 character is the 'real' record type and everything onwards is the data.
383 character is the 'real' record type and everything onwards is the data.
384
384
385 Returns list of records [(TYPE, data), ...]."""
385 Returns list of records [(TYPE, data), ...]."""
386 records = []
386 records = []
387 try:
387 try:
388 f = self._repo.vfs(self.statepathv2)
388 f = self._repo.vfs(self.statepathv2)
389 data = f.read()
389 data = f.read()
390 off = 0
390 off = 0
391 end = len(data)
391 end = len(data)
392 while off < end:
392 while off < end:
393 rtype = data[off : off + 1]
393 rtype = data[off : off + 1]
394 off += 1
394 off += 1
395 length = _unpack(b'>I', data[off : (off + 4)])[0]
395 length = _unpack(b'>I', data[off : (off + 4)])[0]
396 off += 4
396 off += 4
397 record = data[off : (off + length)]
397 record = data[off : (off + length)]
398 off += length
398 off += length
399 if rtype == RECORD_OVERRIDE:
399 if rtype == RECORD_OVERRIDE:
400 rtype, record = record[0:1], record[1:]
400 rtype, record = record[0:1], record[1:]
401 records.append((rtype, record))
401 records.append((rtype, record))
402 f.close()
402 f.close()
403 except IOError as err:
403 except IOError as err:
404 if err.errno != errno.ENOENT:
404 if err.errno != errno.ENOENT:
405 raise
405 raise
406 return records
406 return records
407
407
408 @util.propertycache
408 @util.propertycache
409 def mergedriver(self):
409 def mergedriver(self):
410 # protect against the following:
410 # protect against the following:
411 # - A configures a malicious merge driver in their hgrc, then
411 # - A configures a malicious merge driver in their hgrc, then
412 # pauses the merge
412 # pauses the merge
413 # - A edits their hgrc to remove references to the merge driver
413 # - A edits their hgrc to remove references to the merge driver
414 # - A gives a copy of their entire repo, including .hg, to B
414 # - A gives a copy of their entire repo, including .hg, to B
415 # - B inspects .hgrc and finds it to be clean
415 # - B inspects .hgrc and finds it to be clean
416 # - B then continues the merge and the malicious merge driver
416 # - B then continues the merge and the malicious merge driver
417 # gets invoked
417 # gets invoked
418 configmergedriver = self._repo.ui.config(
418 configmergedriver = self._repo.ui.config(
419 b'experimental', b'mergedriver'
419 b'experimental', b'mergedriver'
420 )
420 )
421 if (
421 if (
422 self._readmergedriver is not None
422 self._readmergedriver is not None
423 and self._readmergedriver != configmergedriver
423 and self._readmergedriver != configmergedriver
424 ):
424 ):
425 raise error.ConfigError(
425 raise error.ConfigError(
426 _(b"merge driver changed since merge started"),
426 _(b"merge driver changed since merge started"),
427 hint=_(b"revert merge driver change or abort merge"),
427 hint=_(b"revert merge driver change or abort merge"),
428 )
428 )
429
429
430 return configmergedriver
430 return configmergedriver
431
431
432 @util.propertycache
432 @util.propertycache
433 def local(self):
433 def local(self):
434 if self._local is None:
434 if self._local is None:
435 msg = b"local accessed but self._local isn't set"
435 msg = b"local accessed but self._local isn't set"
436 raise error.ProgrammingError(msg)
436 raise error.ProgrammingError(msg)
437 return self._local
437 return self._local
438
438
439 @util.propertycache
439 @util.propertycache
440 def localctx(self):
440 def localctx(self):
441 return self._repo[self.local]
441 return self._repo[self.local]
442
442
443 @util.propertycache
443 @util.propertycache
444 def other(self):
444 def other(self):
445 if self._other is None:
445 if self._other is None:
446 msg = b"other accessed but self._other isn't set"
446 msg = b"other accessed but self._other isn't set"
447 raise error.ProgrammingError(msg)
447 raise error.ProgrammingError(msg)
448 return self._other
448 return self._other
449
449
450 @util.propertycache
450 @util.propertycache
451 def otherctx(self):
451 def otherctx(self):
452 return self._repo[self.other]
452 return self._repo[self.other]
453
453
454 def active(self):
454 def active(self):
455 """Whether mergestate is active.
455 """Whether mergestate is active.
456
456
457 Returns True if there appears to be mergestate. This is a rough proxy
457 Returns True if there appears to be mergestate. This is a rough proxy
458 for "is a merge in progress."
458 for "is a merge in progress."
459 """
459 """
460 return bool(self._local) or bool(self._state)
460 return bool(self._local) or bool(self._state)
461
461
462 def commit(self):
462 def commit(self):
463 """Write current state on disk (if necessary)"""
463 """Write current state on disk (if necessary)"""
464 if self._dirty:
464 if self._dirty:
465 records = self._makerecords()
465 records = self._makerecords()
466 self._writerecords(records)
466 self._writerecords(records)
467 self._dirty = False
467 self._dirty = False
468
468
469 def _makerecords(self):
469 def _makerecords(self):
470 records = []
470 records = []
471 records.append((RECORD_LOCAL, hex(self._local)))
471 records.append((RECORD_LOCAL, hex(self._local)))
472 records.append((RECORD_OTHER, hex(self._other)))
472 records.append((RECORD_OTHER, hex(self._other)))
473 if self.mergedriver:
473 if self.mergedriver:
474 records.append(
474 records.append(
475 (
475 (
476 RECORD_MERGE_DRIVER_STATE,
476 RECORD_MERGE_DRIVER_STATE,
477 b'\0'.join([self.mergedriver, self._mdstate]),
477 b'\0'.join([self.mergedriver, self._mdstate]),
478 )
478 )
479 )
479 )
480 # Write out state items. In all cases, the value of the state map entry
480 # Write out state items. In all cases, the value of the state map entry
481 # is written as the contents of the record. The record type depends on
481 # is written as the contents of the record. The record type depends on
482 # the type of state that is stored, and capital-letter records are used
482 # the type of state that is stored, and capital-letter records are used
483 # to prevent older versions of Mercurial that do not support the feature
483 # to prevent older versions of Mercurial that do not support the feature
484 # from loading them.
484 # from loading them.
485 for filename, v in pycompat.iteritems(self._state):
485 for filename, v in pycompat.iteritems(self._state):
486 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
486 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
487 # Driver-resolved merge. These are stored in 'D' records.
487 # Driver-resolved merge. These are stored in 'D' records.
488 records.append(
488 records.append(
489 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
489 (RECORD_MERGE_DRIVER_MERGE, b'\0'.join([filename] + v))
490 )
490 )
491 elif v[0] in (
491 elif v[0] in (
492 MERGE_RECORD_UNRESOLVED_PATH,
492 MERGE_RECORD_UNRESOLVED_PATH,
493 MERGE_RECORD_RESOLVED_PATH,
493 MERGE_RECORD_RESOLVED_PATH,
494 ):
494 ):
495 # Path conflicts. These are stored in 'P' records. The current
495 # Path conflicts. These are stored in 'P' records. The current
496 # resolution state ('pu' or 'pr') is stored within the record.
496 # resolution state ('pu' or 'pr') is stored within the record.
497 records.append(
497 records.append(
498 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
498 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
499 )
499 )
500 elif v[1] == nullhex or v[6] == nullhex:
500 elif v[1] == nullhex or v[6] == nullhex:
501 # Change/Delete or Delete/Change conflicts. These are stored in
501 # Change/Delete or Delete/Change conflicts. These are stored in
502 # 'C' records. v[1] is the local file, and is nullhex when the
502 # 'C' records. v[1] is the local file, and is nullhex when the
503 # file is deleted locally ('dc'). v[6] is the remote file, and
503 # file is deleted locally ('dc'). v[6] is the remote file, and
504 # is nullhex when the file is deleted remotely ('cd').
504 # is nullhex when the file is deleted remotely ('cd').
505 records.append(
505 records.append(
506 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
506 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
507 )
507 )
508 else:
508 else:
509 # Normal files. These are stored in 'F' records.
509 # Normal files. These are stored in 'F' records.
510 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
510 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
511 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
511 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
512 rawextras = b'\0'.join(
512 rawextras = b'\0'.join(
513 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
513 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
514 )
514 )
515 records.append(
515 records.append(
516 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
516 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
517 )
517 )
518 if self._labels is not None:
518 if self._labels is not None:
519 labels = b'\0'.join(self._labels)
519 labels = b'\0'.join(self._labels)
520 records.append((RECORD_LABELS, labels))
520 records.append((RECORD_LABELS, labels))
521 return records
521 return records
522
522
523 def _writerecords(self, records):
523 def _writerecords(self, records):
524 """Write current state on disk (both v1 and v2)"""
524 """Write current state on disk (both v1 and v2)"""
525 self._writerecordsv1(records)
525 self._writerecordsv1(records)
526 self._writerecordsv2(records)
526 self._writerecordsv2(records)
527
527
528 def _writerecordsv1(self, records):
528 def _writerecordsv1(self, records):
529 """Write current state on disk in a version 1 file"""
529 """Write current state on disk in a version 1 file"""
530 f = self._repo.vfs(self.statepathv1, b'wb')
530 f = self._repo.vfs(self.statepathv1, b'wb')
531 irecords = iter(records)
531 irecords = iter(records)
532 lrecords = next(irecords)
532 lrecords = next(irecords)
533 assert lrecords[0] == RECORD_LOCAL
533 assert lrecords[0] == RECORD_LOCAL
534 f.write(hex(self._local) + b'\n')
534 f.write(hex(self._local) + b'\n')
535 for rtype, data in irecords:
535 for rtype, data in irecords:
536 if rtype == RECORD_MERGED:
536 if rtype == RECORD_MERGED:
537 f.write(b'%s\n' % _droponode(data))
537 f.write(b'%s\n' % _droponode(data))
538 f.close()
538 f.close()
539
539
540 def _writerecordsv2(self, records):
540 def _writerecordsv2(self, records):
541 """Write current state on disk in a version 2 file
541 """Write current state on disk in a version 2 file
542
542
543 See the docstring for _readrecordsv2 for why we use 't'."""
543 See the docstring for _readrecordsv2 for why we use 't'."""
544 # these are the records that all version 2 clients can read
544 # these are the records that all version 2 clients can read
545 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
545 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
546 f = self._repo.vfs(self.statepathv2, b'wb')
546 f = self._repo.vfs(self.statepathv2, b'wb')
547 for key, data in records:
547 for key, data in records:
548 assert len(key) == 1
548 assert len(key) == 1
549 if key not in allowlist:
549 if key not in allowlist:
550 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
550 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
551 format = b'>sI%is' % len(data)
551 format = b'>sI%is' % len(data)
552 f.write(_pack(format, key, len(data), data))
552 f.write(_pack(format, key, len(data), data))
553 f.close()
553 f.close()
554
554
555 @staticmethod
555 @staticmethod
556 def getlocalkey(path):
556 def getlocalkey(path):
557 """hash the path of a local file context for storage in the .hg/merge
557 """hash the path of a local file context for storage in the .hg/merge
558 directory."""
558 directory."""
559
559
560 return hex(hashutil.sha1(path).digest())
560 return hex(hashutil.sha1(path).digest())
561
561
562 def add(self, fcl, fco, fca, fd):
562 def add(self, fcl, fco, fca, fd):
563 """add a new (potentially?) conflicting file the merge state
563 """add a new (potentially?) conflicting file the merge state
564 fcl: file context for local,
564 fcl: file context for local,
565 fco: file context for remote,
565 fco: file context for remote,
566 fca: file context for ancestors,
566 fca: file context for ancestors,
567 fd: file path of the resulting merge.
567 fd: file path of the resulting merge.
568
568
569 note: also write the local version to the `.hg/merge` directory.
569 note: also write the local version to the `.hg/merge` directory.
570 """
570 """
571 if fcl.isabsent():
571 if fcl.isabsent():
572 localkey = nullhex
572 localkey = nullhex
573 else:
573 else:
574 localkey = mergestate.getlocalkey(fcl.path())
574 localkey = mergestate.getlocalkey(fcl.path())
575 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
575 self._repo.vfs.write(b'merge/' + localkey, fcl.data())
576 self._state[fd] = [
576 self._state[fd] = [
577 MERGE_RECORD_UNRESOLVED,
577 MERGE_RECORD_UNRESOLVED,
578 localkey,
578 localkey,
579 fcl.path(),
579 fcl.path(),
580 fca.path(),
580 fca.path(),
581 hex(fca.filenode()),
581 hex(fca.filenode()),
582 fco.path(),
582 fco.path(),
583 hex(fco.filenode()),
583 hex(fco.filenode()),
584 fcl.flags(),
584 fcl.flags(),
585 ]
585 ]
586 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
586 self._stateextras[fd] = {b'ancestorlinknode': hex(fca.node())}
587 self._dirty = True
587 self._dirty = True
588
588
589 def addpathconflict(self, path, frename, forigin):
589 def addpathconflict(self, path, frename, forigin):
590 """add a new conflicting path to the merge state
590 """add a new conflicting path to the merge state
591 path: the path that conflicts
591 path: the path that conflicts
592 frename: the filename the conflicting file was renamed to
592 frename: the filename the conflicting file was renamed to
593 forigin: origin of the file ('l' or 'r' for local/remote)
593 forigin: origin of the file ('l' or 'r' for local/remote)
594 """
594 """
595 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
595 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
596 self._dirty = True
596 self._dirty = True
597
597
598 def addcommitinfo(self, path, data):
598 def addcommitinfo(self, path, data):
599 """ stores information which is required at commit
599 """ stores information which is required at commit
600 into _stateextras """
600 into _stateextras """
601 self._stateextras[path].update(data)
601 self._stateextras[path].update(data)
602 self._dirty = True
602 self._dirty = True
603
603
604 def __contains__(self, dfile):
604 def __contains__(self, dfile):
605 return dfile in self._state
605 return dfile in self._state
606
606
607 def __getitem__(self, dfile):
607 def __getitem__(self, dfile):
608 return self._state[dfile][0]
608 return self._state[dfile][0]
609
609
610 def __iter__(self):
610 def __iter__(self):
611 return iter(sorted(self._state))
611 return iter(sorted(self._state))
612
612
613 def files(self):
613 def files(self):
614 return self._state.keys()
614 return self._state.keys()
615
615
616 def mark(self, dfile, state):
616 def mark(self, dfile, state):
617 self._state[dfile][0] = state
617 self._state[dfile][0] = state
618 self._dirty = True
618 self._dirty = True
619
619
620 def mdstate(self):
620 def mdstate(self):
621 return self._mdstate
621 return self._mdstate
622
622
623 def unresolved(self):
623 def unresolved(self):
624 """Obtain the paths of unresolved files."""
624 """Obtain the paths of unresolved files."""
625
625
626 for f, entry in pycompat.iteritems(self._state):
626 for f, entry in pycompat.iteritems(self._state):
627 if entry[0] in (
627 if entry[0] in (
628 MERGE_RECORD_UNRESOLVED,
628 MERGE_RECORD_UNRESOLVED,
629 MERGE_RECORD_UNRESOLVED_PATH,
629 MERGE_RECORD_UNRESOLVED_PATH,
630 ):
630 ):
631 yield f
631 yield f
632
632
633 def driverresolved(self):
633 def driverresolved(self):
634 """Obtain the paths of driver-resolved files."""
634 """Obtain the paths of driver-resolved files."""
635
635
636 for f, entry in self._state.items():
636 for f, entry in self._state.items():
637 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
637 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
638 yield f
638 yield f
639
639
640 def extras(self, filename):
640 def extras(self, filename):
641 return self._stateextras[filename]
641 return self._stateextras[filename]
642
642
643 def _resolve(self, preresolve, dfile, wctx):
643 def _resolve(self, preresolve, dfile, wctx):
644 """rerun merge process for file path `dfile`.
644 """rerun merge process for file path `dfile`.
645 Returns whether the merge was completed and the return value of merge
645 Returns whether the merge was completed and the return value of merge
646 obtained from filemerge._filemerge().
646 obtained from filemerge._filemerge().
647 """
647 """
648 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
648 if self[dfile] in (MERGE_RECORD_RESOLVED, MERGE_RECORD_DRIVER_RESOLVED):
649 return True, 0
649 return True, 0
650 stateentry = self._state[dfile]
650 stateentry = self._state[dfile]
651 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
651 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
652 octx = self._repo[self._other]
652 octx = self._repo[self._other]
653 extras = self.extras(dfile)
653 extras = self.extras(dfile)
654 anccommitnode = extras.get(b'ancestorlinknode')
654 anccommitnode = extras.get(b'ancestorlinknode')
655 if anccommitnode:
655 if anccommitnode:
656 actx = self._repo[anccommitnode]
656 actx = self._repo[anccommitnode]
657 else:
657 else:
658 actx = None
658 actx = None
659 fcd = _filectxorabsent(localkey, wctx, dfile)
659 fcd = _filectxorabsent(localkey, wctx, dfile)
660 fco = _filectxorabsent(onode, octx, ofile)
660 fco = _filectxorabsent(onode, octx, ofile)
661 # TODO: move this to filectxorabsent
661 # TODO: move this to filectxorabsent
662 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
662 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
663 # "premerge" x flags
663 # "premerge" x flags
664 flo = fco.flags()
664 flo = fco.flags()
665 fla = fca.flags()
665 fla = fca.flags()
666 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
666 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
667 if fca.node() == nullid and flags != flo:
667 if fca.node() == nullid and flags != flo:
668 if preresolve:
668 if preresolve:
669 self._repo.ui.warn(
669 self._repo.ui.warn(
670 _(
670 _(
671 b'warning: cannot merge flags for %s '
671 b'warning: cannot merge flags for %s '
672 b'without common ancestor - keeping local flags\n'
672 b'without common ancestor - keeping local flags\n'
673 )
673 )
674 % afile
674 % afile
675 )
675 )
676 elif flags == fla:
676 elif flags == fla:
677 flags = flo
677 flags = flo
678 if preresolve:
678 if preresolve:
679 # restore local
679 # restore local
680 if localkey != nullhex:
680 if localkey != nullhex:
681 f = self._repo.vfs(b'merge/' + localkey)
681 f = self._repo.vfs(b'merge/' + localkey)
682 wctx[dfile].write(f.read(), flags)
682 wctx[dfile].write(f.read(), flags)
683 f.close()
683 f.close()
684 else:
684 else:
685 wctx[dfile].remove(ignoremissing=True)
685 wctx[dfile].remove(ignoremissing=True)
686 complete, merge_ret, deleted = filemerge.premerge(
686 complete, merge_ret, deleted = filemerge.premerge(
687 self._repo,
687 self._repo,
688 wctx,
688 wctx,
689 self._local,
689 self._local,
690 lfile,
690 lfile,
691 fcd,
691 fcd,
692 fco,
692 fco,
693 fca,
693 fca,
694 labels=self._labels,
694 labels=self._labels,
695 )
695 )
696 else:
696 else:
697 complete, merge_ret, deleted = filemerge.filemerge(
697 complete, merge_ret, deleted = filemerge.filemerge(
698 self._repo,
698 self._repo,
699 wctx,
699 wctx,
700 self._local,
700 self._local,
701 lfile,
701 lfile,
702 fcd,
702 fcd,
703 fco,
703 fco,
704 fca,
704 fca,
705 labels=self._labels,
705 labels=self._labels,
706 )
706 )
707 if merge_ret is None:
707 if merge_ret is None:
708 # If return value of merge is None, then there are no real conflict
708 # If return value of merge is None, then there are no real conflict
709 del self._state[dfile]
709 del self._state[dfile]
710 self._stateextras.pop(dfile, None)
710 self._stateextras.pop(dfile, None)
711 self._dirty = True
711 self._dirty = True
712 elif not merge_ret:
712 elif not merge_ret:
713 self.mark(dfile, MERGE_RECORD_RESOLVED)
713 self.mark(dfile, MERGE_RECORD_RESOLVED)
714
714
715 if complete:
715 if complete:
716 action = None
716 action = None
717 if deleted:
717 if deleted:
718 if fcd.isabsent():
718 if fcd.isabsent():
719 # dc: local picked. Need to drop if present, which may
719 # dc: local picked. Need to drop if present, which may
720 # happen on re-resolves.
720 # happen on re-resolves.
721 action = ACTION_FORGET
721 action = ACTION_FORGET
722 else:
722 else:
723 # cd: remote picked (or otherwise deleted)
723 # cd: remote picked (or otherwise deleted)
724 action = ACTION_REMOVE
724 action = ACTION_REMOVE
725 else:
725 else:
726 if fcd.isabsent(): # dc: remote picked
726 if fcd.isabsent(): # dc: remote picked
727 action = ACTION_GET
727 action = ACTION_GET
728 elif fco.isabsent(): # cd: local picked
728 elif fco.isabsent(): # cd: local picked
729 if dfile in self.localctx:
729 if dfile in self.localctx:
730 action = ACTION_ADD_MODIFIED
730 action = ACTION_ADD_MODIFIED
731 else:
731 else:
732 action = ACTION_ADD
732 action = ACTION_ADD
733 # else: regular merges (no action necessary)
733 # else: regular merges (no action necessary)
734 self._results[dfile] = merge_ret, action
734 self._results[dfile] = merge_ret, action
735
735
736 return complete, merge_ret
736 return complete, merge_ret
737
737
738 def preresolve(self, dfile, wctx):
738 def preresolve(self, dfile, wctx):
739 """run premerge process for dfile
739 """run premerge process for dfile
740
740
741 Returns whether the merge is complete, and the exit code."""
741 Returns whether the merge is complete, and the exit code."""
742 return self._resolve(True, dfile, wctx)
742 return self._resolve(True, dfile, wctx)
743
743
744 def resolve(self, dfile, wctx):
744 def resolve(self, dfile, wctx):
745 """run merge process (assuming premerge was run) for dfile
745 """run merge process (assuming premerge was run) for dfile
746
746
747 Returns the exit code of the merge."""
747 Returns the exit code of the merge."""
748 return self._resolve(False, dfile, wctx)[1]
748 return self._resolve(False, dfile, wctx)[1]
749
749
750 def counts(self):
750 def counts(self):
751 """return counts for updated, merged and removed files in this
751 """return counts for updated, merged and removed files in this
752 session"""
752 session"""
753 updated, merged, removed = 0, 0, 0
753 updated, merged, removed = 0, 0, 0
754 for r, action in pycompat.itervalues(self._results):
754 for r, action in pycompat.itervalues(self._results):
755 if r is None:
755 if r is None:
756 updated += 1
756 updated += 1
757 elif r == 0:
757 elif r == 0:
758 if action == ACTION_REMOVE:
758 if action == ACTION_REMOVE:
759 removed += 1
759 removed += 1
760 else:
760 else:
761 merged += 1
761 merged += 1
762 return updated, merged, removed
762 return updated, merged, removed
763
763
764 def unresolvedcount(self):
764 def unresolvedcount(self):
765 """get unresolved count for this merge (persistent)"""
765 """get unresolved count for this merge (persistent)"""
766 return len(list(self.unresolved()))
766 return len(list(self.unresolved()))
767
767
768 def actions(self):
768 def actions(self):
769 """return lists of actions to perform on the dirstate"""
769 """return lists of actions to perform on the dirstate"""
770 actions = {
770 actions = {
771 ACTION_REMOVE: [],
771 ACTION_REMOVE: [],
772 ACTION_FORGET: [],
772 ACTION_FORGET: [],
773 ACTION_ADD: [],
773 ACTION_ADD: [],
774 ACTION_ADD_MODIFIED: [],
774 ACTION_ADD_MODIFIED: [],
775 ACTION_GET: [],
775 ACTION_GET: [],
776 }
776 }
777 for f, (r, action) in pycompat.iteritems(self._results):
777 for f, (r, action) in pycompat.iteritems(self._results):
778 if action is not None:
778 if action is not None:
779 actions[action].append((f, None, b"merge result"))
779 actions[action].append((f, None, b"merge result"))
780 return actions
780 return actions
781
781
782 def queueremove(self, f):
782 def queueremove(self, f):
783 """queues a file to be removed from the dirstate
783 """queues a file to be removed from the dirstate
784
784
785 Meant for use by custom merge drivers."""
785 Meant for use by custom merge drivers."""
786 self._results[f] = 0, ACTION_REMOVE
786 self._results[f] = 0, ACTION_REMOVE
787
787
788 def queueadd(self, f):
788 def queueadd(self, f):
789 """queues a file to be added to the dirstate
789 """queues a file to be added to the dirstate
790
790
791 Meant for use by custom merge drivers."""
791 Meant for use by custom merge drivers."""
792 self._results[f] = 0, ACTION_ADD
792 self._results[f] = 0, ACTION_ADD
793
793
794 def queueget(self, f):
794 def queueget(self, f):
795 """queues a file to be marked modified in the dirstate
795 """queues a file to be marked modified in the dirstate
796
796
797 Meant for use by custom merge drivers."""
797 Meant for use by custom merge drivers."""
798 self._results[f] = 0, ACTION_GET
798 self._results[f] = 0, ACTION_GET
799
799
800
800
801 def recordupdates(repo, actions, branchmerge, getfiledata):
801 def recordupdates(repo, actions, branchmerge, getfiledata):
802 """record merge actions to the dirstate"""
802 """record merge actions to the dirstate"""
803 # remove (must come first)
803 # remove (must come first)
804 for f, args, msg in actions.get(ACTION_REMOVE, []):
804 for f, args, msg in actions.get(ACTION_REMOVE, []):
805 if branchmerge:
805 if branchmerge:
806 repo.dirstate.remove(f)
806 repo.dirstate.remove(f)
807 else:
807 else:
808 repo.dirstate.drop(f)
808 repo.dirstate.drop(f)
809
809
810 # forget (must come first)
810 # forget (must come first)
811 for f, args, msg in actions.get(ACTION_FORGET, []):
811 for f, args, msg in actions.get(ACTION_FORGET, []):
812 repo.dirstate.drop(f)
812 repo.dirstate.drop(f)
813
813
814 # resolve path conflicts
814 # resolve path conflicts
815 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
815 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
816 (f0, origf0) = args
816 (f0, origf0) = args
817 repo.dirstate.add(f)
817 repo.dirstate.add(f)
818 repo.dirstate.copy(origf0, f)
818 repo.dirstate.copy(origf0, f)
819 if f0 == origf0:
819 if f0 == origf0:
820 repo.dirstate.remove(f0)
820 repo.dirstate.remove(f0)
821 else:
821 else:
822 repo.dirstate.drop(f0)
822 repo.dirstate.drop(f0)
823
823
824 # re-add
824 # re-add
825 for f, args, msg in actions.get(ACTION_ADD, []):
825 for f, args, msg in actions.get(ACTION_ADD, []):
826 repo.dirstate.add(f)
826 repo.dirstate.add(f)
827
827
828 # re-add/mark as modified
828 # re-add/mark as modified
829 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
829 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
830 if branchmerge:
830 if branchmerge:
831 repo.dirstate.normallookup(f)
831 repo.dirstate.normallookup(f)
832 else:
832 else:
833 repo.dirstate.add(f)
833 repo.dirstate.add(f)
834
834
835 # exec change
835 # exec change
836 for f, args, msg in actions.get(ACTION_EXEC, []):
836 for f, args, msg in actions.get(ACTION_EXEC, []):
837 repo.dirstate.normallookup(f)
837 repo.dirstate.normallookup(f)
838
838
839 # keep
839 # keep
840 for f, args, msg in actions.get(ACTION_KEEP, []):
840 for f, args, msg in actions.get(ACTION_KEEP, []):
841 pass
841 pass
842
842
843 # keep deleted
843 # keep deleted
844 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
844 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
845 pass
845 pass
846
846
847 # get
847 # get
848 for f, args, msg in actions.get(ACTION_GET, []):
848 for f, args, msg in actions.get(ACTION_GET, []):
849 if branchmerge:
849 if branchmerge:
850 repo.dirstate.otherparent(f)
850 repo.dirstate.otherparent(f)
851 else:
851 else:
852 parentfiledata = getfiledata[f] if getfiledata else None
852 parentfiledata = getfiledata[f] if getfiledata else None
853 repo.dirstate.normal(f, parentfiledata=parentfiledata)
853 repo.dirstate.normal(f, parentfiledata=parentfiledata)
854
854
855 # merge
855 # merge
856 for f, args, msg in actions.get(ACTION_MERGE, []):
856 for f, args, msg in actions.get(ACTION_MERGE, []):
857 f1, f2, fa, move, anc = args
857 f1, f2, fa, move, anc = args
858 if branchmerge:
858 if branchmerge:
859 # We've done a branch merge, mark this file as merged
859 # We've done a branch merge, mark this file as merged
860 # so that we properly record the merger later
860 # so that we properly record the merger later
861 repo.dirstate.merge(f)
861 repo.dirstate.merge(f)
862 if f1 != f2: # copy/rename
862 if f1 != f2: # copy/rename
863 if move:
863 if move:
864 repo.dirstate.remove(f1)
864 repo.dirstate.remove(f1)
865 if f1 != f:
865 if f1 != f:
866 repo.dirstate.copy(f1, f)
866 repo.dirstate.copy(f1, f)
867 else:
867 else:
868 repo.dirstate.copy(f2, f)
868 repo.dirstate.copy(f2, f)
869 else:
869 else:
870 # We've update-merged a locally modified file, so
870 # We've update-merged a locally modified file, so
871 # we set the dirstate to emulate a normal checkout
871 # we set the dirstate to emulate a normal checkout
872 # of that file some time in the past. Thus our
872 # of that file some time in the past. Thus our
873 # merge will appear as a normal local file
873 # merge will appear as a normal local file
874 # modification.
874 # modification.
875 if f2 == f: # file not locally copied/moved
875 if f2 == f: # file not locally copied/moved
876 repo.dirstate.normallookup(f)
876 repo.dirstate.normallookup(f)
877 if move:
877 if move:
878 repo.dirstate.drop(f1)
878 repo.dirstate.drop(f1)
879
879
880 # directory rename, move local
880 # directory rename, move local
881 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
881 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
882 f0, flag = args
882 f0, flag = args
883 if branchmerge:
883 if branchmerge:
884 repo.dirstate.add(f)
884 repo.dirstate.add(f)
885 repo.dirstate.remove(f0)
885 repo.dirstate.remove(f0)
886 repo.dirstate.copy(f0, f)
886 repo.dirstate.copy(f0, f)
887 else:
887 else:
888 repo.dirstate.normal(f)
888 repo.dirstate.normal(f)
889 repo.dirstate.drop(f0)
889 repo.dirstate.drop(f0)
890
890
891 # directory rename, get
891 # directory rename, get
892 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
892 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
893 f0, flag = args
893 f0, flag = args
894 if branchmerge:
894 if branchmerge:
895 repo.dirstate.add(f)
895 repo.dirstate.add(f)
896 repo.dirstate.copy(f0, f)
896 repo.dirstate.copy(f0, f)
897 else:
897 else:
898 repo.dirstate.normal(f)
898 repo.dirstate.normal(f)
General Comments 0
You need to be logged in to leave comments. Login now