##// END OF EJS Templates
merge: check for path conflicts when merging (issue5628)...
Mark Thomas -
r34556:989e884d default
parent child Browse files
Show More
@@ -1,1884 +1,1988 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import shutil
12 import shutil
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullhex,
21 nullhex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 )
24 )
25 from . import (
25 from . import (
26 copies,
26 copies,
27 error,
27 error,
28 filemerge,
28 filemerge,
29 match as matchmod,
29 match as matchmod,
30 obsutil,
30 obsutil,
31 pycompat,
31 pycompat,
32 scmutil,
32 scmutil,
33 subrepo,
33 subrepo,
34 util,
34 util,
35 worker,
35 worker,
36 )
36 )
37
37
38 _pack = struct.pack
38 _pack = struct.pack
39 _unpack = struct.unpack
39 _unpack = struct.unpack
40
40
41 def _droponode(data):
41 def _droponode(data):
42 # used for compatibility for v1
42 # used for compatibility for v1
43 bits = data.split('\0')
43 bits = data.split('\0')
44 bits = bits[:-2] + bits[-1:]
44 bits = bits[:-2] + bits[-1:]
45 return '\0'.join(bits)
45 return '\0'.join(bits)
46
46
47 class mergestate(object):
47 class mergestate(object):
48 '''track 3-way merge state of individual files
48 '''track 3-way merge state of individual files
49
49
50 The merge state is stored on disk when needed. Two files are used: one with
50 The merge state is stored on disk when needed. Two files are used: one with
51 an old format (version 1), and one with a new format (version 2). Version 2
51 an old format (version 1), and one with a new format (version 2). Version 2
52 stores a superset of the data in version 1, including new kinds of records
52 stores a superset of the data in version 1, including new kinds of records
53 in the future. For more about the new format, see the documentation for
53 in the future. For more about the new format, see the documentation for
54 `_readrecordsv2`.
54 `_readrecordsv2`.
55
55
56 Each record can contain arbitrary content, and has an associated type. This
56 Each record can contain arbitrary content, and has an associated type. This
57 `type` should be a letter. If `type` is uppercase, the record is mandatory:
57 `type` should be a letter. If `type` is uppercase, the record is mandatory:
58 versions of Mercurial that don't support it should abort. If `type` is
58 versions of Mercurial that don't support it should abort. If `type` is
59 lowercase, the record can be safely ignored.
59 lowercase, the record can be safely ignored.
60
60
61 Currently known records:
61 Currently known records:
62
62
63 L: the node of the "local" part of the merge (hexified version)
63 L: the node of the "local" part of the merge (hexified version)
64 O: the node of the "other" part of the merge (hexified version)
64 O: the node of the "other" part of the merge (hexified version)
65 F: a file to be merged entry
65 F: a file to be merged entry
66 C: a change/delete or delete/change conflict
66 C: a change/delete or delete/change conflict
67 D: a file that the external merge driver will merge internally
67 D: a file that the external merge driver will merge internally
68 (experimental)
68 (experimental)
69 P: a path conflict (file vs directory)
69 P: a path conflict (file vs directory)
70 m: the external merge driver defined for this merge plus its run state
70 m: the external merge driver defined for this merge plus its run state
71 (experimental)
71 (experimental)
72 f: a (filename, dictionary) tuple of optional values for a given file
72 f: a (filename, dictionary) tuple of optional values for a given file
73 X: unsupported mandatory record type (used in tests)
73 X: unsupported mandatory record type (used in tests)
74 x: unsupported advisory record type (used in tests)
74 x: unsupported advisory record type (used in tests)
75 l: the labels for the parts of the merge.
75 l: the labels for the parts of the merge.
76
76
77 Merge driver run states (experimental):
77 Merge driver run states (experimental):
78 u: driver-resolved files unmarked -- needs to be run next time we're about
78 u: driver-resolved files unmarked -- needs to be run next time we're about
79 to resolve or commit
79 to resolve or commit
80 m: driver-resolved files marked -- only needs to be run before commit
80 m: driver-resolved files marked -- only needs to be run before commit
81 s: success/skipped -- does not need to be run any more
81 s: success/skipped -- does not need to be run any more
82
82
83 Merge record states (stored in self._state, indexed by filename):
83 Merge record states (stored in self._state, indexed by filename):
84 u: unresolved conflict
84 u: unresolved conflict
85 r: resolved conflict
85 r: resolved conflict
86 pu: unresolved path conflict (file conflicts with directory)
86 pu: unresolved path conflict (file conflicts with directory)
87 pr: resolved path conflict
87 pr: resolved path conflict
88 d: driver-resolved conflict
88 d: driver-resolved conflict
89
89
90 The resolve command transitions between 'u' and 'r' for conflicts and
90 The resolve command transitions between 'u' and 'r' for conflicts and
91 'pu' and 'pr' for path conflicts.
91 'pu' and 'pr' for path conflicts.
92 '''
92 '''
93 statepathv1 = 'merge/state'
93 statepathv1 = 'merge/state'
94 statepathv2 = 'merge/state2'
94 statepathv2 = 'merge/state2'
95
95
96 @staticmethod
96 @staticmethod
97 def clean(repo, node=None, other=None, labels=None):
97 def clean(repo, node=None, other=None, labels=None):
98 """Initialize a brand new merge state, removing any existing state on
98 """Initialize a brand new merge state, removing any existing state on
99 disk."""
99 disk."""
100 ms = mergestate(repo)
100 ms = mergestate(repo)
101 ms.reset(node, other, labels)
101 ms.reset(node, other, labels)
102 return ms
102 return ms
103
103
104 @staticmethod
104 @staticmethod
105 def read(repo):
105 def read(repo):
106 """Initialize the merge state, reading it from disk."""
106 """Initialize the merge state, reading it from disk."""
107 ms = mergestate(repo)
107 ms = mergestate(repo)
108 ms._read()
108 ms._read()
109 return ms
109 return ms
110
110
111 def __init__(self, repo):
111 def __init__(self, repo):
112 """Initialize the merge state.
112 """Initialize the merge state.
113
113
114 Do not use this directly! Instead call read() or clean()."""
114 Do not use this directly! Instead call read() or clean()."""
115 self._repo = repo
115 self._repo = repo
116 self._dirty = False
116 self._dirty = False
117 self._labels = None
117 self._labels = None
118
118
119 def reset(self, node=None, other=None, labels=None):
119 def reset(self, node=None, other=None, labels=None):
120 self._state = {}
120 self._state = {}
121 self._stateextras = {}
121 self._stateextras = {}
122 self._local = None
122 self._local = None
123 self._other = None
123 self._other = None
124 self._labels = labels
124 self._labels = labels
125 for var in ('localctx', 'otherctx'):
125 for var in ('localctx', 'otherctx'):
126 if var in vars(self):
126 if var in vars(self):
127 delattr(self, var)
127 delattr(self, var)
128 if node:
128 if node:
129 self._local = node
129 self._local = node
130 self._other = other
130 self._other = other
131 self._readmergedriver = None
131 self._readmergedriver = None
132 if self.mergedriver:
132 if self.mergedriver:
133 self._mdstate = 's'
133 self._mdstate = 's'
134 else:
134 else:
135 self._mdstate = 'u'
135 self._mdstate = 'u'
136 shutil.rmtree(self._repo.vfs.join('merge'), True)
136 shutil.rmtree(self._repo.vfs.join('merge'), True)
137 self._results = {}
137 self._results = {}
138 self._dirty = False
138 self._dirty = False
139
139
140 def _read(self):
140 def _read(self):
141 """Analyse each record content to restore a serialized state from disk
141 """Analyse each record content to restore a serialized state from disk
142
142
143 This function process "record" entry produced by the de-serialization
143 This function process "record" entry produced by the de-serialization
144 of on disk file.
144 of on disk file.
145 """
145 """
146 self._state = {}
146 self._state = {}
147 self._stateextras = {}
147 self._stateextras = {}
148 self._local = None
148 self._local = None
149 self._other = None
149 self._other = None
150 for var in ('localctx', 'otherctx'):
150 for var in ('localctx', 'otherctx'):
151 if var in vars(self):
151 if var in vars(self):
152 delattr(self, var)
152 delattr(self, var)
153 self._readmergedriver = None
153 self._readmergedriver = None
154 self._mdstate = 's'
154 self._mdstate = 's'
155 unsupported = set()
155 unsupported = set()
156 records = self._readrecords()
156 records = self._readrecords()
157 for rtype, record in records:
157 for rtype, record in records:
158 if rtype == 'L':
158 if rtype == 'L':
159 self._local = bin(record)
159 self._local = bin(record)
160 elif rtype == 'O':
160 elif rtype == 'O':
161 self._other = bin(record)
161 self._other = bin(record)
162 elif rtype == 'm':
162 elif rtype == 'm':
163 bits = record.split('\0', 1)
163 bits = record.split('\0', 1)
164 mdstate = bits[1]
164 mdstate = bits[1]
165 if len(mdstate) != 1 or mdstate not in 'ums':
165 if len(mdstate) != 1 or mdstate not in 'ums':
166 # the merge driver should be idempotent, so just rerun it
166 # the merge driver should be idempotent, so just rerun it
167 mdstate = 'u'
167 mdstate = 'u'
168
168
169 self._readmergedriver = bits[0]
169 self._readmergedriver = bits[0]
170 self._mdstate = mdstate
170 self._mdstate = mdstate
171 elif rtype in 'FDCP':
171 elif rtype in 'FDCP':
172 bits = record.split('\0')
172 bits = record.split('\0')
173 self._state[bits[0]] = bits[1:]
173 self._state[bits[0]] = bits[1:]
174 elif rtype == 'f':
174 elif rtype == 'f':
175 filename, rawextras = record.split('\0', 1)
175 filename, rawextras = record.split('\0', 1)
176 extraparts = rawextras.split('\0')
176 extraparts = rawextras.split('\0')
177 extras = {}
177 extras = {}
178 i = 0
178 i = 0
179 while i < len(extraparts):
179 while i < len(extraparts):
180 extras[extraparts[i]] = extraparts[i + 1]
180 extras[extraparts[i]] = extraparts[i + 1]
181 i += 2
181 i += 2
182
182
183 self._stateextras[filename] = extras
183 self._stateextras[filename] = extras
184 elif rtype == 'l':
184 elif rtype == 'l':
185 labels = record.split('\0', 2)
185 labels = record.split('\0', 2)
186 self._labels = [l for l in labels if len(l) > 0]
186 self._labels = [l for l in labels if len(l) > 0]
187 elif not rtype.islower():
187 elif not rtype.islower():
188 unsupported.add(rtype)
188 unsupported.add(rtype)
189 self._results = {}
189 self._results = {}
190 self._dirty = False
190 self._dirty = False
191
191
192 if unsupported:
192 if unsupported:
193 raise error.UnsupportedMergeRecords(unsupported)
193 raise error.UnsupportedMergeRecords(unsupported)
194
194
195 def _readrecords(self):
195 def _readrecords(self):
196 """Read merge state from disk and return a list of record (TYPE, data)
196 """Read merge state from disk and return a list of record (TYPE, data)
197
197
198 We read data from both v1 and v2 files and decide which one to use.
198 We read data from both v1 and v2 files and decide which one to use.
199
199
200 V1 has been used by version prior to 2.9.1 and contains less data than
200 V1 has been used by version prior to 2.9.1 and contains less data than
201 v2. We read both versions and check if no data in v2 contradicts
201 v2. We read both versions and check if no data in v2 contradicts
202 v1. If there is not contradiction we can safely assume that both v1
202 v1. If there is not contradiction we can safely assume that both v1
203 and v2 were written at the same time and use the extract data in v2. If
203 and v2 were written at the same time and use the extract data in v2. If
204 there is contradiction we ignore v2 content as we assume an old version
204 there is contradiction we ignore v2 content as we assume an old version
205 of Mercurial has overwritten the mergestate file and left an old v2
205 of Mercurial has overwritten the mergestate file and left an old v2
206 file around.
206 file around.
207
207
208 returns list of record [(TYPE, data), ...]"""
208 returns list of record [(TYPE, data), ...]"""
209 v1records = self._readrecordsv1()
209 v1records = self._readrecordsv1()
210 v2records = self._readrecordsv2()
210 v2records = self._readrecordsv2()
211 if self._v1v2match(v1records, v2records):
211 if self._v1v2match(v1records, v2records):
212 return v2records
212 return v2records
213 else:
213 else:
214 # v1 file is newer than v2 file, use it
214 # v1 file is newer than v2 file, use it
215 # we have to infer the "other" changeset of the merge
215 # we have to infer the "other" changeset of the merge
216 # we cannot do better than that with v1 of the format
216 # we cannot do better than that with v1 of the format
217 mctx = self._repo[None].parents()[-1]
217 mctx = self._repo[None].parents()[-1]
218 v1records.append(('O', mctx.hex()))
218 v1records.append(('O', mctx.hex()))
219 # add place holder "other" file node information
219 # add place holder "other" file node information
220 # nobody is using it yet so we do no need to fetch the data
220 # nobody is using it yet so we do no need to fetch the data
221 # if mctx was wrong `mctx[bits[-2]]` may fails.
221 # if mctx was wrong `mctx[bits[-2]]` may fails.
222 for idx, r in enumerate(v1records):
222 for idx, r in enumerate(v1records):
223 if r[0] == 'F':
223 if r[0] == 'F':
224 bits = r[1].split('\0')
224 bits = r[1].split('\0')
225 bits.insert(-2, '')
225 bits.insert(-2, '')
226 v1records[idx] = (r[0], '\0'.join(bits))
226 v1records[idx] = (r[0], '\0'.join(bits))
227 return v1records
227 return v1records
228
228
229 def _v1v2match(self, v1records, v2records):
229 def _v1v2match(self, v1records, v2records):
230 oldv2 = set() # old format version of v2 record
230 oldv2 = set() # old format version of v2 record
231 for rec in v2records:
231 for rec in v2records:
232 if rec[0] == 'L':
232 if rec[0] == 'L':
233 oldv2.add(rec)
233 oldv2.add(rec)
234 elif rec[0] == 'F':
234 elif rec[0] == 'F':
235 # drop the onode data (not contained in v1)
235 # drop the onode data (not contained in v1)
236 oldv2.add(('F', _droponode(rec[1])))
236 oldv2.add(('F', _droponode(rec[1])))
237 for rec in v1records:
237 for rec in v1records:
238 if rec not in oldv2:
238 if rec not in oldv2:
239 return False
239 return False
240 else:
240 else:
241 return True
241 return True
242
242
243 def _readrecordsv1(self):
243 def _readrecordsv1(self):
244 """read on disk merge state for version 1 file
244 """read on disk merge state for version 1 file
245
245
246 returns list of record [(TYPE, data), ...]
246 returns list of record [(TYPE, data), ...]
247
247
248 Note: the "F" data from this file are one entry short
248 Note: the "F" data from this file are one entry short
249 (no "other file node" entry)
249 (no "other file node" entry)
250 """
250 """
251 records = []
251 records = []
252 try:
252 try:
253 f = self._repo.vfs(self.statepathv1)
253 f = self._repo.vfs(self.statepathv1)
254 for i, l in enumerate(f):
254 for i, l in enumerate(f):
255 if i == 0:
255 if i == 0:
256 records.append(('L', l[:-1]))
256 records.append(('L', l[:-1]))
257 else:
257 else:
258 records.append(('F', l[:-1]))
258 records.append(('F', l[:-1]))
259 f.close()
259 f.close()
260 except IOError as err:
260 except IOError as err:
261 if err.errno != errno.ENOENT:
261 if err.errno != errno.ENOENT:
262 raise
262 raise
263 return records
263 return records
264
264
265 def _readrecordsv2(self):
265 def _readrecordsv2(self):
266 """read on disk merge state for version 2 file
266 """read on disk merge state for version 2 file
267
267
268 This format is a list of arbitrary records of the form:
268 This format is a list of arbitrary records of the form:
269
269
270 [type][length][content]
270 [type][length][content]
271
271
272 `type` is a single character, `length` is a 4 byte integer, and
272 `type` is a single character, `length` is a 4 byte integer, and
273 `content` is an arbitrary byte sequence of length `length`.
273 `content` is an arbitrary byte sequence of length `length`.
274
274
275 Mercurial versions prior to 3.7 have a bug where if there are
275 Mercurial versions prior to 3.7 have a bug where if there are
276 unsupported mandatory merge records, attempting to clear out the merge
276 unsupported mandatory merge records, attempting to clear out the merge
277 state with hg update --clean or similar aborts. The 't' record type
277 state with hg update --clean or similar aborts. The 't' record type
278 works around that by writing out what those versions treat as an
278 works around that by writing out what those versions treat as an
279 advisory record, but later versions interpret as special: the first
279 advisory record, but later versions interpret as special: the first
280 character is the 'real' record type and everything onwards is the data.
280 character is the 'real' record type and everything onwards is the data.
281
281
282 Returns list of records [(TYPE, data), ...]."""
282 Returns list of records [(TYPE, data), ...]."""
283 records = []
283 records = []
284 try:
284 try:
285 f = self._repo.vfs(self.statepathv2)
285 f = self._repo.vfs(self.statepathv2)
286 data = f.read()
286 data = f.read()
287 off = 0
287 off = 0
288 end = len(data)
288 end = len(data)
289 while off < end:
289 while off < end:
290 rtype = data[off]
290 rtype = data[off]
291 off += 1
291 off += 1
292 length = _unpack('>I', data[off:(off + 4)])[0]
292 length = _unpack('>I', data[off:(off + 4)])[0]
293 off += 4
293 off += 4
294 record = data[off:(off + length)]
294 record = data[off:(off + length)]
295 off += length
295 off += length
296 if rtype == 't':
296 if rtype == 't':
297 rtype, record = record[0], record[1:]
297 rtype, record = record[0], record[1:]
298 records.append((rtype, record))
298 records.append((rtype, record))
299 f.close()
299 f.close()
300 except IOError as err:
300 except IOError as err:
301 if err.errno != errno.ENOENT:
301 if err.errno != errno.ENOENT:
302 raise
302 raise
303 return records
303 return records
304
304
305 @util.propertycache
305 @util.propertycache
306 def mergedriver(self):
306 def mergedriver(self):
307 # protect against the following:
307 # protect against the following:
308 # - A configures a malicious merge driver in their hgrc, then
308 # - A configures a malicious merge driver in their hgrc, then
309 # pauses the merge
309 # pauses the merge
310 # - A edits their hgrc to remove references to the merge driver
310 # - A edits their hgrc to remove references to the merge driver
311 # - A gives a copy of their entire repo, including .hg, to B
311 # - A gives a copy of their entire repo, including .hg, to B
312 # - B inspects .hgrc and finds it to be clean
312 # - B inspects .hgrc and finds it to be clean
313 # - B then continues the merge and the malicious merge driver
313 # - B then continues the merge and the malicious merge driver
314 # gets invoked
314 # gets invoked
315 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
315 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
316 if (self._readmergedriver is not None
316 if (self._readmergedriver is not None
317 and self._readmergedriver != configmergedriver):
317 and self._readmergedriver != configmergedriver):
318 raise error.ConfigError(
318 raise error.ConfigError(
319 _("merge driver changed since merge started"),
319 _("merge driver changed since merge started"),
320 hint=_("revert merge driver change or abort merge"))
320 hint=_("revert merge driver change or abort merge"))
321
321
322 return configmergedriver
322 return configmergedriver
323
323
324 @util.propertycache
324 @util.propertycache
325 def localctx(self):
325 def localctx(self):
326 if self._local is None:
326 if self._local is None:
327 msg = "localctx accessed but self._local isn't set"
327 msg = "localctx accessed but self._local isn't set"
328 raise error.ProgrammingError(msg)
328 raise error.ProgrammingError(msg)
329 return self._repo[self._local]
329 return self._repo[self._local]
330
330
331 @util.propertycache
331 @util.propertycache
332 def otherctx(self):
332 def otherctx(self):
333 if self._other is None:
333 if self._other is None:
334 msg = "otherctx accessed but self._other isn't set"
334 msg = "otherctx accessed but self._other isn't set"
335 raise error.ProgrammingError(msg)
335 raise error.ProgrammingError(msg)
336 return self._repo[self._other]
336 return self._repo[self._other]
337
337
338 def active(self):
338 def active(self):
339 """Whether mergestate is active.
339 """Whether mergestate is active.
340
340
341 Returns True if there appears to be mergestate. This is a rough proxy
341 Returns True if there appears to be mergestate. This is a rough proxy
342 for "is a merge in progress."
342 for "is a merge in progress."
343 """
343 """
344 # Check local variables before looking at filesystem for performance
344 # Check local variables before looking at filesystem for performance
345 # reasons.
345 # reasons.
346 return bool(self._local) or bool(self._state) or \
346 return bool(self._local) or bool(self._state) or \
347 self._repo.vfs.exists(self.statepathv1) or \
347 self._repo.vfs.exists(self.statepathv1) or \
348 self._repo.vfs.exists(self.statepathv2)
348 self._repo.vfs.exists(self.statepathv2)
349
349
350 def commit(self):
350 def commit(self):
351 """Write current state on disk (if necessary)"""
351 """Write current state on disk (if necessary)"""
352 if self._dirty:
352 if self._dirty:
353 records = self._makerecords()
353 records = self._makerecords()
354 self._writerecords(records)
354 self._writerecords(records)
355 self._dirty = False
355 self._dirty = False
356
356
357 def _makerecords(self):
357 def _makerecords(self):
358 records = []
358 records = []
359 records.append(('L', hex(self._local)))
359 records.append(('L', hex(self._local)))
360 records.append(('O', hex(self._other)))
360 records.append(('O', hex(self._other)))
361 if self.mergedriver:
361 if self.mergedriver:
362 records.append(('m', '\0'.join([
362 records.append(('m', '\0'.join([
363 self.mergedriver, self._mdstate])))
363 self.mergedriver, self._mdstate])))
364 for d, v in self._state.iteritems():
364 for d, v in self._state.iteritems():
365 if v[0] == 'd':
365 if v[0] == 'd':
366 records.append(('D', '\0'.join([d] + v)))
366 records.append(('D', '\0'.join([d] + v)))
367 elif v[0] in ('pu', 'pr'):
367 elif v[0] in ('pu', 'pr'):
368 records.append(('P', '\0'.join([d] + v)))
368 records.append(('P', '\0'.join([d] + v)))
369 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
369 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
370 # older versions of Mercurial
370 # older versions of Mercurial
371 elif v[1] == nullhex or v[6] == nullhex:
371 elif v[1] == nullhex or v[6] == nullhex:
372 records.append(('C', '\0'.join([d] + v)))
372 records.append(('C', '\0'.join([d] + v)))
373 else:
373 else:
374 records.append(('F', '\0'.join([d] + v)))
374 records.append(('F', '\0'.join([d] + v)))
375 for filename, extras in sorted(self._stateextras.iteritems()):
375 for filename, extras in sorted(self._stateextras.iteritems()):
376 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
376 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
377 extras.iteritems())
377 extras.iteritems())
378 records.append(('f', '%s\0%s' % (filename, rawextras)))
378 records.append(('f', '%s\0%s' % (filename, rawextras)))
379 if self._labels is not None:
379 if self._labels is not None:
380 labels = '\0'.join(self._labels)
380 labels = '\0'.join(self._labels)
381 records.append(('l', labels))
381 records.append(('l', labels))
382 return records
382 return records
383
383
384 def _writerecords(self, records):
384 def _writerecords(self, records):
385 """Write current state on disk (both v1 and v2)"""
385 """Write current state on disk (both v1 and v2)"""
386 self._writerecordsv1(records)
386 self._writerecordsv1(records)
387 self._writerecordsv2(records)
387 self._writerecordsv2(records)
388
388
389 def _writerecordsv1(self, records):
389 def _writerecordsv1(self, records):
390 """Write current state on disk in a version 1 file"""
390 """Write current state on disk in a version 1 file"""
391 f = self._repo.vfs(self.statepathv1, 'w')
391 f = self._repo.vfs(self.statepathv1, 'w')
392 irecords = iter(records)
392 irecords = iter(records)
393 lrecords = next(irecords)
393 lrecords = next(irecords)
394 assert lrecords[0] == 'L'
394 assert lrecords[0] == 'L'
395 f.write(hex(self._local) + '\n')
395 f.write(hex(self._local) + '\n')
396 for rtype, data in irecords:
396 for rtype, data in irecords:
397 if rtype == 'F':
397 if rtype == 'F':
398 f.write('%s\n' % _droponode(data))
398 f.write('%s\n' % _droponode(data))
399 f.close()
399 f.close()
400
400
401 def _writerecordsv2(self, records):
401 def _writerecordsv2(self, records):
402 """Write current state on disk in a version 2 file
402 """Write current state on disk in a version 2 file
403
403
404 See the docstring for _readrecordsv2 for why we use 't'."""
404 See the docstring for _readrecordsv2 for why we use 't'."""
405 # these are the records that all version 2 clients can read
405 # these are the records that all version 2 clients can read
406 whitelist = 'LOF'
406 whitelist = 'LOF'
407 f = self._repo.vfs(self.statepathv2, 'w')
407 f = self._repo.vfs(self.statepathv2, 'w')
408 for key, data in records:
408 for key, data in records:
409 assert len(key) == 1
409 assert len(key) == 1
410 if key not in whitelist:
410 if key not in whitelist:
411 key, data = 't', '%s%s' % (key, data)
411 key, data = 't', '%s%s' % (key, data)
412 format = '>sI%is' % len(data)
412 format = '>sI%is' % len(data)
413 f.write(_pack(format, key, len(data), data))
413 f.write(_pack(format, key, len(data), data))
414 f.close()
414 f.close()
415
415
416 def add(self, fcl, fco, fca, fd):
416 def add(self, fcl, fco, fca, fd):
417 """add a new (potentially?) conflicting file the merge state
417 """add a new (potentially?) conflicting file the merge state
418 fcl: file context for local,
418 fcl: file context for local,
419 fco: file context for remote,
419 fco: file context for remote,
420 fca: file context for ancestors,
420 fca: file context for ancestors,
421 fd: file path of the resulting merge.
421 fd: file path of the resulting merge.
422
422
423 note: also write the local version to the `.hg/merge` directory.
423 note: also write the local version to the `.hg/merge` directory.
424 """
424 """
425 if fcl.isabsent():
425 if fcl.isabsent():
426 hash = nullhex
426 hash = nullhex
427 else:
427 else:
428 hash = hex(hashlib.sha1(fcl.path()).digest())
428 hash = hex(hashlib.sha1(fcl.path()).digest())
429 self._repo.vfs.write('merge/' + hash, fcl.data())
429 self._repo.vfs.write('merge/' + hash, fcl.data())
430 self._state[fd] = ['u', hash, fcl.path(),
430 self._state[fd] = ['u', hash, fcl.path(),
431 fca.path(), hex(fca.filenode()),
431 fca.path(), hex(fca.filenode()),
432 fco.path(), hex(fco.filenode()),
432 fco.path(), hex(fco.filenode()),
433 fcl.flags()]
433 fcl.flags()]
434 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
434 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
435 self._dirty = True
435 self._dirty = True
436
436
437 def addpath(self, path, frename, forigin):
437 def addpath(self, path, frename, forigin):
438 """add a new conflicting path to the merge state
438 """add a new conflicting path to the merge state
439 path: the path that conflicts
439 path: the path that conflicts
440 frename: the filename the conflicting file was renamed to
440 frename: the filename the conflicting file was renamed to
441 forigin: origin of the file ('l' or 'r' for local/remote)
441 forigin: origin of the file ('l' or 'r' for local/remote)
442 """
442 """
443 self._state[path] = ['pu', frename, forigin]
443 self._state[path] = ['pu', frename, forigin]
444 self._dirty = True
444 self._dirty = True
445
445
446 def __contains__(self, dfile):
446 def __contains__(self, dfile):
447 return dfile in self._state
447 return dfile in self._state
448
448
449 def __getitem__(self, dfile):
449 def __getitem__(self, dfile):
450 return self._state[dfile][0]
450 return self._state[dfile][0]
451
451
452 def __iter__(self):
452 def __iter__(self):
453 return iter(sorted(self._state))
453 return iter(sorted(self._state))
454
454
455 def files(self):
455 def files(self):
456 return self._state.keys()
456 return self._state.keys()
457
457
458 def mark(self, dfile, state):
458 def mark(self, dfile, state):
459 self._state[dfile][0] = state
459 self._state[dfile][0] = state
460 self._dirty = True
460 self._dirty = True
461
461
462 def mdstate(self):
462 def mdstate(self):
463 return self._mdstate
463 return self._mdstate
464
464
465 def unresolved(self):
465 def unresolved(self):
466 """Obtain the paths of unresolved files."""
466 """Obtain the paths of unresolved files."""
467
467
468 for f, entry in self._state.iteritems():
468 for f, entry in self._state.iteritems():
469 if entry[0] in ('u', 'pu'):
469 if entry[0] in ('u', 'pu'):
470 yield f
470 yield f
471
471
472 def driverresolved(self):
472 def driverresolved(self):
473 """Obtain the paths of driver-resolved files."""
473 """Obtain the paths of driver-resolved files."""
474
474
475 for f, entry in self._state.items():
475 for f, entry in self._state.items():
476 if entry[0] == 'd':
476 if entry[0] == 'd':
477 yield f
477 yield f
478
478
479 def extras(self, filename):
479 def extras(self, filename):
480 return self._stateextras.setdefault(filename, {})
480 return self._stateextras.setdefault(filename, {})
481
481
482 def _resolve(self, preresolve, dfile, wctx):
482 def _resolve(self, preresolve, dfile, wctx):
483 """rerun merge process for file path `dfile`"""
483 """rerun merge process for file path `dfile`"""
484 if self[dfile] in 'rd':
484 if self[dfile] in 'rd':
485 return True, 0
485 return True, 0
486 stateentry = self._state[dfile]
486 stateentry = self._state[dfile]
487 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
487 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
488 octx = self._repo[self._other]
488 octx = self._repo[self._other]
489 extras = self.extras(dfile)
489 extras = self.extras(dfile)
490 anccommitnode = extras.get('ancestorlinknode')
490 anccommitnode = extras.get('ancestorlinknode')
491 if anccommitnode:
491 if anccommitnode:
492 actx = self._repo[anccommitnode]
492 actx = self._repo[anccommitnode]
493 else:
493 else:
494 actx = None
494 actx = None
495 fcd = self._filectxorabsent(hash, wctx, dfile)
495 fcd = self._filectxorabsent(hash, wctx, dfile)
496 fco = self._filectxorabsent(onode, octx, ofile)
496 fco = self._filectxorabsent(onode, octx, ofile)
497 # TODO: move this to filectxorabsent
497 # TODO: move this to filectxorabsent
498 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
498 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
499 # "premerge" x flags
499 # "premerge" x flags
500 flo = fco.flags()
500 flo = fco.flags()
501 fla = fca.flags()
501 fla = fca.flags()
502 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
502 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
503 if fca.node() == nullid and flags != flo:
503 if fca.node() == nullid and flags != flo:
504 if preresolve:
504 if preresolve:
505 self._repo.ui.warn(
505 self._repo.ui.warn(
506 _('warning: cannot merge flags for %s '
506 _('warning: cannot merge flags for %s '
507 'without common ancestor - keeping local flags\n')
507 'without common ancestor - keeping local flags\n')
508 % afile)
508 % afile)
509 elif flags == fla:
509 elif flags == fla:
510 flags = flo
510 flags = flo
511 if preresolve:
511 if preresolve:
512 # restore local
512 # restore local
513 if hash != nullhex:
513 if hash != nullhex:
514 f = self._repo.vfs('merge/' + hash)
514 f = self._repo.vfs('merge/' + hash)
515 wctx[dfile].write(f.read(), flags)
515 wctx[dfile].write(f.read(), flags)
516 f.close()
516 f.close()
517 else:
517 else:
518 wctx[dfile].remove(ignoremissing=True)
518 wctx[dfile].remove(ignoremissing=True)
519 complete, r, deleted = filemerge.premerge(self._repo, wctx,
519 complete, r, deleted = filemerge.premerge(self._repo, wctx,
520 self._local, lfile, fcd,
520 self._local, lfile, fcd,
521 fco, fca,
521 fco, fca,
522 labels=self._labels)
522 labels=self._labels)
523 else:
523 else:
524 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
524 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
525 self._local, lfile, fcd,
525 self._local, lfile, fcd,
526 fco, fca,
526 fco, fca,
527 labels=self._labels)
527 labels=self._labels)
528 if r is None:
528 if r is None:
529 # no real conflict
529 # no real conflict
530 del self._state[dfile]
530 del self._state[dfile]
531 self._stateextras.pop(dfile, None)
531 self._stateextras.pop(dfile, None)
532 self._dirty = True
532 self._dirty = True
533 elif not r:
533 elif not r:
534 self.mark(dfile, 'r')
534 self.mark(dfile, 'r')
535
535
536 if complete:
536 if complete:
537 action = None
537 action = None
538 if deleted:
538 if deleted:
539 if fcd.isabsent():
539 if fcd.isabsent():
540 # dc: local picked. Need to drop if present, which may
540 # dc: local picked. Need to drop if present, which may
541 # happen on re-resolves.
541 # happen on re-resolves.
542 action = 'f'
542 action = 'f'
543 else:
543 else:
544 # cd: remote picked (or otherwise deleted)
544 # cd: remote picked (or otherwise deleted)
545 action = 'r'
545 action = 'r'
546 else:
546 else:
547 if fcd.isabsent(): # dc: remote picked
547 if fcd.isabsent(): # dc: remote picked
548 action = 'g'
548 action = 'g'
549 elif fco.isabsent(): # cd: local picked
549 elif fco.isabsent(): # cd: local picked
550 if dfile in self.localctx:
550 if dfile in self.localctx:
551 action = 'am'
551 action = 'am'
552 else:
552 else:
553 action = 'a'
553 action = 'a'
554 # else: regular merges (no action necessary)
554 # else: regular merges (no action necessary)
555 self._results[dfile] = r, action
555 self._results[dfile] = r, action
556
556
557 return complete, r
557 return complete, r
558
558
559 def _filectxorabsent(self, hexnode, ctx, f):
559 def _filectxorabsent(self, hexnode, ctx, f):
560 if hexnode == nullhex:
560 if hexnode == nullhex:
561 return filemerge.absentfilectx(ctx, f)
561 return filemerge.absentfilectx(ctx, f)
562 else:
562 else:
563 return ctx[f]
563 return ctx[f]
564
564
565 def preresolve(self, dfile, wctx):
565 def preresolve(self, dfile, wctx):
566 """run premerge process for dfile
566 """run premerge process for dfile
567
567
568 Returns whether the merge is complete, and the exit code."""
568 Returns whether the merge is complete, and the exit code."""
569 return self._resolve(True, dfile, wctx)
569 return self._resolve(True, dfile, wctx)
570
570
571 def resolve(self, dfile, wctx):
571 def resolve(self, dfile, wctx):
572 """run merge process (assuming premerge was run) for dfile
572 """run merge process (assuming premerge was run) for dfile
573
573
574 Returns the exit code of the merge."""
574 Returns the exit code of the merge."""
575 return self._resolve(False, dfile, wctx)[1]
575 return self._resolve(False, dfile, wctx)[1]
576
576
577 def counts(self):
577 def counts(self):
578 """return counts for updated, merged and removed files in this
578 """return counts for updated, merged and removed files in this
579 session"""
579 session"""
580 updated, merged, removed = 0, 0, 0
580 updated, merged, removed = 0, 0, 0
581 for r, action in self._results.itervalues():
581 for r, action in self._results.itervalues():
582 if r is None:
582 if r is None:
583 updated += 1
583 updated += 1
584 elif r == 0:
584 elif r == 0:
585 if action == 'r':
585 if action == 'r':
586 removed += 1
586 removed += 1
587 else:
587 else:
588 merged += 1
588 merged += 1
589 return updated, merged, removed
589 return updated, merged, removed
590
590
591 def unresolvedcount(self):
591 def unresolvedcount(self):
592 """get unresolved count for this merge (persistent)"""
592 """get unresolved count for this merge (persistent)"""
593 return len(list(self.unresolved()))
593 return len(list(self.unresolved()))
594
594
595 def actions(self):
595 def actions(self):
596 """return lists of actions to perform on the dirstate"""
596 """return lists of actions to perform on the dirstate"""
597 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
597 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
598 for f, (r, action) in self._results.iteritems():
598 for f, (r, action) in self._results.iteritems():
599 if action is not None:
599 if action is not None:
600 actions[action].append((f, None, "merge result"))
600 actions[action].append((f, None, "merge result"))
601 return actions
601 return actions
602
602
603 def recordactions(self):
603 def recordactions(self):
604 """record remove/add/get actions in the dirstate"""
604 """record remove/add/get actions in the dirstate"""
605 branchmerge = self._repo.dirstate.p2() != nullid
605 branchmerge = self._repo.dirstate.p2() != nullid
606 recordupdates(self._repo, self.actions(), branchmerge)
606 recordupdates(self._repo, self.actions(), branchmerge)
607
607
608 def queueremove(self, f):
608 def queueremove(self, f):
609 """queues a file to be removed from the dirstate
609 """queues a file to be removed from the dirstate
610
610
611 Meant for use by custom merge drivers."""
611 Meant for use by custom merge drivers."""
612 self._results[f] = 0, 'r'
612 self._results[f] = 0, 'r'
613
613
614 def queueadd(self, f):
614 def queueadd(self, f):
615 """queues a file to be added to the dirstate
615 """queues a file to be added to the dirstate
616
616
617 Meant for use by custom merge drivers."""
617 Meant for use by custom merge drivers."""
618 self._results[f] = 0, 'a'
618 self._results[f] = 0, 'a'
619
619
620 def queueget(self, f):
620 def queueget(self, f):
621 """queues a file to be marked modified in the dirstate
621 """queues a file to be marked modified in the dirstate
622
622
623 Meant for use by custom merge drivers."""
623 Meant for use by custom merge drivers."""
624 self._results[f] = 0, 'g'
624 self._results[f] = 0, 'g'
625
625
626 def _getcheckunknownconfig(repo, section, name):
626 def _getcheckunknownconfig(repo, section, name):
627 config = repo.ui.config(section, name)
627 config = repo.ui.config(section, name)
628 valid = ['abort', 'ignore', 'warn']
628 valid = ['abort', 'ignore', 'warn']
629 if config not in valid:
629 if config not in valid:
630 validstr = ', '.join(["'" + v + "'" for v in valid])
630 validstr = ', '.join(["'" + v + "'" for v in valid])
631 raise error.ConfigError(_("%s.%s not valid "
631 raise error.ConfigError(_("%s.%s not valid "
632 "('%s' is none of %s)")
632 "('%s' is none of %s)")
633 % (section, name, config, validstr))
633 % (section, name, config, validstr))
634 return config
634 return config
635
635
636 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
636 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
637 if f2 is None:
637 if f2 is None:
638 f2 = f
638 f2 = f
639 return (repo.wvfs.audit.check(f)
639 return (repo.wvfs.audit.check(f)
640 and repo.wvfs.isfileorlink(f)
640 and repo.wvfs.isfileorlink(f)
641 and repo.dirstate.normalize(f) not in repo.dirstate
641 and repo.dirstate.normalize(f) not in repo.dirstate
642 and mctx[f2].cmp(wctx[f]))
642 and mctx[f2].cmp(wctx[f]))
643
643
644 def _checkunknowndirs(repo, f):
644 def _checkunknowndirs(repo, f):
645 """
645 """
646 Look for any unknown files or directories that may have a path conflict
646 Look for any unknown files or directories that may have a path conflict
647 with a file. If any path prefix of the file exists as a file or link,
647 with a file. If any path prefix of the file exists as a file or link,
648 then it conflicts. If the file itself is a directory that contains any
648 then it conflicts. If the file itself is a directory that contains any
649 file that is not tracked, then it conflicts.
649 file that is not tracked, then it conflicts.
650
650
651 Returns the shortest path at which a conflict occurs, or None if there is
651 Returns the shortest path at which a conflict occurs, or None if there is
652 no conflict.
652 no conflict.
653 """
653 """
654
654
655 # Check for path prefixes that exist as unknown files.
655 # Check for path prefixes that exist as unknown files.
656 for p in reversed(list(util.finddirs(f))):
656 for p in reversed(list(util.finddirs(f))):
657 if (repo.wvfs.audit.check(p)
657 if (repo.wvfs.audit.check(p)
658 and repo.wvfs.isfileorlink(p)
658 and repo.wvfs.isfileorlink(p)
659 and repo.dirstate.normalize(p) not in repo.dirstate):
659 and repo.dirstate.normalize(p) not in repo.dirstate):
660 return p
660 return p
661
661
662 # Check if the file conflicts with a directory containing unknown files.
662 # Check if the file conflicts with a directory containing unknown files.
663 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
663 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
664 # Does the directory contain any files that are not in the dirstate?
664 # Does the directory contain any files that are not in the dirstate?
665 for p, dirs, files in repo.wvfs.walk(f):
665 for p, dirs, files in repo.wvfs.walk(f):
666 for fn in files:
666 for fn in files:
667 relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
667 relf = repo.dirstate.normalize(repo.wvfs.reljoin(p, fn))
668 if relf not in repo.dirstate:
668 if relf not in repo.dirstate:
669 return f
669 return f
670 return None
670 return None
671
671
672 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
672 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
673 """
673 """
674 Considers any actions that care about the presence of conflicting unknown
674 Considers any actions that care about the presence of conflicting unknown
675 files. For some actions, the result is to abort; for others, it is to
675 files. For some actions, the result is to abort; for others, it is to
676 choose a different action.
676 choose a different action.
677 """
677 """
678 fileconflicts = set()
678 fileconflicts = set()
679 pathconflicts = set()
679 pathconflicts = set()
680 warnconflicts = set()
680 warnconflicts = set()
681 abortconflicts = set()
681 abortconflicts = set()
682 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
682 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
683 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
683 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
684 if not force:
684 if not force:
685 def collectconflicts(conflicts, config):
685 def collectconflicts(conflicts, config):
686 if config == 'abort':
686 if config == 'abort':
687 abortconflicts.update(conflicts)
687 abortconflicts.update(conflicts)
688 elif config == 'warn':
688 elif config == 'warn':
689 warnconflicts.update(conflicts)
689 warnconflicts.update(conflicts)
690
690
691 for f, (m, args, msg) in actions.iteritems():
691 for f, (m, args, msg) in actions.iteritems():
692 if m in ('c', 'dc'):
692 if m in ('c', 'dc'):
693 if _checkunknownfile(repo, wctx, mctx, f):
693 if _checkunknownfile(repo, wctx, mctx, f):
694 fileconflicts.add(f)
694 fileconflicts.add(f)
695 elif f not in wctx:
695 elif f not in wctx:
696 path = _checkunknowndirs(repo, f)
696 path = _checkunknowndirs(repo, f)
697 if path is not None:
697 if path is not None:
698 pathconflicts.add(path)
698 pathconflicts.add(path)
699 elif m == 'dg':
699 elif m == 'dg':
700 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
700 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
701 fileconflicts.add(f)
701 fileconflicts.add(f)
702
702
703 allconflicts = fileconflicts | pathconflicts
703 allconflicts = fileconflicts | pathconflicts
704 ignoredconflicts = set([c for c in allconflicts
704 ignoredconflicts = set([c for c in allconflicts
705 if repo.dirstate._ignore(c)])
705 if repo.dirstate._ignore(c)])
706 unknownconflicts = allconflicts - ignoredconflicts
706 unknownconflicts = allconflicts - ignoredconflicts
707 collectconflicts(ignoredconflicts, ignoredconfig)
707 collectconflicts(ignoredconflicts, ignoredconfig)
708 collectconflicts(unknownconflicts, unknownconfig)
708 collectconflicts(unknownconflicts, unknownconfig)
709 else:
709 else:
710 for f, (m, args, msg) in actions.iteritems():
710 for f, (m, args, msg) in actions.iteritems():
711 if m == 'cm':
711 if m == 'cm':
712 fl2, anc = args
712 fl2, anc = args
713 different = _checkunknownfile(repo, wctx, mctx, f)
713 different = _checkunknownfile(repo, wctx, mctx, f)
714 if repo.dirstate._ignore(f):
714 if repo.dirstate._ignore(f):
715 config = ignoredconfig
715 config = ignoredconfig
716 else:
716 else:
717 config = unknownconfig
717 config = unknownconfig
718
718
719 # The behavior when force is True is described by this table:
719 # The behavior when force is True is described by this table:
720 # config different mergeforce | action backup
720 # config different mergeforce | action backup
721 # * n * | get n
721 # * n * | get n
722 # * y y | merge -
722 # * y y | merge -
723 # abort y n | merge - (1)
723 # abort y n | merge - (1)
724 # warn y n | warn + get y
724 # warn y n | warn + get y
725 # ignore y n | get y
725 # ignore y n | get y
726 #
726 #
727 # (1) this is probably the wrong behavior here -- we should
727 # (1) this is probably the wrong behavior here -- we should
728 # probably abort, but some actions like rebases currently
728 # probably abort, but some actions like rebases currently
729 # don't like an abort happening in the middle of
729 # don't like an abort happening in the middle of
730 # merge.update.
730 # merge.update.
731 if not different:
731 if not different:
732 actions[f] = ('g', (fl2, False), "remote created")
732 actions[f] = ('g', (fl2, False), "remote created")
733 elif mergeforce or config == 'abort':
733 elif mergeforce or config == 'abort':
734 actions[f] = ('m', (f, f, None, False, anc),
734 actions[f] = ('m', (f, f, None, False, anc),
735 "remote differs from untracked local")
735 "remote differs from untracked local")
736 elif config == 'abort':
736 elif config == 'abort':
737 abortconflicts.add(f)
737 abortconflicts.add(f)
738 else:
738 else:
739 if config == 'warn':
739 if config == 'warn':
740 warnconflicts.add(f)
740 warnconflicts.add(f)
741 actions[f] = ('g', (fl2, True), "remote created")
741 actions[f] = ('g', (fl2, True), "remote created")
742
742
743 for f in sorted(abortconflicts):
743 for f in sorted(abortconflicts):
744 warn = repo.ui.warn
744 warn = repo.ui.warn
745 if f in pathconflicts:
745 if f in pathconflicts:
746 if repo.wvfs.isfileorlink(f):
746 if repo.wvfs.isfileorlink(f):
747 warn(_("%s: untracked file conflicts with directory\n") % f)
747 warn(_("%s: untracked file conflicts with directory\n") % f)
748 else:
748 else:
749 warn(_("%s: untracked directory conflicts with file\n") % f)
749 warn(_("%s: untracked directory conflicts with file\n") % f)
750 else:
750 else:
751 warn(_("%s: untracked file differs\n") % f)
751 warn(_("%s: untracked file differs\n") % f)
752 if abortconflicts:
752 if abortconflicts:
753 raise error.Abort(_("untracked files in working directory "
753 raise error.Abort(_("untracked files in working directory "
754 "differ from files in requested revision"))
754 "differ from files in requested revision"))
755
755
756 for f in sorted(warnconflicts):
756 for f in sorted(warnconflicts):
757 if repo.wvfs.isfileorlink(f):
757 if repo.wvfs.isfileorlink(f):
758 repo.ui.warn(_("%s: replacing untracked file\n") % f)
758 repo.ui.warn(_("%s: replacing untracked file\n") % f)
759 else:
759 else:
760 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
760 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
761
761
762 for f, (m, args, msg) in actions.iteritems():
762 for f, (m, args, msg) in actions.iteritems():
763 if m == 'c':
763 if m == 'c':
764 backup = (f in fileconflicts or f in pathconflicts or
764 backup = (f in fileconflicts or f in pathconflicts or
765 any(p in pathconflicts for p in util.finddirs(f)))
765 any(p in pathconflicts for p in util.finddirs(f)))
766 flags, = args
766 flags, = args
767 actions[f] = ('g', (flags, backup), msg)
767 actions[f] = ('g', (flags, backup), msg)
768
768
769 def _forgetremoved(wctx, mctx, branchmerge):
769 def _forgetremoved(wctx, mctx, branchmerge):
770 """
770 """
771 Forget removed files
771 Forget removed files
772
772
773 If we're jumping between revisions (as opposed to merging), and if
773 If we're jumping between revisions (as opposed to merging), and if
774 neither the working directory nor the target rev has the file,
774 neither the working directory nor the target rev has the file,
775 then we need to remove it from the dirstate, to prevent the
775 then we need to remove it from the dirstate, to prevent the
776 dirstate from listing the file when it is no longer in the
776 dirstate from listing the file when it is no longer in the
777 manifest.
777 manifest.
778
778
779 If we're merging, and the other revision has removed a file
779 If we're merging, and the other revision has removed a file
780 that is not present in the working directory, we need to mark it
780 that is not present in the working directory, we need to mark it
781 as removed.
781 as removed.
782 """
782 """
783
783
784 actions = {}
784 actions = {}
785 m = 'f'
785 m = 'f'
786 if branchmerge:
786 if branchmerge:
787 m = 'r'
787 m = 'r'
788 for f in wctx.deleted():
788 for f in wctx.deleted():
789 if f not in mctx:
789 if f not in mctx:
790 actions[f] = m, None, "forget deleted"
790 actions[f] = m, None, "forget deleted"
791
791
792 if not branchmerge:
792 if not branchmerge:
793 for f in wctx.removed():
793 for f in wctx.removed():
794 if f not in mctx:
794 if f not in mctx:
795 actions[f] = 'f', None, "forget removed"
795 actions[f] = 'f', None, "forget removed"
796
796
797 return actions
797 return actions
798
798
799 def _checkcollision(repo, wmf, actions):
799 def _checkcollision(repo, wmf, actions):
800 # build provisional merged manifest up
800 # build provisional merged manifest up
801 pmmf = set(wmf)
801 pmmf = set(wmf)
802
802
803 if actions:
803 if actions:
804 # k, dr, e and rd are no-op
804 # k, dr, e and rd are no-op
805 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
805 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
806 for f, args, msg in actions[m]:
806 for f, args, msg in actions[m]:
807 pmmf.add(f)
807 pmmf.add(f)
808 for f, args, msg in actions['r']:
808 for f, args, msg in actions['r']:
809 pmmf.discard(f)
809 pmmf.discard(f)
810 for f, args, msg in actions['dm']:
810 for f, args, msg in actions['dm']:
811 f2, flags = args
811 f2, flags = args
812 pmmf.discard(f2)
812 pmmf.discard(f2)
813 pmmf.add(f)
813 pmmf.add(f)
814 for f, args, msg in actions['dg']:
814 for f, args, msg in actions['dg']:
815 pmmf.add(f)
815 pmmf.add(f)
816 for f, args, msg in actions['m']:
816 for f, args, msg in actions['m']:
817 f1, f2, fa, move, anc = args
817 f1, f2, fa, move, anc = args
818 if move:
818 if move:
819 pmmf.discard(f1)
819 pmmf.discard(f1)
820 pmmf.add(f)
820 pmmf.add(f)
821
821
822 # check case-folding collision in provisional merged manifest
822 # check case-folding collision in provisional merged manifest
823 foldmap = {}
823 foldmap = {}
824 for f in pmmf:
824 for f in pmmf:
825 fold = util.normcase(f)
825 fold = util.normcase(f)
826 if fold in foldmap:
826 if fold in foldmap:
827 raise error.Abort(_("case-folding collision between %s and %s")
827 raise error.Abort(_("case-folding collision between %s and %s")
828 % (f, foldmap[fold]))
828 % (f, foldmap[fold]))
829 foldmap[fold] = f
829 foldmap[fold] = f
830
830
831 # check case-folding of directories
831 # check case-folding of directories
832 foldprefix = unfoldprefix = lastfull = ''
832 foldprefix = unfoldprefix = lastfull = ''
833 for fold, f in sorted(foldmap.items()):
833 for fold, f in sorted(foldmap.items()):
834 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
834 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
835 # the folded prefix matches but actual casing is different
835 # the folded prefix matches but actual casing is different
836 raise error.Abort(_("case-folding collision between "
836 raise error.Abort(_("case-folding collision between "
837 "%s and directory of %s") % (lastfull, f))
837 "%s and directory of %s") % (lastfull, f))
838 foldprefix = fold + '/'
838 foldprefix = fold + '/'
839 unfoldprefix = f + '/'
839 unfoldprefix = f + '/'
840 lastfull = f
840 lastfull = f
841
841
842 def driverpreprocess(repo, ms, wctx, labels=None):
842 def driverpreprocess(repo, ms, wctx, labels=None):
843 """run the preprocess step of the merge driver, if any
843 """run the preprocess step of the merge driver, if any
844
844
845 This is currently not implemented -- it's an extension point."""
845 This is currently not implemented -- it's an extension point."""
846 return True
846 return True
847
847
848 def driverconclude(repo, ms, wctx, labels=None):
848 def driverconclude(repo, ms, wctx, labels=None):
849 """run the conclude step of the merge driver, if any
849 """run the conclude step of the merge driver, if any
850
850
851 This is currently not implemented -- it's an extension point."""
851 This is currently not implemented -- it's an extension point."""
852 return True
852 return True
853
853
854 def _filesindirs(repo, manifest, dirs):
855 """
856 Generator that yields pairs of all the files in the manifest that are found
857 inside the directories listed in dirs, and which directory they are found
858 in.
859 """
860 for f in manifest:
861 for p in util.finddirs(f):
862 if p in dirs:
863 yield f, p
864 break
865
866 def checkpathconflicts(repo, wctx, mctx, actions):
867 """
868 Check if any actions introduce path conflicts in the repository, updating
869 actions to record or handle the path conflict accordingly.
870 """
871 mf = wctx.manifest()
872
873 # The set of local files that conflict with a remote directory.
874 localconflicts = set()
875
876 # The set of directories that conflict with a remote file, and so may cause
877 # conflicts if they still contain any files after the merge.
878 remoteconflicts = set()
879
880 # The set of directories that appear as both a file and a directory in the
881 # remote manifest. These indicate an invalid remote manifest, which
882 # can't be updated to cleanly.
883 invalidconflicts = set()
884
885 # The set of files deleted by all the actions.
886 deletedfiles = set()
887
888 for f, (m, args, msg) in actions.items():
889 if m in ('c', 'dc', 'm', 'cm'):
890 # This action may create a new local file.
891 if mf.hasdir(f):
892 # The file aliases a local directory. This might be ok if all
893 # the files in the local directory are being deleted. This
894 # will be checked once we know what all the deleted files are.
895 remoteconflicts.add(f)
896 for p in util.finddirs(f):
897 if p in mf:
898 if p in mctx:
899 # The file is in a directory which aliases both a local
900 # and a remote file. This is an internal inconsistency
901 # within the remote manifest.
902 invalidconflicts.add(p)
903 else:
904 # The file is in a directory which aliases a local file.
905 # We will need to rename the local file.
906 localconflicts.add(p)
907 if p in actions and actions[p][0] in ('c', 'dc', 'm', 'cm'):
908 # The file is in a directory which aliases a remote file.
909 # This is an internal inconsistency within the remote
910 # manifest.
911 invalidconflicts.add(p)
912
913 # Track the names of all deleted files.
914 if m == 'r':
915 deletedfiles.add(f)
916 if m == 'm':
917 f1, f2, fa, move, anc = args
918 if move:
919 deletedfiles.add(f1)
920 if m == 'dm':
921 f2, flags = args
922 deletedfiles.add(f2)
923
924 # Rename all local conflicting files that have not been deleted.
925 for p in localconflicts:
926 if p not in deletedfiles:
927 ctxname = str(wctx).rstrip('+')
928 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
929 actions[pnew] = ('pr', (p,), "local path conflict")
930 actions[p] = ('p', (pnew, 'l'), "path conflict")
931
932 if remoteconflicts:
933 # Check if all files in the conflicting directories have been removed.
934 ctxname = str(mctx).rstrip('+')
935 for f, p in _filesindirs(repo, mf, remoteconflicts):
936 if f not in deletedfiles:
937 m, args, msg = actions[p]
938 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
939 if m in ('dc', 'm'):
940 # Action was merge, just update target.
941 actions[pnew] = (m, args, msg)
942 else:
943 # Action was create, change to renamed get action.
944 fl = args[0]
945 actions[pnew] = ('dg', (p, fl), "remote path conflict")
946 actions[p] = ('p', (pnew, 'r'), "path conflict")
947 remoteconflicts.remove(p)
948 break
949
950 if invalidconflicts:
951 for p in invalidconflicts:
952 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
953 raise error.Abort(_("destination manifest contains path conflicts"))
954
854 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
955 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
855 acceptremote, followcopies, forcefulldiff=False):
956 acceptremote, followcopies, forcefulldiff=False):
856 """
957 """
857 Merge wctx and p2 with ancestor pa and generate merge action list
958 Merge wctx and p2 with ancestor pa and generate merge action list
858
959
859 branchmerge and force are as passed in to update
960 branchmerge and force are as passed in to update
860 matcher = matcher to filter file lists
961 matcher = matcher to filter file lists
861 acceptremote = accept the incoming changes without prompting
962 acceptremote = accept the incoming changes without prompting
862 """
963 """
863 if matcher is not None and matcher.always():
964 if matcher is not None and matcher.always():
864 matcher = None
965 matcher = None
865
966
866 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
967 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
867
968
868 # manifests fetched in order are going to be faster, so prime the caches
969 # manifests fetched in order are going to be faster, so prime the caches
869 [x.manifest() for x in
970 [x.manifest() for x in
870 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
971 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
871
972
872 if followcopies:
973 if followcopies:
873 ret = copies.mergecopies(repo, wctx, p2, pa)
974 ret = copies.mergecopies(repo, wctx, p2, pa)
874 copy, movewithdir, diverge, renamedelete, dirmove = ret
975 copy, movewithdir, diverge, renamedelete, dirmove = ret
875
976
876 boolbm = pycompat.bytestr(bool(branchmerge))
977 boolbm = pycompat.bytestr(bool(branchmerge))
877 boolf = pycompat.bytestr(bool(force))
978 boolf = pycompat.bytestr(bool(force))
878 boolm = pycompat.bytestr(bool(matcher))
979 boolm = pycompat.bytestr(bool(matcher))
879 repo.ui.note(_("resolving manifests\n"))
980 repo.ui.note(_("resolving manifests\n"))
880 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
981 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
881 % (boolbm, boolf, boolm))
982 % (boolbm, boolf, boolm))
882 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
983 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
883
984
884 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
985 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
885 copied = set(copy.values())
986 copied = set(copy.values())
886 copied.update(movewithdir.values())
987 copied.update(movewithdir.values())
887
988
888 if '.hgsubstate' in m1:
989 if '.hgsubstate' in m1:
889 # check whether sub state is modified
990 # check whether sub state is modified
890 if any(wctx.sub(s).dirty() for s in wctx.substate):
991 if any(wctx.sub(s).dirty() for s in wctx.substate):
891 m1['.hgsubstate'] = modifiednodeid
992 m1['.hgsubstate'] = modifiednodeid
892
993
893 # Don't use m2-vs-ma optimization if:
994 # Don't use m2-vs-ma optimization if:
894 # - ma is the same as m1 or m2, which we're just going to diff again later
995 # - ma is the same as m1 or m2, which we're just going to diff again later
895 # - The caller specifically asks for a full diff, which is useful during bid
996 # - The caller specifically asks for a full diff, which is useful during bid
896 # merge.
997 # merge.
897 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
998 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
898 # Identify which files are relevant to the merge, so we can limit the
999 # Identify which files are relevant to the merge, so we can limit the
899 # total m1-vs-m2 diff to just those files. This has significant
1000 # total m1-vs-m2 diff to just those files. This has significant
900 # performance benefits in large repositories.
1001 # performance benefits in large repositories.
901 relevantfiles = set(ma.diff(m2).keys())
1002 relevantfiles = set(ma.diff(m2).keys())
902
1003
903 # For copied and moved files, we need to add the source file too.
1004 # For copied and moved files, we need to add the source file too.
904 for copykey, copyvalue in copy.iteritems():
1005 for copykey, copyvalue in copy.iteritems():
905 if copyvalue in relevantfiles:
1006 if copyvalue in relevantfiles:
906 relevantfiles.add(copykey)
1007 relevantfiles.add(copykey)
907 for movedirkey in movewithdir:
1008 for movedirkey in movewithdir:
908 relevantfiles.add(movedirkey)
1009 relevantfiles.add(movedirkey)
909 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1010 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
910 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1011 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
911
1012
912 diff = m1.diff(m2, match=matcher)
1013 diff = m1.diff(m2, match=matcher)
913
1014
914 if matcher is None:
1015 if matcher is None:
915 matcher = matchmod.always('', '')
1016 matcher = matchmod.always('', '')
916
1017
917 actions = {}
1018 actions = {}
918 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1019 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
919 if n1 and n2: # file exists on both local and remote side
1020 if n1 and n2: # file exists on both local and remote side
920 if f not in ma:
1021 if f not in ma:
921 fa = copy.get(f, None)
1022 fa = copy.get(f, None)
922 if fa is not None:
1023 if fa is not None:
923 actions[f] = ('m', (f, f, fa, False, pa.node()),
1024 actions[f] = ('m', (f, f, fa, False, pa.node()),
924 "both renamed from " + fa)
1025 "both renamed from " + fa)
925 else:
1026 else:
926 actions[f] = ('m', (f, f, None, False, pa.node()),
1027 actions[f] = ('m', (f, f, None, False, pa.node()),
927 "both created")
1028 "both created")
928 else:
1029 else:
929 a = ma[f]
1030 a = ma[f]
930 fla = ma.flags(f)
1031 fla = ma.flags(f)
931 nol = 'l' not in fl1 + fl2 + fla
1032 nol = 'l' not in fl1 + fl2 + fla
932 if n2 == a and fl2 == fla:
1033 if n2 == a and fl2 == fla:
933 actions[f] = ('k', (), "remote unchanged")
1034 actions[f] = ('k', (), "remote unchanged")
934 elif n1 == a and fl1 == fla: # local unchanged - use remote
1035 elif n1 == a and fl1 == fla: # local unchanged - use remote
935 if n1 == n2: # optimization: keep local content
1036 if n1 == n2: # optimization: keep local content
936 actions[f] = ('e', (fl2,), "update permissions")
1037 actions[f] = ('e', (fl2,), "update permissions")
937 else:
1038 else:
938 actions[f] = ('g', (fl2, False), "remote is newer")
1039 actions[f] = ('g', (fl2, False), "remote is newer")
939 elif nol and n2 == a: # remote only changed 'x'
1040 elif nol and n2 == a: # remote only changed 'x'
940 actions[f] = ('e', (fl2,), "update permissions")
1041 actions[f] = ('e', (fl2,), "update permissions")
941 elif nol and n1 == a: # local only changed 'x'
1042 elif nol and n1 == a: # local only changed 'x'
942 actions[f] = ('g', (fl1, False), "remote is newer")
1043 actions[f] = ('g', (fl1, False), "remote is newer")
943 else: # both changed something
1044 else: # both changed something
944 actions[f] = ('m', (f, f, f, False, pa.node()),
1045 actions[f] = ('m', (f, f, f, False, pa.node()),
945 "versions differ")
1046 "versions differ")
946 elif n1: # file exists only on local side
1047 elif n1: # file exists only on local side
947 if f in copied:
1048 if f in copied:
948 pass # we'll deal with it on m2 side
1049 pass # we'll deal with it on m2 side
949 elif f in movewithdir: # directory rename, move local
1050 elif f in movewithdir: # directory rename, move local
950 f2 = movewithdir[f]
1051 f2 = movewithdir[f]
951 if f2 in m2:
1052 if f2 in m2:
952 actions[f2] = ('m', (f, f2, None, True, pa.node()),
1053 actions[f2] = ('m', (f, f2, None, True, pa.node()),
953 "remote directory rename, both created")
1054 "remote directory rename, both created")
954 else:
1055 else:
955 actions[f2] = ('dm', (f, fl1),
1056 actions[f2] = ('dm', (f, fl1),
956 "remote directory rename - move from " + f)
1057 "remote directory rename - move from " + f)
957 elif f in copy:
1058 elif f in copy:
958 f2 = copy[f]
1059 f2 = copy[f]
959 actions[f] = ('m', (f, f2, f2, False, pa.node()),
1060 actions[f] = ('m', (f, f2, f2, False, pa.node()),
960 "local copied/moved from " + f2)
1061 "local copied/moved from " + f2)
961 elif f in ma: # clean, a different, no remote
1062 elif f in ma: # clean, a different, no remote
962 if n1 != ma[f]:
1063 if n1 != ma[f]:
963 if acceptremote:
1064 if acceptremote:
964 actions[f] = ('r', None, "remote delete")
1065 actions[f] = ('r', None, "remote delete")
965 else:
1066 else:
966 actions[f] = ('cd', (f, None, f, False, pa.node()),
1067 actions[f] = ('cd', (f, None, f, False, pa.node()),
967 "prompt changed/deleted")
1068 "prompt changed/deleted")
968 elif n1 == addednodeid:
1069 elif n1 == addednodeid:
969 # This extra 'a' is added by working copy manifest to mark
1070 # This extra 'a' is added by working copy manifest to mark
970 # the file as locally added. We should forget it instead of
1071 # the file as locally added. We should forget it instead of
971 # deleting it.
1072 # deleting it.
972 actions[f] = ('f', None, "remote deleted")
1073 actions[f] = ('f', None, "remote deleted")
973 else:
1074 else:
974 actions[f] = ('r', None, "other deleted")
1075 actions[f] = ('r', None, "other deleted")
975 elif n2: # file exists only on remote side
1076 elif n2: # file exists only on remote side
976 if f in copied:
1077 if f in copied:
977 pass # we'll deal with it on m1 side
1078 pass # we'll deal with it on m1 side
978 elif f in movewithdir:
1079 elif f in movewithdir:
979 f2 = movewithdir[f]
1080 f2 = movewithdir[f]
980 if f2 in m1:
1081 if f2 in m1:
981 actions[f2] = ('m', (f2, f, None, False, pa.node()),
1082 actions[f2] = ('m', (f2, f, None, False, pa.node()),
982 "local directory rename, both created")
1083 "local directory rename, both created")
983 else:
1084 else:
984 actions[f2] = ('dg', (f, fl2),
1085 actions[f2] = ('dg', (f, fl2),
985 "local directory rename - get from " + f)
1086 "local directory rename - get from " + f)
986 elif f in copy:
1087 elif f in copy:
987 f2 = copy[f]
1088 f2 = copy[f]
988 if f2 in m2:
1089 if f2 in m2:
989 actions[f] = ('m', (f2, f, f2, False, pa.node()),
1090 actions[f] = ('m', (f2, f, f2, False, pa.node()),
990 "remote copied from " + f2)
1091 "remote copied from " + f2)
991 else:
1092 else:
992 actions[f] = ('m', (f2, f, f2, True, pa.node()),
1093 actions[f] = ('m', (f2, f, f2, True, pa.node()),
993 "remote moved from " + f2)
1094 "remote moved from " + f2)
994 elif f not in ma:
1095 elif f not in ma:
995 # local unknown, remote created: the logic is described by the
1096 # local unknown, remote created: the logic is described by the
996 # following table:
1097 # following table:
997 #
1098 #
998 # force branchmerge different | action
1099 # force branchmerge different | action
999 # n * * | create
1100 # n * * | create
1000 # y n * | create
1101 # y n * | create
1001 # y y n | create
1102 # y y n | create
1002 # y y y | merge
1103 # y y y | merge
1003 #
1104 #
1004 # Checking whether the files are different is expensive, so we
1105 # Checking whether the files are different is expensive, so we
1005 # don't do that when we can avoid it.
1106 # don't do that when we can avoid it.
1006 if not force:
1107 if not force:
1007 actions[f] = ('c', (fl2,), "remote created")
1108 actions[f] = ('c', (fl2,), "remote created")
1008 elif not branchmerge:
1109 elif not branchmerge:
1009 actions[f] = ('c', (fl2,), "remote created")
1110 actions[f] = ('c', (fl2,), "remote created")
1010 else:
1111 else:
1011 actions[f] = ('cm', (fl2, pa.node()),
1112 actions[f] = ('cm', (fl2, pa.node()),
1012 "remote created, get or merge")
1113 "remote created, get or merge")
1013 elif n2 != ma[f]:
1114 elif n2 != ma[f]:
1014 df = None
1115 df = None
1015 for d in dirmove:
1116 for d in dirmove:
1016 if f.startswith(d):
1117 if f.startswith(d):
1017 # new file added in a directory that was moved
1118 # new file added in a directory that was moved
1018 df = dirmove[d] + f[len(d):]
1119 df = dirmove[d] + f[len(d):]
1019 break
1120 break
1020 if df is not None and df in m1:
1121 if df is not None and df in m1:
1021 actions[df] = ('m', (df, f, f, False, pa.node()),
1122 actions[df] = ('m', (df, f, f, False, pa.node()),
1022 "local directory rename - respect move from " + f)
1123 "local directory rename - respect move from " + f)
1023 elif acceptremote:
1124 elif acceptremote:
1024 actions[f] = ('c', (fl2,), "remote recreating")
1125 actions[f] = ('c', (fl2,), "remote recreating")
1025 else:
1126 else:
1026 actions[f] = ('dc', (None, f, f, False, pa.node()),
1127 actions[f] = ('dc', (None, f, f, False, pa.node()),
1027 "prompt deleted/changed")
1128 "prompt deleted/changed")
1028
1129
1130 # If we are merging, look for path conflicts.
1131 checkpathconflicts(repo, wctx, p2, actions)
1132
1029 return actions, diverge, renamedelete
1133 return actions, diverge, renamedelete
1030
1134
1031 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1135 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1032 """Resolves false conflicts where the nodeid changed but the content
1136 """Resolves false conflicts where the nodeid changed but the content
1033 remained the same."""
1137 remained the same."""
1034
1138
1035 for f, (m, args, msg) in actions.items():
1139 for f, (m, args, msg) in actions.items():
1036 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
1140 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
1037 # local did change but ended up with same content
1141 # local did change but ended up with same content
1038 actions[f] = 'r', None, "prompt same"
1142 actions[f] = 'r', None, "prompt same"
1039 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
1143 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
1040 # remote did change but ended up with same content
1144 # remote did change but ended up with same content
1041 del actions[f] # don't get = keep local deleted
1145 del actions[f] # don't get = keep local deleted
1042
1146
1043 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1147 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1044 acceptremote, followcopies, matcher=None,
1148 acceptremote, followcopies, matcher=None,
1045 mergeforce=False):
1149 mergeforce=False):
1046 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1150 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1047 # Avoid cycle.
1151 # Avoid cycle.
1048 from . import sparse
1152 from . import sparse
1049
1153
1050 if len(ancestors) == 1: # default
1154 if len(ancestors) == 1: # default
1051 actions, diverge, renamedelete = manifestmerge(
1155 actions, diverge, renamedelete = manifestmerge(
1052 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1156 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1053 acceptremote, followcopies)
1157 acceptremote, followcopies)
1054 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1158 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1055
1159
1056 else: # only when merge.preferancestor=* - the default
1160 else: # only when merge.preferancestor=* - the default
1057 repo.ui.note(
1161 repo.ui.note(
1058 _("note: merging %s and %s using bids from ancestors %s\n") %
1162 _("note: merging %s and %s using bids from ancestors %s\n") %
1059 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1163 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1060 for anc in ancestors)))
1164 for anc in ancestors)))
1061
1165
1062 # Call for bids
1166 # Call for bids
1063 fbids = {} # mapping filename to bids (action method to list af actions)
1167 fbids = {} # mapping filename to bids (action method to list af actions)
1064 diverge, renamedelete = None, None
1168 diverge, renamedelete = None, None
1065 for ancestor in ancestors:
1169 for ancestor in ancestors:
1066 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1170 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1067 actions, diverge1, renamedelete1 = manifestmerge(
1171 actions, diverge1, renamedelete1 = manifestmerge(
1068 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1172 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1069 acceptremote, followcopies, forcefulldiff=True)
1173 acceptremote, followcopies, forcefulldiff=True)
1070 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1174 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1071
1175
1072 # Track the shortest set of warning on the theory that bid
1176 # Track the shortest set of warning on the theory that bid
1073 # merge will correctly incorporate more information
1177 # merge will correctly incorporate more information
1074 if diverge is None or len(diverge1) < len(diverge):
1178 if diverge is None or len(diverge1) < len(diverge):
1075 diverge = diverge1
1179 diverge = diverge1
1076 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1180 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1077 renamedelete = renamedelete1
1181 renamedelete = renamedelete1
1078
1182
1079 for f, a in sorted(actions.iteritems()):
1183 for f, a in sorted(actions.iteritems()):
1080 m, args, msg = a
1184 m, args, msg = a
1081 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1185 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1082 if f in fbids:
1186 if f in fbids:
1083 d = fbids[f]
1187 d = fbids[f]
1084 if m in d:
1188 if m in d:
1085 d[m].append(a)
1189 d[m].append(a)
1086 else:
1190 else:
1087 d[m] = [a]
1191 d[m] = [a]
1088 else:
1192 else:
1089 fbids[f] = {m: [a]}
1193 fbids[f] = {m: [a]}
1090
1194
1091 # Pick the best bid for each file
1195 # Pick the best bid for each file
1092 repo.ui.note(_('\nauction for merging merge bids\n'))
1196 repo.ui.note(_('\nauction for merging merge bids\n'))
1093 actions = {}
1197 actions = {}
1094 dms = [] # filenames that have dm actions
1198 dms = [] # filenames that have dm actions
1095 for f, bids in sorted(fbids.items()):
1199 for f, bids in sorted(fbids.items()):
1096 # bids is a mapping from action method to list af actions
1200 # bids is a mapping from action method to list af actions
1097 # Consensus?
1201 # Consensus?
1098 if len(bids) == 1: # all bids are the same kind of method
1202 if len(bids) == 1: # all bids are the same kind of method
1099 m, l = list(bids.items())[0]
1203 m, l = list(bids.items())[0]
1100 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1204 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1101 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1205 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1102 actions[f] = l[0]
1206 actions[f] = l[0]
1103 if m == 'dm':
1207 if m == 'dm':
1104 dms.append(f)
1208 dms.append(f)
1105 continue
1209 continue
1106 # If keep is an option, just do it.
1210 # If keep is an option, just do it.
1107 if 'k' in bids:
1211 if 'k' in bids:
1108 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1212 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1109 actions[f] = bids['k'][0]
1213 actions[f] = bids['k'][0]
1110 continue
1214 continue
1111 # If there are gets and they all agree [how could they not?], do it.
1215 # If there are gets and they all agree [how could they not?], do it.
1112 if 'g' in bids:
1216 if 'g' in bids:
1113 ga0 = bids['g'][0]
1217 ga0 = bids['g'][0]
1114 if all(a == ga0 for a in bids['g'][1:]):
1218 if all(a == ga0 for a in bids['g'][1:]):
1115 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1219 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1116 actions[f] = ga0
1220 actions[f] = ga0
1117 continue
1221 continue
1118 # TODO: Consider other simple actions such as mode changes
1222 # TODO: Consider other simple actions such as mode changes
1119 # Handle inefficient democrazy.
1223 # Handle inefficient democrazy.
1120 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1224 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1121 for m, l in sorted(bids.items()):
1225 for m, l in sorted(bids.items()):
1122 for _f, args, msg in l:
1226 for _f, args, msg in l:
1123 repo.ui.note(' %s -> %s\n' % (msg, m))
1227 repo.ui.note(' %s -> %s\n' % (msg, m))
1124 # Pick random action. TODO: Instead, prompt user when resolving
1228 # Pick random action. TODO: Instead, prompt user when resolving
1125 m, l = list(bids.items())[0]
1229 m, l = list(bids.items())[0]
1126 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1230 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1127 (f, m))
1231 (f, m))
1128 actions[f] = l[0]
1232 actions[f] = l[0]
1129 if m == 'dm':
1233 if m == 'dm':
1130 dms.append(f)
1234 dms.append(f)
1131 continue
1235 continue
1132 # Work around 'dm' that can cause multiple actions for the same file
1236 # Work around 'dm' that can cause multiple actions for the same file
1133 for f in dms:
1237 for f in dms:
1134 dm, (f0, flags), msg = actions[f]
1238 dm, (f0, flags), msg = actions[f]
1135 assert dm == 'dm', dm
1239 assert dm == 'dm', dm
1136 if f0 in actions and actions[f0][0] == 'r':
1240 if f0 in actions and actions[f0][0] == 'r':
1137 # We have one bid for removing a file and another for moving it.
1241 # We have one bid for removing a file and another for moving it.
1138 # These two could be merged as first move and then delete ...
1242 # These two could be merged as first move and then delete ...
1139 # but instead drop moving and just delete.
1243 # but instead drop moving and just delete.
1140 del actions[f]
1244 del actions[f]
1141 repo.ui.note(_('end of auction\n\n'))
1245 repo.ui.note(_('end of auction\n\n'))
1142
1246
1143 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1247 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1144
1248
1145 if wctx.rev() is None:
1249 if wctx.rev() is None:
1146 fractions = _forgetremoved(wctx, mctx, branchmerge)
1250 fractions = _forgetremoved(wctx, mctx, branchmerge)
1147 actions.update(fractions)
1251 actions.update(fractions)
1148
1252
1149 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1253 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1150 actions)
1254 actions)
1151
1255
1152 return prunedactions, diverge, renamedelete
1256 return prunedactions, diverge, renamedelete
1153
1257
1154 def _getcwd():
1258 def _getcwd():
1155 try:
1259 try:
1156 return pycompat.getcwd()
1260 return pycompat.getcwd()
1157 except OSError as err:
1261 except OSError as err:
1158 if err.errno == errno.ENOENT:
1262 if err.errno == errno.ENOENT:
1159 return None
1263 return None
1160 raise
1264 raise
1161
1265
1162 def batchremove(repo, wctx, actions):
1266 def batchremove(repo, wctx, actions):
1163 """apply removes to the working directory
1267 """apply removes to the working directory
1164
1268
1165 yields tuples for progress updates
1269 yields tuples for progress updates
1166 """
1270 """
1167 verbose = repo.ui.verbose
1271 verbose = repo.ui.verbose
1168 cwd = _getcwd()
1272 cwd = _getcwd()
1169 i = 0
1273 i = 0
1170 for f, args, msg in actions:
1274 for f, args, msg in actions:
1171 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1275 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1172 if verbose:
1276 if verbose:
1173 repo.ui.note(_("removing %s\n") % f)
1277 repo.ui.note(_("removing %s\n") % f)
1174 wctx[f].audit()
1278 wctx[f].audit()
1175 try:
1279 try:
1176 wctx[f].remove(ignoremissing=True)
1280 wctx[f].remove(ignoremissing=True)
1177 except OSError as inst:
1281 except OSError as inst:
1178 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1282 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1179 (f, inst.strerror))
1283 (f, inst.strerror))
1180 if i == 100:
1284 if i == 100:
1181 yield i, f
1285 yield i, f
1182 i = 0
1286 i = 0
1183 i += 1
1287 i += 1
1184 if i > 0:
1288 if i > 0:
1185 yield i, f
1289 yield i, f
1186
1290
1187 if cwd and not _getcwd():
1291 if cwd and not _getcwd():
1188 # cwd was removed in the course of removing files; print a helpful
1292 # cwd was removed in the course of removing files; print a helpful
1189 # warning.
1293 # warning.
1190 repo.ui.warn(_("current directory was removed\n"
1294 repo.ui.warn(_("current directory was removed\n"
1191 "(consider changing to repo root: %s)\n") % repo.root)
1295 "(consider changing to repo root: %s)\n") % repo.root)
1192
1296
1193 # It's necessary to flush here in case we're inside a worker fork and will
1297 # It's necessary to flush here in case we're inside a worker fork and will
1194 # quit after this function.
1298 # quit after this function.
1195 wctx.flushall()
1299 wctx.flushall()
1196
1300
1197 def batchget(repo, mctx, wctx, actions):
1301 def batchget(repo, mctx, wctx, actions):
1198 """apply gets to the working directory
1302 """apply gets to the working directory
1199
1303
1200 mctx is the context to get from
1304 mctx is the context to get from
1201
1305
1202 yields tuples for progress updates
1306 yields tuples for progress updates
1203 """
1307 """
1204 verbose = repo.ui.verbose
1308 verbose = repo.ui.verbose
1205 fctx = mctx.filectx
1309 fctx = mctx.filectx
1206 ui = repo.ui
1310 ui = repo.ui
1207 i = 0
1311 i = 0
1208 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1312 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1209 for f, (flags, backup), msg in actions:
1313 for f, (flags, backup), msg in actions:
1210 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1314 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1211 if verbose:
1315 if verbose:
1212 repo.ui.note(_("getting %s\n") % f)
1316 repo.ui.note(_("getting %s\n") % f)
1213
1317
1214 if backup:
1318 if backup:
1215 # If a file or directory exists with the same name, back that
1319 # If a file or directory exists with the same name, back that
1216 # up. Otherwise, look to see if there is a file that conflicts
1320 # up. Otherwise, look to see if there is a file that conflicts
1217 # with a directory this file is in, and if so, back that up.
1321 # with a directory this file is in, and if so, back that up.
1218 absf = repo.wjoin(f)
1322 absf = repo.wjoin(f)
1219 if not repo.wvfs.lexists(f):
1323 if not repo.wvfs.lexists(f):
1220 for p in util.finddirs(f):
1324 for p in util.finddirs(f):
1221 if repo.wvfs.isfileorlink(p):
1325 if repo.wvfs.isfileorlink(p):
1222 absf = repo.wjoin(p)
1326 absf = repo.wjoin(p)
1223 break
1327 break
1224 orig = scmutil.origpath(ui, repo, absf)
1328 orig = scmutil.origpath(ui, repo, absf)
1225 if repo.wvfs.lexists(absf):
1329 if repo.wvfs.lexists(absf):
1226 util.rename(absf, orig)
1330 util.rename(absf, orig)
1227 wctx[f].clearunknown()
1331 wctx[f].clearunknown()
1228 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1332 wctx[f].write(fctx(f).data(), flags, backgroundclose=True)
1229 if i == 100:
1333 if i == 100:
1230 yield i, f
1334 yield i, f
1231 i = 0
1335 i = 0
1232 i += 1
1336 i += 1
1233 if i > 0:
1337 if i > 0:
1234 yield i, f
1338 yield i, f
1235
1339
1236 # It's necessary to flush here in case we're inside a worker fork and will
1340 # It's necessary to flush here in case we're inside a worker fork and will
1237 # quit after this function.
1341 # quit after this function.
1238 wctx.flushall()
1342 wctx.flushall()
1239
1343
1240 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1344 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1241 """apply the merge action list to the working directory
1345 """apply the merge action list to the working directory
1242
1346
1243 wctx is the working copy context
1347 wctx is the working copy context
1244 mctx is the context to be merged into the working copy
1348 mctx is the context to be merged into the working copy
1245
1349
1246 Return a tuple of counts (updated, merged, removed, unresolved) that
1350 Return a tuple of counts (updated, merged, removed, unresolved) that
1247 describes how many files were affected by the update.
1351 describes how many files were affected by the update.
1248 """
1352 """
1249
1353
1250 updated, merged, removed = 0, 0, 0
1354 updated, merged, removed = 0, 0, 0
1251 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1355 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1252 moves = []
1356 moves = []
1253 for m, l in actions.items():
1357 for m, l in actions.items():
1254 l.sort()
1358 l.sort()
1255
1359
1256 # 'cd' and 'dc' actions are treated like other merge conflicts
1360 # 'cd' and 'dc' actions are treated like other merge conflicts
1257 mergeactions = sorted(actions['cd'])
1361 mergeactions = sorted(actions['cd'])
1258 mergeactions.extend(sorted(actions['dc']))
1362 mergeactions.extend(sorted(actions['dc']))
1259 mergeactions.extend(actions['m'])
1363 mergeactions.extend(actions['m'])
1260 for f, args, msg in mergeactions:
1364 for f, args, msg in mergeactions:
1261 f1, f2, fa, move, anc = args
1365 f1, f2, fa, move, anc = args
1262 if f == '.hgsubstate': # merged internally
1366 if f == '.hgsubstate': # merged internally
1263 continue
1367 continue
1264 if f1 is None:
1368 if f1 is None:
1265 fcl = filemerge.absentfilectx(wctx, fa)
1369 fcl = filemerge.absentfilectx(wctx, fa)
1266 else:
1370 else:
1267 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1371 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1268 fcl = wctx[f1]
1372 fcl = wctx[f1]
1269 if f2 is None:
1373 if f2 is None:
1270 fco = filemerge.absentfilectx(mctx, fa)
1374 fco = filemerge.absentfilectx(mctx, fa)
1271 else:
1375 else:
1272 fco = mctx[f2]
1376 fco = mctx[f2]
1273 actx = repo[anc]
1377 actx = repo[anc]
1274 if fa in actx:
1378 if fa in actx:
1275 fca = actx[fa]
1379 fca = actx[fa]
1276 else:
1380 else:
1277 # TODO: move to absentfilectx
1381 # TODO: move to absentfilectx
1278 fca = repo.filectx(f1, fileid=nullrev)
1382 fca = repo.filectx(f1, fileid=nullrev)
1279 ms.add(fcl, fco, fca, f)
1383 ms.add(fcl, fco, fca, f)
1280 if f1 != f and move:
1384 if f1 != f and move:
1281 moves.append(f1)
1385 moves.append(f1)
1282
1386
1283 _updating = _('updating')
1387 _updating = _('updating')
1284 _files = _('files')
1388 _files = _('files')
1285 progress = repo.ui.progress
1389 progress = repo.ui.progress
1286
1390
1287 # remove renamed files after safely stored
1391 # remove renamed files after safely stored
1288 for f in moves:
1392 for f in moves:
1289 if wctx[f].lexists():
1393 if wctx[f].lexists():
1290 repo.ui.debug("removing %s\n" % f)
1394 repo.ui.debug("removing %s\n" % f)
1291 wctx[f].audit()
1395 wctx[f].audit()
1292 wctx[f].remove()
1396 wctx[f].remove()
1293
1397
1294 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1398 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1295 z = 0
1399 z = 0
1296
1400
1297 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1401 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1298 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1402 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1299
1403
1300 # record path conflicts
1404 # record path conflicts
1301 for f, args, msg in actions['p']:
1405 for f, args, msg in actions['p']:
1302 f1, fo = args
1406 f1, fo = args
1303 s = repo.ui.status
1407 s = repo.ui.status
1304 s(_("%s: path conflict - a file or link has the same name as a "
1408 s(_("%s: path conflict - a file or link has the same name as a "
1305 "directory\n") % f)
1409 "directory\n") % f)
1306 if fo == 'l':
1410 if fo == 'l':
1307 s(_("the local file has been renamed to %s\n") % f1)
1411 s(_("the local file has been renamed to %s\n") % f1)
1308 else:
1412 else:
1309 s(_("the remote file has been renamed to %s\n") % f1)
1413 s(_("the remote file has been renamed to %s\n") % f1)
1310 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1414 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1311 ms.addpath(f, f1, fo)
1415 ms.addpath(f, f1, fo)
1312 z += 1
1416 z += 1
1313 progress(_updating, z, item=f, total=numupdates, unit=_files)
1417 progress(_updating, z, item=f, total=numupdates, unit=_files)
1314
1418
1315 # remove in parallel (must come before resolving path conflicts and getting)
1419 # remove in parallel (must come before resolving path conflicts and getting)
1316 prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
1420 prog = worker.worker(repo.ui, 0.001, batchremove, (repo, wctx),
1317 actions['r'])
1421 actions['r'])
1318 for i, item in prog:
1422 for i, item in prog:
1319 z += i
1423 z += i
1320 progress(_updating, z, item=item, total=numupdates, unit=_files)
1424 progress(_updating, z, item=item, total=numupdates, unit=_files)
1321 removed = len(actions['r'])
1425 removed = len(actions['r'])
1322
1426
1323 # resolve path conflicts (must come before getting)
1427 # resolve path conflicts (must come before getting)
1324 for f, args, msg in actions['pr']:
1428 for f, args, msg in actions['pr']:
1325 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1429 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1326 f0, = args
1430 f0, = args
1327 if wctx[f0].lexists():
1431 if wctx[f0].lexists():
1328 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1432 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1329 wctx[f].audit()
1433 wctx[f].audit()
1330 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1434 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1331 wctx[f0].remove()
1435 wctx[f0].remove()
1332 z += 1
1436 z += 1
1333 progress(_updating, z, item=f, total=numupdates, unit=_files)
1437 progress(_updating, z, item=f, total=numupdates, unit=_files)
1334
1438
1335 # We should flush before forking into worker processes, since those workers
1439 # We should flush before forking into worker processes, since those workers
1336 # flush when they complete, and we don't want to duplicate work.
1440 # flush when they complete, and we don't want to duplicate work.
1337 wctx.flushall()
1441 wctx.flushall()
1338
1442
1339 # get in parallel
1443 # get in parallel
1340 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
1444 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx, wctx),
1341 actions['g'])
1445 actions['g'])
1342 for i, item in prog:
1446 for i, item in prog:
1343 z += i
1447 z += i
1344 progress(_updating, z, item=item, total=numupdates, unit=_files)
1448 progress(_updating, z, item=item, total=numupdates, unit=_files)
1345 updated = len(actions['g'])
1449 updated = len(actions['g'])
1346
1450
1347 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1451 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1348 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1452 subrepo.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1349
1453
1350 # forget (manifest only, just log it) (must come first)
1454 # forget (manifest only, just log it) (must come first)
1351 for f, args, msg in actions['f']:
1455 for f, args, msg in actions['f']:
1352 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1456 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1353 z += 1
1457 z += 1
1354 progress(_updating, z, item=f, total=numupdates, unit=_files)
1458 progress(_updating, z, item=f, total=numupdates, unit=_files)
1355
1459
1356 # re-add (manifest only, just log it)
1460 # re-add (manifest only, just log it)
1357 for f, args, msg in actions['a']:
1461 for f, args, msg in actions['a']:
1358 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1462 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1359 z += 1
1463 z += 1
1360 progress(_updating, z, item=f, total=numupdates, unit=_files)
1464 progress(_updating, z, item=f, total=numupdates, unit=_files)
1361
1465
1362 # re-add/mark as modified (manifest only, just log it)
1466 # re-add/mark as modified (manifest only, just log it)
1363 for f, args, msg in actions['am']:
1467 for f, args, msg in actions['am']:
1364 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1468 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1365 z += 1
1469 z += 1
1366 progress(_updating, z, item=f, total=numupdates, unit=_files)
1470 progress(_updating, z, item=f, total=numupdates, unit=_files)
1367
1471
1368 # keep (noop, just log it)
1472 # keep (noop, just log it)
1369 for f, args, msg in actions['k']:
1473 for f, args, msg in actions['k']:
1370 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1474 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1371 # no progress
1475 # no progress
1372
1476
1373 # directory rename, move local
1477 # directory rename, move local
1374 for f, args, msg in actions['dm']:
1478 for f, args, msg in actions['dm']:
1375 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1479 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1376 z += 1
1480 z += 1
1377 progress(_updating, z, item=f, total=numupdates, unit=_files)
1481 progress(_updating, z, item=f, total=numupdates, unit=_files)
1378 f0, flags = args
1482 f0, flags = args
1379 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1483 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1380 wctx[f].audit()
1484 wctx[f].audit()
1381 wctx[f].write(wctx.filectx(f0).data(), flags)
1485 wctx[f].write(wctx.filectx(f0).data(), flags)
1382 wctx[f0].remove()
1486 wctx[f0].remove()
1383 updated += 1
1487 updated += 1
1384
1488
1385 # local directory rename, get
1489 # local directory rename, get
1386 for f, args, msg in actions['dg']:
1490 for f, args, msg in actions['dg']:
1387 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1491 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1388 z += 1
1492 z += 1
1389 progress(_updating, z, item=f, total=numupdates, unit=_files)
1493 progress(_updating, z, item=f, total=numupdates, unit=_files)
1390 f0, flags = args
1494 f0, flags = args
1391 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1495 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1392 wctx[f].write(mctx.filectx(f0).data(), flags)
1496 wctx[f].write(mctx.filectx(f0).data(), flags)
1393 updated += 1
1497 updated += 1
1394
1498
1395 # exec
1499 # exec
1396 for f, args, msg in actions['e']:
1500 for f, args, msg in actions['e']:
1397 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1501 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1398 z += 1
1502 z += 1
1399 progress(_updating, z, item=f, total=numupdates, unit=_files)
1503 progress(_updating, z, item=f, total=numupdates, unit=_files)
1400 flags, = args
1504 flags, = args
1401 wctx[f].audit()
1505 wctx[f].audit()
1402 wctx[f].setflags('l' in flags, 'x' in flags)
1506 wctx[f].setflags('l' in flags, 'x' in flags)
1403 updated += 1
1507 updated += 1
1404
1508
1405 # the ordering is important here -- ms.mergedriver will raise if the merge
1509 # the ordering is important here -- ms.mergedriver will raise if the merge
1406 # driver has changed, and we want to be able to bypass it when overwrite is
1510 # driver has changed, and we want to be able to bypass it when overwrite is
1407 # True
1511 # True
1408 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1512 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1409
1513
1410 if usemergedriver:
1514 if usemergedriver:
1411 ms.commit()
1515 ms.commit()
1412 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1516 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1413 # the driver might leave some files unresolved
1517 # the driver might leave some files unresolved
1414 unresolvedf = set(ms.unresolved())
1518 unresolvedf = set(ms.unresolved())
1415 if not proceed:
1519 if not proceed:
1416 # XXX setting unresolved to at least 1 is a hack to make sure we
1520 # XXX setting unresolved to at least 1 is a hack to make sure we
1417 # error out
1521 # error out
1418 return updated, merged, removed, max(len(unresolvedf), 1)
1522 return updated, merged, removed, max(len(unresolvedf), 1)
1419 newactions = []
1523 newactions = []
1420 for f, args, msg in mergeactions:
1524 for f, args, msg in mergeactions:
1421 if f in unresolvedf:
1525 if f in unresolvedf:
1422 newactions.append((f, args, msg))
1526 newactions.append((f, args, msg))
1423 mergeactions = newactions
1527 mergeactions = newactions
1424
1528
1425 # premerge
1529 # premerge
1426 tocomplete = []
1530 tocomplete = []
1427 for f, args, msg in mergeactions:
1531 for f, args, msg in mergeactions:
1428 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1532 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1429 z += 1
1533 z += 1
1430 progress(_updating, z, item=f, total=numupdates, unit=_files)
1534 progress(_updating, z, item=f, total=numupdates, unit=_files)
1431 if f == '.hgsubstate': # subrepo states need updating
1535 if f == '.hgsubstate': # subrepo states need updating
1432 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1536 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1433 overwrite, labels)
1537 overwrite, labels)
1434 continue
1538 continue
1435 wctx[f].audit()
1539 wctx[f].audit()
1436 complete, r = ms.preresolve(f, wctx)
1540 complete, r = ms.preresolve(f, wctx)
1437 if not complete:
1541 if not complete:
1438 numupdates += 1
1542 numupdates += 1
1439 tocomplete.append((f, args, msg))
1543 tocomplete.append((f, args, msg))
1440
1544
1441 # merge
1545 # merge
1442 for f, args, msg in tocomplete:
1546 for f, args, msg in tocomplete:
1443 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1547 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1444 z += 1
1548 z += 1
1445 progress(_updating, z, item=f, total=numupdates, unit=_files)
1549 progress(_updating, z, item=f, total=numupdates, unit=_files)
1446 ms.resolve(f, wctx)
1550 ms.resolve(f, wctx)
1447
1551
1448 ms.commit()
1552 ms.commit()
1449
1553
1450 unresolved = ms.unresolvedcount()
1554 unresolved = ms.unresolvedcount()
1451
1555
1452 if usemergedriver and not unresolved and ms.mdstate() != 's':
1556 if usemergedriver and not unresolved and ms.mdstate() != 's':
1453 if not driverconclude(repo, ms, wctx, labels=labels):
1557 if not driverconclude(repo, ms, wctx, labels=labels):
1454 # XXX setting unresolved to at least 1 is a hack to make sure we
1558 # XXX setting unresolved to at least 1 is a hack to make sure we
1455 # error out
1559 # error out
1456 unresolved = max(unresolved, 1)
1560 unresolved = max(unresolved, 1)
1457
1561
1458 ms.commit()
1562 ms.commit()
1459
1563
1460 msupdated, msmerged, msremoved = ms.counts()
1564 msupdated, msmerged, msremoved = ms.counts()
1461 updated += msupdated
1565 updated += msupdated
1462 merged += msmerged
1566 merged += msmerged
1463 removed += msremoved
1567 removed += msremoved
1464
1568
1465 extraactions = ms.actions()
1569 extraactions = ms.actions()
1466 if extraactions:
1570 if extraactions:
1467 mfiles = set(a[0] for a in actions['m'])
1571 mfiles = set(a[0] for a in actions['m'])
1468 for k, acts in extraactions.iteritems():
1572 for k, acts in extraactions.iteritems():
1469 actions[k].extend(acts)
1573 actions[k].extend(acts)
1470 # Remove these files from actions['m'] as well. This is important
1574 # Remove these files from actions['m'] as well. This is important
1471 # because in recordupdates, files in actions['m'] are processed
1575 # because in recordupdates, files in actions['m'] are processed
1472 # after files in other actions, and the merge driver might add
1576 # after files in other actions, and the merge driver might add
1473 # files to those actions via extraactions above. This can lead to a
1577 # files to those actions via extraactions above. This can lead to a
1474 # file being recorded twice, with poor results. This is especially
1578 # file being recorded twice, with poor results. This is especially
1475 # problematic for actions['r'] (currently only possible with the
1579 # problematic for actions['r'] (currently only possible with the
1476 # merge driver in the initial merge process; interrupted merges
1580 # merge driver in the initial merge process; interrupted merges
1477 # don't go through this flow).
1581 # don't go through this flow).
1478 #
1582 #
1479 # The real fix here is to have indexes by both file and action so
1583 # The real fix here is to have indexes by both file and action so
1480 # that when the action for a file is changed it is automatically
1584 # that when the action for a file is changed it is automatically
1481 # reflected in the other action lists. But that involves a more
1585 # reflected in the other action lists. But that involves a more
1482 # complex data structure, so this will do for now.
1586 # complex data structure, so this will do for now.
1483 #
1587 #
1484 # We don't need to do the same operation for 'dc' and 'cd' because
1588 # We don't need to do the same operation for 'dc' and 'cd' because
1485 # those lists aren't consulted again.
1589 # those lists aren't consulted again.
1486 mfiles.difference_update(a[0] for a in acts)
1590 mfiles.difference_update(a[0] for a in acts)
1487
1591
1488 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1592 actions['m'] = [a for a in actions['m'] if a[0] in mfiles]
1489
1593
1490 progress(_updating, None, total=numupdates, unit=_files)
1594 progress(_updating, None, total=numupdates, unit=_files)
1491
1595
1492 return updated, merged, removed, unresolved
1596 return updated, merged, removed, unresolved
1493
1597
1494 def recordupdates(repo, actions, branchmerge):
1598 def recordupdates(repo, actions, branchmerge):
1495 "record merge actions to the dirstate"
1599 "record merge actions to the dirstate"
1496 # remove (must come first)
1600 # remove (must come first)
1497 for f, args, msg in actions.get('r', []):
1601 for f, args, msg in actions.get('r', []):
1498 if branchmerge:
1602 if branchmerge:
1499 repo.dirstate.remove(f)
1603 repo.dirstate.remove(f)
1500 else:
1604 else:
1501 repo.dirstate.drop(f)
1605 repo.dirstate.drop(f)
1502
1606
1503 # forget (must come first)
1607 # forget (must come first)
1504 for f, args, msg in actions.get('f', []):
1608 for f, args, msg in actions.get('f', []):
1505 repo.dirstate.drop(f)
1609 repo.dirstate.drop(f)
1506
1610
1507 # resolve path conflicts
1611 # resolve path conflicts
1508 for f, args, msg in actions.get('pr', []):
1612 for f, args, msg in actions.get('pr', []):
1509 f0, = args
1613 f0, = args
1510 origf0 = repo.dirstate.copied(f0) or f0
1614 origf0 = repo.dirstate.copied(f0) or f0
1511 repo.dirstate.add(f)
1615 repo.dirstate.add(f)
1512 repo.dirstate.copy(origf0, f)
1616 repo.dirstate.copy(origf0, f)
1513 if f0 == origf0:
1617 if f0 == origf0:
1514 repo.dirstate.remove(f0)
1618 repo.dirstate.remove(f0)
1515 else:
1619 else:
1516 repo.dirstate.drop(f0)
1620 repo.dirstate.drop(f0)
1517
1621
1518 # re-add
1622 # re-add
1519 for f, args, msg in actions.get('a', []):
1623 for f, args, msg in actions.get('a', []):
1520 repo.dirstate.add(f)
1624 repo.dirstate.add(f)
1521
1625
1522 # re-add/mark as modified
1626 # re-add/mark as modified
1523 for f, args, msg in actions.get('am', []):
1627 for f, args, msg in actions.get('am', []):
1524 if branchmerge:
1628 if branchmerge:
1525 repo.dirstate.normallookup(f)
1629 repo.dirstate.normallookup(f)
1526 else:
1630 else:
1527 repo.dirstate.add(f)
1631 repo.dirstate.add(f)
1528
1632
1529 # exec change
1633 # exec change
1530 for f, args, msg in actions.get('e', []):
1634 for f, args, msg in actions.get('e', []):
1531 repo.dirstate.normallookup(f)
1635 repo.dirstate.normallookup(f)
1532
1636
1533 # keep
1637 # keep
1534 for f, args, msg in actions.get('k', []):
1638 for f, args, msg in actions.get('k', []):
1535 pass
1639 pass
1536
1640
1537 # get
1641 # get
1538 for f, args, msg in actions.get('g', []):
1642 for f, args, msg in actions.get('g', []):
1539 if branchmerge:
1643 if branchmerge:
1540 repo.dirstate.otherparent(f)
1644 repo.dirstate.otherparent(f)
1541 else:
1645 else:
1542 repo.dirstate.normal(f)
1646 repo.dirstate.normal(f)
1543
1647
1544 # merge
1648 # merge
1545 for f, args, msg in actions.get('m', []):
1649 for f, args, msg in actions.get('m', []):
1546 f1, f2, fa, move, anc = args
1650 f1, f2, fa, move, anc = args
1547 if branchmerge:
1651 if branchmerge:
1548 # We've done a branch merge, mark this file as merged
1652 # We've done a branch merge, mark this file as merged
1549 # so that we properly record the merger later
1653 # so that we properly record the merger later
1550 repo.dirstate.merge(f)
1654 repo.dirstate.merge(f)
1551 if f1 != f2: # copy/rename
1655 if f1 != f2: # copy/rename
1552 if move:
1656 if move:
1553 repo.dirstate.remove(f1)
1657 repo.dirstate.remove(f1)
1554 if f1 != f:
1658 if f1 != f:
1555 repo.dirstate.copy(f1, f)
1659 repo.dirstate.copy(f1, f)
1556 else:
1660 else:
1557 repo.dirstate.copy(f2, f)
1661 repo.dirstate.copy(f2, f)
1558 else:
1662 else:
1559 # We've update-merged a locally modified file, so
1663 # We've update-merged a locally modified file, so
1560 # we set the dirstate to emulate a normal checkout
1664 # we set the dirstate to emulate a normal checkout
1561 # of that file some time in the past. Thus our
1665 # of that file some time in the past. Thus our
1562 # merge will appear as a normal local file
1666 # merge will appear as a normal local file
1563 # modification.
1667 # modification.
1564 if f2 == f: # file not locally copied/moved
1668 if f2 == f: # file not locally copied/moved
1565 repo.dirstate.normallookup(f)
1669 repo.dirstate.normallookup(f)
1566 if move:
1670 if move:
1567 repo.dirstate.drop(f1)
1671 repo.dirstate.drop(f1)
1568
1672
1569 # directory rename, move local
1673 # directory rename, move local
1570 for f, args, msg in actions.get('dm', []):
1674 for f, args, msg in actions.get('dm', []):
1571 f0, flag = args
1675 f0, flag = args
1572 if branchmerge:
1676 if branchmerge:
1573 repo.dirstate.add(f)
1677 repo.dirstate.add(f)
1574 repo.dirstate.remove(f0)
1678 repo.dirstate.remove(f0)
1575 repo.dirstate.copy(f0, f)
1679 repo.dirstate.copy(f0, f)
1576 else:
1680 else:
1577 repo.dirstate.normal(f)
1681 repo.dirstate.normal(f)
1578 repo.dirstate.drop(f0)
1682 repo.dirstate.drop(f0)
1579
1683
1580 # directory rename, get
1684 # directory rename, get
1581 for f, args, msg in actions.get('dg', []):
1685 for f, args, msg in actions.get('dg', []):
1582 f0, flag = args
1686 f0, flag = args
1583 if branchmerge:
1687 if branchmerge:
1584 repo.dirstate.add(f)
1688 repo.dirstate.add(f)
1585 repo.dirstate.copy(f0, f)
1689 repo.dirstate.copy(f0, f)
1586 else:
1690 else:
1587 repo.dirstate.normal(f)
1691 repo.dirstate.normal(f)
1588
1692
1589 def update(repo, node, branchmerge, force, ancestor=None,
1693 def update(repo, node, branchmerge, force, ancestor=None,
1590 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1694 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1591 updatecheck=None, wc=None):
1695 updatecheck=None, wc=None):
1592 """
1696 """
1593 Perform a merge between the working directory and the given node
1697 Perform a merge between the working directory and the given node
1594
1698
1595 node = the node to update to
1699 node = the node to update to
1596 branchmerge = whether to merge between branches
1700 branchmerge = whether to merge between branches
1597 force = whether to force branch merging or file overwriting
1701 force = whether to force branch merging or file overwriting
1598 matcher = a matcher to filter file lists (dirstate not updated)
1702 matcher = a matcher to filter file lists (dirstate not updated)
1599 mergeancestor = whether it is merging with an ancestor. If true,
1703 mergeancestor = whether it is merging with an ancestor. If true,
1600 we should accept the incoming changes for any prompts that occur.
1704 we should accept the incoming changes for any prompts that occur.
1601 If false, merging with an ancestor (fast-forward) is only allowed
1705 If false, merging with an ancestor (fast-forward) is only allowed
1602 between different named branches. This flag is used by rebase extension
1706 between different named branches. This flag is used by rebase extension
1603 as a temporary fix and should be avoided in general.
1707 as a temporary fix and should be avoided in general.
1604 labels = labels to use for base, local and other
1708 labels = labels to use for base, local and other
1605 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1709 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1606 this is True, then 'force' should be True as well.
1710 this is True, then 'force' should be True as well.
1607
1711
1608 The table below shows all the behaviors of the update command
1712 The table below shows all the behaviors of the update command
1609 given the -c and -C or no options, whether the working directory
1713 given the -c and -C or no options, whether the working directory
1610 is dirty, whether a revision is specified, and the relationship of
1714 is dirty, whether a revision is specified, and the relationship of
1611 the parent rev to the target rev (linear or not). Match from top first. The
1715 the parent rev to the target rev (linear or not). Match from top first. The
1612 -n option doesn't exist on the command line, but represents the
1716 -n option doesn't exist on the command line, but represents the
1613 experimental.updatecheck=noconflict option.
1717 experimental.updatecheck=noconflict option.
1614
1718
1615 This logic is tested by test-update-branches.t.
1719 This logic is tested by test-update-branches.t.
1616
1720
1617 -c -C -n -m dirty rev linear | result
1721 -c -C -n -m dirty rev linear | result
1618 y y * * * * * | (1)
1722 y y * * * * * | (1)
1619 y * y * * * * | (1)
1723 y * y * * * * | (1)
1620 y * * y * * * | (1)
1724 y * * y * * * | (1)
1621 * y y * * * * | (1)
1725 * y y * * * * | (1)
1622 * y * y * * * | (1)
1726 * y * y * * * | (1)
1623 * * y y * * * | (1)
1727 * * y y * * * | (1)
1624 * * * * * n n | x
1728 * * * * * n n | x
1625 * * * * n * * | ok
1729 * * * * n * * | ok
1626 n n n n y * y | merge
1730 n n n n y * y | merge
1627 n n n n y y n | (2)
1731 n n n n y y n | (2)
1628 n n n y y * * | merge
1732 n n n y y * * | merge
1629 n n y n y * * | merge if no conflict
1733 n n y n y * * | merge if no conflict
1630 n y n n y * * | discard
1734 n y n n y * * | discard
1631 y n n n y * * | (3)
1735 y n n n y * * | (3)
1632
1736
1633 x = can't happen
1737 x = can't happen
1634 * = don't-care
1738 * = don't-care
1635 1 = incompatible options (checked in commands.py)
1739 1 = incompatible options (checked in commands.py)
1636 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1740 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1637 3 = abort: uncommitted changes (checked in commands.py)
1741 3 = abort: uncommitted changes (checked in commands.py)
1638
1742
1639 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1743 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1640 to repo[None] if None is passed.
1744 to repo[None] if None is passed.
1641
1745
1642 Return the same tuple as applyupdates().
1746 Return the same tuple as applyupdates().
1643 """
1747 """
1644 # Avoid cycle.
1748 # Avoid cycle.
1645 from . import sparse
1749 from . import sparse
1646
1750
1647 # This function used to find the default destination if node was None, but
1751 # This function used to find the default destination if node was None, but
1648 # that's now in destutil.py.
1752 # that's now in destutil.py.
1649 assert node is not None
1753 assert node is not None
1650 if not branchmerge and not force:
1754 if not branchmerge and not force:
1651 # TODO: remove the default once all callers that pass branchmerge=False
1755 # TODO: remove the default once all callers that pass branchmerge=False
1652 # and force=False pass a value for updatecheck. We may want to allow
1756 # and force=False pass a value for updatecheck. We may want to allow
1653 # updatecheck='abort' to better suppport some of these callers.
1757 # updatecheck='abort' to better suppport some of these callers.
1654 if updatecheck is None:
1758 if updatecheck is None:
1655 updatecheck = 'linear'
1759 updatecheck = 'linear'
1656 assert updatecheck in ('none', 'linear', 'noconflict')
1760 assert updatecheck in ('none', 'linear', 'noconflict')
1657 # If we're doing a partial update, we need to skip updating
1761 # If we're doing a partial update, we need to skip updating
1658 # the dirstate, so make a note of any partial-ness to the
1762 # the dirstate, so make a note of any partial-ness to the
1659 # update here.
1763 # update here.
1660 if matcher is None or matcher.always():
1764 if matcher is None or matcher.always():
1661 partial = False
1765 partial = False
1662 else:
1766 else:
1663 partial = True
1767 partial = True
1664 with repo.wlock():
1768 with repo.wlock():
1665 if wc is None:
1769 if wc is None:
1666 wc = repo[None]
1770 wc = repo[None]
1667 pl = wc.parents()
1771 pl = wc.parents()
1668 p1 = pl[0]
1772 p1 = pl[0]
1669 pas = [None]
1773 pas = [None]
1670 if ancestor is not None:
1774 if ancestor is not None:
1671 pas = [repo[ancestor]]
1775 pas = [repo[ancestor]]
1672
1776
1673 overwrite = force and not branchmerge
1777 overwrite = force and not branchmerge
1674
1778
1675 p2 = repo[node]
1779 p2 = repo[node]
1676 if pas[0] is None:
1780 if pas[0] is None:
1677 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1781 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1678 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1782 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1679 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1783 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1680 else:
1784 else:
1681 pas = [p1.ancestor(p2, warn=branchmerge)]
1785 pas = [p1.ancestor(p2, warn=branchmerge)]
1682
1786
1683 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1787 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1684
1788
1685 ### check phase
1789 ### check phase
1686 if not overwrite:
1790 if not overwrite:
1687 if len(pl) > 1:
1791 if len(pl) > 1:
1688 raise error.Abort(_("outstanding uncommitted merge"))
1792 raise error.Abort(_("outstanding uncommitted merge"))
1689 ms = mergestate.read(repo)
1793 ms = mergestate.read(repo)
1690 if list(ms.unresolved()):
1794 if list(ms.unresolved()):
1691 raise error.Abort(_("outstanding merge conflicts"))
1795 raise error.Abort(_("outstanding merge conflicts"))
1692 if branchmerge:
1796 if branchmerge:
1693 if pas == [p2]:
1797 if pas == [p2]:
1694 raise error.Abort(_("merging with a working directory ancestor"
1798 raise error.Abort(_("merging with a working directory ancestor"
1695 " has no effect"))
1799 " has no effect"))
1696 elif pas == [p1]:
1800 elif pas == [p1]:
1697 if not mergeancestor and wc.branch() == p2.branch():
1801 if not mergeancestor and wc.branch() == p2.branch():
1698 raise error.Abort(_("nothing to merge"),
1802 raise error.Abort(_("nothing to merge"),
1699 hint=_("use 'hg update' "
1803 hint=_("use 'hg update' "
1700 "or check 'hg heads'"))
1804 "or check 'hg heads'"))
1701 if not force and (wc.files() or wc.deleted()):
1805 if not force and (wc.files() or wc.deleted()):
1702 raise error.Abort(_("uncommitted changes"),
1806 raise error.Abort(_("uncommitted changes"),
1703 hint=_("use 'hg status' to list changes"))
1807 hint=_("use 'hg status' to list changes"))
1704 for s in sorted(wc.substate):
1808 for s in sorted(wc.substate):
1705 wc.sub(s).bailifchanged()
1809 wc.sub(s).bailifchanged()
1706
1810
1707 elif not overwrite:
1811 elif not overwrite:
1708 if p1 == p2: # no-op update
1812 if p1 == p2: # no-op update
1709 # call the hooks and exit early
1813 # call the hooks and exit early
1710 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1814 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1711 repo.hook('update', parent1=xp2, parent2='', error=0)
1815 repo.hook('update', parent1=xp2, parent2='', error=0)
1712 return 0, 0, 0, 0
1816 return 0, 0, 0, 0
1713
1817
1714 if (updatecheck == 'linear' and
1818 if (updatecheck == 'linear' and
1715 pas not in ([p1], [p2])): # nonlinear
1819 pas not in ([p1], [p2])): # nonlinear
1716 dirty = wc.dirty(missing=True)
1820 dirty = wc.dirty(missing=True)
1717 if dirty:
1821 if dirty:
1718 # Branching is a bit strange to ensure we do the minimal
1822 # Branching is a bit strange to ensure we do the minimal
1719 # amount of call to obsutil.foreground.
1823 # amount of call to obsutil.foreground.
1720 foreground = obsutil.foreground(repo, [p1.node()])
1824 foreground = obsutil.foreground(repo, [p1.node()])
1721 # note: the <node> variable contains a random identifier
1825 # note: the <node> variable contains a random identifier
1722 if repo[node].node() in foreground:
1826 if repo[node].node() in foreground:
1723 pass # allow updating to successors
1827 pass # allow updating to successors
1724 else:
1828 else:
1725 msg = _("uncommitted changes")
1829 msg = _("uncommitted changes")
1726 hint = _("commit or update --clean to discard changes")
1830 hint = _("commit or update --clean to discard changes")
1727 raise error.UpdateAbort(msg, hint=hint)
1831 raise error.UpdateAbort(msg, hint=hint)
1728 else:
1832 else:
1729 # Allow jumping branches if clean and specific rev given
1833 # Allow jumping branches if clean and specific rev given
1730 pass
1834 pass
1731
1835
1732 if overwrite:
1836 if overwrite:
1733 pas = [wc]
1837 pas = [wc]
1734 elif not branchmerge:
1838 elif not branchmerge:
1735 pas = [p1]
1839 pas = [p1]
1736
1840
1737 # deprecated config: merge.followcopies
1841 # deprecated config: merge.followcopies
1738 followcopies = repo.ui.configbool('merge', 'followcopies')
1842 followcopies = repo.ui.configbool('merge', 'followcopies')
1739 if overwrite:
1843 if overwrite:
1740 followcopies = False
1844 followcopies = False
1741 elif not pas[0]:
1845 elif not pas[0]:
1742 followcopies = False
1846 followcopies = False
1743 if not branchmerge and not wc.dirty(missing=True):
1847 if not branchmerge and not wc.dirty(missing=True):
1744 followcopies = False
1848 followcopies = False
1745
1849
1746 ### calculate phase
1850 ### calculate phase
1747 actionbyfile, diverge, renamedelete = calculateupdates(
1851 actionbyfile, diverge, renamedelete = calculateupdates(
1748 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1852 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1749 followcopies, matcher=matcher, mergeforce=mergeforce)
1853 followcopies, matcher=matcher, mergeforce=mergeforce)
1750
1854
1751 if updatecheck == 'noconflict':
1855 if updatecheck == 'noconflict':
1752 for f, (m, args, msg) in actionbyfile.iteritems():
1856 for f, (m, args, msg) in actionbyfile.iteritems():
1753 if m not in ('g', 'k', 'e', 'r', 'pr'):
1857 if m not in ('g', 'k', 'e', 'r', 'pr'):
1754 msg = _("conflicting changes")
1858 msg = _("conflicting changes")
1755 hint = _("commit or update --clean to discard changes")
1859 hint = _("commit or update --clean to discard changes")
1756 raise error.Abort(msg, hint=hint)
1860 raise error.Abort(msg, hint=hint)
1757
1861
1758 # Prompt and create actions. Most of this is in the resolve phase
1862 # Prompt and create actions. Most of this is in the resolve phase
1759 # already, but we can't handle .hgsubstate in filemerge or
1863 # already, but we can't handle .hgsubstate in filemerge or
1760 # subrepo.submerge yet so we have to keep prompting for it.
1864 # subrepo.submerge yet so we have to keep prompting for it.
1761 if '.hgsubstate' in actionbyfile:
1865 if '.hgsubstate' in actionbyfile:
1762 f = '.hgsubstate'
1866 f = '.hgsubstate'
1763 m, args, msg = actionbyfile[f]
1867 m, args, msg = actionbyfile[f]
1764 prompts = filemerge.partextras(labels)
1868 prompts = filemerge.partextras(labels)
1765 prompts['f'] = f
1869 prompts['f'] = f
1766 if m == 'cd':
1870 if m == 'cd':
1767 if repo.ui.promptchoice(
1871 if repo.ui.promptchoice(
1768 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1872 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
1769 "use (c)hanged version or (d)elete?"
1873 "use (c)hanged version or (d)elete?"
1770 "$$ &Changed $$ &Delete") % prompts, 0):
1874 "$$ &Changed $$ &Delete") % prompts, 0):
1771 actionbyfile[f] = ('r', None, "prompt delete")
1875 actionbyfile[f] = ('r', None, "prompt delete")
1772 elif f in p1:
1876 elif f in p1:
1773 actionbyfile[f] = ('am', None, "prompt keep")
1877 actionbyfile[f] = ('am', None, "prompt keep")
1774 else:
1878 else:
1775 actionbyfile[f] = ('a', None, "prompt keep")
1879 actionbyfile[f] = ('a', None, "prompt keep")
1776 elif m == 'dc':
1880 elif m == 'dc':
1777 f1, f2, fa, move, anc = args
1881 f1, f2, fa, move, anc = args
1778 flags = p2[f2].flags()
1882 flags = p2[f2].flags()
1779 if repo.ui.promptchoice(
1883 if repo.ui.promptchoice(
1780 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1884 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
1781 "use (c)hanged version or leave (d)eleted?"
1885 "use (c)hanged version or leave (d)eleted?"
1782 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1886 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
1783 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1887 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1784 else:
1888 else:
1785 del actionbyfile[f]
1889 del actionbyfile[f]
1786
1890
1787 # Convert to dictionary-of-lists format
1891 # Convert to dictionary-of-lists format
1788 actions = dict((m, [])
1892 actions = dict((m, [])
1789 for m in 'a am f g cd dc r dm dg m e k p pr'.split())
1893 for m in 'a am f g cd dc r dm dg m e k p pr'.split())
1790 for f, (m, args, msg) in actionbyfile.iteritems():
1894 for f, (m, args, msg) in actionbyfile.iteritems():
1791 if m not in actions:
1895 if m not in actions:
1792 actions[m] = []
1896 actions[m] = []
1793 actions[m].append((f, args, msg))
1897 actions[m].append((f, args, msg))
1794
1898
1795 if not util.fscasesensitive(repo.path):
1899 if not util.fscasesensitive(repo.path):
1796 # check collision between files only in p2 for clean update
1900 # check collision between files only in p2 for clean update
1797 if (not branchmerge and
1901 if (not branchmerge and
1798 (force or not wc.dirty(missing=True, branch=False))):
1902 (force or not wc.dirty(missing=True, branch=False))):
1799 _checkcollision(repo, p2.manifest(), None)
1903 _checkcollision(repo, p2.manifest(), None)
1800 else:
1904 else:
1801 _checkcollision(repo, wc.manifest(), actions)
1905 _checkcollision(repo, wc.manifest(), actions)
1802
1906
1803 # divergent renames
1907 # divergent renames
1804 for f, fl in sorted(diverge.iteritems()):
1908 for f, fl in sorted(diverge.iteritems()):
1805 repo.ui.warn(_("note: possible conflict - %s was renamed "
1909 repo.ui.warn(_("note: possible conflict - %s was renamed "
1806 "multiple times to:\n") % f)
1910 "multiple times to:\n") % f)
1807 for nf in fl:
1911 for nf in fl:
1808 repo.ui.warn(" %s\n" % nf)
1912 repo.ui.warn(" %s\n" % nf)
1809
1913
1810 # rename and delete
1914 # rename and delete
1811 for f, fl in sorted(renamedelete.iteritems()):
1915 for f, fl in sorted(renamedelete.iteritems()):
1812 repo.ui.warn(_("note: possible conflict - %s was deleted "
1916 repo.ui.warn(_("note: possible conflict - %s was deleted "
1813 "and renamed to:\n") % f)
1917 "and renamed to:\n") % f)
1814 for nf in fl:
1918 for nf in fl:
1815 repo.ui.warn(" %s\n" % nf)
1919 repo.ui.warn(" %s\n" % nf)
1816
1920
1817 ### apply phase
1921 ### apply phase
1818 if not branchmerge: # just jump to the new rev
1922 if not branchmerge: # just jump to the new rev
1819 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1923 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1820 if not partial:
1924 if not partial:
1821 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1925 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1822 # note that we're in the middle of an update
1926 # note that we're in the middle of an update
1823 repo.vfs.write('updatestate', p2.hex())
1927 repo.vfs.write('updatestate', p2.hex())
1824
1928
1825 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1929 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1826 wc.flushall()
1930 wc.flushall()
1827
1931
1828 if not partial:
1932 if not partial:
1829 with repo.dirstate.parentchange():
1933 with repo.dirstate.parentchange():
1830 repo.setparents(fp1, fp2)
1934 repo.setparents(fp1, fp2)
1831 recordupdates(repo, actions, branchmerge)
1935 recordupdates(repo, actions, branchmerge)
1832 # update completed, clear state
1936 # update completed, clear state
1833 util.unlink(repo.vfs.join('updatestate'))
1937 util.unlink(repo.vfs.join('updatestate'))
1834
1938
1835 if not branchmerge:
1939 if not branchmerge:
1836 repo.dirstate.setbranch(p2.branch())
1940 repo.dirstate.setbranch(p2.branch())
1837
1941
1838 # If we're updating to a location, clean up any stale temporary includes
1942 # If we're updating to a location, clean up any stale temporary includes
1839 # (ex: this happens during hg rebase --abort).
1943 # (ex: this happens during hg rebase --abort).
1840 if not branchmerge:
1944 if not branchmerge:
1841 sparse.prunetemporaryincludes(repo)
1945 sparse.prunetemporaryincludes(repo)
1842
1946
1843 if not partial:
1947 if not partial:
1844 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1948 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1845 return stats
1949 return stats
1846
1950
1847 def graft(repo, ctx, pctx, labels, keepparent=False):
1951 def graft(repo, ctx, pctx, labels, keepparent=False):
1848 """Do a graft-like merge.
1952 """Do a graft-like merge.
1849
1953
1850 This is a merge where the merge ancestor is chosen such that one
1954 This is a merge where the merge ancestor is chosen such that one
1851 or more changesets are grafted onto the current changeset. In
1955 or more changesets are grafted onto the current changeset. In
1852 addition to the merge, this fixes up the dirstate to include only
1956 addition to the merge, this fixes up the dirstate to include only
1853 a single parent (if keepparent is False) and tries to duplicate any
1957 a single parent (if keepparent is False) and tries to duplicate any
1854 renames/copies appropriately.
1958 renames/copies appropriately.
1855
1959
1856 ctx - changeset to rebase
1960 ctx - changeset to rebase
1857 pctx - merge base, usually ctx.p1()
1961 pctx - merge base, usually ctx.p1()
1858 labels - merge labels eg ['local', 'graft']
1962 labels - merge labels eg ['local', 'graft']
1859 keepparent - keep second parent if any
1963 keepparent - keep second parent if any
1860
1964
1861 """
1965 """
1862 # If we're grafting a descendant onto an ancestor, be sure to pass
1966 # If we're grafting a descendant onto an ancestor, be sure to pass
1863 # mergeancestor=True to update. This does two things: 1) allows the merge if
1967 # mergeancestor=True to update. This does two things: 1) allows the merge if
1864 # the destination is the same as the parent of the ctx (so we can use graft
1968 # the destination is the same as the parent of the ctx (so we can use graft
1865 # to copy commits), and 2) informs update that the incoming changes are
1969 # to copy commits), and 2) informs update that the incoming changes are
1866 # newer than the destination so it doesn't prompt about "remote changed foo
1970 # newer than the destination so it doesn't prompt about "remote changed foo
1867 # which local deleted".
1971 # which local deleted".
1868 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1972 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1869
1973
1870 stats = update(repo, ctx.node(), True, True, pctx.node(),
1974 stats = update(repo, ctx.node(), True, True, pctx.node(),
1871 mergeancestor=mergeancestor, labels=labels)
1975 mergeancestor=mergeancestor, labels=labels)
1872
1976
1873 pother = nullid
1977 pother = nullid
1874 parents = ctx.parents()
1978 parents = ctx.parents()
1875 if keepparent and len(parents) == 2 and pctx in parents:
1979 if keepparent and len(parents) == 2 and pctx in parents:
1876 parents.remove(pctx)
1980 parents.remove(pctx)
1877 pother = parents[0].node()
1981 pother = parents[0].node()
1878
1982
1879 with repo.dirstate.parentchange():
1983 with repo.dirstate.parentchange():
1880 repo.setparents(repo['.'].node(), pother)
1984 repo.setparents(repo['.'].node(), pother)
1881 repo.dirstate.write(repo.currenttransaction())
1985 repo.dirstate.write(repo.currenttransaction())
1882 # fix up dirstate for copies and renames
1986 # fix up dirstate for copies and renames
1883 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1987 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1884 return stats
1988 return stats
@@ -1,231 +1,242 b''
1 $ hg init
1 $ hg init
2
2
3 audit of .hg
3 audit of .hg
4
4
5 $ hg add .hg/00changelog.i
5 $ hg add .hg/00changelog.i
6 abort: path contains illegal component: .hg/00changelog.i (glob)
6 abort: path contains illegal component: .hg/00changelog.i (glob)
7 [255]
7 [255]
8
8
9 #if symlink
9 #if symlink
10
10
11 Symlinks
11 Symlinks
12
12
13 $ mkdir a
13 $ mkdir a
14 $ echo a > a/a
14 $ echo a > a/a
15 $ hg ci -Ama
15 $ hg ci -Ama
16 adding a/a
16 adding a/a
17 $ ln -s a b
17 $ ln -s a b
18 $ echo b > a/b
18 $ echo b > a/b
19 $ hg add b/b
19 $ hg add b/b
20 abort: path 'b/b' traverses symbolic link 'b' (glob)
20 abort: path 'b/b' traverses symbolic link 'b' (glob)
21 [255]
21 [255]
22 $ hg add b
22 $ hg add b
23
23
24 should still fail - maybe
24 should still fail - maybe
25
25
26 $ hg add b/b
26 $ hg add b/b
27 abort: path 'b/b' traverses symbolic link 'b' (glob)
27 abort: path 'b/b' traverses symbolic link 'b' (glob)
28 [255]
28 [255]
29
29
30 $ hg commit -m 'add symlink b'
30 $ hg commit -m 'add symlink b'
31
31
32
32
33 Test symlink traversing when accessing history:
33 Test symlink traversing when accessing history:
34 -----------------------------------------------
34 -----------------------------------------------
35
35
36 (build a changeset where the path exists as a directory)
36 (build a changeset where the path exists as a directory)
37
37
38 $ hg up 0
38 $ hg up 0
39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
39 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
40 $ mkdir b
40 $ mkdir b
41 $ echo c > b/a
41 $ echo c > b/a
42 $ hg add b/a
42 $ hg add b/a
43 $ hg ci -m 'add directory b'
43 $ hg ci -m 'add directory b'
44 created new head
44 created new head
45
45
46 Test that hg cat does not do anything wrong the working copy has 'b' as directory
46 Test that hg cat does not do anything wrong the working copy has 'b' as directory
47
47
48 $ hg cat b/a
48 $ hg cat b/a
49 c
49 c
50 $ hg cat -r "desc(directory)" b/a
50 $ hg cat -r "desc(directory)" b/a
51 c
51 c
52 $ hg cat -r "desc(symlink)" b/a
52 $ hg cat -r "desc(symlink)" b/a
53 b/a: no such file in rev bc151a1f53bd
53 b/a: no such file in rev bc151a1f53bd
54 [1]
54 [1]
55
55
56 Test that hg cat does not do anything wrong the working copy has 'b' as a symlink (issue4749)
56 Test that hg cat does not do anything wrong the working copy has 'b' as a symlink (issue4749)
57
57
58 $ hg up 'desc(symlink)'
58 $ hg up 'desc(symlink)'
59 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
59 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
60 $ hg cat b/a
60 $ hg cat b/a
61 b/a: no such file in rev bc151a1f53bd
61 b/a: no such file in rev bc151a1f53bd
62 [1]
62 [1]
63 $ hg cat -r "desc(directory)" b/a
63 $ hg cat -r "desc(directory)" b/a
64 c
64 c
65 $ hg cat -r "desc(symlink)" b/a
65 $ hg cat -r "desc(symlink)" b/a
66 b/a: no such file in rev bc151a1f53bd
66 b/a: no such file in rev bc151a1f53bd
67 [1]
67 [1]
68
68
69 #endif
69 #endif
70
70
71
71
72 unbundle tampered bundle
72 unbundle tampered bundle
73
73
74 $ hg init target
74 $ hg init target
75 $ cd target
75 $ cd target
76 $ hg unbundle "$TESTDIR/bundles/tampered.hg"
76 $ hg unbundle "$TESTDIR/bundles/tampered.hg"
77 adding changesets
77 adding changesets
78 adding manifests
78 adding manifests
79 adding file changes
79 adding file changes
80 added 5 changesets with 6 changes to 6 files (+4 heads)
80 added 5 changesets with 6 changes to 6 files (+4 heads)
81 (run 'hg heads' to see heads, 'hg merge' to merge)
81 (run 'hg heads' to see heads, 'hg merge' to merge)
82
82
83 attack .hg/test
83 attack .hg/test
84
84
85 $ hg manifest -r0
85 $ hg manifest -r0
86 .hg/test
86 .hg/test
87 $ hg update -Cr0
87 $ hg update -Cr0
88 abort: path contains illegal component: .hg/test (glob)
88 abort: path contains illegal component: .hg/test (glob)
89 [255]
89 [255]
90
90
91 attack foo/.hg/test
91 attack foo/.hg/test
92
92
93 $ hg manifest -r1
93 $ hg manifest -r1
94 foo/.hg/test
94 foo/.hg/test
95 $ hg update -Cr1
95 $ hg update -Cr1
96 abort: path 'foo/.hg/test' is inside nested repo 'foo' (glob)
96 abort: path 'foo/.hg/test' is inside nested repo 'foo' (glob)
97 [255]
97 [255]
98
98
99 attack back/test where back symlinks to ..
99 attack back/test where back symlinks to ..
100
100
101 $ hg manifest -r2
101 $ hg manifest -r2
102 back
102 back
103 back/test
103 back/test
104 #if symlink
104 #if symlink
105 $ hg update -Cr2
105 $ hg update -Cr2
106 abort: path 'back/test' traverses symbolic link 'back'
106 back: is both a file and a directory
107 abort: destination manifest contains path conflicts
107 [255]
108 [255]
108 #else
109 #else
109 ('back' will be a file and cause some other system specific error)
110 ('back' will be a file and cause some other system specific error)
110 $ hg update -Cr2
111 $ hg update -Cr2
111 abort: * (glob)
112 abort: * (glob)
112 [255]
113 [255]
113 #endif
114 #endif
114
115
115 attack ../test
116 attack ../test
116
117
117 $ hg manifest -r3
118 $ hg manifest -r3
118 ../test
119 ../test
119 $ hg update -Cr3
120 $ hg update -Cr3
120 abort: path contains illegal component: ../test (glob)
121 abort: path contains illegal component: ../test (glob)
121 [255]
122 [255]
122
123
123 attack /tmp/test
124 attack /tmp/test
124
125
125 $ hg manifest -r4
126 $ hg manifest -r4
126 /tmp/test
127 /tmp/test
127 $ hg update -Cr4
128 $ hg update -Cr4
128 abort: path contains illegal component: /tmp/test (glob)
129 abort: path contains illegal component: /tmp/test (glob)
129 [255]
130 [255]
130
131
131 $ cd ..
132 $ cd ..
132
133
133 Test symlink traversal on merge:
134 Test symlink traversal on merge:
134 --------------------------------
135 --------------------------------
135
136
136 #if symlink
137 #if symlink
137
138
138 set up symlink hell
139 set up symlink hell
139
140
140 $ mkdir merge-symlink-out
141 $ mkdir merge-symlink-out
141 $ hg init merge-symlink
142 $ hg init merge-symlink
142 $ cd merge-symlink
143 $ cd merge-symlink
143 $ touch base
144 $ touch base
144 $ hg commit -qAm base
145 $ hg commit -qAm base
145 $ ln -s ../merge-symlink-out a
146 $ ln -s ../merge-symlink-out a
146 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
147 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
147 $ hg up -q 0
148 $ hg up -q 0
148 $ mkdir a
149 $ mkdir a
149 $ touch a/poisoned
150 $ touch a/poisoned
150 $ hg commit -qAm 'file a/poisoned'
151 $ hg commit -qAm 'file a/poisoned'
151 $ hg log -G -T '{rev}: {desc}\n'
152 $ hg log -G -T '{rev}: {desc}\n'
152 @ 2: file a/poisoned
153 @ 2: file a/poisoned
153 |
154 |
154 | o 1: symlink a -> ../merge-symlink-out
155 | o 1: symlink a -> ../merge-symlink-out
155 |/
156 |/
156 o 0: base
157 o 0: base
157
158
158
159
159 try trivial merge
160 try trivial merge
160
161
161 $ hg up -qC 1
162 $ hg up -qC 1
162 $ hg merge 2
163 $ hg merge 2
163 abort: path 'a/poisoned' traverses symbolic link 'a'
164 a: path conflict - a file or link has the same name as a directory
164 [255]
165 the local file has been renamed to a~aa04623eb0c3
166 resolve manually then use 'hg resolve --mark a'
167 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
168 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
169 [1]
165
170
166 try rebase onto other revision: cache of audited paths should be discarded,
171 try rebase onto other revision: cache of audited paths should be discarded,
167 and the rebase should fail (issue5628)
172 and the rebase should fail (issue5628)
168
173
169 $ hg up -qC 2
174 $ hg up -qC 2
170 $ hg rebase -s 2 -d 1 --config extensions.rebase=
175 $ hg rebase -s 2 -d 1 --config extensions.rebase=
171 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
176 rebasing 2:e73c21d6b244 "file a/poisoned" (tip)
172 abort: path 'a/poisoned' traverses symbolic link 'a'
177 a: path conflict - a file or link has the same name as a directory
173 [255]
178 the local file has been renamed to a~aa04623eb0c3
179 resolve manually then use 'hg resolve --mark a'
180 unresolved conflicts (see hg resolve, then hg rebase --continue)
181 [1]
174 $ ls ../merge-symlink-out
182 $ ls ../merge-symlink-out
175
183
176 $ cd ..
184 $ cd ..
177
185
178 Test symlink traversal on update:
186 Test symlink traversal on update:
179 ---------------------------------
187 ---------------------------------
180
188
181 $ mkdir update-symlink-out
189 $ mkdir update-symlink-out
182 $ hg init update-symlink
190 $ hg init update-symlink
183 $ cd update-symlink
191 $ cd update-symlink
184 $ ln -s ../update-symlink-out a
192 $ ln -s ../update-symlink-out a
185 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
193 $ hg commit -qAm 'symlink a -> ../update-symlink-out'
186 $ hg rm a
194 $ hg rm a
187 $ mkdir a && touch a/b
195 $ mkdir a && touch a/b
188 $ hg ci -qAm 'file a/b' a/b
196 $ hg ci -qAm 'file a/b' a/b
189 $ hg up -qC 0
197 $ hg up -qC 0
190 $ hg rm a
198 $ hg rm a
191 $ mkdir a && touch a/c
199 $ mkdir a && touch a/c
192 $ hg ci -qAm 'rm a, file a/c'
200 $ hg ci -qAm 'rm a, file a/c'
193 $ hg log -G -T '{rev}: {desc}\n'
201 $ hg log -G -T '{rev}: {desc}\n'
194 @ 2: rm a, file a/c
202 @ 2: rm a, file a/c
195 |
203 |
196 | o 1: file a/b
204 | o 1: file a/b
197 |/
205 |/
198 o 0: symlink a -> ../update-symlink-out
206 o 0: symlink a -> ../update-symlink-out
199
207
200
208
201 try linear update where symlink already exists:
209 try linear update where symlink already exists:
202
210
203 $ hg up -qC 0
211 $ hg up -qC 0
204 $ hg up 1
212 $ hg up 1
205 abort: path 'a/b' traverses symbolic link 'a'
213 a: is both a file and a directory
214 abort: destination manifest contains path conflicts
206 [255]
215 [255]
207
216
208 try linear update including symlinked directory and its content: paths are
217 try linear update including symlinked directory and its content: paths are
209 audited first by calculateupdates(), where no symlink is created so both
218 audited first by calculateupdates(), where no symlink is created so both
210 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
219 'a' and 'a/b' are taken as good paths. still applyupdates() should fail.
211
220
212 $ hg up -qC null
221 $ hg up -qC null
213 $ hg up 1
222 $ hg up 1
214 abort: path 'a/b' traverses symbolic link 'a'
223 a: is both a file and a directory
224 abort: destination manifest contains path conflicts
215 [255]
225 [255]
216 $ ls ../update-symlink-out
226 $ ls ../update-symlink-out
217
227
218 try branch update replacing directory with symlink, and its content: the
228 try branch update replacing directory with symlink, and its content: the
219 path 'a' is audited as a directory first, which should be audited again as
229 path 'a' is audited as a directory first, which should be audited again as
220 a symlink.
230 a symlink.
221
231
222 $ rm -f a
232 $ rm -f a
223 $ hg up -qC 2
233 $ hg up -qC 2
224 $ hg up 1
234 $ hg up 1
225 abort: path 'a/b' traverses symbolic link 'a'
235 a: is both a file and a directory
236 abort: destination manifest contains path conflicts
226 [255]
237 [255]
227 $ ls ../update-symlink-out
238 $ ls ../update-symlink-out
228
239
229 $ cd ..
240 $ cd ..
230
241
231 #endif
242 #endif
@@ -1,1000 +1,1004 b''
1 #if windows
1 #if windows
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
2 $ PYTHONPATH="$TESTDIR/../contrib;$PYTHONPATH"
3 #else
3 #else
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
4 $ PYTHONPATH="$TESTDIR/../contrib:$PYTHONPATH"
5 #endif
5 #endif
6 $ export PYTHONPATH
6 $ export PYTHONPATH
7
7
8 typical client does not want echo-back messages, so test without it:
8 typical client does not want echo-back messages, so test without it:
9
9
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
10 $ grep -v '^promptecho ' < $HGRCPATH >> $HGRCPATH.new
11 $ mv $HGRCPATH.new $HGRCPATH
11 $ mv $HGRCPATH.new $HGRCPATH
12
12
13 $ hg init repo
13 $ hg init repo
14 $ cd repo
14 $ cd repo
15
15
16 >>> from __future__ import absolute_import, print_function
16 >>> from __future__ import absolute_import, print_function
17 >>> import os
17 >>> import os
18 >>> import sys
18 >>> import sys
19 >>> from hgclient import check, readchannel, runcommand
19 >>> from hgclient import check, readchannel, runcommand
20 >>> @check
20 >>> @check
21 ... def hellomessage(server):
21 ... def hellomessage(server):
22 ... ch, data = readchannel(server)
22 ... ch, data = readchannel(server)
23 ... print('%c, %r' % (ch, data))
23 ... print('%c, %r' % (ch, data))
24 ... # run an arbitrary command to make sure the next thing the server
24 ... # run an arbitrary command to make sure the next thing the server
25 ... # sends isn't part of the hello message
25 ... # sends isn't part of the hello message
26 ... runcommand(server, ['id'])
26 ... runcommand(server, ['id'])
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
27 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
28 *** runcommand id
28 *** runcommand id
29 000000000000 tip
29 000000000000 tip
30
30
31 >>> from hgclient import check
31 >>> from hgclient import check
32 >>> @check
32 >>> @check
33 ... def unknowncommand(server):
33 ... def unknowncommand(server):
34 ... server.stdin.write('unknowncommand\n')
34 ... server.stdin.write('unknowncommand\n')
35 abort: unknown command unknowncommand
35 abort: unknown command unknowncommand
36
36
37 >>> from hgclient import check, readchannel, runcommand
37 >>> from hgclient import check, readchannel, runcommand
38 >>> @check
38 >>> @check
39 ... def checkruncommand(server):
39 ... def checkruncommand(server):
40 ... # hello block
40 ... # hello block
41 ... readchannel(server)
41 ... readchannel(server)
42 ...
42 ...
43 ... # no args
43 ... # no args
44 ... runcommand(server, [])
44 ... runcommand(server, [])
45 ...
45 ...
46 ... # global options
46 ... # global options
47 ... runcommand(server, ['id', '--quiet'])
47 ... runcommand(server, ['id', '--quiet'])
48 ...
48 ...
49 ... # make sure global options don't stick through requests
49 ... # make sure global options don't stick through requests
50 ... runcommand(server, ['id'])
50 ... runcommand(server, ['id'])
51 ...
51 ...
52 ... # --config
52 ... # --config
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
53 ... runcommand(server, ['id', '--config', 'ui.quiet=True'])
54 ...
54 ...
55 ... # make sure --config doesn't stick
55 ... # make sure --config doesn't stick
56 ... runcommand(server, ['id'])
56 ... runcommand(server, ['id'])
57 ...
57 ...
58 ... # negative return code should be masked
58 ... # negative return code should be masked
59 ... runcommand(server, ['id', '-runknown'])
59 ... runcommand(server, ['id', '-runknown'])
60 *** runcommand
60 *** runcommand
61 Mercurial Distributed SCM
61 Mercurial Distributed SCM
62
62
63 basic commands:
63 basic commands:
64
64
65 add add the specified files on the next commit
65 add add the specified files on the next commit
66 annotate show changeset information by line for each file
66 annotate show changeset information by line for each file
67 clone make a copy of an existing repository
67 clone make a copy of an existing repository
68 commit commit the specified files or all outstanding changes
68 commit commit the specified files or all outstanding changes
69 diff diff repository (or selected files)
69 diff diff repository (or selected files)
70 export dump the header and diffs for one or more changesets
70 export dump the header and diffs for one or more changesets
71 forget forget the specified files on the next commit
71 forget forget the specified files on the next commit
72 init create a new repository in the given directory
72 init create a new repository in the given directory
73 log show revision history of entire repository or files
73 log show revision history of entire repository or files
74 merge merge another revision into working directory
74 merge merge another revision into working directory
75 pull pull changes from the specified source
75 pull pull changes from the specified source
76 push push changes to the specified destination
76 push push changes to the specified destination
77 remove remove the specified files on the next commit
77 remove remove the specified files on the next commit
78 serve start stand-alone webserver
78 serve start stand-alone webserver
79 status show changed files in the working directory
79 status show changed files in the working directory
80 summary summarize working directory state
80 summary summarize working directory state
81 update update working directory (or switch revisions)
81 update update working directory (or switch revisions)
82
82
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
83 (use 'hg help' for the full list of commands or 'hg -v' for details)
84 *** runcommand id --quiet
84 *** runcommand id --quiet
85 000000000000
85 000000000000
86 *** runcommand id
86 *** runcommand id
87 000000000000 tip
87 000000000000 tip
88 *** runcommand id --config ui.quiet=True
88 *** runcommand id --config ui.quiet=True
89 000000000000
89 000000000000
90 *** runcommand id
90 *** runcommand id
91 000000000000 tip
91 000000000000 tip
92 *** runcommand id -runknown
92 *** runcommand id -runknown
93 abort: unknown revision 'unknown'!
93 abort: unknown revision 'unknown'!
94 [255]
94 [255]
95
95
96 >>> from hgclient import check, readchannel
96 >>> from hgclient import check, readchannel
97 >>> @check
97 >>> @check
98 ... def inputeof(server):
98 ... def inputeof(server):
99 ... readchannel(server)
99 ... readchannel(server)
100 ... server.stdin.write('runcommand\n')
100 ... server.stdin.write('runcommand\n')
101 ... # close stdin while server is waiting for input
101 ... # close stdin while server is waiting for input
102 ... server.stdin.close()
102 ... server.stdin.close()
103 ...
103 ...
104 ... # server exits with 1 if the pipe closed while reading the command
104 ... # server exits with 1 if the pipe closed while reading the command
105 ... print('server exit code =', server.wait())
105 ... print('server exit code =', server.wait())
106 server exit code = 1
106 server exit code = 1
107
107
108 >>> from hgclient import check, readchannel, runcommand, stringio
108 >>> from hgclient import check, readchannel, runcommand, stringio
109 >>> @check
109 >>> @check
110 ... def serverinput(server):
110 ... def serverinput(server):
111 ... readchannel(server)
111 ... readchannel(server)
112 ...
112 ...
113 ... patch = """
113 ... patch = """
114 ... # HG changeset patch
114 ... # HG changeset patch
115 ... # User test
115 ... # User test
116 ... # Date 0 0
116 ... # Date 0 0
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
117 ... # Node ID c103a3dec114d882c98382d684d8af798d09d857
118 ... # Parent 0000000000000000000000000000000000000000
118 ... # Parent 0000000000000000000000000000000000000000
119 ... 1
119 ... 1
120 ...
120 ...
121 ... diff -r 000000000000 -r c103a3dec114 a
121 ... diff -r 000000000000 -r c103a3dec114 a
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
122 ... --- /dev/null Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
123 ... +++ b/a Thu Jan 01 00:00:00 1970 +0000
124 ... @@ -0,0 +1,1 @@
124 ... @@ -0,0 +1,1 @@
125 ... +1
125 ... +1
126 ... """
126 ... """
127 ...
127 ...
128 ... runcommand(server, ['import', '-'], input=stringio(patch))
128 ... runcommand(server, ['import', '-'], input=stringio(patch))
129 ... runcommand(server, ['log'])
129 ... runcommand(server, ['log'])
130 *** runcommand import -
130 *** runcommand import -
131 applying patch from stdin
131 applying patch from stdin
132 *** runcommand log
132 *** runcommand log
133 changeset: 0:eff892de26ec
133 changeset: 0:eff892de26ec
134 tag: tip
134 tag: tip
135 user: test
135 user: test
136 date: Thu Jan 01 00:00:00 1970 +0000
136 date: Thu Jan 01 00:00:00 1970 +0000
137 summary: 1
137 summary: 1
138
138
139
139
140 check that "histedit --commands=-" can read rules from the input channel:
140 check that "histedit --commands=-" can read rules from the input channel:
141
141
142 >>> import cStringIO
142 >>> import cStringIO
143 >>> from hgclient import check, readchannel, runcommand
143 >>> from hgclient import check, readchannel, runcommand
144 >>> @check
144 >>> @check
145 ... def serverinput(server):
145 ... def serverinput(server):
146 ... readchannel(server)
146 ... readchannel(server)
147 ... rules = 'pick eff892de26ec\n'
147 ... rules = 'pick eff892de26ec\n'
148 ... runcommand(server, ['histedit', '0', '--commands=-',
148 ... runcommand(server, ['histedit', '0', '--commands=-',
149 ... '--config', 'extensions.histedit='],
149 ... '--config', 'extensions.histedit='],
150 ... input=cStringIO.StringIO(rules))
150 ... input=cStringIO.StringIO(rules))
151 *** runcommand histedit 0 --commands=- --config extensions.histedit=
151 *** runcommand histedit 0 --commands=- --config extensions.histedit=
152
152
153 check that --cwd doesn't persist between requests:
153 check that --cwd doesn't persist between requests:
154
154
155 $ mkdir foo
155 $ mkdir foo
156 $ touch foo/bar
156 $ touch foo/bar
157 >>> from hgclient import check, readchannel, runcommand
157 >>> from hgclient import check, readchannel, runcommand
158 >>> @check
158 >>> @check
159 ... def cwd(server):
159 ... def cwd(server):
160 ... readchannel(server)
160 ... readchannel(server)
161 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
161 ... runcommand(server, ['--cwd', 'foo', 'st', 'bar'])
162 ... runcommand(server, ['st', 'foo/bar'])
162 ... runcommand(server, ['st', 'foo/bar'])
163 *** runcommand --cwd foo st bar
163 *** runcommand --cwd foo st bar
164 ? bar
164 ? bar
165 *** runcommand st foo/bar
165 *** runcommand st foo/bar
166 ? foo/bar
166 ? foo/bar
167
167
168 $ rm foo/bar
168 $ rm foo/bar
169
169
170
170
171 check that local configs for the cached repo aren't inherited when -R is used:
171 check that local configs for the cached repo aren't inherited when -R is used:
172
172
173 $ cat <<EOF >> .hg/hgrc
173 $ cat <<EOF >> .hg/hgrc
174 > [ui]
174 > [ui]
175 > foo = bar
175 > foo = bar
176 > EOF
176 > EOF
177
177
178 >>> from hgclient import check, readchannel, runcommand, sep
178 >>> from hgclient import check, readchannel, runcommand, sep
179 >>> @check
179 >>> @check
180 ... def localhgrc(server):
180 ... def localhgrc(server):
181 ... readchannel(server)
181 ... readchannel(server)
182 ...
182 ...
183 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
183 ... # the cached repo local hgrc contains ui.foo=bar, so showconfig should
184 ... # show it
184 ... # show it
185 ... runcommand(server, ['showconfig'], outfilter=sep)
185 ... runcommand(server, ['showconfig'], outfilter=sep)
186 ...
186 ...
187 ... # but not for this repo
187 ... # but not for this repo
188 ... runcommand(server, ['init', 'foo'])
188 ... runcommand(server, ['init', 'foo'])
189 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
189 ... runcommand(server, ['-R', 'foo', 'showconfig', 'ui', 'defaults'])
190 *** runcommand showconfig
190 *** runcommand showconfig
191 bundle.mainreporoot=$TESTTMP/repo
191 bundle.mainreporoot=$TESTTMP/repo
192 devel.all-warnings=true
192 devel.all-warnings=true
193 devel.default-date=0 0
193 devel.default-date=0 0
194 extensions.fsmonitor= (fsmonitor !)
194 extensions.fsmonitor= (fsmonitor !)
195 largefiles.usercache=$TESTTMP/.cache/largefiles
195 largefiles.usercache=$TESTTMP/.cache/largefiles
196 ui.slash=True
196 ui.slash=True
197 ui.interactive=False
197 ui.interactive=False
198 ui.mergemarkers=detailed
198 ui.mergemarkers=detailed
199 ui.usehttp2=true (?)
199 ui.usehttp2=true (?)
200 ui.foo=bar
200 ui.foo=bar
201 ui.nontty=true
201 ui.nontty=true
202 web.address=localhost
202 web.address=localhost
203 web\.ipv6=(?:True|False) (re)
203 web\.ipv6=(?:True|False) (re)
204 *** runcommand init foo
204 *** runcommand init foo
205 *** runcommand -R foo showconfig ui defaults
205 *** runcommand -R foo showconfig ui defaults
206 ui.slash=True
206 ui.slash=True
207 ui.interactive=False
207 ui.interactive=False
208 ui.mergemarkers=detailed
208 ui.mergemarkers=detailed
209 ui.usehttp2=true (?)
209 ui.usehttp2=true (?)
210 ui.nontty=true
210 ui.nontty=true
211
211
212 $ rm -R foo
212 $ rm -R foo
213
213
214 #if windows
214 #if windows
215 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
215 $ PYTHONPATH="$TESTTMP/repo;$PYTHONPATH"
216 #else
216 #else
217 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
217 $ PYTHONPATH="$TESTTMP/repo:$PYTHONPATH"
218 #endif
218 #endif
219
219
220 $ cat <<EOF > hook.py
220 $ cat <<EOF > hook.py
221 > from __future__ import print_function
221 > from __future__ import print_function
222 > import sys
222 > import sys
223 > def hook(**args):
223 > def hook(**args):
224 > print('hook talking')
224 > print('hook talking')
225 > print('now try to read something: %r' % sys.stdin.read())
225 > print('now try to read something: %r' % sys.stdin.read())
226 > EOF
226 > EOF
227
227
228 >>> from hgclient import check, readchannel, runcommand, stringio
228 >>> from hgclient import check, readchannel, runcommand, stringio
229 >>> @check
229 >>> @check
230 ... def hookoutput(server):
230 ... def hookoutput(server):
231 ... readchannel(server)
231 ... readchannel(server)
232 ... runcommand(server, ['--config',
232 ... runcommand(server, ['--config',
233 ... 'hooks.pre-identify=python:hook.hook',
233 ... 'hooks.pre-identify=python:hook.hook',
234 ... 'id'],
234 ... 'id'],
235 ... input=stringio('some input'))
235 ... input=stringio('some input'))
236 *** runcommand --config hooks.pre-identify=python:hook.hook id
236 *** runcommand --config hooks.pre-identify=python:hook.hook id
237 eff892de26ec tip
237 eff892de26ec tip
238
238
239 Clean hook cached version
239 Clean hook cached version
240 $ rm hook.py*
240 $ rm hook.py*
241 $ rm -Rf __pycache__
241 $ rm -Rf __pycache__
242
242
243 $ echo a >> a
243 $ echo a >> a
244 >>> import os
244 >>> import os
245 >>> from hgclient import check, readchannel, runcommand
245 >>> from hgclient import check, readchannel, runcommand
246 >>> @check
246 >>> @check
247 ... def outsidechanges(server):
247 ... def outsidechanges(server):
248 ... readchannel(server)
248 ... readchannel(server)
249 ... runcommand(server, ['status'])
249 ... runcommand(server, ['status'])
250 ... os.system('hg ci -Am2')
250 ... os.system('hg ci -Am2')
251 ... runcommand(server, ['tip'])
251 ... runcommand(server, ['tip'])
252 ... runcommand(server, ['status'])
252 ... runcommand(server, ['status'])
253 *** runcommand status
253 *** runcommand status
254 M a
254 M a
255 *** runcommand tip
255 *** runcommand tip
256 changeset: 1:d3a0a68be6de
256 changeset: 1:d3a0a68be6de
257 tag: tip
257 tag: tip
258 user: test
258 user: test
259 date: Thu Jan 01 00:00:00 1970 +0000
259 date: Thu Jan 01 00:00:00 1970 +0000
260 summary: 2
260 summary: 2
261
261
262 *** runcommand status
262 *** runcommand status
263
263
264 >>> import os
264 >>> import os
265 >>> from hgclient import check, readchannel, runcommand
265 >>> from hgclient import check, readchannel, runcommand
266 >>> @check
266 >>> @check
267 ... def bookmarks(server):
267 ... def bookmarks(server):
268 ... readchannel(server)
268 ... readchannel(server)
269 ... runcommand(server, ['bookmarks'])
269 ... runcommand(server, ['bookmarks'])
270 ...
270 ...
271 ... # changes .hg/bookmarks
271 ... # changes .hg/bookmarks
272 ... os.system('hg bookmark -i bm1')
272 ... os.system('hg bookmark -i bm1')
273 ... os.system('hg bookmark -i bm2')
273 ... os.system('hg bookmark -i bm2')
274 ... runcommand(server, ['bookmarks'])
274 ... runcommand(server, ['bookmarks'])
275 ...
275 ...
276 ... # changes .hg/bookmarks.current
276 ... # changes .hg/bookmarks.current
277 ... os.system('hg upd bm1 -q')
277 ... os.system('hg upd bm1 -q')
278 ... runcommand(server, ['bookmarks'])
278 ... runcommand(server, ['bookmarks'])
279 ...
279 ...
280 ... runcommand(server, ['bookmarks', 'bm3'])
280 ... runcommand(server, ['bookmarks', 'bm3'])
281 ... f = open('a', 'ab')
281 ... f = open('a', 'ab')
282 ... f.write('a\n')
282 ... f.write('a\n')
283 ... f.close()
283 ... f.close()
284 ... runcommand(server, ['commit', '-Amm'])
284 ... runcommand(server, ['commit', '-Amm'])
285 ... runcommand(server, ['bookmarks'])
285 ... runcommand(server, ['bookmarks'])
286 ... print('')
286 ... print('')
287 *** runcommand bookmarks
287 *** runcommand bookmarks
288 no bookmarks set
288 no bookmarks set
289 *** runcommand bookmarks
289 *** runcommand bookmarks
290 bm1 1:d3a0a68be6de
290 bm1 1:d3a0a68be6de
291 bm2 1:d3a0a68be6de
291 bm2 1:d3a0a68be6de
292 *** runcommand bookmarks
292 *** runcommand bookmarks
293 * bm1 1:d3a0a68be6de
293 * bm1 1:d3a0a68be6de
294 bm2 1:d3a0a68be6de
294 bm2 1:d3a0a68be6de
295 *** runcommand bookmarks bm3
295 *** runcommand bookmarks bm3
296 *** runcommand commit -Amm
296 *** runcommand commit -Amm
297 *** runcommand bookmarks
297 *** runcommand bookmarks
298 bm1 1:d3a0a68be6de
298 bm1 1:d3a0a68be6de
299 bm2 1:d3a0a68be6de
299 bm2 1:d3a0a68be6de
300 * bm3 2:aef17e88f5f0
300 * bm3 2:aef17e88f5f0
301
301
302
302
303 >>> import os
303 >>> import os
304 >>> from hgclient import check, readchannel, runcommand
304 >>> from hgclient import check, readchannel, runcommand
305 >>> @check
305 >>> @check
306 ... def tagscache(server):
306 ... def tagscache(server):
307 ... readchannel(server)
307 ... readchannel(server)
308 ... runcommand(server, ['id', '-t', '-r', '0'])
308 ... runcommand(server, ['id', '-t', '-r', '0'])
309 ... os.system('hg tag -r 0 foo')
309 ... os.system('hg tag -r 0 foo')
310 ... runcommand(server, ['id', '-t', '-r', '0'])
310 ... runcommand(server, ['id', '-t', '-r', '0'])
311 *** runcommand id -t -r 0
311 *** runcommand id -t -r 0
312
312
313 *** runcommand id -t -r 0
313 *** runcommand id -t -r 0
314 foo
314 foo
315
315
316 >>> import os
316 >>> import os
317 >>> from hgclient import check, readchannel, runcommand
317 >>> from hgclient import check, readchannel, runcommand
318 >>> @check
318 >>> @check
319 ... def setphase(server):
319 ... def setphase(server):
320 ... readchannel(server)
320 ... readchannel(server)
321 ... runcommand(server, ['phase', '-r', '.'])
321 ... runcommand(server, ['phase', '-r', '.'])
322 ... os.system('hg phase -r . -p')
322 ... os.system('hg phase -r . -p')
323 ... runcommand(server, ['phase', '-r', '.'])
323 ... runcommand(server, ['phase', '-r', '.'])
324 *** runcommand phase -r .
324 *** runcommand phase -r .
325 3: draft
325 3: draft
326 *** runcommand phase -r .
326 *** runcommand phase -r .
327 3: public
327 3: public
328
328
329 $ echo a >> a
329 $ echo a >> a
330 >>> from hgclient import check, readchannel, runcommand
330 >>> from hgclient import check, readchannel, runcommand
331 >>> @check
331 >>> @check
332 ... def rollback(server):
332 ... def rollback(server):
333 ... readchannel(server)
333 ... readchannel(server)
334 ... runcommand(server, ['phase', '-r', '.', '-p'])
334 ... runcommand(server, ['phase', '-r', '.', '-p'])
335 ... runcommand(server, ['commit', '-Am.'])
335 ... runcommand(server, ['commit', '-Am.'])
336 ... runcommand(server, ['rollback'])
336 ... runcommand(server, ['rollback'])
337 ... runcommand(server, ['phase', '-r', '.'])
337 ... runcommand(server, ['phase', '-r', '.'])
338 ... print('')
338 ... print('')
339 *** runcommand phase -r . -p
339 *** runcommand phase -r . -p
340 no phases changed
340 no phases changed
341 *** runcommand commit -Am.
341 *** runcommand commit -Am.
342 *** runcommand rollback
342 *** runcommand rollback
343 repository tip rolled back to revision 3 (undo commit)
343 repository tip rolled back to revision 3 (undo commit)
344 working directory now based on revision 3
344 working directory now based on revision 3
345 *** runcommand phase -r .
345 *** runcommand phase -r .
346 3: public
346 3: public
347
347
348
348
349 >>> import os
349 >>> import os
350 >>> from hgclient import check, readchannel, runcommand
350 >>> from hgclient import check, readchannel, runcommand
351 >>> @check
351 >>> @check
352 ... def branch(server):
352 ... def branch(server):
353 ... readchannel(server)
353 ... readchannel(server)
354 ... runcommand(server, ['branch'])
354 ... runcommand(server, ['branch'])
355 ... os.system('hg branch foo')
355 ... os.system('hg branch foo')
356 ... runcommand(server, ['branch'])
356 ... runcommand(server, ['branch'])
357 ... os.system('hg branch default')
357 ... os.system('hg branch default')
358 *** runcommand branch
358 *** runcommand branch
359 default
359 default
360 marked working directory as branch foo
360 marked working directory as branch foo
361 (branches are permanent and global, did you want a bookmark?)
361 (branches are permanent and global, did you want a bookmark?)
362 *** runcommand branch
362 *** runcommand branch
363 foo
363 foo
364 marked working directory as branch default
364 marked working directory as branch default
365 (branches are permanent and global, did you want a bookmark?)
365 (branches are permanent and global, did you want a bookmark?)
366
366
367 $ touch .hgignore
367 $ touch .hgignore
368 >>> import os
368 >>> import os
369 >>> from hgclient import check, readchannel, runcommand
369 >>> from hgclient import check, readchannel, runcommand
370 >>> @check
370 >>> @check
371 ... def hgignore(server):
371 ... def hgignore(server):
372 ... readchannel(server)
372 ... readchannel(server)
373 ... runcommand(server, ['commit', '-Am.'])
373 ... runcommand(server, ['commit', '-Am.'])
374 ... f = open('ignored-file', 'ab')
374 ... f = open('ignored-file', 'ab')
375 ... f.write('')
375 ... f.write('')
376 ... f.close()
376 ... f.close()
377 ... f = open('.hgignore', 'ab')
377 ... f = open('.hgignore', 'ab')
378 ... f.write('ignored-file')
378 ... f.write('ignored-file')
379 ... f.close()
379 ... f.close()
380 ... runcommand(server, ['status', '-i', '-u'])
380 ... runcommand(server, ['status', '-i', '-u'])
381 ... print('')
381 ... print('')
382 *** runcommand commit -Am.
382 *** runcommand commit -Am.
383 adding .hgignore
383 adding .hgignore
384 *** runcommand status -i -u
384 *** runcommand status -i -u
385 I ignored-file
385 I ignored-file
386
386
387
387
388 cache of non-public revisions should be invalidated on repository change
388 cache of non-public revisions should be invalidated on repository change
389 (issue4855):
389 (issue4855):
390
390
391 >>> import os
391 >>> import os
392 >>> from hgclient import check, readchannel, runcommand
392 >>> from hgclient import check, readchannel, runcommand
393 >>> @check
393 >>> @check
394 ... def phasesetscacheaftercommit(server):
394 ... def phasesetscacheaftercommit(server):
395 ... readchannel(server)
395 ... readchannel(server)
396 ... # load _phasecache._phaserevs and _phasesets
396 ... # load _phasecache._phaserevs and _phasesets
397 ... runcommand(server, ['log', '-qr', 'draft()'])
397 ... runcommand(server, ['log', '-qr', 'draft()'])
398 ... # create draft commits by another process
398 ... # create draft commits by another process
399 ... for i in xrange(5, 7):
399 ... for i in xrange(5, 7):
400 ... f = open('a', 'ab')
400 ... f = open('a', 'ab')
401 ... f.seek(0, os.SEEK_END)
401 ... f.seek(0, os.SEEK_END)
402 ... f.write('a\n')
402 ... f.write('a\n')
403 ... f.close()
403 ... f.close()
404 ... os.system('hg commit -Aqm%d' % i)
404 ... os.system('hg commit -Aqm%d' % i)
405 ... # new commits should be listed as draft revisions
405 ... # new commits should be listed as draft revisions
406 ... runcommand(server, ['log', '-qr', 'draft()'])
406 ... runcommand(server, ['log', '-qr', 'draft()'])
407 ... print('')
407 ... print('')
408 *** runcommand log -qr draft()
408 *** runcommand log -qr draft()
409 4:7966c8e3734d
409 4:7966c8e3734d
410 *** runcommand log -qr draft()
410 *** runcommand log -qr draft()
411 4:7966c8e3734d
411 4:7966c8e3734d
412 5:41f6602d1c4f
412 5:41f6602d1c4f
413 6:10501e202c35
413 6:10501e202c35
414
414
415
415
416 >>> import os
416 >>> import os
417 >>> from hgclient import check, readchannel, runcommand
417 >>> from hgclient import check, readchannel, runcommand
418 >>> @check
418 >>> @check
419 ... def phasesetscacheafterstrip(server):
419 ... def phasesetscacheafterstrip(server):
420 ... readchannel(server)
420 ... readchannel(server)
421 ... # load _phasecache._phaserevs and _phasesets
421 ... # load _phasecache._phaserevs and _phasesets
422 ... runcommand(server, ['log', '-qr', 'draft()'])
422 ... runcommand(server, ['log', '-qr', 'draft()'])
423 ... # strip cached revisions by another process
423 ... # strip cached revisions by another process
424 ... os.system('hg --config extensions.strip= strip -q 5')
424 ... os.system('hg --config extensions.strip= strip -q 5')
425 ... # shouldn't abort by "unknown revision '6'"
425 ... # shouldn't abort by "unknown revision '6'"
426 ... runcommand(server, ['log', '-qr', 'draft()'])
426 ... runcommand(server, ['log', '-qr', 'draft()'])
427 ... print('')
427 ... print('')
428 *** runcommand log -qr draft()
428 *** runcommand log -qr draft()
429 4:7966c8e3734d
429 4:7966c8e3734d
430 5:41f6602d1c4f
430 5:41f6602d1c4f
431 6:10501e202c35
431 6:10501e202c35
432 *** runcommand log -qr draft()
432 *** runcommand log -qr draft()
433 4:7966c8e3734d
433 4:7966c8e3734d
434
434
435
435
436 cache of phase roots should be invalidated on strip (issue3827):
436 cache of phase roots should be invalidated on strip (issue3827):
437
437
438 >>> import os
438 >>> import os
439 >>> from hgclient import check, readchannel, runcommand, sep
439 >>> from hgclient import check, readchannel, runcommand, sep
440 >>> @check
440 >>> @check
441 ... def phasecacheafterstrip(server):
441 ... def phasecacheafterstrip(server):
442 ... readchannel(server)
442 ... readchannel(server)
443 ...
443 ...
444 ... # create new head, 5:731265503d86
444 ... # create new head, 5:731265503d86
445 ... runcommand(server, ['update', '-C', '0'])
445 ... runcommand(server, ['update', '-C', '0'])
446 ... f = open('a', 'ab')
446 ... f = open('a', 'ab')
447 ... f.write('a\n')
447 ... f.write('a\n')
448 ... f.close()
448 ... f.close()
449 ... runcommand(server, ['commit', '-Am.', 'a'])
449 ... runcommand(server, ['commit', '-Am.', 'a'])
450 ... runcommand(server, ['log', '-Gq'])
450 ... runcommand(server, ['log', '-Gq'])
451 ...
451 ...
452 ... # make it public; draft marker moves to 4:7966c8e3734d
452 ... # make it public; draft marker moves to 4:7966c8e3734d
453 ... runcommand(server, ['phase', '-p', '.'])
453 ... runcommand(server, ['phase', '-p', '.'])
454 ... # load _phasecache.phaseroots
454 ... # load _phasecache.phaseroots
455 ... runcommand(server, ['phase', '.'], outfilter=sep)
455 ... runcommand(server, ['phase', '.'], outfilter=sep)
456 ...
456 ...
457 ... # strip 1::4 outside server
457 ... # strip 1::4 outside server
458 ... os.system('hg -q --config extensions.mq= strip 1')
458 ... os.system('hg -q --config extensions.mq= strip 1')
459 ...
459 ...
460 ... # shouldn't raise "7966c8e3734d: no node!"
460 ... # shouldn't raise "7966c8e3734d: no node!"
461 ... runcommand(server, ['branches'])
461 ... runcommand(server, ['branches'])
462 *** runcommand update -C 0
462 *** runcommand update -C 0
463 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
463 1 files updated, 0 files merged, 2 files removed, 0 files unresolved
464 (leaving bookmark bm3)
464 (leaving bookmark bm3)
465 *** runcommand commit -Am. a
465 *** runcommand commit -Am. a
466 created new head
466 created new head
467 *** runcommand log -Gq
467 *** runcommand log -Gq
468 @ 5:731265503d86
468 @ 5:731265503d86
469 |
469 |
470 | o 4:7966c8e3734d
470 | o 4:7966c8e3734d
471 | |
471 | |
472 | o 3:b9b85890c400
472 | o 3:b9b85890c400
473 | |
473 | |
474 | o 2:aef17e88f5f0
474 | o 2:aef17e88f5f0
475 | |
475 | |
476 | o 1:d3a0a68be6de
476 | o 1:d3a0a68be6de
477 |/
477 |/
478 o 0:eff892de26ec
478 o 0:eff892de26ec
479
479
480 *** runcommand phase -p .
480 *** runcommand phase -p .
481 *** runcommand phase .
481 *** runcommand phase .
482 5: public
482 5: public
483 *** runcommand branches
483 *** runcommand branches
484 default 1:731265503d86
484 default 1:731265503d86
485
485
486 in-memory cache must be reloaded if transaction is aborted. otherwise
486 in-memory cache must be reloaded if transaction is aborted. otherwise
487 changelog and manifest would have invalid node:
487 changelog and manifest would have invalid node:
488
488
489 $ echo a >> a
489 $ echo a >> a
490 >>> from hgclient import check, readchannel, runcommand
490 >>> from hgclient import check, readchannel, runcommand
491 >>> @check
491 >>> @check
492 ... def txabort(server):
492 ... def txabort(server):
493 ... readchannel(server)
493 ... readchannel(server)
494 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
494 ... runcommand(server, ['commit', '--config', 'hooks.pretxncommit=false',
495 ... '-mfoo'])
495 ... '-mfoo'])
496 ... runcommand(server, ['verify'])
496 ... runcommand(server, ['verify'])
497 *** runcommand commit --config hooks.pretxncommit=false -mfoo
497 *** runcommand commit --config hooks.pretxncommit=false -mfoo
498 transaction abort!
498 transaction abort!
499 rollback completed
499 rollback completed
500 abort: pretxncommit hook exited with status 1
500 abort: pretxncommit hook exited with status 1
501 [255]
501 [255]
502 *** runcommand verify
502 *** runcommand verify
503 checking changesets
503 checking changesets
504 checking manifests
504 checking manifests
505 crosschecking files in changesets and manifests
505 crosschecking files in changesets and manifests
506 checking files
506 checking files
507 1 files, 2 changesets, 2 total revisions
507 1 files, 2 changesets, 2 total revisions
508 $ hg revert --no-backup -aq
508 $ hg revert --no-backup -aq
509
509
510 $ cat >> .hg/hgrc << EOF
510 $ cat >> .hg/hgrc << EOF
511 > [experimental]
511 > [experimental]
512 > stabilization=createmarkers
512 > stabilization=createmarkers
513 > EOF
513 > EOF
514
514
515 >>> import os
515 >>> import os
516 >>> from hgclient import check, readchannel, runcommand
516 >>> from hgclient import check, readchannel, runcommand
517 >>> @check
517 >>> @check
518 ... def obsolete(server):
518 ... def obsolete(server):
519 ... readchannel(server)
519 ... readchannel(server)
520 ...
520 ...
521 ... runcommand(server, ['up', 'null'])
521 ... runcommand(server, ['up', 'null'])
522 ... runcommand(server, ['phase', '-df', 'tip'])
522 ... runcommand(server, ['phase', '-df', 'tip'])
523 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
523 ... cmd = 'hg debugobsolete `hg log -r tip --template {node}`'
524 ... if os.name == 'nt':
524 ... if os.name == 'nt':
525 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
525 ... cmd = 'sh -c "%s"' % cmd # run in sh, not cmd.exe
526 ... os.system(cmd)
526 ... os.system(cmd)
527 ... runcommand(server, ['log', '--hidden'])
527 ... runcommand(server, ['log', '--hidden'])
528 ... runcommand(server, ['log'])
528 ... runcommand(server, ['log'])
529 *** runcommand up null
529 *** runcommand up null
530 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
530 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
531 *** runcommand phase -df tip
531 *** runcommand phase -df tip
532 obsoleted 1 changesets
532 obsoleted 1 changesets
533 *** runcommand log --hidden
533 *** runcommand log --hidden
534 changeset: 1:731265503d86
534 changeset: 1:731265503d86
535 tag: tip
535 tag: tip
536 user: test
536 user: test
537 date: Thu Jan 01 00:00:00 1970 +0000
537 date: Thu Jan 01 00:00:00 1970 +0000
538 summary: .
538 summary: .
539
539
540 changeset: 0:eff892de26ec
540 changeset: 0:eff892de26ec
541 bookmark: bm1
541 bookmark: bm1
542 bookmark: bm2
542 bookmark: bm2
543 bookmark: bm3
543 bookmark: bm3
544 user: test
544 user: test
545 date: Thu Jan 01 00:00:00 1970 +0000
545 date: Thu Jan 01 00:00:00 1970 +0000
546 summary: 1
546 summary: 1
547
547
548 *** runcommand log
548 *** runcommand log
549 changeset: 0:eff892de26ec
549 changeset: 0:eff892de26ec
550 bookmark: bm1
550 bookmark: bm1
551 bookmark: bm2
551 bookmark: bm2
552 bookmark: bm3
552 bookmark: bm3
553 tag: tip
553 tag: tip
554 user: test
554 user: test
555 date: Thu Jan 01 00:00:00 1970 +0000
555 date: Thu Jan 01 00:00:00 1970 +0000
556 summary: 1
556 summary: 1
557
557
558
558
559 $ cat <<EOF >> .hg/hgrc
559 $ cat <<EOF >> .hg/hgrc
560 > [extensions]
560 > [extensions]
561 > mq =
561 > mq =
562 > EOF
562 > EOF
563
563
564 >>> import os
564 >>> import os
565 >>> from hgclient import check, readchannel, runcommand
565 >>> from hgclient import check, readchannel, runcommand
566 >>> @check
566 >>> @check
567 ... def mqoutsidechanges(server):
567 ... def mqoutsidechanges(server):
568 ... readchannel(server)
568 ... readchannel(server)
569 ...
569 ...
570 ... # load repo.mq
570 ... # load repo.mq
571 ... runcommand(server, ['qapplied'])
571 ... runcommand(server, ['qapplied'])
572 ... os.system('hg qnew 0.diff')
572 ... os.system('hg qnew 0.diff')
573 ... # repo.mq should be invalidated
573 ... # repo.mq should be invalidated
574 ... runcommand(server, ['qapplied'])
574 ... runcommand(server, ['qapplied'])
575 ...
575 ...
576 ... runcommand(server, ['qpop', '--all'])
576 ... runcommand(server, ['qpop', '--all'])
577 ... os.system('hg qqueue --create foo')
577 ... os.system('hg qqueue --create foo')
578 ... # repo.mq should be recreated to point to new queue
578 ... # repo.mq should be recreated to point to new queue
579 ... runcommand(server, ['qqueue', '--active'])
579 ... runcommand(server, ['qqueue', '--active'])
580 *** runcommand qapplied
580 *** runcommand qapplied
581 *** runcommand qapplied
581 *** runcommand qapplied
582 0.diff
582 0.diff
583 *** runcommand qpop --all
583 *** runcommand qpop --all
584 popping 0.diff
584 popping 0.diff
585 patch queue now empty
585 patch queue now empty
586 *** runcommand qqueue --active
586 *** runcommand qqueue --active
587 foo
587 foo
588
588
589 $ cat <<EOF > dbgui.py
589 $ cat <<EOF > dbgui.py
590 > import os
590 > import os
591 > import sys
591 > import sys
592 > from mercurial import commands, registrar
592 > from mercurial import commands, registrar
593 > cmdtable = {}
593 > cmdtable = {}
594 > command = registrar.command(cmdtable)
594 > command = registrar.command(cmdtable)
595 > @command(b"debuggetpass", norepo=True)
595 > @command(b"debuggetpass", norepo=True)
596 > def debuggetpass(ui):
596 > def debuggetpass(ui):
597 > ui.write("%s\\n" % ui.getpass())
597 > ui.write("%s\\n" % ui.getpass())
598 > @command(b"debugprompt", norepo=True)
598 > @command(b"debugprompt", norepo=True)
599 > def debugprompt(ui):
599 > def debugprompt(ui):
600 > ui.write("%s\\n" % ui.prompt("prompt:"))
600 > ui.write("%s\\n" % ui.prompt("prompt:"))
601 > @command(b"debugreadstdin", norepo=True)
601 > @command(b"debugreadstdin", norepo=True)
602 > def debugreadstdin(ui):
602 > def debugreadstdin(ui):
603 > ui.write("read: %r\n" % sys.stdin.read(1))
603 > ui.write("read: %r\n" % sys.stdin.read(1))
604 > @command(b"debugwritestdout", norepo=True)
604 > @command(b"debugwritestdout", norepo=True)
605 > def debugwritestdout(ui):
605 > def debugwritestdout(ui):
606 > os.write(1, "low-level stdout fd and\n")
606 > os.write(1, "low-level stdout fd and\n")
607 > sys.stdout.write("stdout should be redirected to /dev/null\n")
607 > sys.stdout.write("stdout should be redirected to /dev/null\n")
608 > sys.stdout.flush()
608 > sys.stdout.flush()
609 > EOF
609 > EOF
610 $ cat <<EOF >> .hg/hgrc
610 $ cat <<EOF >> .hg/hgrc
611 > [extensions]
611 > [extensions]
612 > dbgui = dbgui.py
612 > dbgui = dbgui.py
613 > EOF
613 > EOF
614
614
615 >>> from hgclient import check, readchannel, runcommand, stringio
615 >>> from hgclient import check, readchannel, runcommand, stringio
616 >>> @check
616 >>> @check
617 ... def getpass(server):
617 ... def getpass(server):
618 ... readchannel(server)
618 ... readchannel(server)
619 ... runcommand(server, ['debuggetpass', '--config',
619 ... runcommand(server, ['debuggetpass', '--config',
620 ... 'ui.interactive=True'],
620 ... 'ui.interactive=True'],
621 ... input=stringio('1234\n'))
621 ... input=stringio('1234\n'))
622 ... runcommand(server, ['debuggetpass', '--config',
622 ... runcommand(server, ['debuggetpass', '--config',
623 ... 'ui.interactive=True'],
623 ... 'ui.interactive=True'],
624 ... input=stringio('\n'))
624 ... input=stringio('\n'))
625 ... runcommand(server, ['debuggetpass', '--config',
625 ... runcommand(server, ['debuggetpass', '--config',
626 ... 'ui.interactive=True'],
626 ... 'ui.interactive=True'],
627 ... input=stringio(''))
627 ... input=stringio(''))
628 ... runcommand(server, ['debugprompt', '--config',
628 ... runcommand(server, ['debugprompt', '--config',
629 ... 'ui.interactive=True'],
629 ... 'ui.interactive=True'],
630 ... input=stringio('5678\n'))
630 ... input=stringio('5678\n'))
631 ... runcommand(server, ['debugreadstdin'])
631 ... runcommand(server, ['debugreadstdin'])
632 ... runcommand(server, ['debugwritestdout'])
632 ... runcommand(server, ['debugwritestdout'])
633 *** runcommand debuggetpass --config ui.interactive=True
633 *** runcommand debuggetpass --config ui.interactive=True
634 password: 1234
634 password: 1234
635 *** runcommand debuggetpass --config ui.interactive=True
635 *** runcommand debuggetpass --config ui.interactive=True
636 password:
636 password:
637 *** runcommand debuggetpass --config ui.interactive=True
637 *** runcommand debuggetpass --config ui.interactive=True
638 password: abort: response expected
638 password: abort: response expected
639 [255]
639 [255]
640 *** runcommand debugprompt --config ui.interactive=True
640 *** runcommand debugprompt --config ui.interactive=True
641 prompt: 5678
641 prompt: 5678
642 *** runcommand debugreadstdin
642 *** runcommand debugreadstdin
643 read: ''
643 read: ''
644 *** runcommand debugwritestdout
644 *** runcommand debugwritestdout
645
645
646
646
647 run commandserver in commandserver, which is silly but should work:
647 run commandserver in commandserver, which is silly but should work:
648
648
649 >>> from __future__ import print_function
649 >>> from __future__ import print_function
650 >>> from hgclient import check, readchannel, runcommand, stringio
650 >>> from hgclient import check, readchannel, runcommand, stringio
651 >>> @check
651 >>> @check
652 ... def nested(server):
652 ... def nested(server):
653 ... print('%c, %r' % readchannel(server))
653 ... print('%c, %r' % readchannel(server))
654 ... class nestedserver(object):
654 ... class nestedserver(object):
655 ... stdin = stringio('getencoding\n')
655 ... stdin = stringio('getencoding\n')
656 ... stdout = stringio()
656 ... stdout = stringio()
657 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
657 ... runcommand(server, ['serve', '--cmdserver', 'pipe'],
658 ... output=nestedserver.stdout, input=nestedserver.stdin)
658 ... output=nestedserver.stdout, input=nestedserver.stdin)
659 ... nestedserver.stdout.seek(0)
659 ... nestedserver.stdout.seek(0)
660 ... print('%c, %r' % readchannel(nestedserver)) # hello
660 ... print('%c, %r' % readchannel(nestedserver)) # hello
661 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
661 ... print('%c, %r' % readchannel(nestedserver)) # getencoding
662 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
662 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
663 *** runcommand serve --cmdserver pipe
663 *** runcommand serve --cmdserver pipe
664 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
664 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
665 r, '*' (glob)
665 r, '*' (glob)
666
666
667
667
668 start without repository:
668 start without repository:
669
669
670 $ cd ..
670 $ cd ..
671
671
672 >>> from __future__ import print_function
672 >>> from __future__ import print_function
673 >>> from hgclient import check, readchannel, runcommand
673 >>> from hgclient import check, readchannel, runcommand
674 >>> @check
674 >>> @check
675 ... def hellomessage(server):
675 ... def hellomessage(server):
676 ... ch, data = readchannel(server)
676 ... ch, data = readchannel(server)
677 ... print('%c, %r' % (ch, data))
677 ... print('%c, %r' % (ch, data))
678 ... # run an arbitrary command to make sure the next thing the server
678 ... # run an arbitrary command to make sure the next thing the server
679 ... # sends isn't part of the hello message
679 ... # sends isn't part of the hello message
680 ... runcommand(server, ['id'])
680 ... runcommand(server, ['id'])
681 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
681 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
682 *** runcommand id
682 *** runcommand id
683 abort: there is no Mercurial repository here (.hg not found)
683 abort: there is no Mercurial repository here (.hg not found)
684 [255]
684 [255]
685
685
686 >>> from hgclient import check, readchannel, runcommand
686 >>> from hgclient import check, readchannel, runcommand
687 >>> @check
687 >>> @check
688 ... def startwithoutrepo(server):
688 ... def startwithoutrepo(server):
689 ... readchannel(server)
689 ... readchannel(server)
690 ... runcommand(server, ['init', 'repo2'])
690 ... runcommand(server, ['init', 'repo2'])
691 ... runcommand(server, ['id', '-R', 'repo2'])
691 ... runcommand(server, ['id', '-R', 'repo2'])
692 *** runcommand init repo2
692 *** runcommand init repo2
693 *** runcommand id -R repo2
693 *** runcommand id -R repo2
694 000000000000 tip
694 000000000000 tip
695
695
696
696
697 don't fall back to cwd if invalid -R path is specified (issue4805):
697 don't fall back to cwd if invalid -R path is specified (issue4805):
698
698
699 $ cd repo
699 $ cd repo
700 $ hg serve --cmdserver pipe -R ../nonexistent
700 $ hg serve --cmdserver pipe -R ../nonexistent
701 abort: repository ../nonexistent not found!
701 abort: repository ../nonexistent not found!
702 [255]
702 [255]
703 $ cd ..
703 $ cd ..
704
704
705
705
706 unix domain socket:
706 unix domain socket:
707
707
708 $ cd repo
708 $ cd repo
709 $ hg update -q
709 $ hg update -q
710
710
711 #if unix-socket unix-permissions
711 #if unix-socket unix-permissions
712
712
713 >>> from __future__ import print_function
713 >>> from __future__ import print_function
714 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
714 >>> from hgclient import check, readchannel, runcommand, stringio, unixserver
715 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
715 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
716 >>> def hellomessage(conn):
716 >>> def hellomessage(conn):
717 ... ch, data = readchannel(conn)
717 ... ch, data = readchannel(conn)
718 ... print('%c, %r' % (ch, data))
718 ... print('%c, %r' % (ch, data))
719 ... runcommand(conn, ['id'])
719 ... runcommand(conn, ['id'])
720 >>> check(hellomessage, server.connect)
720 >>> check(hellomessage, server.connect)
721 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
721 o, 'capabilities: getencoding runcommand\nencoding: *\npid: *' (glob)
722 *** runcommand id
722 *** runcommand id
723 eff892de26ec tip bm1/bm2/bm3
723 eff892de26ec tip bm1/bm2/bm3
724 >>> def unknowncommand(conn):
724 >>> def unknowncommand(conn):
725 ... readchannel(conn)
725 ... readchannel(conn)
726 ... conn.stdin.write('unknowncommand\n')
726 ... conn.stdin.write('unknowncommand\n')
727 >>> check(unknowncommand, server.connect) # error sent to server.log
727 >>> check(unknowncommand, server.connect) # error sent to server.log
728 >>> def serverinput(conn):
728 >>> def serverinput(conn):
729 ... readchannel(conn)
729 ... readchannel(conn)
730 ... patch = """
730 ... patch = """
731 ... # HG changeset patch
731 ... # HG changeset patch
732 ... # User test
732 ... # User test
733 ... # Date 0 0
733 ... # Date 0 0
734 ... 2
734 ... 2
735 ...
735 ...
736 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
736 ... diff -r eff892de26ec -r 1ed24be7e7a0 a
737 ... --- a/a
737 ... --- a/a
738 ... +++ b/a
738 ... +++ b/a
739 ... @@ -1,1 +1,2 @@
739 ... @@ -1,1 +1,2 @@
740 ... 1
740 ... 1
741 ... +2
741 ... +2
742 ... """
742 ... """
743 ... runcommand(conn, ['import', '-'], input=stringio(patch))
743 ... runcommand(conn, ['import', '-'], input=stringio(patch))
744 ... runcommand(conn, ['log', '-rtip', '-q'])
744 ... runcommand(conn, ['log', '-rtip', '-q'])
745 >>> check(serverinput, server.connect)
745 >>> check(serverinput, server.connect)
746 *** runcommand import -
746 *** runcommand import -
747 applying patch from stdin
747 applying patch from stdin
748 *** runcommand log -rtip -q
748 *** runcommand log -rtip -q
749 2:1ed24be7e7a0
749 2:1ed24be7e7a0
750 >>> server.shutdown()
750 >>> server.shutdown()
751
751
752 $ cat .hg/server.log
752 $ cat .hg/server.log
753 listening at .hg/server.sock
753 listening at .hg/server.sock
754 abort: unknown command unknowncommand
754 abort: unknown command unknowncommand
755 killed!
755 killed!
756 $ rm .hg/server.log
756 $ rm .hg/server.log
757
757
758 if server crashed before hello, traceback will be sent to 'e' channel as
758 if server crashed before hello, traceback will be sent to 'e' channel as
759 last ditch:
759 last ditch:
760
760
761 $ cat <<EOF >> .hg/hgrc
761 $ cat <<EOF >> .hg/hgrc
762 > [cmdserver]
762 > [cmdserver]
763 > log = inexistent/path.log
763 > log = inexistent/path.log
764 > EOF
764 > EOF
765 >>> from __future__ import print_function
765 >>> from __future__ import print_function
766 >>> from hgclient import check, readchannel, unixserver
766 >>> from hgclient import check, readchannel, unixserver
767 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
767 >>> server = unixserver('.hg/server.sock', '.hg/server.log')
768 >>> def earlycrash(conn):
768 >>> def earlycrash(conn):
769 ... while True:
769 ... while True:
770 ... try:
770 ... try:
771 ... ch, data = readchannel(conn)
771 ... ch, data = readchannel(conn)
772 ... if not data.startswith(' '):
772 ... if not data.startswith(' '):
773 ... print('%c, %r' % (ch, data))
773 ... print('%c, %r' % (ch, data))
774 ... except EOFError:
774 ... except EOFError:
775 ... break
775 ... break
776 >>> check(earlycrash, server.connect)
776 >>> check(earlycrash, server.connect)
777 e, 'Traceback (most recent call last):\n'
777 e, 'Traceback (most recent call last):\n'
778 e, "IOError: *" (glob)
778 e, "IOError: *" (glob)
779 >>> server.shutdown()
779 >>> server.shutdown()
780
780
781 $ cat .hg/server.log | grep -v '^ '
781 $ cat .hg/server.log | grep -v '^ '
782 listening at .hg/server.sock
782 listening at .hg/server.sock
783 Traceback (most recent call last):
783 Traceback (most recent call last):
784 IOError: * (glob)
784 IOError: * (glob)
785 killed!
785 killed!
786 #endif
786 #endif
787 #if no-unix-socket
787 #if no-unix-socket
788
788
789 $ hg serve --cmdserver unix -a .hg/server.sock
789 $ hg serve --cmdserver unix -a .hg/server.sock
790 abort: unsupported platform
790 abort: unsupported platform
791 [255]
791 [255]
792
792
793 #endif
793 #endif
794
794
795 $ cd ..
795 $ cd ..
796
796
797 Test that accessing to invalid changelog cache is avoided at
797 Test that accessing to invalid changelog cache is avoided at
798 subsequent operations even if repo object is reused even after failure
798 subsequent operations even if repo object is reused even after failure
799 of transaction (see 0a7610758c42 also)
799 of transaction (see 0a7610758c42 also)
800
800
801 "hg log" after failure of transaction is needed to detect invalid
801 "hg log" after failure of transaction is needed to detect invalid
802 cache in repoview: this can't detect by "hg verify" only.
802 cache in repoview: this can't detect by "hg verify" only.
803
803
804 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
804 Combination of "finalization" and "empty-ness of changelog" (2 x 2 =
805 4) are tested, because '00changelog.i' are differently changed in each
805 4) are tested, because '00changelog.i' are differently changed in each
806 cases.
806 cases.
807
807
808 $ cat > $TESTTMP/failafterfinalize.py <<EOF
808 $ cat > $TESTTMP/failafterfinalize.py <<EOF
809 > # extension to abort transaction after finalization forcibly
809 > # extension to abort transaction after finalization forcibly
810 > from mercurial import commands, error, extensions, lock as lockmod
810 > from mercurial import commands, error, extensions, lock as lockmod
811 > def fail(tr):
811 > def fail(tr):
812 > raise error.Abort('fail after finalization')
812 > raise error.Abort('fail after finalization')
813 > def reposetup(ui, repo):
813 > def reposetup(ui, repo):
814 > class failrepo(repo.__class__):
814 > class failrepo(repo.__class__):
815 > def commitctx(self, ctx, error=False):
815 > def commitctx(self, ctx, error=False):
816 > if self.ui.configbool('failafterfinalize', 'fail'):
816 > if self.ui.configbool('failafterfinalize', 'fail'):
817 > # 'sorted()' by ASCII code on category names causes
817 > # 'sorted()' by ASCII code on category names causes
818 > # invoking 'fail' after finalization of changelog
818 > # invoking 'fail' after finalization of changelog
819 > # using "'cl-%i' % id(self)" as category name
819 > # using "'cl-%i' % id(self)" as category name
820 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
820 > self.currenttransaction().addfinalize('zzzzzzzz', fail)
821 > return super(failrepo, self).commitctx(ctx, error)
821 > return super(failrepo, self).commitctx(ctx, error)
822 > repo.__class__ = failrepo
822 > repo.__class__ = failrepo
823 > EOF
823 > EOF
824
824
825 $ hg init repo3
825 $ hg init repo3
826 $ cd repo3
826 $ cd repo3
827
827
828 $ cat <<EOF >> $HGRCPATH
828 $ cat <<EOF >> $HGRCPATH
829 > [ui]
829 > [ui]
830 > logtemplate = {rev} {desc|firstline} ({files})\n
830 > logtemplate = {rev} {desc|firstline} ({files})\n
831 >
831 >
832 > [extensions]
832 > [extensions]
833 > failafterfinalize = $TESTTMP/failafterfinalize.py
833 > failafterfinalize = $TESTTMP/failafterfinalize.py
834 > EOF
834 > EOF
835
835
836 - test failure with "empty changelog"
836 - test failure with "empty changelog"
837
837
838 $ echo foo > foo
838 $ echo foo > foo
839 $ hg add foo
839 $ hg add foo
840
840
841 (failure before finalization)
841 (failure before finalization)
842
842
843 >>> from hgclient import check, readchannel, runcommand
843 >>> from hgclient import check, readchannel, runcommand
844 >>> @check
844 >>> @check
845 ... def abort(server):
845 ... def abort(server):
846 ... readchannel(server)
846 ... readchannel(server)
847 ... runcommand(server, ['commit',
847 ... runcommand(server, ['commit',
848 ... '--config', 'hooks.pretxncommit=false',
848 ... '--config', 'hooks.pretxncommit=false',
849 ... '-mfoo'])
849 ... '-mfoo'])
850 ... runcommand(server, ['log'])
850 ... runcommand(server, ['log'])
851 ... runcommand(server, ['verify', '-q'])
851 ... runcommand(server, ['verify', '-q'])
852 *** runcommand commit --config hooks.pretxncommit=false -mfoo
852 *** runcommand commit --config hooks.pretxncommit=false -mfoo
853 transaction abort!
853 transaction abort!
854 rollback completed
854 rollback completed
855 abort: pretxncommit hook exited with status 1
855 abort: pretxncommit hook exited with status 1
856 [255]
856 [255]
857 *** runcommand log
857 *** runcommand log
858 *** runcommand verify -q
858 *** runcommand verify -q
859
859
860 (failure after finalization)
860 (failure after finalization)
861
861
862 >>> from hgclient import check, readchannel, runcommand
862 >>> from hgclient import check, readchannel, runcommand
863 >>> @check
863 >>> @check
864 ... def abort(server):
864 ... def abort(server):
865 ... readchannel(server)
865 ... readchannel(server)
866 ... runcommand(server, ['commit',
866 ... runcommand(server, ['commit',
867 ... '--config', 'failafterfinalize.fail=true',
867 ... '--config', 'failafterfinalize.fail=true',
868 ... '-mfoo'])
868 ... '-mfoo'])
869 ... runcommand(server, ['log'])
869 ... runcommand(server, ['log'])
870 ... runcommand(server, ['verify', '-q'])
870 ... runcommand(server, ['verify', '-q'])
871 *** runcommand commit --config failafterfinalize.fail=true -mfoo
871 *** runcommand commit --config failafterfinalize.fail=true -mfoo
872 transaction abort!
872 transaction abort!
873 rollback completed
873 rollback completed
874 abort: fail after finalization
874 abort: fail after finalization
875 [255]
875 [255]
876 *** runcommand log
876 *** runcommand log
877 *** runcommand verify -q
877 *** runcommand verify -q
878
878
879 - test failure with "not-empty changelog"
879 - test failure with "not-empty changelog"
880
880
881 $ echo bar > bar
881 $ echo bar > bar
882 $ hg add bar
882 $ hg add bar
883 $ hg commit -mbar bar
883 $ hg commit -mbar bar
884
884
885 (failure before finalization)
885 (failure before finalization)
886
886
887 >>> from hgclient import check, readchannel, runcommand
887 >>> from hgclient import check, readchannel, runcommand
888 >>> @check
888 >>> @check
889 ... def abort(server):
889 ... def abort(server):
890 ... readchannel(server)
890 ... readchannel(server)
891 ... runcommand(server, ['commit',
891 ... runcommand(server, ['commit',
892 ... '--config', 'hooks.pretxncommit=false',
892 ... '--config', 'hooks.pretxncommit=false',
893 ... '-mfoo', 'foo'])
893 ... '-mfoo', 'foo'])
894 ... runcommand(server, ['log'])
894 ... runcommand(server, ['log'])
895 ... runcommand(server, ['verify', '-q'])
895 ... runcommand(server, ['verify', '-q'])
896 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
896 *** runcommand commit --config hooks.pretxncommit=false -mfoo foo
897 transaction abort!
897 transaction abort!
898 rollback completed
898 rollback completed
899 abort: pretxncommit hook exited with status 1
899 abort: pretxncommit hook exited with status 1
900 [255]
900 [255]
901 *** runcommand log
901 *** runcommand log
902 0 bar (bar)
902 0 bar (bar)
903 *** runcommand verify -q
903 *** runcommand verify -q
904
904
905 (failure after finalization)
905 (failure after finalization)
906
906
907 >>> from hgclient import check, readchannel, runcommand
907 >>> from hgclient import check, readchannel, runcommand
908 >>> @check
908 >>> @check
909 ... def abort(server):
909 ... def abort(server):
910 ... readchannel(server)
910 ... readchannel(server)
911 ... runcommand(server, ['commit',
911 ... runcommand(server, ['commit',
912 ... '--config', 'failafterfinalize.fail=true',
912 ... '--config', 'failafterfinalize.fail=true',
913 ... '-mfoo', 'foo'])
913 ... '-mfoo', 'foo'])
914 ... runcommand(server, ['log'])
914 ... runcommand(server, ['log'])
915 ... runcommand(server, ['verify', '-q'])
915 ... runcommand(server, ['verify', '-q'])
916 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
916 *** runcommand commit --config failafterfinalize.fail=true -mfoo foo
917 transaction abort!
917 transaction abort!
918 rollback completed
918 rollback completed
919 abort: fail after finalization
919 abort: fail after finalization
920 [255]
920 [255]
921 *** runcommand log
921 *** runcommand log
922 0 bar (bar)
922 0 bar (bar)
923 *** runcommand verify -q
923 *** runcommand verify -q
924
924
925 $ cd ..
925 $ cd ..
926
926
927 Test symlink traversal over cached audited paths:
927 Test symlink traversal over cached audited paths:
928 -------------------------------------------------
928 -------------------------------------------------
929
929
930 #if symlink
930 #if symlink
931
931
932 set up symlink hell
932 set up symlink hell
933
933
934 $ mkdir merge-symlink-out
934 $ mkdir merge-symlink-out
935 $ hg init merge-symlink
935 $ hg init merge-symlink
936 $ cd merge-symlink
936 $ cd merge-symlink
937 $ touch base
937 $ touch base
938 $ hg commit -qAm base
938 $ hg commit -qAm base
939 $ ln -s ../merge-symlink-out a
939 $ ln -s ../merge-symlink-out a
940 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
940 $ hg commit -qAm 'symlink a -> ../merge-symlink-out'
941 $ hg up -q 0
941 $ hg up -q 0
942 $ mkdir a
942 $ mkdir a
943 $ touch a/poisoned
943 $ touch a/poisoned
944 $ hg commit -qAm 'file a/poisoned'
944 $ hg commit -qAm 'file a/poisoned'
945 $ hg log -G -T '{rev}: {desc}\n'
945 $ hg log -G -T '{rev}: {desc}\n'
946 @ 2: file a/poisoned
946 @ 2: file a/poisoned
947 |
947 |
948 | o 1: symlink a -> ../merge-symlink-out
948 | o 1: symlink a -> ../merge-symlink-out
949 |/
949 |/
950 o 0: base
950 o 0: base
951
951
952
952
953 try trivial merge after update: cache of audited paths should be discarded,
953 try trivial merge after update: cache of audited paths should be discarded,
954 and the merge should fail (issue5628)
954 and the merge should fail (issue5628)
955
955
956 $ hg up -q null
956 $ hg up -q null
957 >>> from hgclient import check, readchannel, runcommand
957 >>> from hgclient import check, readchannel, runcommand
958 >>> @check
958 >>> @check
959 ... def merge(server):
959 ... def merge(server):
960 ... readchannel(server)
960 ... readchannel(server)
961 ... # audit a/poisoned as a good path
961 ... # audit a/poisoned as a good path
962 ... runcommand(server, ['up', '-qC', '2'])
962 ... runcommand(server, ['up', '-qC', '2'])
963 ... runcommand(server, ['up', '-qC', '1'])
963 ... runcommand(server, ['up', '-qC', '1'])
964 ... # here a is a symlink, so a/poisoned is bad
964 ... # here a is a symlink, so a/poisoned is bad
965 ... runcommand(server, ['merge', '2'])
965 ... runcommand(server, ['merge', '2'])
966 *** runcommand up -qC 2
966 *** runcommand up -qC 2
967 *** runcommand up -qC 1
967 *** runcommand up -qC 1
968 *** runcommand merge 2
968 *** runcommand merge 2
969 abort: path 'a/poisoned' traverses symbolic link 'a'
969 a: path conflict - a file or link has the same name as a directory
970 [255]
970 the local file has been renamed to a~aa04623eb0c3
971 resolve manually then use 'hg resolve --mark a'
972 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
973 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
974 [1]
971 $ ls ../merge-symlink-out
975 $ ls ../merge-symlink-out
972
976
973 cache of repo.auditor should be discarded, so matcher would never traverse
977 cache of repo.auditor should be discarded, so matcher would never traverse
974 symlinks:
978 symlinks:
975
979
976 $ hg up -qC 0
980 $ hg up -qC 0
977 $ touch ../merge-symlink-out/poisoned
981 $ touch ../merge-symlink-out/poisoned
978 >>> from hgclient import check, readchannel, runcommand
982 >>> from hgclient import check, readchannel, runcommand
979 >>> @check
983 >>> @check
980 ... def files(server):
984 ... def files(server):
981 ... readchannel(server)
985 ... readchannel(server)
982 ... runcommand(server, ['up', '-qC', '2'])
986 ... runcommand(server, ['up', '-qC', '2'])
983 ... # audit a/poisoned as a good path
987 ... # audit a/poisoned as a good path
984 ... runcommand(server, ['files', 'a/poisoned'])
988 ... runcommand(server, ['files', 'a/poisoned'])
985 ... runcommand(server, ['up', '-qC', '0'])
989 ... runcommand(server, ['up', '-qC', '0'])
986 ... runcommand(server, ['up', '-qC', '1'])
990 ... runcommand(server, ['up', '-qC', '1'])
987 ... # here 'a' is a symlink, so a/poisoned should be warned
991 ... # here 'a' is a symlink, so a/poisoned should be warned
988 ... runcommand(server, ['files', 'a/poisoned'])
992 ... runcommand(server, ['files', 'a/poisoned'])
989 *** runcommand up -qC 2
993 *** runcommand up -qC 2
990 *** runcommand files a/poisoned
994 *** runcommand files a/poisoned
991 a/poisoned
995 a/poisoned
992 *** runcommand up -qC 0
996 *** runcommand up -qC 0
993 *** runcommand up -qC 1
997 *** runcommand up -qC 1
994 *** runcommand files a/poisoned
998 *** runcommand files a/poisoned
995 abort: path 'a/poisoned' traverses symbolic link 'a'
999 abort: path 'a/poisoned' traverses symbolic link 'a'
996 [255]
1000 [255]
997
1001
998 $ cd ..
1002 $ cd ..
999
1003
1000 #endif
1004 #endif
@@ -1,78 +1,83 b''
1 $ hg init repo
1 $ hg init repo
2 $ cd repo
2 $ cd repo
3 $ echo base > base
3 $ echo base > base
4 $ hg add base
4 $ hg add base
5 $ hg commit -m "base"
5 $ hg commit -m "base"
6 $ hg bookmark -i base
6 $ hg bookmark -i base
7 $ echo 1 > a
7 $ echo 1 > a
8 $ hg add a
8 $ hg add a
9 $ hg commit -m "file"
9 $ hg commit -m "file"
10 $ hg bookmark -i file
10 $ hg bookmark -i file
11 $ echo 2 > a
11 $ echo 2 > a
12 $ hg commit -m "file2"
12 $ hg commit -m "file2"
13 $ hg bookmark -i file2
13 $ hg bookmark -i file2
14 $ hg up -q 0
14 $ hg up -q 0
15 $ mkdir a
15 $ mkdir a
16 $ echo 2 > a/b
16 $ echo 2 > a/b
17 $ hg add a/b
17 $ hg add a/b
18 $ hg commit -m "dir"
18 $ hg commit -m "dir"
19 created new head
19 created new head
20 $ hg bookmark -i dir
20 $ hg bookmark -i dir
21
21
22 Basic merge - local file conflicts with remote directory
22 Basic merge - local file conflicts with remote directory
23
23
24 $ hg up -q file
24 $ hg up -q file
25 $ hg bookmark -i
25 $ hg bookmark -i
26 $ hg merge --verbose dir
26 $ hg merge --verbose dir
27 resolving manifests
27 resolving manifests
28 a: path conflict - a file or link has the same name as a directory
29 the local file has been renamed to a~853701544ac3
30 resolve manually then use 'hg resolve --mark a'
31 moving a to a~853701544ac3
28 getting a/b
32 getting a/b
29 abort: *: '$TESTTMP/repo/a/b' (glob)
33 1 files updated, 0 files merged, 0 files removed, 1 files unresolved
30 [255]
34 use 'hg resolve' to retry unresolved file merges or 'hg update -C .' to abandon
35 [1]
31 $ hg update --clean .
36 $ hg update --clean .
32 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
37 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
33
38
34 Basic update - local directory conflicts with remote file
39 Basic update - local directory conflicts with remote file
35
40
36 $ hg up -q 0
41 $ hg up -q 0
37 $ mkdir a
42 $ mkdir a
38 $ echo 3 > a/b
43 $ echo 3 > a/b
39 $ hg up file
44 $ hg up file
40 a: untracked directory conflicts with file
45 a: untracked directory conflicts with file
41 abort: untracked files in working directory differ from files in requested revision
46 abort: untracked files in working directory differ from files in requested revision
42 [255]
47 [255]
43 $ hg up --clean file
48 $ hg up --clean file
44 abort: *: '$TESTTMP/repo/a' (glob)
49 abort: *: '$TESTTMP/repo/a' (glob)
45 [255]
50 [255]
46
51
47 Repo is in a very bad state now - recover manually
52 Repo is in a very bad state now - recover manually
48
53
49 $ rm -r a
54 $ rm -r a
50 $ hg up -q --clean 0
55 $ hg up -q --clean 0
51
56
52 Basic update - untracked file conflicts with remote directory
57 Basic update - untracked file conflicts with remote directory
53
58
54 $ hg up -q 0
59 $ hg up -q 0
55 $ echo untracked > a
60 $ echo untracked > a
56 $ hg up --config merge.checkunknown=warn dir
61 $ hg up --config merge.checkunknown=warn dir
57 a: replacing untracked file
62 a: replacing untracked file
58 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
63 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
59 (activating bookmark dir)
64 (activating bookmark dir)
60
65
61 Basic clean update - local directory conflicts with changed remote file
66 Basic clean update - local directory conflicts with changed remote file
62
67
63 $ hg up -q file
68 $ hg up -q file
64 $ rm a
69 $ rm a
65 $ mkdir a
70 $ mkdir a
66 $ echo 4 > a/b
71 $ echo 4 > a/b
67 $ hg up file2
72 $ hg up file2
68 abort: *: '$TESTTMP/repo/a' (glob)
73 abort: *: '$TESTTMP/repo/a' (glob)
69 [255]
74 [255]
70 $ hg up --clean file2
75 $ hg up --clean file2
71 abort: *: '$TESTTMP/repo/a' (glob)
76 abort: *: '$TESTTMP/repo/a' (glob)
72 [255]
77 [255]
73
78
74 Repo is in a very bad state now - recover manually
79 Repo is in a very bad state now - recover manually
75
80
76 $ rm -r a
81 $ rm -r a
77 $ hg up -q --clean 0
82 $ hg up -q --clean 0
78
83
General Comments 0
You need to be logged in to leave comments. Login now