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