##// END OF EJS Templates
mergestate: remove unused import...
Joerg Sonnenberger -
r47604:94ea9451 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,
14 nullrev,
13 nullrev,
15 )
14 )
16 from . import (
15 from . import (
17 error,
16 error,
18 filemerge,
17 filemerge,
19 pycompat,
18 pycompat,
20 util,
19 util,
21 )
20 )
22 from .utils import hashutil
21 from .utils import hashutil
23
22
24 _pack = struct.pack
23 _pack = struct.pack
25 _unpack = struct.unpack
24 _unpack = struct.unpack
26
25
27
26
28 def _droponode(data):
27 def _droponode(data):
29 # used for compatibility for v1
28 # used for compatibility for v1
30 bits = data.split(b'\0')
29 bits = data.split(b'\0')
31 bits = bits[:-2] + bits[-1:]
30 bits = bits[:-2] + bits[-1:]
32 return b'\0'.join(bits)
31 return b'\0'.join(bits)
33
32
34
33
35 def _filectxorabsent(hexnode, ctx, f):
34 def _filectxorabsent(hexnode, ctx, f):
36 if hexnode == nullhex:
35 if hexnode == nullhex:
37 return filemerge.absentfilectx(ctx, f)
36 return filemerge.absentfilectx(ctx, f)
38 else:
37 else:
39 return ctx[f]
38 return ctx[f]
40
39
41
40
42 # Merge state record types. See ``mergestate`` docs for more.
41 # Merge state record types. See ``mergestate`` docs for more.
43
42
44 ####
43 ####
45 # merge records which records metadata about a current merge
44 # merge records which records metadata about a current merge
46 # exists only once in a mergestate
45 # exists only once in a mergestate
47 #####
46 #####
48 RECORD_LOCAL = b'L'
47 RECORD_LOCAL = b'L'
49 RECORD_OTHER = b'O'
48 RECORD_OTHER = b'O'
50 # record merge labels
49 # record merge labels
51 RECORD_LABELS = b'l'
50 RECORD_LABELS = b'l'
52
51
53 #####
52 #####
54 # 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
55 # file. Hence, multiple of them can exists
54 # file. Hence, multiple of them can exists
56 #####
55 #####
57 RECORD_FILE_VALUES = b'f'
56 RECORD_FILE_VALUES = b'f'
58
57
59 #####
58 #####
60 # merge records which represents state of individual merges of files/folders
59 # merge records which represents state of individual merges of files/folders
61 # 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.
62 # 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
63 # exists
62 # exists
64 #####
63 #####
65 RECORD_MERGED = b'F'
64 RECORD_MERGED = b'F'
66 RECORD_CHANGEDELETE_CONFLICT = b'C'
65 RECORD_CHANGEDELETE_CONFLICT = b'C'
67 # 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
68 RECORD_PATH_CONFLICT = b'P'
67 RECORD_PATH_CONFLICT = b'P'
69
68
70 #####
69 #####
71 # 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
72 # merge records mentioned just above.
71 # merge records mentioned just above.
73 #####
72 #####
74 MERGE_RECORD_UNRESOLVED = b'u'
73 MERGE_RECORD_UNRESOLVED = b'u'
75 MERGE_RECORD_RESOLVED = b'r'
74 MERGE_RECORD_RESOLVED = b'r'
76 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
75 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
77 MERGE_RECORD_RESOLVED_PATH = b'pr'
76 MERGE_RECORD_RESOLVED_PATH = b'pr'
78 # represents that the file was automatically merged in favor
77 # represents that the file was automatically merged in favor
79 # of other version. This info is used on commit.
78 # of other version. This info is used on commit.
80 # This is now deprecated and commit related information is now
79 # This is now deprecated and commit related information is now
81 # stored in RECORD_FILE_VALUES
80 # stored in RECORD_FILE_VALUES
82 MERGE_RECORD_MERGED_OTHER = b'o'
81 MERGE_RECORD_MERGED_OTHER = b'o'
83
82
84 #####
83 #####
85 # 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
86 # exists
85 # exists
87 #####
86 #####
88 RECORD_OVERRIDE = b't'
87 RECORD_OVERRIDE = b't'
89
88
90 #####
89 #####
91 # 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
92 #####
91 #####
93 # 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
94 LEGACY_RECORD_RESOLVED_OTHER = b'R'
93 LEGACY_RECORD_RESOLVED_OTHER = b'R'
95 # 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
96 LEGACY_RECORD_DRIVER_RESOLVED = b'd'
95 LEGACY_RECORD_DRIVER_RESOLVED = b'd'
97 # 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
98 LEGACY_MERGE_DRIVER_STATE = b'm'
97 LEGACY_MERGE_DRIVER_STATE = b'm'
99 # 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
100 LEGACY_MERGE_DRIVER_MERGE = b'D'
99 LEGACY_MERGE_DRIVER_MERGE = b'D'
101
100
102
101
103 ACTION_FORGET = b'f'
102 ACTION_FORGET = b'f'
104 ACTION_REMOVE = b'r'
103 ACTION_REMOVE = b'r'
105 ACTION_ADD = b'a'
104 ACTION_ADD = b'a'
106 ACTION_GET = b'g'
105 ACTION_GET = b'g'
107 ACTION_PATH_CONFLICT = b'p'
106 ACTION_PATH_CONFLICT = b'p'
108 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
107 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
109 ACTION_ADD_MODIFIED = b'am'
108 ACTION_ADD_MODIFIED = b'am'
110 ACTION_CREATED = b'c'
109 ACTION_CREATED = b'c'
111 ACTION_DELETED_CHANGED = b'dc'
110 ACTION_DELETED_CHANGED = b'dc'
112 ACTION_CHANGED_DELETED = b'cd'
111 ACTION_CHANGED_DELETED = b'cd'
113 ACTION_MERGE = b'm'
112 ACTION_MERGE = b'm'
114 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
113 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
115 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
114 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
116 ACTION_KEEP = b'k'
115 ACTION_KEEP = b'k'
117 # 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
118 # 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
119 # of file deletion, rename etc.)
118 # of file deletion, rename etc.)
120 ACTION_KEEP_ABSENT = b'ka'
119 ACTION_KEEP_ABSENT = b'ka'
121 # 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
122 # hence this file is new and we should keep it
121 # hence this file is new and we should keep it
123 ACTION_KEEP_NEW = b'kn'
122 ACTION_KEEP_NEW = b'kn'
124 ACTION_EXEC = b'e'
123 ACTION_EXEC = b'e'
125 ACTION_CREATED_MERGE = b'cm'
124 ACTION_CREATED_MERGE = b'cm'
126
125
127 # actions which are no op
126 # actions which are no op
128 NO_OP_ACTIONS = (
127 NO_OP_ACTIONS = (
129 ACTION_KEEP,
128 ACTION_KEEP,
130 ACTION_KEEP_ABSENT,
129 ACTION_KEEP_ABSENT,
131 ACTION_KEEP_NEW,
130 ACTION_KEEP_NEW,
132 )
131 )
133
132
134
133
135 class _mergestate_base(object):
134 class _mergestate_base(object):
136 """track 3-way merge state of individual files
135 """track 3-way merge state of individual files
137
136
138 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
139 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
140 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
141 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
142 `_readrecordsv2`.
141 `_readrecordsv2`.
143
142
144 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
145 `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:
146 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
147 lowercase, the record can be safely ignored.
146 lowercase, the record can be safely ignored.
148
147
149 Currently known records:
148 Currently known records:
150
149
151 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)
152 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)
153 F: a file to be merged entry
152 F: a file to be merged entry
154 C: a change/delete or delete/change conflict
153 C: a change/delete or delete/change conflict
155 P: a path conflict (file vs directory)
154 P: a path conflict (file vs directory)
156 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
157 l: the labels for the parts of the merge.
156 l: the labels for the parts of the merge.
158
157
159 Merge record states (stored in self._state, indexed by filename):
158 Merge record states (stored in self._state, indexed by filename):
160 u: unresolved conflict
159 u: unresolved conflict
161 r: resolved conflict
160 r: resolved conflict
162 pu: unresolved path conflict (file conflicts with directory)
161 pu: unresolved path conflict (file conflicts with directory)
163 pr: resolved path conflict
162 pr: resolved path conflict
164 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)
165
164
166 The resolve command transitions between 'u' and 'r' for conflicts and
165 The resolve command transitions between 'u' and 'r' for conflicts and
167 'pu' and 'pr' for path conflicts.
166 'pu' and 'pr' for path conflicts.
168 """
167 """
169
168
170 def __init__(self, repo):
169 def __init__(self, repo):
171 """Initialize the merge state.
170 """Initialize the merge state.
172
171
173 Do not use this directly! Instead call read() or clean()."""
172 Do not use this directly! Instead call read() or clean()."""
174 self._repo = repo
173 self._repo = repo
175 self._state = {}
174 self._state = {}
176 self._stateextras = collections.defaultdict(dict)
175 self._stateextras = collections.defaultdict(dict)
177 self._local = None
176 self._local = None
178 self._other = None
177 self._other = None
179 self._labels = None
178 self._labels = None
180 # contains a mapping of form:
179 # contains a mapping of form:
181 # {filename : (merge_return_value, action_to_be_performed}
180 # {filename : (merge_return_value, action_to_be_performed}
182 # these are results of re-running merge process
181 # these are results of re-running merge process
183 # 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
184 # the merge
183 # the merge
185 self._results = {}
184 self._results = {}
186 self._dirty = False
185 self._dirty = False
187
186
188 def reset(self):
187 def reset(self):
189 pass
188 pass
190
189
191 def start(self, node, other, labels=None):
190 def start(self, node, other, labels=None):
192 self._local = node
191 self._local = node
193 self._other = other
192 self._other = other
194 self._labels = labels
193 self._labels = labels
195
194
196 @util.propertycache
195 @util.propertycache
197 def local(self):
196 def local(self):
198 if self._local is None:
197 if self._local is None:
199 msg = b"local accessed but self._local isn't set"
198 msg = b"local accessed but self._local isn't set"
200 raise error.ProgrammingError(msg)
199 raise error.ProgrammingError(msg)
201 return self._local
200 return self._local
202
201
203 @util.propertycache
202 @util.propertycache
204 def localctx(self):
203 def localctx(self):
205 return self._repo[self.local]
204 return self._repo[self.local]
206
205
207 @util.propertycache
206 @util.propertycache
208 def other(self):
207 def other(self):
209 if self._other is None:
208 if self._other is None:
210 msg = b"other accessed but self._other isn't set"
209 msg = b"other accessed but self._other isn't set"
211 raise error.ProgrammingError(msg)
210 raise error.ProgrammingError(msg)
212 return self._other
211 return self._other
213
212
214 @util.propertycache
213 @util.propertycache
215 def otherctx(self):
214 def otherctx(self):
216 return self._repo[self.other]
215 return self._repo[self.other]
217
216
218 def active(self):
217 def active(self):
219 """Whether mergestate is active.
218 """Whether mergestate is active.
220
219
221 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
222 for "is a merge in progress."
221 for "is a merge in progress."
223 """
222 """
224 return bool(self._local) or bool(self._state)
223 return bool(self._local) or bool(self._state)
225
224
226 def commit(self):
225 def commit(self):
227 """Write current state on disk (if necessary)"""
226 """Write current state on disk (if necessary)"""
228
227
229 @staticmethod
228 @staticmethod
230 def getlocalkey(path):
229 def getlocalkey(path):
231 """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
232 directory."""
231 directory."""
233
232
234 return hex(hashutil.sha1(path).digest())
233 return hex(hashutil.sha1(path).digest())
235
234
236 def _make_backup(self, fctx, localkey):
235 def _make_backup(self, fctx, localkey):
237 raise NotImplementedError()
236 raise NotImplementedError()
238
237
239 def _restore_backup(self, fctx, localkey, flags):
238 def _restore_backup(self, fctx, localkey, flags):
240 raise NotImplementedError()
239 raise NotImplementedError()
241
240
242 def add(self, fcl, fco, fca, fd):
241 def add(self, fcl, fco, fca, fd):
243 """add a new (potentially?) conflicting file the merge state
242 """add a new (potentially?) conflicting file the merge state
244 fcl: file context for local,
243 fcl: file context for local,
245 fco: file context for remote,
244 fco: file context for remote,
246 fca: file context for ancestors,
245 fca: file context for ancestors,
247 fd: file path of the resulting merge.
246 fd: file path of the resulting merge.
248
247
249 note: also write the local version to the `.hg/merge` directory.
248 note: also write the local version to the `.hg/merge` directory.
250 """
249 """
251 if fcl.isabsent():
250 if fcl.isabsent():
252 localkey = nullhex
251 localkey = nullhex
253 else:
252 else:
254 localkey = mergestate.getlocalkey(fcl.path())
253 localkey = mergestate.getlocalkey(fcl.path())
255 self._make_backup(fcl, localkey)
254 self._make_backup(fcl, localkey)
256 self._state[fd] = [
255 self._state[fd] = [
257 MERGE_RECORD_UNRESOLVED,
256 MERGE_RECORD_UNRESOLVED,
258 localkey,
257 localkey,
259 fcl.path(),
258 fcl.path(),
260 fca.path(),
259 fca.path(),
261 hex(fca.filenode()),
260 hex(fca.filenode()),
262 fco.path(),
261 fco.path(),
263 hex(fco.filenode()),
262 hex(fco.filenode()),
264 fcl.flags(),
263 fcl.flags(),
265 ]
264 ]
266 self._stateextras[fd][b'ancestorlinknode'] = hex(fca.node())
265 self._stateextras[fd][b'ancestorlinknode'] = hex(fca.node())
267 self._dirty = True
266 self._dirty = True
268
267
269 def addpathconflict(self, path, frename, forigin):
268 def addpathconflict(self, path, frename, forigin):
270 """add a new conflicting path to the merge state
269 """add a new conflicting path to the merge state
271 path: the path that conflicts
270 path: the path that conflicts
272 frename: the filename the conflicting file was renamed to
271 frename: the filename the conflicting file was renamed to
273 forigin: origin of the file ('l' or 'r' for local/remote)
272 forigin: origin of the file ('l' or 'r' for local/remote)
274 """
273 """
275 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
274 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
276 self._dirty = True
275 self._dirty = True
277
276
278 def addcommitinfo(self, path, data):
277 def addcommitinfo(self, path, data):
279 """stores information which is required at commit
278 """stores information which is required at commit
280 into _stateextras"""
279 into _stateextras"""
281 self._stateextras[path].update(data)
280 self._stateextras[path].update(data)
282 self._dirty = True
281 self._dirty = True
283
282
284 def __contains__(self, dfile):
283 def __contains__(self, dfile):
285 return dfile in self._state
284 return dfile in self._state
286
285
287 def __getitem__(self, dfile):
286 def __getitem__(self, dfile):
288 return self._state[dfile][0]
287 return self._state[dfile][0]
289
288
290 def __iter__(self):
289 def __iter__(self):
291 return iter(sorted(self._state))
290 return iter(sorted(self._state))
292
291
293 def files(self):
292 def files(self):
294 return self._state.keys()
293 return self._state.keys()
295
294
296 def mark(self, dfile, state):
295 def mark(self, dfile, state):
297 self._state[dfile][0] = state
296 self._state[dfile][0] = state
298 self._dirty = True
297 self._dirty = True
299
298
300 def unresolved(self):
299 def unresolved(self):
301 """Obtain the paths of unresolved files."""
300 """Obtain the paths of unresolved files."""
302
301
303 for f, entry in pycompat.iteritems(self._state):
302 for f, entry in pycompat.iteritems(self._state):
304 if entry[0] in (
303 if entry[0] in (
305 MERGE_RECORD_UNRESOLVED,
304 MERGE_RECORD_UNRESOLVED,
306 MERGE_RECORD_UNRESOLVED_PATH,
305 MERGE_RECORD_UNRESOLVED_PATH,
307 ):
306 ):
308 yield f
307 yield f
309
308
310 def allextras(self):
309 def allextras(self):
311 """ return all extras information stored with the mergestate """
310 """ return all extras information stored with the mergestate """
312 return self._stateextras
311 return self._stateextras
313
312
314 def extras(self, filename):
313 def extras(self, filename):
315 """ return extras stored with the mergestate for the given filename """
314 """ return extras stored with the mergestate for the given filename """
316 return self._stateextras[filename]
315 return self._stateextras[filename]
317
316
318 def _resolve(self, preresolve, dfile, wctx):
317 def _resolve(self, preresolve, dfile, wctx):
319 """rerun merge process for file path `dfile`.
318 """rerun merge process for file path `dfile`.
320 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
321 obtained from filemerge._filemerge().
320 obtained from filemerge._filemerge().
322 """
321 """
323 if self[dfile] in (
322 if self[dfile] in (
324 MERGE_RECORD_RESOLVED,
323 MERGE_RECORD_RESOLVED,
325 LEGACY_RECORD_DRIVER_RESOLVED,
324 LEGACY_RECORD_DRIVER_RESOLVED,
326 ):
325 ):
327 return True, 0
326 return True, 0
328 stateentry = self._state[dfile]
327 stateentry = self._state[dfile]
329 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
328 state, localkey, lfile, afile, anode, ofile, onode, flags = stateentry
330 octx = self._repo[self._other]
329 octx = self._repo[self._other]
331 extras = self.extras(dfile)
330 extras = self.extras(dfile)
332 anccommitnode = extras.get(b'ancestorlinknode')
331 anccommitnode = extras.get(b'ancestorlinknode')
333 if anccommitnode:
332 if anccommitnode:
334 actx = self._repo[anccommitnode]
333 actx = self._repo[anccommitnode]
335 else:
334 else:
336 actx = None
335 actx = None
337 fcd = _filectxorabsent(localkey, wctx, dfile)
336 fcd = _filectxorabsent(localkey, wctx, dfile)
338 fco = _filectxorabsent(onode, octx, ofile)
337 fco = _filectxorabsent(onode, octx, ofile)
339 # TODO: move this to filectxorabsent
338 # TODO: move this to filectxorabsent
340 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
339 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
341 # "premerge" x flags
340 # "premerge" x flags
342 flo = fco.flags()
341 flo = fco.flags()
343 fla = fca.flags()
342 fla = fca.flags()
344 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:
345 if fca.rev() == nullrev and flags != flo:
344 if fca.rev() == nullrev and flags != flo:
346 if preresolve:
345 if preresolve:
347 self._repo.ui.warn(
346 self._repo.ui.warn(
348 _(
347 _(
349 b'warning: cannot merge flags for %s '
348 b'warning: cannot merge flags for %s '
350 b'without common ancestor - keeping local flags\n'
349 b'without common ancestor - keeping local flags\n'
351 )
350 )
352 % afile
351 % afile
353 )
352 )
354 elif flags == fla:
353 elif flags == fla:
355 flags = flo
354 flags = flo
356 if preresolve:
355 if preresolve:
357 # restore local
356 # restore local
358 if localkey != nullhex:
357 if localkey != nullhex:
359 self._restore_backup(wctx[dfile], localkey, flags)
358 self._restore_backup(wctx[dfile], localkey, flags)
360 else:
359 else:
361 wctx[dfile].remove(ignoremissing=True)
360 wctx[dfile].remove(ignoremissing=True)
362 complete, merge_ret, deleted = filemerge.premerge(
361 complete, merge_ret, deleted = filemerge.premerge(
363 self._repo,
362 self._repo,
364 wctx,
363 wctx,
365 self._local,
364 self._local,
366 lfile,
365 lfile,
367 fcd,
366 fcd,
368 fco,
367 fco,
369 fca,
368 fca,
370 labels=self._labels,
369 labels=self._labels,
371 )
370 )
372 else:
371 else:
373 complete, merge_ret, deleted = filemerge.filemerge(
372 complete, merge_ret, deleted = filemerge.filemerge(
374 self._repo,
373 self._repo,
375 wctx,
374 wctx,
376 self._local,
375 self._local,
377 lfile,
376 lfile,
378 fcd,
377 fcd,
379 fco,
378 fco,
380 fca,
379 fca,
381 labels=self._labels,
380 labels=self._labels,
382 )
381 )
383 if merge_ret is None:
382 if merge_ret is None:
384 # 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
385 del self._state[dfile]
384 del self._state[dfile]
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