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