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