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