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