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