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