##// END OF EJS Templates
mergestate: inline `_resolve()` into `resolve()`...
Martin von Zweigbergk -
r49258:f9bc3686 default
parent child Browse files
Show More
@@ -1,862 +1,856 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, dfile, wctx):
316 def resolve(self, dfile, wctx):
317 """rerun merge process for file path `dfile`.
317 """run merge process for dfile
318 Returns the return value of merge obtained from filemerge._filemerge().
318
319 """
319 Returns the exit code of the merge."""
320 if self[dfile] in (
320 if self[dfile] in (
321 MERGE_RECORD_RESOLVED,
321 MERGE_RECORD_RESOLVED,
322 LEGACY_RECORD_DRIVER_RESOLVED,
322 LEGACY_RECORD_DRIVER_RESOLVED,
323 ):
323 ):
324 return 0
324 return 0
325 stateentry = self._state[dfile]
325 stateentry = self._state[dfile]
326 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
326 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
327 octx = self._repo[self._other]
327 octx = self._repo[self._other]
328 extras = self.extras(dfile)
328 extras = self.extras(dfile)
329 anccommitnode = extras.get(b'ancestorlinknode')
329 anccommitnode = extras.get(b'ancestorlinknode')
330 if anccommitnode:
330 if anccommitnode:
331 actx = self._repo[anccommitnode]
331 actx = self._repo[anccommitnode]
332 else:
332 else:
333 actx = None
333 actx = None
334 fcd = _filectxorabsent(localkey, wctx, dfile)
334 fcd = _filectxorabsent(localkey, wctx, dfile)
335 fco = _filectxorabsent(onode, octx, ofile)
335 fco = _filectxorabsent(onode, octx, ofile)
336 # TODO: move this to filectxorabsent
336 # TODO: move this to filectxorabsent
337 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
337 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
338 # "premerge" x flags
338 # "premerge" x flags
339 flo = fco.flags()
339 flo = fco.flags()
340 fla = fca.flags()
340 fla = fca.flags()
341 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
341 if b'x' in flags + flo + fla and b'l' not in flags + flo + fla:
342 if fca.rev() == nullrev and flags != flo:
342 if fca.rev() == nullrev and flags != flo:
343 self._repo.ui.warn(
343 self._repo.ui.warn(
344 _(
344 _(
345 b'warning: cannot merge flags for %s '
345 b'warning: cannot merge flags for %s '
346 b'without common ancestor - keeping local flags\n'
346 b'without common ancestor - keeping local flags\n'
347 )
347 )
348 % afile
348 % afile
349 )
349 )
350 elif flags == fla:
350 elif flags == fla:
351 flags = flo
351 flags = flo
352 # restore local
352 # restore local
353 if localkey != self._repo.nodeconstants.nullhex:
353 if localkey != self._repo.nodeconstants.nullhex:
354 self._restore_backup(wctx[dfile], localkey, flags)
354 self._restore_backup(wctx[dfile], localkey, flags)
355 else:
355 else:
356 wctx[dfile].remove(ignoremissing=True)
356 wctx[dfile].remove(ignoremissing=True)
357 complete, merge_ret, deleted = filemerge.premerge(
357 complete, merge_ret, deleted = filemerge.premerge(
358 self._repo,
358 self._repo,
359 wctx,
359 wctx,
360 self._local,
360 self._local,
361 lfile,
361 lfile,
362 fcd,
362 fcd,
363 fco,
363 fco,
364 fca,
364 fca,
365 labels=self._labels,
365 labels=self._labels,
366 )
366 )
367 if not complete:
367 if not complete:
368 complete, merge_ret, deleted = filemerge.filemerge(
368 complete, merge_ret, deleted = filemerge.filemerge(
369 self._repo,
369 self._repo,
370 wctx,
370 wctx,
371 self._local,
371 self._local,
372 lfile,
372 lfile,
373 fcd,
373 fcd,
374 fco,
374 fco,
375 fca,
375 fca,
376 labels=self._labels,
376 labels=self._labels,
377 )
377 )
378 if merge_ret is None:
378 if merge_ret is None:
379 # If return value of merge is None, then there are no real conflict
379 # If return value of merge is None, then there are no real conflict
380 del self._state[dfile]
380 del self._state[dfile]
381 self._dirty = True
381 self._dirty = True
382 elif not merge_ret:
382 elif not merge_ret:
383 self.mark(dfile, MERGE_RECORD_RESOLVED)
383 self.mark(dfile, MERGE_RECORD_RESOLVED)
384
384
385 if complete:
385 if complete:
386 action = None
386 action = None
387 if deleted:
387 if deleted:
388 if fcd.isabsent():
388 if fcd.isabsent():
389 # dc: local picked. Need to drop if present, which may
389 # dc: local picked. Need to drop if present, which may
390 # happen on re-resolves.
390 # happen on re-resolves.
391 action = ACTION_FORGET
391 action = ACTION_FORGET
392 else:
392 else:
393 # cd: remote picked (or otherwise deleted)
393 # cd: remote picked (or otherwise deleted)
394 action = ACTION_REMOVE
394 action = ACTION_REMOVE
395 else:
395 else:
396 if fcd.isabsent(): # dc: remote picked
396 if fcd.isabsent(): # dc: remote picked
397 action = ACTION_GET
397 action = ACTION_GET
398 elif fco.isabsent(): # cd: local picked
398 elif fco.isabsent(): # cd: local picked
399 if dfile in self.localctx:
399 if dfile in self.localctx:
400 action = ACTION_ADD_MODIFIED
400 action = ACTION_ADD_MODIFIED
401 else:
401 else:
402 action = ACTION_ADD
402 action = ACTION_ADD
403 # else: regular merges (no action necessary)
403 # else: regular merges (no action necessary)
404 self._results[dfile] = merge_ret, action
404 self._results[dfile] = merge_ret, action
405
405
406 return merge_ret
406 return merge_ret
407
407
408 def resolve(self, dfile, wctx):
409 """run merge process for dfile
410
411 Returns the exit code of the merge."""
412 return self._resolve(dfile, wctx)
413
414 def counts(self):
408 def counts(self):
415 """return counts for updated, merged and removed files in this
409 """return counts for updated, merged and removed files in this
416 session"""
410 session"""
417 updated, merged, removed = 0, 0, 0
411 updated, merged, removed = 0, 0, 0
418 for r, action in pycompat.itervalues(self._results):
412 for r, action in pycompat.itervalues(self._results):
419 if r is None:
413 if r is None:
420 updated += 1
414 updated += 1
421 elif r == 0:
415 elif r == 0:
422 if action == ACTION_REMOVE:
416 if action == ACTION_REMOVE:
423 removed += 1
417 removed += 1
424 else:
418 else:
425 merged += 1
419 merged += 1
426 return updated, merged, removed
420 return updated, merged, removed
427
421
428 def unresolvedcount(self):
422 def unresolvedcount(self):
429 """get unresolved count for this merge (persistent)"""
423 """get unresolved count for this merge (persistent)"""
430 return len(list(self.unresolved()))
424 return len(list(self.unresolved()))
431
425
432 def actions(self):
426 def actions(self):
433 """return lists of actions to perform on the dirstate"""
427 """return lists of actions to perform on the dirstate"""
434 actions = {
428 actions = {
435 ACTION_REMOVE: [],
429 ACTION_REMOVE: [],
436 ACTION_FORGET: [],
430 ACTION_FORGET: [],
437 ACTION_ADD: [],
431 ACTION_ADD: [],
438 ACTION_ADD_MODIFIED: [],
432 ACTION_ADD_MODIFIED: [],
439 ACTION_GET: [],
433 ACTION_GET: [],
440 }
434 }
441 for f, (r, action) in pycompat.iteritems(self._results):
435 for f, (r, action) in pycompat.iteritems(self._results):
442 if action is not None:
436 if action is not None:
443 actions[action].append((f, None, b"merge result"))
437 actions[action].append((f, None, b"merge result"))
444 return actions
438 return actions
445
439
446
440
447 class mergestate(_mergestate_base):
441 class mergestate(_mergestate_base):
448
442
449 statepathv1 = b'merge/state'
443 statepathv1 = b'merge/state'
450 statepathv2 = b'merge/state2'
444 statepathv2 = b'merge/state2'
451
445
452 @staticmethod
446 @staticmethod
453 def clean(repo):
447 def clean(repo):
454 """Initialize a brand new merge state, removing any existing state on
448 """Initialize a brand new merge state, removing any existing state on
455 disk."""
449 disk."""
456 ms = mergestate(repo)
450 ms = mergestate(repo)
457 ms.reset()
451 ms.reset()
458 return ms
452 return ms
459
453
460 @staticmethod
454 @staticmethod
461 def read(repo):
455 def read(repo):
462 """Initialize the merge state, reading it from disk."""
456 """Initialize the merge state, reading it from disk."""
463 ms = mergestate(repo)
457 ms = mergestate(repo)
464 ms._read()
458 ms._read()
465 return ms
459 return ms
466
460
467 def _read(self):
461 def _read(self):
468 """Analyse each record content to restore a serialized state from disk
462 """Analyse each record content to restore a serialized state from disk
469
463
470 This function process "record" entry produced by the de-serialization
464 This function process "record" entry produced by the de-serialization
471 of on disk file.
465 of on disk file.
472 """
466 """
473 unsupported = set()
467 unsupported = set()
474 records = self._readrecords()
468 records = self._readrecords()
475 for rtype, record in records:
469 for rtype, record in records:
476 if rtype == RECORD_LOCAL:
470 if rtype == RECORD_LOCAL:
477 self._local = bin(record)
471 self._local = bin(record)
478 elif rtype == RECORD_OTHER:
472 elif rtype == RECORD_OTHER:
479 self._other = bin(record)
473 self._other = bin(record)
480 elif rtype == LEGACY_MERGE_DRIVER_STATE:
474 elif rtype == LEGACY_MERGE_DRIVER_STATE:
481 pass
475 pass
482 elif rtype in (
476 elif rtype in (
483 RECORD_MERGED,
477 RECORD_MERGED,
484 RECORD_CHANGEDELETE_CONFLICT,
478 RECORD_CHANGEDELETE_CONFLICT,
485 RECORD_PATH_CONFLICT,
479 RECORD_PATH_CONFLICT,
486 LEGACY_MERGE_DRIVER_MERGE,
480 LEGACY_MERGE_DRIVER_MERGE,
487 LEGACY_RECORD_RESOLVED_OTHER,
481 LEGACY_RECORD_RESOLVED_OTHER,
488 ):
482 ):
489 bits = record.split(b'\0')
483 bits = record.split(b'\0')
490 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
484 # merge entry type MERGE_RECORD_MERGED_OTHER is deprecated
491 # and we now store related information in _stateextras, so
485 # and we now store related information in _stateextras, so
492 # lets write to _stateextras directly
486 # lets write to _stateextras directly
493 if bits[1] == MERGE_RECORD_MERGED_OTHER:
487 if bits[1] == MERGE_RECORD_MERGED_OTHER:
494 self._stateextras[bits[0]][b'filenode-source'] = b'other'
488 self._stateextras[bits[0]][b'filenode-source'] = b'other'
495 else:
489 else:
496 self._state[bits[0]] = bits[1:]
490 self._state[bits[0]] = bits[1:]
497 elif rtype == RECORD_FILE_VALUES:
491 elif rtype == RECORD_FILE_VALUES:
498 filename, rawextras = record.split(b'\0', 1)
492 filename, rawextras = record.split(b'\0', 1)
499 extraparts = rawextras.split(b'\0')
493 extraparts = rawextras.split(b'\0')
500 extras = {}
494 extras = {}
501 i = 0
495 i = 0
502 while i < len(extraparts):
496 while i < len(extraparts):
503 extras[extraparts[i]] = extraparts[i + 1]
497 extras[extraparts[i]] = extraparts[i + 1]
504 i += 2
498 i += 2
505
499
506 self._stateextras[filename] = extras
500 self._stateextras[filename] = extras
507 elif rtype == RECORD_LABELS:
501 elif rtype == RECORD_LABELS:
508 labels = record.split(b'\0', 2)
502 labels = record.split(b'\0', 2)
509 self._labels = [l for l in labels if len(l) > 0]
503 self._labels = [l for l in labels if len(l) > 0]
510 elif not rtype.islower():
504 elif not rtype.islower():
511 unsupported.add(rtype)
505 unsupported.add(rtype)
512
506
513 if unsupported:
507 if unsupported:
514 raise error.UnsupportedMergeRecords(unsupported)
508 raise error.UnsupportedMergeRecords(unsupported)
515
509
516 def _readrecords(self):
510 def _readrecords(self):
517 """Read merge state from disk and return a list of record (TYPE, data)
511 """Read merge state from disk and return a list of record (TYPE, data)
518
512
519 We read data from both v1 and v2 files and decide which one to use.
513 We read data from both v1 and v2 files and decide which one to use.
520
514
521 V1 has been used by version prior to 2.9.1 and contains less data than
515 V1 has been used by version prior to 2.9.1 and contains less data than
522 v2. We read both versions and check if no data in v2 contradicts
516 v2. We read both versions and check if no data in v2 contradicts
523 v1. If there is not contradiction we can safely assume that both v1
517 v1. If there is not contradiction we can safely assume that both v1
524 and v2 were written at the same time and use the extract data in v2. If
518 and v2 were written at the same time and use the extract data in v2. If
525 there is contradiction we ignore v2 content as we assume an old version
519 there is contradiction we ignore v2 content as we assume an old version
526 of Mercurial has overwritten the mergestate file and left an old v2
520 of Mercurial has overwritten the mergestate file and left an old v2
527 file around.
521 file around.
528
522
529 returns list of record [(TYPE, data), ...]"""
523 returns list of record [(TYPE, data), ...]"""
530 v1records = self._readrecordsv1()
524 v1records = self._readrecordsv1()
531 v2records = self._readrecordsv2()
525 v2records = self._readrecordsv2()
532 if self._v1v2match(v1records, v2records):
526 if self._v1v2match(v1records, v2records):
533 return v2records
527 return v2records
534 else:
528 else:
535 # v1 file is newer than v2 file, use it
529 # v1 file is newer than v2 file, use it
536 # we have to infer the "other" changeset of the merge
530 # we have to infer the "other" changeset of the merge
537 # we cannot do better than that with v1 of the format
531 # we cannot do better than that with v1 of the format
538 mctx = self._repo[None].parents()[-1]
532 mctx = self._repo[None].parents()[-1]
539 v1records.append((RECORD_OTHER, mctx.hex()))
533 v1records.append((RECORD_OTHER, mctx.hex()))
540 # add place holder "other" file node information
534 # add place holder "other" file node information
541 # nobody is using it yet so we do no need to fetch the data
535 # nobody is using it yet so we do no need to fetch the data
542 # if mctx was wrong `mctx[bits[-2]]` may fails.
536 # if mctx was wrong `mctx[bits[-2]]` may fails.
543 for idx, r in enumerate(v1records):
537 for idx, r in enumerate(v1records):
544 if r[0] == RECORD_MERGED:
538 if r[0] == RECORD_MERGED:
545 bits = r[1].split(b'\0')
539 bits = r[1].split(b'\0')
546 bits.insert(-2, b'')
540 bits.insert(-2, b'')
547 v1records[idx] = (r[0], b'\0'.join(bits))
541 v1records[idx] = (r[0], b'\0'.join(bits))
548 return v1records
542 return v1records
549
543
550 def _v1v2match(self, v1records, v2records):
544 def _v1v2match(self, v1records, v2records):
551 oldv2 = set() # old format version of v2 record
545 oldv2 = set() # old format version of v2 record
552 for rec in v2records:
546 for rec in v2records:
553 if rec[0] == RECORD_LOCAL:
547 if rec[0] == RECORD_LOCAL:
554 oldv2.add(rec)
548 oldv2.add(rec)
555 elif rec[0] == RECORD_MERGED:
549 elif rec[0] == RECORD_MERGED:
556 # drop the onode data (not contained in v1)
550 # drop the onode data (not contained in v1)
557 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
551 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
558 for rec in v1records:
552 for rec in v1records:
559 if rec not in oldv2:
553 if rec not in oldv2:
560 return False
554 return False
561 else:
555 else:
562 return True
556 return True
563
557
564 def _readrecordsv1(self):
558 def _readrecordsv1(self):
565 """read on disk merge state for version 1 file
559 """read on disk merge state for version 1 file
566
560
567 returns list of record [(TYPE, data), ...]
561 returns list of record [(TYPE, data), ...]
568
562
569 Note: the "F" data from this file are one entry short
563 Note: the "F" data from this file are one entry short
570 (no "other file node" entry)
564 (no "other file node" entry)
571 """
565 """
572 records = []
566 records = []
573 try:
567 try:
574 f = self._repo.vfs(self.statepathv1)
568 f = self._repo.vfs(self.statepathv1)
575 for i, l in enumerate(f):
569 for i, l in enumerate(f):
576 if i == 0:
570 if i == 0:
577 records.append((RECORD_LOCAL, l[:-1]))
571 records.append((RECORD_LOCAL, l[:-1]))
578 else:
572 else:
579 records.append((RECORD_MERGED, l[:-1]))
573 records.append((RECORD_MERGED, l[:-1]))
580 f.close()
574 f.close()
581 except IOError as err:
575 except IOError as err:
582 if err.errno != errno.ENOENT:
576 if err.errno != errno.ENOENT:
583 raise
577 raise
584 return records
578 return records
585
579
586 def _readrecordsv2(self):
580 def _readrecordsv2(self):
587 """read on disk merge state for version 2 file
581 """read on disk merge state for version 2 file
588
582
589 This format is a list of arbitrary records of the form:
583 This format is a list of arbitrary records of the form:
590
584
591 [type][length][content]
585 [type][length][content]
592
586
593 `type` is a single character, `length` is a 4 byte integer, and
587 `type` is a single character, `length` is a 4 byte integer, and
594 `content` is an arbitrary byte sequence of length `length`.
588 `content` is an arbitrary byte sequence of length `length`.
595
589
596 Mercurial versions prior to 3.7 have a bug where if there are
590 Mercurial versions prior to 3.7 have a bug where if there are
597 unsupported mandatory merge records, attempting to clear out the merge
591 unsupported mandatory merge records, attempting to clear out the merge
598 state with hg update --clean or similar aborts. The 't' record type
592 state with hg update --clean or similar aborts. The 't' record type
599 works around that by writing out what those versions treat as an
593 works around that by writing out what those versions treat as an
600 advisory record, but later versions interpret as special: the first
594 advisory record, but later versions interpret as special: the first
601 character is the 'real' record type and everything onwards is the data.
595 character is the 'real' record type and everything onwards is the data.
602
596
603 Returns list of records [(TYPE, data), ...]."""
597 Returns list of records [(TYPE, data), ...]."""
604 records = []
598 records = []
605 try:
599 try:
606 f = self._repo.vfs(self.statepathv2)
600 f = self._repo.vfs(self.statepathv2)
607 data = f.read()
601 data = f.read()
608 off = 0
602 off = 0
609 end = len(data)
603 end = len(data)
610 while off < end:
604 while off < end:
611 rtype = data[off : off + 1]
605 rtype = data[off : off + 1]
612 off += 1
606 off += 1
613 length = _unpack(b'>I', data[off : (off + 4)])[0]
607 length = _unpack(b'>I', data[off : (off + 4)])[0]
614 off += 4
608 off += 4
615 record = data[off : (off + length)]
609 record = data[off : (off + length)]
616 off += length
610 off += length
617 if rtype == RECORD_OVERRIDE:
611 if rtype == RECORD_OVERRIDE:
618 rtype, record = record[0:1], record[1:]
612 rtype, record = record[0:1], record[1:]
619 records.append((rtype, record))
613 records.append((rtype, record))
620 f.close()
614 f.close()
621 except IOError as err:
615 except IOError as err:
622 if err.errno != errno.ENOENT:
616 if err.errno != errno.ENOENT:
623 raise
617 raise
624 return records
618 return records
625
619
626 def commit(self):
620 def commit(self):
627 if self._dirty:
621 if self._dirty:
628 records = self._makerecords()
622 records = self._makerecords()
629 self._writerecords(records)
623 self._writerecords(records)
630 self._dirty = False
624 self._dirty = False
631
625
632 def _makerecords(self):
626 def _makerecords(self):
633 records = []
627 records = []
634 records.append((RECORD_LOCAL, hex(self._local)))
628 records.append((RECORD_LOCAL, hex(self._local)))
635 records.append((RECORD_OTHER, hex(self._other)))
629 records.append((RECORD_OTHER, hex(self._other)))
636 # Write out state items. In all cases, the value of the state map entry
630 # Write out state items. In all cases, the value of the state map entry
637 # is written as the contents of the record. The record type depends on
631 # is written as the contents of the record. The record type depends on
638 # the type of state that is stored, and capital-letter records are used
632 # the type of state that is stored, and capital-letter records are used
639 # to prevent older versions of Mercurial that do not support the feature
633 # to prevent older versions of Mercurial that do not support the feature
640 # from loading them.
634 # from loading them.
641 for filename, v in pycompat.iteritems(self._state):
635 for filename, v in pycompat.iteritems(self._state):
642 if v[0] in (
636 if v[0] in (
643 MERGE_RECORD_UNRESOLVED_PATH,
637 MERGE_RECORD_UNRESOLVED_PATH,
644 MERGE_RECORD_RESOLVED_PATH,
638 MERGE_RECORD_RESOLVED_PATH,
645 ):
639 ):
646 # Path conflicts. These are stored in 'P' records. The current
640 # Path conflicts. These are stored in 'P' records. The current
647 # resolution state ('pu' or 'pr') is stored within the record.
641 # resolution state ('pu' or 'pr') is stored within the record.
648 records.append(
642 records.append(
649 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
643 (RECORD_PATH_CONFLICT, b'\0'.join([filename] + v))
650 )
644 )
651 elif (
645 elif (
652 v[1] == self._repo.nodeconstants.nullhex
646 v[1] == self._repo.nodeconstants.nullhex
653 or v[6] == self._repo.nodeconstants.nullhex
647 or v[6] == self._repo.nodeconstants.nullhex
654 ):
648 ):
655 # Change/Delete or Delete/Change conflicts. These are stored in
649 # Change/Delete or Delete/Change conflicts. These are stored in
656 # 'C' records. v[1] is the local file, and is nullhex when the
650 # 'C' records. v[1] is the local file, and is nullhex when the
657 # file is deleted locally ('dc'). v[6] is the remote file, and
651 # file is deleted locally ('dc'). v[6] is the remote file, and
658 # is nullhex when the file is deleted remotely ('cd').
652 # is nullhex when the file is deleted remotely ('cd').
659 records.append(
653 records.append(
660 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
654 (RECORD_CHANGEDELETE_CONFLICT, b'\0'.join([filename] + v))
661 )
655 )
662 else:
656 else:
663 # Normal files. These are stored in 'F' records.
657 # Normal files. These are stored in 'F' records.
664 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
658 records.append((RECORD_MERGED, b'\0'.join([filename] + v)))
665 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
659 for filename, extras in sorted(pycompat.iteritems(self._stateextras)):
666 rawextras = b'\0'.join(
660 rawextras = b'\0'.join(
667 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
661 b'%s\0%s' % (k, v) for k, v in pycompat.iteritems(extras)
668 )
662 )
669 records.append(
663 records.append(
670 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
664 (RECORD_FILE_VALUES, b'%s\0%s' % (filename, rawextras))
671 )
665 )
672 if self._labels is not None:
666 if self._labels is not None:
673 labels = b'\0'.join(self._labels)
667 labels = b'\0'.join(self._labels)
674 records.append((RECORD_LABELS, labels))
668 records.append((RECORD_LABELS, labels))
675 return records
669 return records
676
670
677 def _writerecords(self, records):
671 def _writerecords(self, records):
678 """Write current state on disk (both v1 and v2)"""
672 """Write current state on disk (both v1 and v2)"""
679 self._writerecordsv1(records)
673 self._writerecordsv1(records)
680 self._writerecordsv2(records)
674 self._writerecordsv2(records)
681
675
682 def _writerecordsv1(self, records):
676 def _writerecordsv1(self, records):
683 """Write current state on disk in a version 1 file"""
677 """Write current state on disk in a version 1 file"""
684 f = self._repo.vfs(self.statepathv1, b'wb')
678 f = self._repo.vfs(self.statepathv1, b'wb')
685 irecords = iter(records)
679 irecords = iter(records)
686 lrecords = next(irecords)
680 lrecords = next(irecords)
687 assert lrecords[0] == RECORD_LOCAL
681 assert lrecords[0] == RECORD_LOCAL
688 f.write(hex(self._local) + b'\n')
682 f.write(hex(self._local) + b'\n')
689 for rtype, data in irecords:
683 for rtype, data in irecords:
690 if rtype == RECORD_MERGED:
684 if rtype == RECORD_MERGED:
691 f.write(b'%s\n' % _droponode(data))
685 f.write(b'%s\n' % _droponode(data))
692 f.close()
686 f.close()
693
687
694 def _writerecordsv2(self, records):
688 def _writerecordsv2(self, records):
695 """Write current state on disk in a version 2 file
689 """Write current state on disk in a version 2 file
696
690
697 See the docstring for _readrecordsv2 for why we use 't'."""
691 See the docstring for _readrecordsv2 for why we use 't'."""
698 # these are the records that all version 2 clients can read
692 # these are the records that all version 2 clients can read
699 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
693 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
700 f = self._repo.vfs(self.statepathv2, b'wb')
694 f = self._repo.vfs(self.statepathv2, b'wb')
701 for key, data in records:
695 for key, data in records:
702 assert len(key) == 1
696 assert len(key) == 1
703 if key not in allowlist:
697 if key not in allowlist:
704 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
698 key, data = RECORD_OVERRIDE, b'%s%s' % (key, data)
705 format = b'>sI%is' % len(data)
699 format = b'>sI%is' % len(data)
706 f.write(_pack(format, key, len(data), data))
700 f.write(_pack(format, key, len(data), data))
707 f.close()
701 f.close()
708
702
709 def _make_backup(self, fctx, localkey):
703 def _make_backup(self, fctx, localkey):
710 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
704 self._repo.vfs.write(b'merge/' + localkey, fctx.data())
711
705
712 def _restore_backup(self, fctx, localkey, flags):
706 def _restore_backup(self, fctx, localkey, flags):
713 with self._repo.vfs(b'merge/' + localkey) as f:
707 with self._repo.vfs(b'merge/' + localkey) as f:
714 fctx.write(f.read(), flags)
708 fctx.write(f.read(), flags)
715
709
716 def reset(self):
710 def reset(self):
717 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
711 shutil.rmtree(self._repo.vfs.join(b'merge'), True)
718
712
719
713
720 class memmergestate(_mergestate_base):
714 class memmergestate(_mergestate_base):
721 def __init__(self, repo):
715 def __init__(self, repo):
722 super(memmergestate, self).__init__(repo)
716 super(memmergestate, self).__init__(repo)
723 self._backups = {}
717 self._backups = {}
724
718
725 def _make_backup(self, fctx, localkey):
719 def _make_backup(self, fctx, localkey):
726 self._backups[localkey] = fctx.data()
720 self._backups[localkey] = fctx.data()
727
721
728 def _restore_backup(self, fctx, localkey, flags):
722 def _restore_backup(self, fctx, localkey, flags):
729 fctx.write(self._backups[localkey], flags)
723 fctx.write(self._backups[localkey], flags)
730
724
731
725
732 def recordupdates(repo, actions, branchmerge, getfiledata):
726 def recordupdates(repo, actions, branchmerge, getfiledata):
733 """record merge actions to the dirstate"""
727 """record merge actions to the dirstate"""
734 # remove (must come first)
728 # remove (must come first)
735 for f, args, msg in actions.get(ACTION_REMOVE, []):
729 for f, args, msg in actions.get(ACTION_REMOVE, []):
736 if branchmerge:
730 if branchmerge:
737 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=False)
731 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=False)
738 else:
732 else:
739 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
733 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
740
734
741 # forget (must come first)
735 # forget (must come first)
742 for f, args, msg in actions.get(ACTION_FORGET, []):
736 for f, args, msg in actions.get(ACTION_FORGET, []):
743 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
737 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=False)
744
738
745 # resolve path conflicts
739 # resolve path conflicts
746 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
740 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
747 (f0, origf0) = args
741 (f0, origf0) = args
748 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
742 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
749 repo.dirstate.copy(origf0, f)
743 repo.dirstate.copy(origf0, f)
750 if f0 == origf0:
744 if f0 == origf0:
751 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
745 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
752 else:
746 else:
753 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
747 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
754
748
755 # re-add
749 # re-add
756 for f, args, msg in actions.get(ACTION_ADD, []):
750 for f, args, msg in actions.get(ACTION_ADD, []):
757 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
751 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
758
752
759 # re-add/mark as modified
753 # re-add/mark as modified
760 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
754 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
761 if branchmerge:
755 if branchmerge:
762 repo.dirstate.update_file(
756 repo.dirstate.update_file(
763 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
757 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
764 )
758 )
765 else:
759 else:
766 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
760 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
767
761
768 # exec change
762 # exec change
769 for f, args, msg in actions.get(ACTION_EXEC, []):
763 for f, args, msg in actions.get(ACTION_EXEC, []):
770 repo.dirstate.update_file(
764 repo.dirstate.update_file(
771 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
765 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
772 )
766 )
773
767
774 # keep
768 # keep
775 for f, args, msg in actions.get(ACTION_KEEP, []):
769 for f, args, msg in actions.get(ACTION_KEEP, []):
776 pass
770 pass
777
771
778 # keep deleted
772 # keep deleted
779 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
773 for f, args, msg in actions.get(ACTION_KEEP_ABSENT, []):
780 pass
774 pass
781
775
782 # keep new
776 # keep new
783 for f, args, msg in actions.get(ACTION_KEEP_NEW, []):
777 for f, args, msg in actions.get(ACTION_KEEP_NEW, []):
784 pass
778 pass
785
779
786 # get
780 # get
787 for f, args, msg in actions.get(ACTION_GET, []):
781 for f, args, msg in actions.get(ACTION_GET, []):
788 if branchmerge:
782 if branchmerge:
789 # tracked in p1 can be True also but update_file should not care
783 # tracked in p1 can be True also but update_file should not care
790 old_entry = repo.dirstate.get_entry(f)
784 old_entry = repo.dirstate.get_entry(f)
791 p1_tracked = old_entry.any_tracked and not old_entry.added
785 p1_tracked = old_entry.any_tracked and not old_entry.added
792 repo.dirstate.update_file(
786 repo.dirstate.update_file(
793 f,
787 f,
794 p1_tracked=p1_tracked,
788 p1_tracked=p1_tracked,
795 wc_tracked=True,
789 wc_tracked=True,
796 p2_info=True,
790 p2_info=True,
797 )
791 )
798 else:
792 else:
799 parentfiledata = getfiledata[f] if getfiledata else None
793 parentfiledata = getfiledata[f] if getfiledata else None
800 repo.dirstate.update_file(
794 repo.dirstate.update_file(
801 f,
795 f,
802 p1_tracked=True,
796 p1_tracked=True,
803 wc_tracked=True,
797 wc_tracked=True,
804 parentfiledata=parentfiledata,
798 parentfiledata=parentfiledata,
805 )
799 )
806
800
807 # merge
801 # merge
808 for f, args, msg in actions.get(ACTION_MERGE, []):
802 for f, args, msg in actions.get(ACTION_MERGE, []):
809 f1, f2, fa, move, anc = args
803 f1, f2, fa, move, anc = args
810 if branchmerge:
804 if branchmerge:
811 # We've done a branch merge, mark this file as merged
805 # We've done a branch merge, mark this file as merged
812 # so that we properly record the merger later
806 # so that we properly record the merger later
813 p1_tracked = f1 == f
807 p1_tracked = f1 == f
814 repo.dirstate.update_file(
808 repo.dirstate.update_file(
815 f,
809 f,
816 p1_tracked=p1_tracked,
810 p1_tracked=p1_tracked,
817 wc_tracked=True,
811 wc_tracked=True,
818 p2_info=True,
812 p2_info=True,
819 )
813 )
820 if f1 != f2: # copy/rename
814 if f1 != f2: # copy/rename
821 if move:
815 if move:
822 repo.dirstate.update_file(
816 repo.dirstate.update_file(
823 f1, p1_tracked=True, wc_tracked=False
817 f1, p1_tracked=True, wc_tracked=False
824 )
818 )
825 if f1 != f:
819 if f1 != f:
826 repo.dirstate.copy(f1, f)
820 repo.dirstate.copy(f1, f)
827 else:
821 else:
828 repo.dirstate.copy(f2, f)
822 repo.dirstate.copy(f2, f)
829 else:
823 else:
830 # We've update-merged a locally modified file, so
824 # We've update-merged a locally modified file, so
831 # we set the dirstate to emulate a normal checkout
825 # we set the dirstate to emulate a normal checkout
832 # of that file some time in the past. Thus our
826 # of that file some time in the past. Thus our
833 # merge will appear as a normal local file
827 # merge will appear as a normal local file
834 # modification.
828 # modification.
835 if f2 == f: # file not locally copied/moved
829 if f2 == f: # file not locally copied/moved
836 repo.dirstate.update_file(
830 repo.dirstate.update_file(
837 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
831 f, p1_tracked=True, wc_tracked=True, possibly_dirty=True
838 )
832 )
839 if move:
833 if move:
840 repo.dirstate.update_file(
834 repo.dirstate.update_file(
841 f1, p1_tracked=False, wc_tracked=False
835 f1, p1_tracked=False, wc_tracked=False
842 )
836 )
843
837
844 # directory rename, move local
838 # directory rename, move local
845 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
839 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
846 f0, flag = args
840 f0, flag = args
847 if branchmerge:
841 if branchmerge:
848 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
842 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
849 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
843 repo.dirstate.update_file(f0, p1_tracked=True, wc_tracked=False)
850 repo.dirstate.copy(f0, f)
844 repo.dirstate.copy(f0, f)
851 else:
845 else:
852 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
846 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
853 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
847 repo.dirstate.update_file(f0, p1_tracked=False, wc_tracked=False)
854
848
855 # directory rename, get
849 # directory rename, get
856 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
850 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
857 f0, flag = args
851 f0, flag = args
858 if branchmerge:
852 if branchmerge:
859 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
853 repo.dirstate.update_file(f, p1_tracked=False, wc_tracked=True)
860 repo.dirstate.copy(f0, f)
854 repo.dirstate.copy(f0, f)
861 else:
855 else:
862 repo.dirstate.update_file(f, p1_tracked=True, wc_tracked=True)
856 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