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