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