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