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