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