59 Currently known record: |
59 Currently known record: |
60 |
60 |
61 L: the node of the "local" part of the merge (hexified version) |
61 L: the node of the "local" part of the merge (hexified version) |
62 O: the node of the "other" part of the merge (hexified version) |
62 O: the node of the "other" part of the merge (hexified version) |
63 F: a file to be merged entry |
63 F: a file to be merged entry |
|
64 m: the external merge driver defined for this merge plus its run state |
|
65 (experimental) |
|
66 |
|
67 Merge driver run states (experimental): |
|
68 u: driver-resolved files unmarked -- needs to be run next time we're about |
|
69 to resolve or commit |
|
70 m: driver-resolved files marked -- only needs to be run before commit |
|
71 s: success/skipped -- does not need to be run any more |
64 ''' |
72 ''' |
65 statepathv1 = 'merge/state' |
73 statepathv1 = 'merge/state' |
66 statepathv2 = 'merge/state2' |
74 statepathv2 = 'merge/state2' |
67 |
75 |
68 def __init__(self, repo): |
76 def __init__(self, repo): |
87 of on disk file. |
96 of on disk file. |
88 """ |
97 """ |
89 self._state = {} |
98 self._state = {} |
90 self._local = None |
99 self._local = None |
91 self._other = None |
100 self._other = None |
|
101 self._mdstate = 'u' |
92 records = self._readrecords() |
102 records = self._readrecords() |
93 for rtype, record in records: |
103 for rtype, record in records: |
94 if rtype == 'L': |
104 if rtype == 'L': |
95 self._local = bin(record) |
105 self._local = bin(record) |
96 elif rtype == 'O': |
106 elif rtype == 'O': |
97 self._other = bin(record) |
107 self._other = bin(record) |
|
108 elif rtype == 'm': |
|
109 bits = record.split('\0', 1) |
|
110 mdstate = bits[1] |
|
111 if len(mdstate) != 1 or mdstate not in 'ums': |
|
112 # the merge driver should be idempotent, so just rerun it |
|
113 mdstate = 'u' |
|
114 |
|
115 # protect against the following: |
|
116 # - A configures a malicious merge driver in their hgrc, then |
|
117 # pauses the merge |
|
118 # - A edits their hgrc to remove references to the merge driver |
|
119 # - A gives a copy of their entire repo, including .hg, to B |
|
120 # - B inspects .hgrc and finds it to be clean |
|
121 # - B then continues the merge and the malicious merge driver |
|
122 # gets invoked |
|
123 if self.mergedriver != bits[0]: |
|
124 raise error.ConfigError( |
|
125 _("merge driver changed since merge started"), |
|
126 hint=_("revert merge driver change or abort merge")) |
|
127 self._mdstate = mdstate |
98 elif rtype == 'F': |
128 elif rtype == 'F': |
99 bits = record.split('\0') |
129 bits = record.split('\0') |
100 self._state[bits[0]] = bits[1:] |
130 self._state[bits[0]] = bits[1:] |
101 elif not rtype.islower(): |
131 elif not rtype.islower(): |
102 raise error.Abort(_('unsupported merge state record: %s') |
132 raise error.Abort(_('unsupported merge state record: %s') |
196 except IOError as err: |
226 except IOError as err: |
197 if err.errno != errno.ENOENT: |
227 if err.errno != errno.ENOENT: |
198 raise |
228 raise |
199 return records |
229 return records |
200 |
230 |
|
231 @util.propertycache |
|
232 def mergedriver(self): |
|
233 return self._repo.ui.config('experimental', 'mergedriver') |
|
234 |
201 def active(self): |
235 def active(self): |
202 """Whether mergestate is active. |
236 """Whether mergestate is active. |
203 |
237 |
204 Returns True if there appears to be mergestate. This is a rough proxy |
238 Returns True if there appears to be mergestate. This is a rough proxy |
205 for "is a merge in progress." |
239 for "is a merge in progress." |
214 """Write current state on disk (if necessary)""" |
248 """Write current state on disk (if necessary)""" |
215 if self._dirty: |
249 if self._dirty: |
216 records = [] |
250 records = [] |
217 records.append(('L', hex(self._local))) |
251 records.append(('L', hex(self._local))) |
218 records.append(('O', hex(self._other))) |
252 records.append(('O', hex(self._other))) |
|
253 if self.mergedriver: |
|
254 records.append(('m', '\0'.join([ |
|
255 self.mergedriver, self._mdstate]))) |
219 for d, v in self._state.iteritems(): |
256 for d, v in self._state.iteritems(): |
220 records.append(('F', '\0'.join([d] + v))) |
257 records.append(('F', '\0'.join([d] + v))) |
221 self._writerecords(records) |
258 self._writerecords(records) |
222 self._dirty = False |
259 self._dirty = False |
223 |
260 |