##// END OF EJS Templates
update: fix bug when update tries to modify folder symlink...
Kostia Balytskyi -
r29629:b33c0c38 stable
parent child Browse files
Show More
@@ -1,1651 +1,1651 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 os
12 import os
13 import shutil
13 import shutil
14 import struct
14 import struct
15
15
16 from .i18n import _
16 from .i18n import _
17 from .node import (
17 from .node import (
18 bin,
18 bin,
19 hex,
19 hex,
20 nullhex,
20 nullhex,
21 nullid,
21 nullid,
22 nullrev,
22 nullrev,
23 )
23 )
24 from . import (
24 from . import (
25 copies,
25 copies,
26 destutil,
26 destutil,
27 error,
27 error,
28 filemerge,
28 filemerge,
29 obsolete,
29 obsolete,
30 scmutil,
30 scmutil,
31 subrepo,
31 subrepo,
32 util,
32 util,
33 worker,
33 worker,
34 )
34 )
35
35
36 _pack = struct.pack
36 _pack = struct.pack
37 _unpack = struct.unpack
37 _unpack = struct.unpack
38
38
39 def _droponode(data):
39 def _droponode(data):
40 # used for compatibility for v1
40 # used for compatibility for v1
41 bits = data.split('\0')
41 bits = data.split('\0')
42 bits = bits[:-2] + bits[-1:]
42 bits = bits[:-2] + bits[-1:]
43 return '\0'.join(bits)
43 return '\0'.join(bits)
44
44
45 class mergestate(object):
45 class mergestate(object):
46 '''track 3-way merge state of individual files
46 '''track 3-way merge state of individual files
47
47
48 The merge state is stored on disk when needed. Two files are used: one with
48 The merge state is stored on disk when needed. Two files are used: one with
49 an old format (version 1), and one with a new format (version 2). Version 2
49 an old format (version 1), and one with a new format (version 2). Version 2
50 stores a superset of the data in version 1, including new kinds of records
50 stores a superset of the data in version 1, including new kinds of records
51 in the future. For more about the new format, see the documentation for
51 in the future. For more about the new format, see the documentation for
52 `_readrecordsv2`.
52 `_readrecordsv2`.
53
53
54 Each record can contain arbitrary content, and has an associated type. This
54 Each record can contain arbitrary content, and has an associated type. This
55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
55 `type` should be a letter. If `type` is uppercase, the record is mandatory:
56 versions of Mercurial that don't support it should abort. If `type` is
56 versions of Mercurial that don't support it should abort. If `type` is
57 lowercase, the record can be safely ignored.
57 lowercase, the record can be safely ignored.
58
58
59 Currently known records:
59 Currently known records:
60
60
61 L: the node of the "local" part of the merge (hexified version)
61 L: the node of the "local" part of the merge (hexified version)
62 O: the node of the "other" part of the merge (hexified version)
62 O: the node of the "other" part of the merge (hexified version)
63 F: a file to be merged entry
63 F: a file to be merged entry
64 C: a change/delete or delete/change conflict
64 C: a change/delete or delete/change conflict
65 D: a file that the external merge driver will merge internally
65 D: a file that the external merge driver will merge internally
66 (experimental)
66 (experimental)
67 m: the external merge driver defined for this merge plus its run state
67 m: the external merge driver defined for this merge plus its run state
68 (experimental)
68 (experimental)
69 f: a (filename, dictonary) tuple of optional values for a given file
69 f: a (filename, dictonary) tuple of optional values for a given file
70 X: unsupported mandatory record type (used in tests)
70 X: unsupported mandatory record type (used in tests)
71 x: unsupported advisory record type (used in tests)
71 x: unsupported advisory record type (used in tests)
72 l: the labels for the parts of the merge.
72 l: the labels for the parts of the merge.
73
73
74 Merge driver run states (experimental):
74 Merge driver run states (experimental):
75 u: driver-resolved files unmarked -- needs to be run next time we're about
75 u: driver-resolved files unmarked -- needs to be run next time we're about
76 to resolve or commit
76 to resolve or commit
77 m: driver-resolved files marked -- only needs to be run before commit
77 m: driver-resolved files marked -- only needs to be run before commit
78 s: success/skipped -- does not need to be run any more
78 s: success/skipped -- does not need to be run any more
79
79
80 '''
80 '''
81 statepathv1 = 'merge/state'
81 statepathv1 = 'merge/state'
82 statepathv2 = 'merge/state2'
82 statepathv2 = 'merge/state2'
83
83
84 @staticmethod
84 @staticmethod
85 def clean(repo, node=None, other=None, labels=None):
85 def clean(repo, node=None, other=None, labels=None):
86 """Initialize a brand new merge state, removing any existing state on
86 """Initialize a brand new merge state, removing any existing state on
87 disk."""
87 disk."""
88 ms = mergestate(repo)
88 ms = mergestate(repo)
89 ms.reset(node, other, labels)
89 ms.reset(node, other, labels)
90 return ms
90 return ms
91
91
92 @staticmethod
92 @staticmethod
93 def read(repo):
93 def read(repo):
94 """Initialize the merge state, reading it from disk."""
94 """Initialize the merge state, reading it from disk."""
95 ms = mergestate(repo)
95 ms = mergestate(repo)
96 ms._read()
96 ms._read()
97 return ms
97 return ms
98
98
99 def __init__(self, repo):
99 def __init__(self, repo):
100 """Initialize the merge state.
100 """Initialize the merge state.
101
101
102 Do not use this directly! Instead call read() or clean()."""
102 Do not use this directly! Instead call read() or clean()."""
103 self._repo = repo
103 self._repo = repo
104 self._dirty = False
104 self._dirty = False
105 self._labels = None
105 self._labels = None
106
106
107 def reset(self, node=None, other=None, labels=None):
107 def reset(self, node=None, other=None, labels=None):
108 self._state = {}
108 self._state = {}
109 self._stateextras = {}
109 self._stateextras = {}
110 self._local = None
110 self._local = None
111 self._other = None
111 self._other = None
112 self._labels = labels
112 self._labels = labels
113 for var in ('localctx', 'otherctx'):
113 for var in ('localctx', 'otherctx'):
114 if var in vars(self):
114 if var in vars(self):
115 delattr(self, var)
115 delattr(self, var)
116 if node:
116 if node:
117 self._local = node
117 self._local = node
118 self._other = other
118 self._other = other
119 self._readmergedriver = None
119 self._readmergedriver = None
120 if self.mergedriver:
120 if self.mergedriver:
121 self._mdstate = 's'
121 self._mdstate = 's'
122 else:
122 else:
123 self._mdstate = 'u'
123 self._mdstate = 'u'
124 shutil.rmtree(self._repo.join('merge'), True)
124 shutil.rmtree(self._repo.join('merge'), True)
125 self._results = {}
125 self._results = {}
126 self._dirty = False
126 self._dirty = False
127
127
128 def _read(self):
128 def _read(self):
129 """Analyse each record content to restore a serialized state from disk
129 """Analyse each record content to restore a serialized state from disk
130
130
131 This function process "record" entry produced by the de-serialization
131 This function process "record" entry produced by the de-serialization
132 of on disk file.
132 of on disk file.
133 """
133 """
134 self._state = {}
134 self._state = {}
135 self._stateextras = {}
135 self._stateextras = {}
136 self._local = None
136 self._local = None
137 self._other = None
137 self._other = None
138 for var in ('localctx', 'otherctx'):
138 for var in ('localctx', 'otherctx'):
139 if var in vars(self):
139 if var in vars(self):
140 delattr(self, var)
140 delattr(self, var)
141 self._readmergedriver = None
141 self._readmergedriver = None
142 self._mdstate = 's'
142 self._mdstate = 's'
143 unsupported = set()
143 unsupported = set()
144 records = self._readrecords()
144 records = self._readrecords()
145 for rtype, record in records:
145 for rtype, record in records:
146 if rtype == 'L':
146 if rtype == 'L':
147 self._local = bin(record)
147 self._local = bin(record)
148 elif rtype == 'O':
148 elif rtype == 'O':
149 self._other = bin(record)
149 self._other = bin(record)
150 elif rtype == 'm':
150 elif rtype == 'm':
151 bits = record.split('\0', 1)
151 bits = record.split('\0', 1)
152 mdstate = bits[1]
152 mdstate = bits[1]
153 if len(mdstate) != 1 or mdstate not in 'ums':
153 if len(mdstate) != 1 or mdstate not in 'ums':
154 # the merge driver should be idempotent, so just rerun it
154 # the merge driver should be idempotent, so just rerun it
155 mdstate = 'u'
155 mdstate = 'u'
156
156
157 self._readmergedriver = bits[0]
157 self._readmergedriver = bits[0]
158 self._mdstate = mdstate
158 self._mdstate = mdstate
159 elif rtype in 'FDC':
159 elif rtype in 'FDC':
160 bits = record.split('\0')
160 bits = record.split('\0')
161 self._state[bits[0]] = bits[1:]
161 self._state[bits[0]] = bits[1:]
162 elif rtype == 'f':
162 elif rtype == 'f':
163 filename, rawextras = record.split('\0', 1)
163 filename, rawextras = record.split('\0', 1)
164 extraparts = rawextras.split('\0')
164 extraparts = rawextras.split('\0')
165 extras = {}
165 extras = {}
166 i = 0
166 i = 0
167 while i < len(extraparts):
167 while i < len(extraparts):
168 extras[extraparts[i]] = extraparts[i + 1]
168 extras[extraparts[i]] = extraparts[i + 1]
169 i += 2
169 i += 2
170
170
171 self._stateextras[filename] = extras
171 self._stateextras[filename] = extras
172 elif rtype == 'l':
172 elif rtype == 'l':
173 labels = record.split('\0', 2)
173 labels = record.split('\0', 2)
174 self._labels = [l for l in labels if len(l) > 0]
174 self._labels = [l for l in labels if len(l) > 0]
175 elif not rtype.islower():
175 elif not rtype.islower():
176 unsupported.add(rtype)
176 unsupported.add(rtype)
177 self._results = {}
177 self._results = {}
178 self._dirty = False
178 self._dirty = False
179
179
180 if unsupported:
180 if unsupported:
181 raise error.UnsupportedMergeRecords(unsupported)
181 raise error.UnsupportedMergeRecords(unsupported)
182
182
183 def _readrecords(self):
183 def _readrecords(self):
184 """Read merge state from disk and return a list of record (TYPE, data)
184 """Read merge state from disk and return a list of record (TYPE, data)
185
185
186 We read data from both v1 and v2 files and decide which one to use.
186 We read data from both v1 and v2 files and decide which one to use.
187
187
188 V1 has been used by version prior to 2.9.1 and contains less data than
188 V1 has been used by version prior to 2.9.1 and contains less data than
189 v2. We read both versions and check if no data in v2 contradicts
189 v2. We read both versions and check if no data in v2 contradicts
190 v1. If there is not contradiction we can safely assume that both v1
190 v1. If there is not contradiction we can safely assume that both v1
191 and v2 were written at the same time and use the extract data in v2. If
191 and v2 were written at the same time and use the extract data in v2. If
192 there is contradiction we ignore v2 content as we assume an old version
192 there is contradiction we ignore v2 content as we assume an old version
193 of Mercurial has overwritten the mergestate file and left an old v2
193 of Mercurial has overwritten the mergestate file and left an old v2
194 file around.
194 file around.
195
195
196 returns list of record [(TYPE, data), ...]"""
196 returns list of record [(TYPE, data), ...]"""
197 v1records = self._readrecordsv1()
197 v1records = self._readrecordsv1()
198 v2records = self._readrecordsv2()
198 v2records = self._readrecordsv2()
199 if self._v1v2match(v1records, v2records):
199 if self._v1v2match(v1records, v2records):
200 return v2records
200 return v2records
201 else:
201 else:
202 # v1 file is newer than v2 file, use it
202 # v1 file is newer than v2 file, use it
203 # we have to infer the "other" changeset of the merge
203 # we have to infer the "other" changeset of the merge
204 # we cannot do better than that with v1 of the format
204 # we cannot do better than that with v1 of the format
205 mctx = self._repo[None].parents()[-1]
205 mctx = self._repo[None].parents()[-1]
206 v1records.append(('O', mctx.hex()))
206 v1records.append(('O', mctx.hex()))
207 # add place holder "other" file node information
207 # add place holder "other" file node information
208 # nobody is using it yet so we do no need to fetch the data
208 # nobody is using it yet so we do no need to fetch the data
209 # if mctx was wrong `mctx[bits[-2]]` may fails.
209 # if mctx was wrong `mctx[bits[-2]]` may fails.
210 for idx, r in enumerate(v1records):
210 for idx, r in enumerate(v1records):
211 if r[0] == 'F':
211 if r[0] == 'F':
212 bits = r[1].split('\0')
212 bits = r[1].split('\0')
213 bits.insert(-2, '')
213 bits.insert(-2, '')
214 v1records[idx] = (r[0], '\0'.join(bits))
214 v1records[idx] = (r[0], '\0'.join(bits))
215 return v1records
215 return v1records
216
216
217 def _v1v2match(self, v1records, v2records):
217 def _v1v2match(self, v1records, v2records):
218 oldv2 = set() # old format version of v2 record
218 oldv2 = set() # old format version of v2 record
219 for rec in v2records:
219 for rec in v2records:
220 if rec[0] == 'L':
220 if rec[0] == 'L':
221 oldv2.add(rec)
221 oldv2.add(rec)
222 elif rec[0] == 'F':
222 elif rec[0] == 'F':
223 # drop the onode data (not contained in v1)
223 # drop the onode data (not contained in v1)
224 oldv2.add(('F', _droponode(rec[1])))
224 oldv2.add(('F', _droponode(rec[1])))
225 for rec in v1records:
225 for rec in v1records:
226 if rec not in oldv2:
226 if rec not in oldv2:
227 return False
227 return False
228 else:
228 else:
229 return True
229 return True
230
230
231 def _readrecordsv1(self):
231 def _readrecordsv1(self):
232 """read on disk merge state for version 1 file
232 """read on disk merge state for version 1 file
233
233
234 returns list of record [(TYPE, data), ...]
234 returns list of record [(TYPE, data), ...]
235
235
236 Note: the "F" data from this file are one entry short
236 Note: the "F" data from this file are one entry short
237 (no "other file node" entry)
237 (no "other file node" entry)
238 """
238 """
239 records = []
239 records = []
240 try:
240 try:
241 f = self._repo.vfs(self.statepathv1)
241 f = self._repo.vfs(self.statepathv1)
242 for i, l in enumerate(f):
242 for i, l in enumerate(f):
243 if i == 0:
243 if i == 0:
244 records.append(('L', l[:-1]))
244 records.append(('L', l[:-1]))
245 else:
245 else:
246 records.append(('F', l[:-1]))
246 records.append(('F', l[:-1]))
247 f.close()
247 f.close()
248 except IOError as err:
248 except IOError as err:
249 if err.errno != errno.ENOENT:
249 if err.errno != errno.ENOENT:
250 raise
250 raise
251 return records
251 return records
252
252
253 def _readrecordsv2(self):
253 def _readrecordsv2(self):
254 """read on disk merge state for version 2 file
254 """read on disk merge state for version 2 file
255
255
256 This format is a list of arbitrary records of the form:
256 This format is a list of arbitrary records of the form:
257
257
258 [type][length][content]
258 [type][length][content]
259
259
260 `type` is a single character, `length` is a 4 byte integer, and
260 `type` is a single character, `length` is a 4 byte integer, and
261 `content` is an arbitrary byte sequence of length `length`.
261 `content` is an arbitrary byte sequence of length `length`.
262
262
263 Mercurial versions prior to 3.7 have a bug where if there are
263 Mercurial versions prior to 3.7 have a bug where if there are
264 unsupported mandatory merge records, attempting to clear out the merge
264 unsupported mandatory merge records, attempting to clear out the merge
265 state with hg update --clean or similar aborts. The 't' record type
265 state with hg update --clean or similar aborts. The 't' record type
266 works around that by writing out what those versions treat as an
266 works around that by writing out what those versions treat as an
267 advisory record, but later versions interpret as special: the first
267 advisory record, but later versions interpret as special: the first
268 character is the 'real' record type and everything onwards is the data.
268 character is the 'real' record type and everything onwards is the data.
269
269
270 Returns list of records [(TYPE, data), ...]."""
270 Returns list of records [(TYPE, data), ...]."""
271 records = []
271 records = []
272 try:
272 try:
273 f = self._repo.vfs(self.statepathv2)
273 f = self._repo.vfs(self.statepathv2)
274 data = f.read()
274 data = f.read()
275 off = 0
275 off = 0
276 end = len(data)
276 end = len(data)
277 while off < end:
277 while off < end:
278 rtype = data[off]
278 rtype = data[off]
279 off += 1
279 off += 1
280 length = _unpack('>I', data[off:(off + 4)])[0]
280 length = _unpack('>I', data[off:(off + 4)])[0]
281 off += 4
281 off += 4
282 record = data[off:(off + length)]
282 record = data[off:(off + length)]
283 off += length
283 off += length
284 if rtype == 't':
284 if rtype == 't':
285 rtype, record = record[0], record[1:]
285 rtype, record = record[0], record[1:]
286 records.append((rtype, record))
286 records.append((rtype, record))
287 f.close()
287 f.close()
288 except IOError as err:
288 except IOError as err:
289 if err.errno != errno.ENOENT:
289 if err.errno != errno.ENOENT:
290 raise
290 raise
291 return records
291 return records
292
292
293 @util.propertycache
293 @util.propertycache
294 def mergedriver(self):
294 def mergedriver(self):
295 # protect against the following:
295 # protect against the following:
296 # - A configures a malicious merge driver in their hgrc, then
296 # - A configures a malicious merge driver in their hgrc, then
297 # pauses the merge
297 # pauses the merge
298 # - A edits their hgrc to remove references to the merge driver
298 # - A edits their hgrc to remove references to the merge driver
299 # - A gives a copy of their entire repo, including .hg, to B
299 # - A gives a copy of their entire repo, including .hg, to B
300 # - B inspects .hgrc and finds it to be clean
300 # - B inspects .hgrc and finds it to be clean
301 # - B then continues the merge and the malicious merge driver
301 # - B then continues the merge and the malicious merge driver
302 # gets invoked
302 # gets invoked
303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
303 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
304 if (self._readmergedriver is not None
304 if (self._readmergedriver is not None
305 and self._readmergedriver != configmergedriver):
305 and self._readmergedriver != configmergedriver):
306 raise error.ConfigError(
306 raise error.ConfigError(
307 _("merge driver changed since merge started"),
307 _("merge driver changed since merge started"),
308 hint=_("revert merge driver change or abort merge"))
308 hint=_("revert merge driver change or abort merge"))
309
309
310 return configmergedriver
310 return configmergedriver
311
311
312 @util.propertycache
312 @util.propertycache
313 def localctx(self):
313 def localctx(self):
314 if self._local is None:
314 if self._local is None:
315 raise RuntimeError("localctx accessed but self._local isn't set")
315 raise RuntimeError("localctx accessed but self._local isn't set")
316 return self._repo[self._local]
316 return self._repo[self._local]
317
317
318 @util.propertycache
318 @util.propertycache
319 def otherctx(self):
319 def otherctx(self):
320 if self._other is None:
320 if self._other is None:
321 raise RuntimeError("otherctx accessed but self._other isn't set")
321 raise RuntimeError("otherctx accessed but self._other isn't set")
322 return self._repo[self._other]
322 return self._repo[self._other]
323
323
324 def active(self):
324 def active(self):
325 """Whether mergestate is active.
325 """Whether mergestate is active.
326
326
327 Returns True if there appears to be mergestate. This is a rough proxy
327 Returns True if there appears to be mergestate. This is a rough proxy
328 for "is a merge in progress."
328 for "is a merge in progress."
329 """
329 """
330 # Check local variables before looking at filesystem for performance
330 # Check local variables before looking at filesystem for performance
331 # reasons.
331 # reasons.
332 return bool(self._local) or bool(self._state) or \
332 return bool(self._local) or bool(self._state) or \
333 self._repo.vfs.exists(self.statepathv1) or \
333 self._repo.vfs.exists(self.statepathv1) or \
334 self._repo.vfs.exists(self.statepathv2)
334 self._repo.vfs.exists(self.statepathv2)
335
335
336 def commit(self):
336 def commit(self):
337 """Write current state on disk (if necessary)"""
337 """Write current state on disk (if necessary)"""
338 if self._dirty:
338 if self._dirty:
339 records = self._makerecords()
339 records = self._makerecords()
340 self._writerecords(records)
340 self._writerecords(records)
341 self._dirty = False
341 self._dirty = False
342
342
343 def _makerecords(self):
343 def _makerecords(self):
344 records = []
344 records = []
345 records.append(('L', hex(self._local)))
345 records.append(('L', hex(self._local)))
346 records.append(('O', hex(self._other)))
346 records.append(('O', hex(self._other)))
347 if self.mergedriver:
347 if self.mergedriver:
348 records.append(('m', '\0'.join([
348 records.append(('m', '\0'.join([
349 self.mergedriver, self._mdstate])))
349 self.mergedriver, self._mdstate])))
350 for d, v in self._state.iteritems():
350 for d, v in self._state.iteritems():
351 if v[0] == 'd':
351 if v[0] == 'd':
352 records.append(('D', '\0'.join([d] + v)))
352 records.append(('D', '\0'.join([d] + v)))
353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
353 # v[1] == local ('cd'), v[6] == other ('dc') -- not supported by
354 # older versions of Mercurial
354 # older versions of Mercurial
355 elif v[1] == nullhex or v[6] == nullhex:
355 elif v[1] == nullhex or v[6] == nullhex:
356 records.append(('C', '\0'.join([d] + v)))
356 records.append(('C', '\0'.join([d] + v)))
357 else:
357 else:
358 records.append(('F', '\0'.join([d] + v)))
358 records.append(('F', '\0'.join([d] + v)))
359 for filename, extras in sorted(self._stateextras.iteritems()):
359 for filename, extras in sorted(self._stateextras.iteritems()):
360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
360 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
361 extras.iteritems())
361 extras.iteritems())
362 records.append(('f', '%s\0%s' % (filename, rawextras)))
362 records.append(('f', '%s\0%s' % (filename, rawextras)))
363 if self._labels is not None:
363 if self._labels is not None:
364 labels = '\0'.join(self._labels)
364 labels = '\0'.join(self._labels)
365 records.append(('l', labels))
365 records.append(('l', labels))
366 return records
366 return records
367
367
368 def _writerecords(self, records):
368 def _writerecords(self, records):
369 """Write current state on disk (both v1 and v2)"""
369 """Write current state on disk (both v1 and v2)"""
370 self._writerecordsv1(records)
370 self._writerecordsv1(records)
371 self._writerecordsv2(records)
371 self._writerecordsv2(records)
372
372
373 def _writerecordsv1(self, records):
373 def _writerecordsv1(self, records):
374 """Write current state on disk in a version 1 file"""
374 """Write current state on disk in a version 1 file"""
375 f = self._repo.vfs(self.statepathv1, 'w')
375 f = self._repo.vfs(self.statepathv1, 'w')
376 irecords = iter(records)
376 irecords = iter(records)
377 lrecords = next(irecords)
377 lrecords = next(irecords)
378 assert lrecords[0] == 'L'
378 assert lrecords[0] == 'L'
379 f.write(hex(self._local) + '\n')
379 f.write(hex(self._local) + '\n')
380 for rtype, data in irecords:
380 for rtype, data in irecords:
381 if rtype == 'F':
381 if rtype == 'F':
382 f.write('%s\n' % _droponode(data))
382 f.write('%s\n' % _droponode(data))
383 f.close()
383 f.close()
384
384
385 def _writerecordsv2(self, records):
385 def _writerecordsv2(self, records):
386 """Write current state on disk in a version 2 file
386 """Write current state on disk in a version 2 file
387
387
388 See the docstring for _readrecordsv2 for why we use 't'."""
388 See the docstring for _readrecordsv2 for why we use 't'."""
389 # these are the records that all version 2 clients can read
389 # these are the records that all version 2 clients can read
390 whitelist = 'LOF'
390 whitelist = 'LOF'
391 f = self._repo.vfs(self.statepathv2, 'w')
391 f = self._repo.vfs(self.statepathv2, 'w')
392 for key, data in records:
392 for key, data in records:
393 assert len(key) == 1
393 assert len(key) == 1
394 if key not in whitelist:
394 if key not in whitelist:
395 key, data = 't', '%s%s' % (key, data)
395 key, data = 't', '%s%s' % (key, data)
396 format = '>sI%is' % len(data)
396 format = '>sI%is' % len(data)
397 f.write(_pack(format, key, len(data), data))
397 f.write(_pack(format, key, len(data), data))
398 f.close()
398 f.close()
399
399
400 def add(self, fcl, fco, fca, fd):
400 def add(self, fcl, fco, fca, fd):
401 """add a new (potentially?) conflicting file the merge state
401 """add a new (potentially?) conflicting file the merge state
402 fcl: file context for local,
402 fcl: file context for local,
403 fco: file context for remote,
403 fco: file context for remote,
404 fca: file context for ancestors,
404 fca: file context for ancestors,
405 fd: file path of the resulting merge.
405 fd: file path of the resulting merge.
406
406
407 note: also write the local version to the `.hg/merge` directory.
407 note: also write the local version to the `.hg/merge` directory.
408 """
408 """
409 if fcl.isabsent():
409 if fcl.isabsent():
410 hash = nullhex
410 hash = nullhex
411 else:
411 else:
412 hash = hashlib.sha1(fcl.path()).hexdigest()
412 hash = hashlib.sha1(fcl.path()).hexdigest()
413 self._repo.vfs.write('merge/' + hash, fcl.data())
413 self._repo.vfs.write('merge/' + hash, fcl.data())
414 self._state[fd] = ['u', hash, fcl.path(),
414 self._state[fd] = ['u', hash, fcl.path(),
415 fca.path(), hex(fca.filenode()),
415 fca.path(), hex(fca.filenode()),
416 fco.path(), hex(fco.filenode()),
416 fco.path(), hex(fco.filenode()),
417 fcl.flags()]
417 fcl.flags()]
418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
418 self._stateextras[fd] = { 'ancestorlinknode' : hex(fca.node()) }
419 self._dirty = True
419 self._dirty = True
420
420
421 def __contains__(self, dfile):
421 def __contains__(self, dfile):
422 return dfile in self._state
422 return dfile in self._state
423
423
424 def __getitem__(self, dfile):
424 def __getitem__(self, dfile):
425 return self._state[dfile][0]
425 return self._state[dfile][0]
426
426
427 def __iter__(self):
427 def __iter__(self):
428 return iter(sorted(self._state))
428 return iter(sorted(self._state))
429
429
430 def files(self):
430 def files(self):
431 return self._state.keys()
431 return self._state.keys()
432
432
433 def mark(self, dfile, state):
433 def mark(self, dfile, state):
434 self._state[dfile][0] = state
434 self._state[dfile][0] = state
435 self._dirty = True
435 self._dirty = True
436
436
437 def mdstate(self):
437 def mdstate(self):
438 return self._mdstate
438 return self._mdstate
439
439
440 def unresolved(self):
440 def unresolved(self):
441 """Obtain the paths of unresolved files."""
441 """Obtain the paths of unresolved files."""
442
442
443 for f, entry in self._state.items():
443 for f, entry in self._state.items():
444 if entry[0] == 'u':
444 if entry[0] == 'u':
445 yield f
445 yield f
446
446
447 def driverresolved(self):
447 def driverresolved(self):
448 """Obtain the paths of driver-resolved files."""
448 """Obtain the paths of driver-resolved files."""
449
449
450 for f, entry in self._state.items():
450 for f, entry in self._state.items():
451 if entry[0] == 'd':
451 if entry[0] == 'd':
452 yield f
452 yield f
453
453
454 def extras(self, filename):
454 def extras(self, filename):
455 return self._stateextras.setdefault(filename, {})
455 return self._stateextras.setdefault(filename, {})
456
456
457 def _resolve(self, preresolve, dfile, wctx):
457 def _resolve(self, preresolve, dfile, wctx):
458 """rerun merge process for file path `dfile`"""
458 """rerun merge process for file path `dfile`"""
459 if self[dfile] in 'rd':
459 if self[dfile] in 'rd':
460 return True, 0
460 return True, 0
461 stateentry = self._state[dfile]
461 stateentry = self._state[dfile]
462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
462 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
463 octx = self._repo[self._other]
463 octx = self._repo[self._other]
464 extras = self.extras(dfile)
464 extras = self.extras(dfile)
465 anccommitnode = extras.get('ancestorlinknode')
465 anccommitnode = extras.get('ancestorlinknode')
466 if anccommitnode:
466 if anccommitnode:
467 actx = self._repo[anccommitnode]
467 actx = self._repo[anccommitnode]
468 else:
468 else:
469 actx = None
469 actx = None
470 fcd = self._filectxorabsent(hash, wctx, dfile)
470 fcd = self._filectxorabsent(hash, wctx, dfile)
471 fco = self._filectxorabsent(onode, octx, ofile)
471 fco = self._filectxorabsent(onode, octx, ofile)
472 # TODO: move this to filectxorabsent
472 # TODO: move this to filectxorabsent
473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
473 fca = self._repo.filectx(afile, fileid=anode, changeid=actx)
474 # "premerge" x flags
474 # "premerge" x flags
475 flo = fco.flags()
475 flo = fco.flags()
476 fla = fca.flags()
476 fla = fca.flags()
477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
477 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
478 if fca.node() == nullid:
478 if fca.node() == nullid:
479 if preresolve:
479 if preresolve:
480 self._repo.ui.warn(
480 self._repo.ui.warn(
481 _('warning: cannot merge flags for %s\n') % afile)
481 _('warning: cannot merge flags for %s\n') % afile)
482 elif flags == fla:
482 elif flags == fla:
483 flags = flo
483 flags = flo
484 if preresolve:
484 if preresolve:
485 # restore local
485 # restore local
486 if hash != nullhex:
486 if hash != nullhex:
487 f = self._repo.vfs('merge/' + hash)
487 f = self._repo.vfs('merge/' + hash)
488 self._repo.wwrite(dfile, f.read(), flags)
488 self._repo.wwrite(dfile, f.read(), flags)
489 f.close()
489 f.close()
490 else:
490 else:
491 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
491 self._repo.wvfs.unlinkpath(dfile, ignoremissing=True)
492 complete, r, deleted = filemerge.premerge(self._repo, self._local,
492 complete, r, deleted = filemerge.premerge(self._repo, self._local,
493 lfile, fcd, fco, fca,
493 lfile, fcd, fco, fca,
494 labels=self._labels)
494 labels=self._labels)
495 else:
495 else:
496 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
496 complete, r, deleted = filemerge.filemerge(self._repo, self._local,
497 lfile, fcd, fco, fca,
497 lfile, fcd, fco, fca,
498 labels=self._labels)
498 labels=self._labels)
499 if r is None:
499 if r is None:
500 # no real conflict
500 # no real conflict
501 del self._state[dfile]
501 del self._state[dfile]
502 self._stateextras.pop(dfile, None)
502 self._stateextras.pop(dfile, None)
503 self._dirty = True
503 self._dirty = True
504 elif not r:
504 elif not r:
505 self.mark(dfile, 'r')
505 self.mark(dfile, 'r')
506
506
507 if complete:
507 if complete:
508 action = None
508 action = None
509 if deleted:
509 if deleted:
510 if fcd.isabsent():
510 if fcd.isabsent():
511 # dc: local picked. Need to drop if present, which may
511 # dc: local picked. Need to drop if present, which may
512 # happen on re-resolves.
512 # happen on re-resolves.
513 action = 'f'
513 action = 'f'
514 else:
514 else:
515 # cd: remote picked (or otherwise deleted)
515 # cd: remote picked (or otherwise deleted)
516 action = 'r'
516 action = 'r'
517 else:
517 else:
518 if fcd.isabsent(): # dc: remote picked
518 if fcd.isabsent(): # dc: remote picked
519 action = 'g'
519 action = 'g'
520 elif fco.isabsent(): # cd: local picked
520 elif fco.isabsent(): # cd: local picked
521 if dfile in self.localctx:
521 if dfile in self.localctx:
522 action = 'am'
522 action = 'am'
523 else:
523 else:
524 action = 'a'
524 action = 'a'
525 # else: regular merges (no action necessary)
525 # else: regular merges (no action necessary)
526 self._results[dfile] = r, action
526 self._results[dfile] = r, action
527
527
528 return complete, r
528 return complete, r
529
529
530 def _filectxorabsent(self, hexnode, ctx, f):
530 def _filectxorabsent(self, hexnode, ctx, f):
531 if hexnode == nullhex:
531 if hexnode == nullhex:
532 return filemerge.absentfilectx(ctx, f)
532 return filemerge.absentfilectx(ctx, f)
533 else:
533 else:
534 return ctx[f]
534 return ctx[f]
535
535
536 def preresolve(self, dfile, wctx):
536 def preresolve(self, dfile, wctx):
537 """run premerge process for dfile
537 """run premerge process for dfile
538
538
539 Returns whether the merge is complete, and the exit code."""
539 Returns whether the merge is complete, and the exit code."""
540 return self._resolve(True, dfile, wctx)
540 return self._resolve(True, dfile, wctx)
541
541
542 def resolve(self, dfile, wctx):
542 def resolve(self, dfile, wctx):
543 """run merge process (assuming premerge was run) for dfile
543 """run merge process (assuming premerge was run) for dfile
544
544
545 Returns the exit code of the merge."""
545 Returns the exit code of the merge."""
546 return self._resolve(False, dfile, wctx)[1]
546 return self._resolve(False, dfile, wctx)[1]
547
547
548 def counts(self):
548 def counts(self):
549 """return counts for updated, merged and removed files in this
549 """return counts for updated, merged and removed files in this
550 session"""
550 session"""
551 updated, merged, removed = 0, 0, 0
551 updated, merged, removed = 0, 0, 0
552 for r, action in self._results.itervalues():
552 for r, action in self._results.itervalues():
553 if r is None:
553 if r is None:
554 updated += 1
554 updated += 1
555 elif r == 0:
555 elif r == 0:
556 if action == 'r':
556 if action == 'r':
557 removed += 1
557 removed += 1
558 else:
558 else:
559 merged += 1
559 merged += 1
560 return updated, merged, removed
560 return updated, merged, removed
561
561
562 def unresolvedcount(self):
562 def unresolvedcount(self):
563 """get unresolved count for this merge (persistent)"""
563 """get unresolved count for this merge (persistent)"""
564 return len([True for f, entry in self._state.iteritems()
564 return len([True for f, entry in self._state.iteritems()
565 if entry[0] == 'u'])
565 if entry[0] == 'u'])
566
566
567 def actions(self):
567 def actions(self):
568 """return lists of actions to perform on the dirstate"""
568 """return lists of actions to perform on the dirstate"""
569 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
569 actions = {'r': [], 'f': [], 'a': [], 'am': [], 'g': []}
570 for f, (r, action) in self._results.iteritems():
570 for f, (r, action) in self._results.iteritems():
571 if action is not None:
571 if action is not None:
572 actions[action].append((f, None, "merge result"))
572 actions[action].append((f, None, "merge result"))
573 return actions
573 return actions
574
574
575 def recordactions(self):
575 def recordactions(self):
576 """record remove/add/get actions in the dirstate"""
576 """record remove/add/get actions in the dirstate"""
577 branchmerge = self._repo.dirstate.p2() != nullid
577 branchmerge = self._repo.dirstate.p2() != nullid
578 recordupdates(self._repo, self.actions(), branchmerge)
578 recordupdates(self._repo, self.actions(), branchmerge)
579
579
580 def queueremove(self, f):
580 def queueremove(self, f):
581 """queues a file to be removed from the dirstate
581 """queues a file to be removed from the dirstate
582
582
583 Meant for use by custom merge drivers."""
583 Meant for use by custom merge drivers."""
584 self._results[f] = 0, 'r'
584 self._results[f] = 0, 'r'
585
585
586 def queueadd(self, f):
586 def queueadd(self, f):
587 """queues a file to be added to the dirstate
587 """queues a file to be added to the dirstate
588
588
589 Meant for use by custom merge drivers."""
589 Meant for use by custom merge drivers."""
590 self._results[f] = 0, 'a'
590 self._results[f] = 0, 'a'
591
591
592 def queueget(self, f):
592 def queueget(self, f):
593 """queues a file to be marked modified in the dirstate
593 """queues a file to be marked modified in the dirstate
594
594
595 Meant for use by custom merge drivers."""
595 Meant for use by custom merge drivers."""
596 self._results[f] = 0, 'g'
596 self._results[f] = 0, 'g'
597
597
598 def _getcheckunknownconfig(repo, section, name):
598 def _getcheckunknownconfig(repo, section, name):
599 config = repo.ui.config(section, name, default='abort')
599 config = repo.ui.config(section, name, default='abort')
600 valid = ['abort', 'ignore', 'warn']
600 valid = ['abort', 'ignore', 'warn']
601 if config not in valid:
601 if config not in valid:
602 validstr = ', '.join(["'" + v + "'" for v in valid])
602 validstr = ', '.join(["'" + v + "'" for v in valid])
603 raise error.ConfigError(_("%s.%s not valid "
603 raise error.ConfigError(_("%s.%s not valid "
604 "('%s' is none of %s)")
604 "('%s' is none of %s)")
605 % (section, name, config, validstr))
605 % (section, name, config, validstr))
606 return config
606 return config
607
607
608 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
608 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
609 if f2 is None:
609 if f2 is None:
610 f2 = f
610 f2 = f
611 return (repo.wvfs.audit.check(f)
611 return (repo.wvfs.audit.check(f)
612 and repo.wvfs.isfileorlink(f)
612 and repo.wvfs.isfileorlink(f)
613 and repo.dirstate.normalize(f) not in repo.dirstate
613 and repo.dirstate.normalize(f) not in repo.dirstate
614 and mctx[f2].cmp(wctx[f]))
614 and mctx[f2].cmp(wctx[f]))
615
615
616 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
616 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
617 """
617 """
618 Considers any actions that care about the presence of conflicting unknown
618 Considers any actions that care about the presence of conflicting unknown
619 files. For some actions, the result is to abort; for others, it is to
619 files. For some actions, the result is to abort; for others, it is to
620 choose a different action.
620 choose a different action.
621 """
621 """
622 conflicts = set()
622 conflicts = set()
623 warnconflicts = set()
623 warnconflicts = set()
624 abortconflicts = set()
624 abortconflicts = set()
625 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
625 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
626 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
626 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
627 if not force:
627 if not force:
628 def collectconflicts(conflicts, config):
628 def collectconflicts(conflicts, config):
629 if config == 'abort':
629 if config == 'abort':
630 abortconflicts.update(conflicts)
630 abortconflicts.update(conflicts)
631 elif config == 'warn':
631 elif config == 'warn':
632 warnconflicts.update(conflicts)
632 warnconflicts.update(conflicts)
633
633
634 for f, (m, args, msg) in actions.iteritems():
634 for f, (m, args, msg) in actions.iteritems():
635 if m in ('c', 'dc'):
635 if m in ('c', 'dc'):
636 if _checkunknownfile(repo, wctx, mctx, f):
636 if _checkunknownfile(repo, wctx, mctx, f):
637 conflicts.add(f)
637 conflicts.add(f)
638 elif m == 'dg':
638 elif m == 'dg':
639 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
639 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
640 conflicts.add(f)
640 conflicts.add(f)
641
641
642 ignoredconflicts = set([c for c in conflicts
642 ignoredconflicts = set([c for c in conflicts
643 if repo.dirstate._ignore(c)])
643 if repo.dirstate._ignore(c)])
644 unknownconflicts = conflicts - ignoredconflicts
644 unknownconflicts = conflicts - ignoredconflicts
645 collectconflicts(ignoredconflicts, ignoredconfig)
645 collectconflicts(ignoredconflicts, ignoredconfig)
646 collectconflicts(unknownconflicts, unknownconfig)
646 collectconflicts(unknownconflicts, unknownconfig)
647 else:
647 else:
648 for f, (m, args, msg) in actions.iteritems():
648 for f, (m, args, msg) in actions.iteritems():
649 if m == 'cm':
649 if m == 'cm':
650 fl2, anc = args
650 fl2, anc = args
651 different = _checkunknownfile(repo, wctx, mctx, f)
651 different = _checkunknownfile(repo, wctx, mctx, f)
652 if repo.dirstate._ignore(f):
652 if repo.dirstate._ignore(f):
653 config = ignoredconfig
653 config = ignoredconfig
654 else:
654 else:
655 config = unknownconfig
655 config = unknownconfig
656
656
657 # The behavior when force is True is described by this table:
657 # The behavior when force is True is described by this table:
658 # config different mergeforce | action backup
658 # config different mergeforce | action backup
659 # * n * | get n
659 # * n * | get n
660 # * y y | merge -
660 # * y y | merge -
661 # abort y n | merge - (1)
661 # abort y n | merge - (1)
662 # warn y n | warn + get y
662 # warn y n | warn + get y
663 # ignore y n | get y
663 # ignore y n | get y
664 #
664 #
665 # (1) this is probably the wrong behavior here -- we should
665 # (1) this is probably the wrong behavior here -- we should
666 # probably abort, but some actions like rebases currently
666 # probably abort, but some actions like rebases currently
667 # don't like an abort happening in the middle of
667 # don't like an abort happening in the middle of
668 # merge.update.
668 # merge.update.
669 if not different:
669 if not different:
670 actions[f] = ('g', (fl2, False), "remote created")
670 actions[f] = ('g', (fl2, False), "remote created")
671 elif mergeforce or config == 'abort':
671 elif mergeforce or config == 'abort':
672 actions[f] = ('m', (f, f, None, False, anc),
672 actions[f] = ('m', (f, f, None, False, anc),
673 "remote differs from untracked local")
673 "remote differs from untracked local")
674 elif config == 'abort':
674 elif config == 'abort':
675 abortconflicts.add(f)
675 abortconflicts.add(f)
676 else:
676 else:
677 if config == 'warn':
677 if config == 'warn':
678 warnconflicts.add(f)
678 warnconflicts.add(f)
679 actions[f] = ('g', (fl2, True), "remote created")
679 actions[f] = ('g', (fl2, True), "remote created")
680
680
681 for f in sorted(abortconflicts):
681 for f in sorted(abortconflicts):
682 repo.ui.warn(_("%s: untracked file differs\n") % f)
682 repo.ui.warn(_("%s: untracked file differs\n") % f)
683 if abortconflicts:
683 if abortconflicts:
684 raise error.Abort(_("untracked files in working directory "
684 raise error.Abort(_("untracked files in working directory "
685 "differ from files in requested revision"))
685 "differ from files in requested revision"))
686
686
687 for f in sorted(warnconflicts):
687 for f in sorted(warnconflicts):
688 repo.ui.warn(_("%s: replacing untracked file\n") % f)
688 repo.ui.warn(_("%s: replacing untracked file\n") % f)
689
689
690 for f, (m, args, msg) in actions.iteritems():
690 for f, (m, args, msg) in actions.iteritems():
691 backup = f in conflicts
691 backup = f in conflicts
692 if m == 'c':
692 if m == 'c':
693 flags, = args
693 flags, = args
694 actions[f] = ('g', (flags, backup), msg)
694 actions[f] = ('g', (flags, backup), msg)
695
695
696 def _forgetremoved(wctx, mctx, branchmerge):
696 def _forgetremoved(wctx, mctx, branchmerge):
697 """
697 """
698 Forget removed files
698 Forget removed files
699
699
700 If we're jumping between revisions (as opposed to merging), and if
700 If we're jumping between revisions (as opposed to merging), and if
701 neither the working directory nor the target rev has the file,
701 neither the working directory nor the target rev has the file,
702 then we need to remove it from the dirstate, to prevent the
702 then we need to remove it from the dirstate, to prevent the
703 dirstate from listing the file when it is no longer in the
703 dirstate from listing the file when it is no longer in the
704 manifest.
704 manifest.
705
705
706 If we're merging, and the other revision has removed a file
706 If we're merging, and the other revision has removed a file
707 that is not present in the working directory, we need to mark it
707 that is not present in the working directory, we need to mark it
708 as removed.
708 as removed.
709 """
709 """
710
710
711 actions = {}
711 actions = {}
712 m = 'f'
712 m = 'f'
713 if branchmerge:
713 if branchmerge:
714 m = 'r'
714 m = 'r'
715 for f in wctx.deleted():
715 for f in wctx.deleted():
716 if f not in mctx:
716 if f not in mctx:
717 actions[f] = m, None, "forget deleted"
717 actions[f] = m, None, "forget deleted"
718
718
719 if not branchmerge:
719 if not branchmerge:
720 for f in wctx.removed():
720 for f in wctx.removed():
721 if f not in mctx:
721 if f not in mctx:
722 actions[f] = 'f', None, "forget removed"
722 actions[f] = 'f', None, "forget removed"
723
723
724 return actions
724 return actions
725
725
726 def _checkcollision(repo, wmf, actions):
726 def _checkcollision(repo, wmf, actions):
727 # build provisional merged manifest up
727 # build provisional merged manifest up
728 pmmf = set(wmf)
728 pmmf = set(wmf)
729
729
730 if actions:
730 if actions:
731 # k, dr, e and rd are no-op
731 # k, dr, e and rd are no-op
732 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
732 for m in 'a', 'am', 'f', 'g', 'cd', 'dc':
733 for f, args, msg in actions[m]:
733 for f, args, msg in actions[m]:
734 pmmf.add(f)
734 pmmf.add(f)
735 for f, args, msg in actions['r']:
735 for f, args, msg in actions['r']:
736 pmmf.discard(f)
736 pmmf.discard(f)
737 for f, args, msg in actions['dm']:
737 for f, args, msg in actions['dm']:
738 f2, flags = args
738 f2, flags = args
739 pmmf.discard(f2)
739 pmmf.discard(f2)
740 pmmf.add(f)
740 pmmf.add(f)
741 for f, args, msg in actions['dg']:
741 for f, args, msg in actions['dg']:
742 pmmf.add(f)
742 pmmf.add(f)
743 for f, args, msg in actions['m']:
743 for f, args, msg in actions['m']:
744 f1, f2, fa, move, anc = args
744 f1, f2, fa, move, anc = args
745 if move:
745 if move:
746 pmmf.discard(f1)
746 pmmf.discard(f1)
747 pmmf.add(f)
747 pmmf.add(f)
748
748
749 # check case-folding collision in provisional merged manifest
749 # check case-folding collision in provisional merged manifest
750 foldmap = {}
750 foldmap = {}
751 for f in sorted(pmmf):
751 for f in sorted(pmmf):
752 fold = util.normcase(f)
752 fold = util.normcase(f)
753 if fold in foldmap:
753 if fold in foldmap:
754 raise error.Abort(_("case-folding collision between %s and %s")
754 raise error.Abort(_("case-folding collision between %s and %s")
755 % (f, foldmap[fold]))
755 % (f, foldmap[fold]))
756 foldmap[fold] = f
756 foldmap[fold] = f
757
757
758 # check case-folding of directories
758 # check case-folding of directories
759 foldprefix = unfoldprefix = lastfull = ''
759 foldprefix = unfoldprefix = lastfull = ''
760 for fold, f in sorted(foldmap.items()):
760 for fold, f in sorted(foldmap.items()):
761 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
761 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
762 # the folded prefix matches but actual casing is different
762 # the folded prefix matches but actual casing is different
763 raise error.Abort(_("case-folding collision between "
763 raise error.Abort(_("case-folding collision between "
764 "%s and directory of %s") % (lastfull, f))
764 "%s and directory of %s") % (lastfull, f))
765 foldprefix = fold + '/'
765 foldprefix = fold + '/'
766 unfoldprefix = f + '/'
766 unfoldprefix = f + '/'
767 lastfull = f
767 lastfull = f
768
768
769 def driverpreprocess(repo, ms, wctx, labels=None):
769 def driverpreprocess(repo, ms, wctx, labels=None):
770 """run the preprocess step of the merge driver, if any
770 """run the preprocess step of the merge driver, if any
771
771
772 This is currently not implemented -- it's an extension point."""
772 This is currently not implemented -- it's an extension point."""
773 return True
773 return True
774
774
775 def driverconclude(repo, ms, wctx, labels=None):
775 def driverconclude(repo, ms, wctx, labels=None):
776 """run the conclude step of the merge driver, if any
776 """run the conclude step of the merge driver, if any
777
777
778 This is currently not implemented -- it's an extension point."""
778 This is currently not implemented -- it's an extension point."""
779 return True
779 return True
780
780
781 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
781 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
782 acceptremote, followcopies):
782 acceptremote, followcopies):
783 """
783 """
784 Merge p1 and p2 with ancestor pa and generate merge action list
784 Merge p1 and p2 with ancestor pa and generate merge action list
785
785
786 branchmerge and force are as passed in to update
786 branchmerge and force are as passed in to update
787 matcher = matcher to filter file lists
787 matcher = matcher to filter file lists
788 acceptremote = accept the incoming changes without prompting
788 acceptremote = accept the incoming changes without prompting
789 """
789 """
790 if matcher is not None and matcher.always():
790 if matcher is not None and matcher.always():
791 matcher = None
791 matcher = None
792
792
793 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
793 copy, movewithdir, diverge, renamedelete = {}, {}, {}, {}
794
794
795 # manifests fetched in order are going to be faster, so prime the caches
795 # manifests fetched in order are going to be faster, so prime the caches
796 [x.manifest() for x in
796 [x.manifest() for x in
797 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
797 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
798
798
799 if followcopies:
799 if followcopies:
800 ret = copies.mergecopies(repo, wctx, p2, pa)
800 ret = copies.mergecopies(repo, wctx, p2, pa)
801 copy, movewithdir, diverge, renamedelete = ret
801 copy, movewithdir, diverge, renamedelete = ret
802
802
803 repo.ui.note(_("resolving manifests\n"))
803 repo.ui.note(_("resolving manifests\n"))
804 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
804 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
805 % (bool(branchmerge), bool(force), bool(matcher)))
805 % (bool(branchmerge), bool(force), bool(matcher)))
806 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
806 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
807
807
808 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
808 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
809 copied = set(copy.values())
809 copied = set(copy.values())
810 copied.update(movewithdir.values())
810 copied.update(movewithdir.values())
811
811
812 if '.hgsubstate' in m1:
812 if '.hgsubstate' in m1:
813 # check whether sub state is modified
813 # check whether sub state is modified
814 if any(wctx.sub(s).dirty() for s in wctx.substate):
814 if any(wctx.sub(s).dirty() for s in wctx.substate):
815 m1['.hgsubstate'] += '+'
815 m1['.hgsubstate'] += '+'
816
816
817 # Compare manifests
817 # Compare manifests
818 if matcher is not None:
818 if matcher is not None:
819 m1 = m1.matches(matcher)
819 m1 = m1.matches(matcher)
820 m2 = m2.matches(matcher)
820 m2 = m2.matches(matcher)
821 diff = m1.diff(m2)
821 diff = m1.diff(m2)
822
822
823 actions = {}
823 actions = {}
824 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
824 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
825 if n1 and n2: # file exists on both local and remote side
825 if n1 and n2: # file exists on both local and remote side
826 if f not in ma:
826 if f not in ma:
827 fa = copy.get(f, None)
827 fa = copy.get(f, None)
828 if fa is not None:
828 if fa is not None:
829 actions[f] = ('m', (f, f, fa, False, pa.node()),
829 actions[f] = ('m', (f, f, fa, False, pa.node()),
830 "both renamed from " + fa)
830 "both renamed from " + fa)
831 else:
831 else:
832 actions[f] = ('m', (f, f, None, False, pa.node()),
832 actions[f] = ('m', (f, f, None, False, pa.node()),
833 "both created")
833 "both created")
834 else:
834 else:
835 a = ma[f]
835 a = ma[f]
836 fla = ma.flags(f)
836 fla = ma.flags(f)
837 nol = 'l' not in fl1 + fl2 + fla
837 nol = 'l' not in fl1 + fl2 + fla
838 if n2 == a and fl2 == fla:
838 if n2 == a and fl2 == fla:
839 actions[f] = ('k' , (), "remote unchanged")
839 actions[f] = ('k' , (), "remote unchanged")
840 elif n1 == a and fl1 == fla: # local unchanged - use remote
840 elif n1 == a and fl1 == fla: # local unchanged - use remote
841 if n1 == n2: # optimization: keep local content
841 if n1 == n2: # optimization: keep local content
842 actions[f] = ('e', (fl2,), "update permissions")
842 actions[f] = ('e', (fl2,), "update permissions")
843 else:
843 else:
844 actions[f] = ('g', (fl2, False), "remote is newer")
844 actions[f] = ('g', (fl2, False), "remote is newer")
845 elif nol and n2 == a: # remote only changed 'x'
845 elif nol and n2 == a: # remote only changed 'x'
846 actions[f] = ('e', (fl2,), "update permissions")
846 actions[f] = ('e', (fl2,), "update permissions")
847 elif nol and n1 == a: # local only changed 'x'
847 elif nol and n1 == a: # local only changed 'x'
848 actions[f] = ('g', (fl1, False), "remote is newer")
848 actions[f] = ('g', (fl1, False), "remote is newer")
849 else: # both changed something
849 else: # both changed something
850 actions[f] = ('m', (f, f, f, False, pa.node()),
850 actions[f] = ('m', (f, f, f, False, pa.node()),
851 "versions differ")
851 "versions differ")
852 elif n1: # file exists only on local side
852 elif n1: # file exists only on local side
853 if f in copied:
853 if f in copied:
854 pass # we'll deal with it on m2 side
854 pass # we'll deal with it on m2 side
855 elif f in movewithdir: # directory rename, move local
855 elif f in movewithdir: # directory rename, move local
856 f2 = movewithdir[f]
856 f2 = movewithdir[f]
857 if f2 in m2:
857 if f2 in m2:
858 actions[f2] = ('m', (f, f2, None, True, pa.node()),
858 actions[f2] = ('m', (f, f2, None, True, pa.node()),
859 "remote directory rename, both created")
859 "remote directory rename, both created")
860 else:
860 else:
861 actions[f2] = ('dm', (f, fl1),
861 actions[f2] = ('dm', (f, fl1),
862 "remote directory rename - move from " + f)
862 "remote directory rename - move from " + f)
863 elif f in copy:
863 elif f in copy:
864 f2 = copy[f]
864 f2 = copy[f]
865 actions[f] = ('m', (f, f2, f2, False, pa.node()),
865 actions[f] = ('m', (f, f2, f2, False, pa.node()),
866 "local copied/moved from " + f2)
866 "local copied/moved from " + f2)
867 elif f in ma: # clean, a different, no remote
867 elif f in ma: # clean, a different, no remote
868 if n1 != ma[f]:
868 if n1 != ma[f]:
869 if acceptremote:
869 if acceptremote:
870 actions[f] = ('r', None, "remote delete")
870 actions[f] = ('r', None, "remote delete")
871 else:
871 else:
872 actions[f] = ('cd', (f, None, f, False, pa.node()),
872 actions[f] = ('cd', (f, None, f, False, pa.node()),
873 "prompt changed/deleted")
873 "prompt changed/deleted")
874 elif n1[20:] == 'a':
874 elif n1[20:] == 'a':
875 # This extra 'a' is added by working copy manifest to mark
875 # This extra 'a' is added by working copy manifest to mark
876 # the file as locally added. We should forget it instead of
876 # the file as locally added. We should forget it instead of
877 # deleting it.
877 # deleting it.
878 actions[f] = ('f', None, "remote deleted")
878 actions[f] = ('f', None, "remote deleted")
879 else:
879 else:
880 actions[f] = ('r', None, "other deleted")
880 actions[f] = ('r', None, "other deleted")
881 elif n2: # file exists only on remote side
881 elif n2: # file exists only on remote side
882 if f in copied:
882 if f in copied:
883 pass # we'll deal with it on m1 side
883 pass # we'll deal with it on m1 side
884 elif f in movewithdir:
884 elif f in movewithdir:
885 f2 = movewithdir[f]
885 f2 = movewithdir[f]
886 if f2 in m1:
886 if f2 in m1:
887 actions[f2] = ('m', (f2, f, None, False, pa.node()),
887 actions[f2] = ('m', (f2, f, None, False, pa.node()),
888 "local directory rename, both created")
888 "local directory rename, both created")
889 else:
889 else:
890 actions[f2] = ('dg', (f, fl2),
890 actions[f2] = ('dg', (f, fl2),
891 "local directory rename - get from " + f)
891 "local directory rename - get from " + f)
892 elif f in copy:
892 elif f in copy:
893 f2 = copy[f]
893 f2 = copy[f]
894 if f2 in m2:
894 if f2 in m2:
895 actions[f] = ('m', (f2, f, f2, False, pa.node()),
895 actions[f] = ('m', (f2, f, f2, False, pa.node()),
896 "remote copied from " + f2)
896 "remote copied from " + f2)
897 else:
897 else:
898 actions[f] = ('m', (f2, f, f2, True, pa.node()),
898 actions[f] = ('m', (f2, f, f2, True, pa.node()),
899 "remote moved from " + f2)
899 "remote moved from " + f2)
900 elif f not in ma:
900 elif f not in ma:
901 # local unknown, remote created: the logic is described by the
901 # local unknown, remote created: the logic is described by the
902 # following table:
902 # following table:
903 #
903 #
904 # force branchmerge different | action
904 # force branchmerge different | action
905 # n * * | create
905 # n * * | create
906 # y n * | create
906 # y n * | create
907 # y y n | create
907 # y y n | create
908 # y y y | merge
908 # y y y | merge
909 #
909 #
910 # Checking whether the files are different is expensive, so we
910 # Checking whether the files are different is expensive, so we
911 # don't do that when we can avoid it.
911 # don't do that when we can avoid it.
912 if not force:
912 if not force:
913 actions[f] = ('c', (fl2,), "remote created")
913 actions[f] = ('c', (fl2,), "remote created")
914 elif not branchmerge:
914 elif not branchmerge:
915 actions[f] = ('c', (fl2,), "remote created")
915 actions[f] = ('c', (fl2,), "remote created")
916 else:
916 else:
917 actions[f] = ('cm', (fl2, pa.node()),
917 actions[f] = ('cm', (fl2, pa.node()),
918 "remote created, get or merge")
918 "remote created, get or merge")
919 elif n2 != ma[f]:
919 elif n2 != ma[f]:
920 if acceptremote:
920 if acceptremote:
921 actions[f] = ('c', (fl2,), "remote recreating")
921 actions[f] = ('c', (fl2,), "remote recreating")
922 else:
922 else:
923 actions[f] = ('dc', (None, f, f, False, pa.node()),
923 actions[f] = ('dc', (None, f, f, False, pa.node()),
924 "prompt deleted/changed")
924 "prompt deleted/changed")
925
925
926 return actions, diverge, renamedelete
926 return actions, diverge, renamedelete
927
927
928 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
928 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
929 """Resolves false conflicts where the nodeid changed but the content
929 """Resolves false conflicts where the nodeid changed but the content
930 remained the same."""
930 remained the same."""
931
931
932 for f, (m, args, msg) in actions.items():
932 for f, (m, args, msg) in actions.items():
933 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
933 if m == 'cd' and f in ancestor and not wctx[f].cmp(ancestor[f]):
934 # local did change but ended up with same content
934 # local did change but ended up with same content
935 actions[f] = 'r', None, "prompt same"
935 actions[f] = 'r', None, "prompt same"
936 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
936 elif m == 'dc' and f in ancestor and not mctx[f].cmp(ancestor[f]):
937 # remote did change but ended up with same content
937 # remote did change but ended up with same content
938 del actions[f] # don't get = keep local deleted
938 del actions[f] # don't get = keep local deleted
939
939
940 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
940 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
941 acceptremote, followcopies, matcher=None,
941 acceptremote, followcopies, matcher=None,
942 mergeforce=False):
942 mergeforce=False):
943 "Calculate the actions needed to merge mctx into wctx using ancestors"
943 "Calculate the actions needed to merge mctx into wctx using ancestors"
944 if len(ancestors) == 1: # default
944 if len(ancestors) == 1: # default
945 actions, diverge, renamedelete = manifestmerge(
945 actions, diverge, renamedelete = manifestmerge(
946 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
946 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
947 acceptremote, followcopies)
947 acceptremote, followcopies)
948 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
948 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
949
949
950 else: # only when merge.preferancestor=* - the default
950 else: # only when merge.preferancestor=* - the default
951 repo.ui.note(
951 repo.ui.note(
952 _("note: merging %s and %s using bids from ancestors %s\n") %
952 _("note: merging %s and %s using bids from ancestors %s\n") %
953 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
953 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
954
954
955 # Call for bids
955 # Call for bids
956 fbids = {} # mapping filename to bids (action method to list af actions)
956 fbids = {} # mapping filename to bids (action method to list af actions)
957 diverge, renamedelete = None, None
957 diverge, renamedelete = None, None
958 for ancestor in ancestors:
958 for ancestor in ancestors:
959 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
959 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
960 actions, diverge1, renamedelete1 = manifestmerge(
960 actions, diverge1, renamedelete1 = manifestmerge(
961 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
961 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
962 acceptremote, followcopies)
962 acceptremote, followcopies)
963 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
963 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
964
964
965 # Track the shortest set of warning on the theory that bid
965 # Track the shortest set of warning on the theory that bid
966 # merge will correctly incorporate more information
966 # merge will correctly incorporate more information
967 if diverge is None or len(diverge1) < len(diverge):
967 if diverge is None or len(diverge1) < len(diverge):
968 diverge = diverge1
968 diverge = diverge1
969 if renamedelete is None or len(renamedelete) < len(renamedelete1):
969 if renamedelete is None or len(renamedelete) < len(renamedelete1):
970 renamedelete = renamedelete1
970 renamedelete = renamedelete1
971
971
972 for f, a in sorted(actions.iteritems()):
972 for f, a in sorted(actions.iteritems()):
973 m, args, msg = a
973 m, args, msg = a
974 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
974 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
975 if f in fbids:
975 if f in fbids:
976 d = fbids[f]
976 d = fbids[f]
977 if m in d:
977 if m in d:
978 d[m].append(a)
978 d[m].append(a)
979 else:
979 else:
980 d[m] = [a]
980 d[m] = [a]
981 else:
981 else:
982 fbids[f] = {m: [a]}
982 fbids[f] = {m: [a]}
983
983
984 # Pick the best bid for each file
984 # Pick the best bid for each file
985 repo.ui.note(_('\nauction for merging merge bids\n'))
985 repo.ui.note(_('\nauction for merging merge bids\n'))
986 actions = {}
986 actions = {}
987 for f, bids in sorted(fbids.items()):
987 for f, bids in sorted(fbids.items()):
988 # bids is a mapping from action method to list af actions
988 # bids is a mapping from action method to list af actions
989 # Consensus?
989 # Consensus?
990 if len(bids) == 1: # all bids are the same kind of method
990 if len(bids) == 1: # all bids are the same kind of method
991 m, l = bids.items()[0]
991 m, l = bids.items()[0]
992 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
992 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
993 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
993 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
994 actions[f] = l[0]
994 actions[f] = l[0]
995 continue
995 continue
996 # If keep is an option, just do it.
996 # If keep is an option, just do it.
997 if 'k' in bids:
997 if 'k' in bids:
998 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
998 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
999 actions[f] = bids['k'][0]
999 actions[f] = bids['k'][0]
1000 continue
1000 continue
1001 # If there are gets and they all agree [how could they not?], do it.
1001 # If there are gets and they all agree [how could they not?], do it.
1002 if 'g' in bids:
1002 if 'g' in bids:
1003 ga0 = bids['g'][0]
1003 ga0 = bids['g'][0]
1004 if all(a == ga0 for a in bids['g'][1:]):
1004 if all(a == ga0 for a in bids['g'][1:]):
1005 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1005 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1006 actions[f] = ga0
1006 actions[f] = ga0
1007 continue
1007 continue
1008 # TODO: Consider other simple actions such as mode changes
1008 # TODO: Consider other simple actions such as mode changes
1009 # Handle inefficient democrazy.
1009 # Handle inefficient democrazy.
1010 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1010 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1011 for m, l in sorted(bids.items()):
1011 for m, l in sorted(bids.items()):
1012 for _f, args, msg in l:
1012 for _f, args, msg in l:
1013 repo.ui.note(' %s -> %s\n' % (msg, m))
1013 repo.ui.note(' %s -> %s\n' % (msg, m))
1014 # Pick random action. TODO: Instead, prompt user when resolving
1014 # Pick random action. TODO: Instead, prompt user when resolving
1015 m, l = bids.items()[0]
1015 m, l = bids.items()[0]
1016 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1016 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1017 (f, m))
1017 (f, m))
1018 actions[f] = l[0]
1018 actions[f] = l[0]
1019 continue
1019 continue
1020 repo.ui.note(_('end of auction\n\n'))
1020 repo.ui.note(_('end of auction\n\n'))
1021
1021
1022 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1022 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1023
1023
1024 if wctx.rev() is None:
1024 if wctx.rev() is None:
1025 fractions = _forgetremoved(wctx, mctx, branchmerge)
1025 fractions = _forgetremoved(wctx, mctx, branchmerge)
1026 actions.update(fractions)
1026 actions.update(fractions)
1027
1027
1028 return actions, diverge, renamedelete
1028 return actions, diverge, renamedelete
1029
1029
1030 def batchremove(repo, actions):
1030 def batchremove(repo, actions):
1031 """apply removes to the working directory
1031 """apply removes to the working directory
1032
1032
1033 yields tuples for progress updates
1033 yields tuples for progress updates
1034 """
1034 """
1035 verbose = repo.ui.verbose
1035 verbose = repo.ui.verbose
1036 unlink = util.unlinkpath
1036 unlink = util.unlinkpath
1037 wjoin = repo.wjoin
1037 wjoin = repo.wjoin
1038 audit = repo.wvfs.audit
1038 audit = repo.wvfs.audit
1039 i = 0
1039 i = 0
1040 for f, args, msg in actions:
1040 for f, args, msg in actions:
1041 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1041 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1042 if verbose:
1042 if verbose:
1043 repo.ui.note(_("removing %s\n") % f)
1043 repo.ui.note(_("removing %s\n") % f)
1044 audit(f)
1044 audit(f)
1045 try:
1045 try:
1046 unlink(wjoin(f), ignoremissing=True)
1046 unlink(wjoin(f), ignoremissing=True)
1047 except OSError as inst:
1047 except OSError as inst:
1048 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1048 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1049 (f, inst.strerror))
1049 (f, inst.strerror))
1050 if i == 100:
1050 if i == 100:
1051 yield i, f
1051 yield i, f
1052 i = 0
1052 i = 0
1053 i += 1
1053 i += 1
1054 if i > 0:
1054 if i > 0:
1055 yield i, f
1055 yield i, f
1056
1056
1057 def batchget(repo, mctx, actions):
1057 def batchget(repo, mctx, actions):
1058 """apply gets to the working directory
1058 """apply gets to the working directory
1059
1059
1060 mctx is the context to get from
1060 mctx is the context to get from
1061
1061
1062 yields tuples for progress updates
1062 yields tuples for progress updates
1063 """
1063 """
1064 verbose = repo.ui.verbose
1064 verbose = repo.ui.verbose
1065 fctx = mctx.filectx
1065 fctx = mctx.filectx
1066 wwrite = repo.wwrite
1066 wwrite = repo.wwrite
1067 ui = repo.ui
1067 ui = repo.ui
1068 i = 0
1068 i = 0
1069 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1069 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1070 for f, (flags, backup), msg in actions:
1070 for f, (flags, backup), msg in actions:
1071 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1071 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1072 if verbose:
1072 if verbose:
1073 repo.ui.note(_("getting %s\n") % f)
1073 repo.ui.note(_("getting %s\n") % f)
1074
1074
1075 if backup:
1075 if backup:
1076 absf = repo.wjoin(f)
1076 absf = repo.wjoin(f)
1077 orig = scmutil.origpath(ui, repo, absf)
1077 orig = scmutil.origpath(ui, repo, absf)
1078 try:
1078 try:
1079 if repo.wvfs.isfileorlink(f):
1079 if repo.wvfs.isfileorlink(f):
1080 util.rename(absf, orig)
1080 util.rename(absf, orig)
1081 except OSError as e:
1081 except OSError as e:
1082 if e.errno != errno.ENOENT:
1082 if e.errno != errno.ENOENT:
1083 raise
1083 raise
1084
1084
1085 if repo.wvfs.isdir(f):
1085 if repo.wvfs.isdir(f) and not repo.wvfs.islink(f):
1086 repo.wvfs.removedirs(f)
1086 repo.wvfs.removedirs(f)
1087 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1087 wwrite(f, fctx(f).data(), flags, backgroundclose=True)
1088 if i == 100:
1088 if i == 100:
1089 yield i, f
1089 yield i, f
1090 i = 0
1090 i = 0
1091 i += 1
1091 i += 1
1092 if i > 0:
1092 if i > 0:
1093 yield i, f
1093 yield i, f
1094
1094
1095 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1095 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1096 """apply the merge action list to the working directory
1096 """apply the merge action list to the working directory
1097
1097
1098 wctx is the working copy context
1098 wctx is the working copy context
1099 mctx is the context to be merged into the working copy
1099 mctx is the context to be merged into the working copy
1100
1100
1101 Return a tuple of counts (updated, merged, removed, unresolved) that
1101 Return a tuple of counts (updated, merged, removed, unresolved) that
1102 describes how many files were affected by the update.
1102 describes how many files were affected by the update.
1103 """
1103 """
1104
1104
1105 updated, merged, removed = 0, 0, 0
1105 updated, merged, removed = 0, 0, 0
1106 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1106 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1107 moves = []
1107 moves = []
1108 for m, l in actions.items():
1108 for m, l in actions.items():
1109 l.sort()
1109 l.sort()
1110
1110
1111 # 'cd' and 'dc' actions are treated like other merge conflicts
1111 # 'cd' and 'dc' actions are treated like other merge conflicts
1112 mergeactions = sorted(actions['cd'])
1112 mergeactions = sorted(actions['cd'])
1113 mergeactions.extend(sorted(actions['dc']))
1113 mergeactions.extend(sorted(actions['dc']))
1114 mergeactions.extend(actions['m'])
1114 mergeactions.extend(actions['m'])
1115 for f, args, msg in mergeactions:
1115 for f, args, msg in mergeactions:
1116 f1, f2, fa, move, anc = args
1116 f1, f2, fa, move, anc = args
1117 if f == '.hgsubstate': # merged internally
1117 if f == '.hgsubstate': # merged internally
1118 continue
1118 continue
1119 if f1 is None:
1119 if f1 is None:
1120 fcl = filemerge.absentfilectx(wctx, fa)
1120 fcl = filemerge.absentfilectx(wctx, fa)
1121 else:
1121 else:
1122 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1122 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1123 fcl = wctx[f1]
1123 fcl = wctx[f1]
1124 if f2 is None:
1124 if f2 is None:
1125 fco = filemerge.absentfilectx(mctx, fa)
1125 fco = filemerge.absentfilectx(mctx, fa)
1126 else:
1126 else:
1127 fco = mctx[f2]
1127 fco = mctx[f2]
1128 actx = repo[anc]
1128 actx = repo[anc]
1129 if fa in actx:
1129 if fa in actx:
1130 fca = actx[fa]
1130 fca = actx[fa]
1131 else:
1131 else:
1132 # TODO: move to absentfilectx
1132 # TODO: move to absentfilectx
1133 fca = repo.filectx(f1, fileid=nullrev)
1133 fca = repo.filectx(f1, fileid=nullrev)
1134 ms.add(fcl, fco, fca, f)
1134 ms.add(fcl, fco, fca, f)
1135 if f1 != f and move:
1135 if f1 != f and move:
1136 moves.append(f1)
1136 moves.append(f1)
1137
1137
1138 audit = repo.wvfs.audit
1138 audit = repo.wvfs.audit
1139 _updating = _('updating')
1139 _updating = _('updating')
1140 _files = _('files')
1140 _files = _('files')
1141 progress = repo.ui.progress
1141 progress = repo.ui.progress
1142
1142
1143 # remove renamed files after safely stored
1143 # remove renamed files after safely stored
1144 for f in moves:
1144 for f in moves:
1145 if os.path.lexists(repo.wjoin(f)):
1145 if os.path.lexists(repo.wjoin(f)):
1146 repo.ui.debug("removing %s\n" % f)
1146 repo.ui.debug("removing %s\n" % f)
1147 audit(f)
1147 audit(f)
1148 util.unlinkpath(repo.wjoin(f))
1148 util.unlinkpath(repo.wjoin(f))
1149
1149
1150 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1150 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
1151
1151
1152 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1152 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
1153 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
1153 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
1154
1154
1155 # remove in parallel (must come first)
1155 # remove in parallel (must come first)
1156 z = 0
1156 z = 0
1157 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1157 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
1158 for i, item in prog:
1158 for i, item in prog:
1159 z += i
1159 z += i
1160 progress(_updating, z, item=item, total=numupdates, unit=_files)
1160 progress(_updating, z, item=item, total=numupdates, unit=_files)
1161 removed = len(actions['r'])
1161 removed = len(actions['r'])
1162
1162
1163 # get in parallel
1163 # get in parallel
1164 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1164 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
1165 for i, item in prog:
1165 for i, item in prog:
1166 z += i
1166 z += i
1167 progress(_updating, z, item=item, total=numupdates, unit=_files)
1167 progress(_updating, z, item=item, total=numupdates, unit=_files)
1168 updated = len(actions['g'])
1168 updated = len(actions['g'])
1169
1169
1170 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1170 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
1171 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
1171 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
1172
1172
1173 # forget (manifest only, just log it) (must come first)
1173 # forget (manifest only, just log it) (must come first)
1174 for f, args, msg in actions['f']:
1174 for f, args, msg in actions['f']:
1175 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1175 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1176 z += 1
1176 z += 1
1177 progress(_updating, z, item=f, total=numupdates, unit=_files)
1177 progress(_updating, z, item=f, total=numupdates, unit=_files)
1178
1178
1179 # re-add (manifest only, just log it)
1179 # re-add (manifest only, just log it)
1180 for f, args, msg in actions['a']:
1180 for f, args, msg in actions['a']:
1181 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1181 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1182 z += 1
1182 z += 1
1183 progress(_updating, z, item=f, total=numupdates, unit=_files)
1183 progress(_updating, z, item=f, total=numupdates, unit=_files)
1184
1184
1185 # re-add/mark as modified (manifest only, just log it)
1185 # re-add/mark as modified (manifest only, just log it)
1186 for f, args, msg in actions['am']:
1186 for f, args, msg in actions['am']:
1187 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1187 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1188 z += 1
1188 z += 1
1189 progress(_updating, z, item=f, total=numupdates, unit=_files)
1189 progress(_updating, z, item=f, total=numupdates, unit=_files)
1190
1190
1191 # keep (noop, just log it)
1191 # keep (noop, just log it)
1192 for f, args, msg in actions['k']:
1192 for f, args, msg in actions['k']:
1193 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1193 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1194 # no progress
1194 # no progress
1195
1195
1196 # directory rename, move local
1196 # directory rename, move local
1197 for f, args, msg in actions['dm']:
1197 for f, args, msg in actions['dm']:
1198 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1198 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1199 z += 1
1199 z += 1
1200 progress(_updating, z, item=f, total=numupdates, unit=_files)
1200 progress(_updating, z, item=f, total=numupdates, unit=_files)
1201 f0, flags = args
1201 f0, flags = args
1202 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1202 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1203 audit(f)
1203 audit(f)
1204 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1204 repo.wwrite(f, wctx.filectx(f0).data(), flags)
1205 util.unlinkpath(repo.wjoin(f0))
1205 util.unlinkpath(repo.wjoin(f0))
1206 updated += 1
1206 updated += 1
1207
1207
1208 # local directory rename, get
1208 # local directory rename, get
1209 for f, args, msg in actions['dg']:
1209 for f, args, msg in actions['dg']:
1210 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1210 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1211 z += 1
1211 z += 1
1212 progress(_updating, z, item=f, total=numupdates, unit=_files)
1212 progress(_updating, z, item=f, total=numupdates, unit=_files)
1213 f0, flags = args
1213 f0, flags = args
1214 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1214 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1215 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1215 repo.wwrite(f, mctx.filectx(f0).data(), flags)
1216 updated += 1
1216 updated += 1
1217
1217
1218 # exec
1218 # exec
1219 for f, args, msg in actions['e']:
1219 for f, args, msg in actions['e']:
1220 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1220 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1221 z += 1
1221 z += 1
1222 progress(_updating, z, item=f, total=numupdates, unit=_files)
1222 progress(_updating, z, item=f, total=numupdates, unit=_files)
1223 flags, = args
1223 flags, = args
1224 audit(f)
1224 audit(f)
1225 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1225 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
1226 updated += 1
1226 updated += 1
1227
1227
1228 # the ordering is important here -- ms.mergedriver will raise if the merge
1228 # the ordering is important here -- ms.mergedriver will raise if the merge
1229 # driver has changed, and we want to be able to bypass it when overwrite is
1229 # driver has changed, and we want to be able to bypass it when overwrite is
1230 # True
1230 # True
1231 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1231 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1232
1232
1233 if usemergedriver:
1233 if usemergedriver:
1234 ms.commit()
1234 ms.commit()
1235 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1235 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1236 # the driver might leave some files unresolved
1236 # the driver might leave some files unresolved
1237 unresolvedf = set(ms.unresolved())
1237 unresolvedf = set(ms.unresolved())
1238 if not proceed:
1238 if not proceed:
1239 # XXX setting unresolved to at least 1 is a hack to make sure we
1239 # XXX setting unresolved to at least 1 is a hack to make sure we
1240 # error out
1240 # error out
1241 return updated, merged, removed, max(len(unresolvedf), 1)
1241 return updated, merged, removed, max(len(unresolvedf), 1)
1242 newactions = []
1242 newactions = []
1243 for f, args, msg in mergeactions:
1243 for f, args, msg in mergeactions:
1244 if f in unresolvedf:
1244 if f in unresolvedf:
1245 newactions.append((f, args, msg))
1245 newactions.append((f, args, msg))
1246 mergeactions = newactions
1246 mergeactions = newactions
1247
1247
1248 # premerge
1248 # premerge
1249 tocomplete = []
1249 tocomplete = []
1250 for f, args, msg in mergeactions:
1250 for f, args, msg in mergeactions:
1251 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1251 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1252 z += 1
1252 z += 1
1253 progress(_updating, z, item=f, total=numupdates, unit=_files)
1253 progress(_updating, z, item=f, total=numupdates, unit=_files)
1254 if f == '.hgsubstate': # subrepo states need updating
1254 if f == '.hgsubstate': # subrepo states need updating
1255 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1255 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1256 overwrite)
1256 overwrite)
1257 continue
1257 continue
1258 audit(f)
1258 audit(f)
1259 complete, r = ms.preresolve(f, wctx)
1259 complete, r = ms.preresolve(f, wctx)
1260 if not complete:
1260 if not complete:
1261 numupdates += 1
1261 numupdates += 1
1262 tocomplete.append((f, args, msg))
1262 tocomplete.append((f, args, msg))
1263
1263
1264 # merge
1264 # merge
1265 for f, args, msg in tocomplete:
1265 for f, args, msg in tocomplete:
1266 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1266 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1267 z += 1
1267 z += 1
1268 progress(_updating, z, item=f, total=numupdates, unit=_files)
1268 progress(_updating, z, item=f, total=numupdates, unit=_files)
1269 ms.resolve(f, wctx)
1269 ms.resolve(f, wctx)
1270
1270
1271 ms.commit()
1271 ms.commit()
1272
1272
1273 unresolved = ms.unresolvedcount()
1273 unresolved = ms.unresolvedcount()
1274
1274
1275 if usemergedriver and not unresolved and ms.mdstate() != 's':
1275 if usemergedriver and not unresolved and ms.mdstate() != 's':
1276 if not driverconclude(repo, ms, wctx, labels=labels):
1276 if not driverconclude(repo, ms, wctx, labels=labels):
1277 # XXX setting unresolved to at least 1 is a hack to make sure we
1277 # XXX setting unresolved to at least 1 is a hack to make sure we
1278 # error out
1278 # error out
1279 unresolved = max(unresolved, 1)
1279 unresolved = max(unresolved, 1)
1280
1280
1281 ms.commit()
1281 ms.commit()
1282
1282
1283 msupdated, msmerged, msremoved = ms.counts()
1283 msupdated, msmerged, msremoved = ms.counts()
1284 updated += msupdated
1284 updated += msupdated
1285 merged += msmerged
1285 merged += msmerged
1286 removed += msremoved
1286 removed += msremoved
1287
1287
1288 extraactions = ms.actions()
1288 extraactions = ms.actions()
1289 for k, acts in extraactions.iteritems():
1289 for k, acts in extraactions.iteritems():
1290 actions[k].extend(acts)
1290 actions[k].extend(acts)
1291
1291
1292 progress(_updating, None, total=numupdates, unit=_files)
1292 progress(_updating, None, total=numupdates, unit=_files)
1293
1293
1294 return updated, merged, removed, unresolved
1294 return updated, merged, removed, unresolved
1295
1295
1296 def recordupdates(repo, actions, branchmerge):
1296 def recordupdates(repo, actions, branchmerge):
1297 "record merge actions to the dirstate"
1297 "record merge actions to the dirstate"
1298 # remove (must come first)
1298 # remove (must come first)
1299 for f, args, msg in actions.get('r', []):
1299 for f, args, msg in actions.get('r', []):
1300 if branchmerge:
1300 if branchmerge:
1301 repo.dirstate.remove(f)
1301 repo.dirstate.remove(f)
1302 else:
1302 else:
1303 repo.dirstate.drop(f)
1303 repo.dirstate.drop(f)
1304
1304
1305 # forget (must come first)
1305 # forget (must come first)
1306 for f, args, msg in actions.get('f', []):
1306 for f, args, msg in actions.get('f', []):
1307 repo.dirstate.drop(f)
1307 repo.dirstate.drop(f)
1308
1308
1309 # re-add
1309 # re-add
1310 for f, args, msg in actions.get('a', []):
1310 for f, args, msg in actions.get('a', []):
1311 repo.dirstate.add(f)
1311 repo.dirstate.add(f)
1312
1312
1313 # re-add/mark as modified
1313 # re-add/mark as modified
1314 for f, args, msg in actions.get('am', []):
1314 for f, args, msg in actions.get('am', []):
1315 if branchmerge:
1315 if branchmerge:
1316 repo.dirstate.normallookup(f)
1316 repo.dirstate.normallookup(f)
1317 else:
1317 else:
1318 repo.dirstate.add(f)
1318 repo.dirstate.add(f)
1319
1319
1320 # exec change
1320 # exec change
1321 for f, args, msg in actions.get('e', []):
1321 for f, args, msg in actions.get('e', []):
1322 repo.dirstate.normallookup(f)
1322 repo.dirstate.normallookup(f)
1323
1323
1324 # keep
1324 # keep
1325 for f, args, msg in actions.get('k', []):
1325 for f, args, msg in actions.get('k', []):
1326 pass
1326 pass
1327
1327
1328 # get
1328 # get
1329 for f, args, msg in actions.get('g', []):
1329 for f, args, msg in actions.get('g', []):
1330 if branchmerge:
1330 if branchmerge:
1331 repo.dirstate.otherparent(f)
1331 repo.dirstate.otherparent(f)
1332 else:
1332 else:
1333 repo.dirstate.normal(f)
1333 repo.dirstate.normal(f)
1334
1334
1335 # merge
1335 # merge
1336 for f, args, msg in actions.get('m', []):
1336 for f, args, msg in actions.get('m', []):
1337 f1, f2, fa, move, anc = args
1337 f1, f2, fa, move, anc = args
1338 if branchmerge:
1338 if branchmerge:
1339 # We've done a branch merge, mark this file as merged
1339 # We've done a branch merge, mark this file as merged
1340 # so that we properly record the merger later
1340 # so that we properly record the merger later
1341 repo.dirstate.merge(f)
1341 repo.dirstate.merge(f)
1342 if f1 != f2: # copy/rename
1342 if f1 != f2: # copy/rename
1343 if move:
1343 if move:
1344 repo.dirstate.remove(f1)
1344 repo.dirstate.remove(f1)
1345 if f1 != f:
1345 if f1 != f:
1346 repo.dirstate.copy(f1, f)
1346 repo.dirstate.copy(f1, f)
1347 else:
1347 else:
1348 repo.dirstate.copy(f2, f)
1348 repo.dirstate.copy(f2, f)
1349 else:
1349 else:
1350 # We've update-merged a locally modified file, so
1350 # We've update-merged a locally modified file, so
1351 # we set the dirstate to emulate a normal checkout
1351 # we set the dirstate to emulate a normal checkout
1352 # of that file some time in the past. Thus our
1352 # of that file some time in the past. Thus our
1353 # merge will appear as a normal local file
1353 # merge will appear as a normal local file
1354 # modification.
1354 # modification.
1355 if f2 == f: # file not locally copied/moved
1355 if f2 == f: # file not locally copied/moved
1356 repo.dirstate.normallookup(f)
1356 repo.dirstate.normallookup(f)
1357 if move:
1357 if move:
1358 repo.dirstate.drop(f1)
1358 repo.dirstate.drop(f1)
1359
1359
1360 # directory rename, move local
1360 # directory rename, move local
1361 for f, args, msg in actions.get('dm', []):
1361 for f, args, msg in actions.get('dm', []):
1362 f0, flag = args
1362 f0, flag = args
1363 if branchmerge:
1363 if branchmerge:
1364 repo.dirstate.add(f)
1364 repo.dirstate.add(f)
1365 repo.dirstate.remove(f0)
1365 repo.dirstate.remove(f0)
1366 repo.dirstate.copy(f0, f)
1366 repo.dirstate.copy(f0, f)
1367 else:
1367 else:
1368 repo.dirstate.normal(f)
1368 repo.dirstate.normal(f)
1369 repo.dirstate.drop(f0)
1369 repo.dirstate.drop(f0)
1370
1370
1371 # directory rename, get
1371 # directory rename, get
1372 for f, args, msg in actions.get('dg', []):
1372 for f, args, msg in actions.get('dg', []):
1373 f0, flag = args
1373 f0, flag = args
1374 if branchmerge:
1374 if branchmerge:
1375 repo.dirstate.add(f)
1375 repo.dirstate.add(f)
1376 repo.dirstate.copy(f0, f)
1376 repo.dirstate.copy(f0, f)
1377 else:
1377 else:
1378 repo.dirstate.normal(f)
1378 repo.dirstate.normal(f)
1379
1379
1380 def update(repo, node, branchmerge, force, ancestor=None,
1380 def update(repo, node, branchmerge, force, ancestor=None,
1381 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1381 mergeancestor=False, labels=None, matcher=None, mergeforce=False):
1382 """
1382 """
1383 Perform a merge between the working directory and the given node
1383 Perform a merge between the working directory and the given node
1384
1384
1385 node = the node to update to, or None if unspecified
1385 node = the node to update to, or None if unspecified
1386 branchmerge = whether to merge between branches
1386 branchmerge = whether to merge between branches
1387 force = whether to force branch merging or file overwriting
1387 force = whether to force branch merging or file overwriting
1388 matcher = a matcher to filter file lists (dirstate not updated)
1388 matcher = a matcher to filter file lists (dirstate not updated)
1389 mergeancestor = whether it is merging with an ancestor. If true,
1389 mergeancestor = whether it is merging with an ancestor. If true,
1390 we should accept the incoming changes for any prompts that occur.
1390 we should accept the incoming changes for any prompts that occur.
1391 If false, merging with an ancestor (fast-forward) is only allowed
1391 If false, merging with an ancestor (fast-forward) is only allowed
1392 between different named branches. This flag is used by rebase extension
1392 between different named branches. This flag is used by rebase extension
1393 as a temporary fix and should be avoided in general.
1393 as a temporary fix and should be avoided in general.
1394 labels = labels to use for base, local and other
1394 labels = labels to use for base, local and other
1395 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1395 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1396 this is True, then 'force' should be True as well.
1396 this is True, then 'force' should be True as well.
1397
1397
1398 The table below shows all the behaviors of the update command
1398 The table below shows all the behaviors of the update command
1399 given the -c and -C or no options, whether the working directory
1399 given the -c and -C or no options, whether the working directory
1400 is dirty, whether a revision is specified, and the relationship of
1400 is dirty, whether a revision is specified, and the relationship of
1401 the parent rev to the target rev (linear, on the same named
1401 the parent rev to the target rev (linear, on the same named
1402 branch, or on another named branch).
1402 branch, or on another named branch).
1403
1403
1404 This logic is tested by test-update-branches.t.
1404 This logic is tested by test-update-branches.t.
1405
1405
1406 -c -C dirty rev | linear same cross
1406 -c -C dirty rev | linear same cross
1407 n n n n | ok (1) x
1407 n n n n | ok (1) x
1408 n n n y | ok ok ok
1408 n n n y | ok ok ok
1409 n n y n | merge (2) (2)
1409 n n y n | merge (2) (2)
1410 n n y y | merge (3) (3)
1410 n n y y | merge (3) (3)
1411 n y * * | discard discard discard
1411 n y * * | discard discard discard
1412 y n y * | (4) (4) (4)
1412 y n y * | (4) (4) (4)
1413 y n n * | ok ok ok
1413 y n n * | ok ok ok
1414 y y * * | (5) (5) (5)
1414 y y * * | (5) (5) (5)
1415
1415
1416 x = can't happen
1416 x = can't happen
1417 * = don't-care
1417 * = don't-care
1418 1 = abort: not a linear update (merge or update --check to force update)
1418 1 = abort: not a linear update (merge or update --check to force update)
1419 2 = abort: uncommitted changes (commit and merge, or update --clean to
1419 2 = abort: uncommitted changes (commit and merge, or update --clean to
1420 discard changes)
1420 discard changes)
1421 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1421 3 = abort: uncommitted changes (commit or update --clean to discard changes)
1422 4 = abort: uncommitted changes (checked in commands.py)
1422 4 = abort: uncommitted changes (checked in commands.py)
1423 5 = incompatible options (checked in commands.py)
1423 5 = incompatible options (checked in commands.py)
1424
1424
1425 Return the same tuple as applyupdates().
1425 Return the same tuple as applyupdates().
1426 """
1426 """
1427
1427
1428 onode = node
1428 onode = node
1429 # If we're doing a partial update, we need to skip updating
1429 # If we're doing a partial update, we need to skip updating
1430 # the dirstate, so make a note of any partial-ness to the
1430 # the dirstate, so make a note of any partial-ness to the
1431 # update here.
1431 # update here.
1432 if matcher is None or matcher.always():
1432 if matcher is None or matcher.always():
1433 partial = False
1433 partial = False
1434 else:
1434 else:
1435 partial = True
1435 partial = True
1436 with repo.wlock():
1436 with repo.wlock():
1437 wc = repo[None]
1437 wc = repo[None]
1438 pl = wc.parents()
1438 pl = wc.parents()
1439 p1 = pl[0]
1439 p1 = pl[0]
1440 pas = [None]
1440 pas = [None]
1441 if ancestor is not None:
1441 if ancestor is not None:
1442 pas = [repo[ancestor]]
1442 pas = [repo[ancestor]]
1443
1443
1444 if node is None:
1444 if node is None:
1445 repo.ui.deprecwarn('update with no target', '3.9')
1445 repo.ui.deprecwarn('update with no target', '3.9')
1446 rev, _mark, _act = destutil.destupdate(repo)
1446 rev, _mark, _act = destutil.destupdate(repo)
1447 node = repo[rev].node()
1447 node = repo[rev].node()
1448
1448
1449 overwrite = force and not branchmerge
1449 overwrite = force and not branchmerge
1450
1450
1451 p2 = repo[node]
1451 p2 = repo[node]
1452 if pas[0] is None:
1452 if pas[0] is None:
1453 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1453 if repo.ui.configlist('merge', 'preferancestor', ['*']) == ['*']:
1454 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1454 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1455 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1455 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1456 else:
1456 else:
1457 pas = [p1.ancestor(p2, warn=branchmerge)]
1457 pas = [p1.ancestor(p2, warn=branchmerge)]
1458
1458
1459 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1459 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1460
1460
1461 ### check phase
1461 ### check phase
1462 if not overwrite:
1462 if not overwrite:
1463 if len(pl) > 1:
1463 if len(pl) > 1:
1464 raise error.Abort(_("outstanding uncommitted merge"))
1464 raise error.Abort(_("outstanding uncommitted merge"))
1465 ms = mergestate.read(repo)
1465 ms = mergestate.read(repo)
1466 if list(ms.unresolved()):
1466 if list(ms.unresolved()):
1467 raise error.Abort(_("outstanding merge conflicts"))
1467 raise error.Abort(_("outstanding merge conflicts"))
1468 if branchmerge:
1468 if branchmerge:
1469 if pas == [p2]:
1469 if pas == [p2]:
1470 raise error.Abort(_("merging with a working directory ancestor"
1470 raise error.Abort(_("merging with a working directory ancestor"
1471 " has no effect"))
1471 " has no effect"))
1472 elif pas == [p1]:
1472 elif pas == [p1]:
1473 if not mergeancestor and p1.branch() == p2.branch():
1473 if not mergeancestor and p1.branch() == p2.branch():
1474 raise error.Abort(_("nothing to merge"),
1474 raise error.Abort(_("nothing to merge"),
1475 hint=_("use 'hg update' "
1475 hint=_("use 'hg update' "
1476 "or check 'hg heads'"))
1476 "or check 'hg heads'"))
1477 if not force and (wc.files() or wc.deleted()):
1477 if not force and (wc.files() or wc.deleted()):
1478 raise error.Abort(_("uncommitted changes"),
1478 raise error.Abort(_("uncommitted changes"),
1479 hint=_("use 'hg status' to list changes"))
1479 hint=_("use 'hg status' to list changes"))
1480 for s in sorted(wc.substate):
1480 for s in sorted(wc.substate):
1481 wc.sub(s).bailifchanged()
1481 wc.sub(s).bailifchanged()
1482
1482
1483 elif not overwrite:
1483 elif not overwrite:
1484 if p1 == p2: # no-op update
1484 if p1 == p2: # no-op update
1485 # call the hooks and exit early
1485 # call the hooks and exit early
1486 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1486 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1487 repo.hook('update', parent1=xp2, parent2='', error=0)
1487 repo.hook('update', parent1=xp2, parent2='', error=0)
1488 return 0, 0, 0, 0
1488 return 0, 0, 0, 0
1489
1489
1490 if pas not in ([p1], [p2]): # nonlinear
1490 if pas not in ([p1], [p2]): # nonlinear
1491 dirty = wc.dirty(missing=True)
1491 dirty = wc.dirty(missing=True)
1492 if dirty or onode is None:
1492 if dirty or onode is None:
1493 # Branching is a bit strange to ensure we do the minimal
1493 # Branching is a bit strange to ensure we do the minimal
1494 # amount of call to obsolete.background.
1494 # amount of call to obsolete.background.
1495 foreground = obsolete.foreground(repo, [p1.node()])
1495 foreground = obsolete.foreground(repo, [p1.node()])
1496 # note: the <node> variable contains a random identifier
1496 # note: the <node> variable contains a random identifier
1497 if repo[node].node() in foreground:
1497 if repo[node].node() in foreground:
1498 pas = [p1] # allow updating to successors
1498 pas = [p1] # allow updating to successors
1499 elif dirty:
1499 elif dirty:
1500 msg = _("uncommitted changes")
1500 msg = _("uncommitted changes")
1501 if onode is None:
1501 if onode is None:
1502 hint = _("commit and merge, or update --clean to"
1502 hint = _("commit and merge, or update --clean to"
1503 " discard changes")
1503 " discard changes")
1504 else:
1504 else:
1505 hint = _("commit or update --clean to discard"
1505 hint = _("commit or update --clean to discard"
1506 " changes")
1506 " changes")
1507 raise error.Abort(msg, hint=hint)
1507 raise error.Abort(msg, hint=hint)
1508 else: # node is none
1508 else: # node is none
1509 msg = _("not a linear update")
1509 msg = _("not a linear update")
1510 hint = _("merge or update --check to force update")
1510 hint = _("merge or update --check to force update")
1511 raise error.Abort(msg, hint=hint)
1511 raise error.Abort(msg, hint=hint)
1512 else:
1512 else:
1513 # Allow jumping branches if clean and specific rev given
1513 # Allow jumping branches if clean and specific rev given
1514 pas = [p1]
1514 pas = [p1]
1515
1515
1516 # deprecated config: merge.followcopies
1516 # deprecated config: merge.followcopies
1517 followcopies = False
1517 followcopies = False
1518 if overwrite:
1518 if overwrite:
1519 pas = [wc]
1519 pas = [wc]
1520 elif pas == [p2]: # backwards
1520 elif pas == [p2]: # backwards
1521 pas = [wc.p1()]
1521 pas = [wc.p1()]
1522 elif not branchmerge and not wc.dirty(missing=True):
1522 elif not branchmerge and not wc.dirty(missing=True):
1523 pass
1523 pass
1524 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1524 elif pas[0] and repo.ui.configbool('merge', 'followcopies', True):
1525 followcopies = True
1525 followcopies = True
1526
1526
1527 ### calculate phase
1527 ### calculate phase
1528 actionbyfile, diverge, renamedelete = calculateupdates(
1528 actionbyfile, diverge, renamedelete = calculateupdates(
1529 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1529 repo, wc, p2, pas, branchmerge, force, mergeancestor,
1530 followcopies, matcher=matcher, mergeforce=mergeforce)
1530 followcopies, matcher=matcher, mergeforce=mergeforce)
1531
1531
1532 # Prompt and create actions. Most of this is in the resolve phase
1532 # Prompt and create actions. Most of this is in the resolve phase
1533 # already, but we can't handle .hgsubstate in filemerge or
1533 # already, but we can't handle .hgsubstate in filemerge or
1534 # subrepo.submerge yet so we have to keep prompting for it.
1534 # subrepo.submerge yet so we have to keep prompting for it.
1535 if '.hgsubstate' in actionbyfile:
1535 if '.hgsubstate' in actionbyfile:
1536 f = '.hgsubstate'
1536 f = '.hgsubstate'
1537 m, args, msg = actionbyfile[f]
1537 m, args, msg = actionbyfile[f]
1538 if m == 'cd':
1538 if m == 'cd':
1539 if repo.ui.promptchoice(
1539 if repo.ui.promptchoice(
1540 _("local changed %s which remote deleted\n"
1540 _("local changed %s which remote deleted\n"
1541 "use (c)hanged version or (d)elete?"
1541 "use (c)hanged version or (d)elete?"
1542 "$$ &Changed $$ &Delete") % f, 0):
1542 "$$ &Changed $$ &Delete") % f, 0):
1543 actionbyfile[f] = ('r', None, "prompt delete")
1543 actionbyfile[f] = ('r', None, "prompt delete")
1544 elif f in p1:
1544 elif f in p1:
1545 actionbyfile[f] = ('am', None, "prompt keep")
1545 actionbyfile[f] = ('am', None, "prompt keep")
1546 else:
1546 else:
1547 actionbyfile[f] = ('a', None, "prompt keep")
1547 actionbyfile[f] = ('a', None, "prompt keep")
1548 elif m == 'dc':
1548 elif m == 'dc':
1549 f1, f2, fa, move, anc = args
1549 f1, f2, fa, move, anc = args
1550 flags = p2[f2].flags()
1550 flags = p2[f2].flags()
1551 if repo.ui.promptchoice(
1551 if repo.ui.promptchoice(
1552 _("remote changed %s which local deleted\n"
1552 _("remote changed %s which local deleted\n"
1553 "use (c)hanged version or leave (d)eleted?"
1553 "use (c)hanged version or leave (d)eleted?"
1554 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1554 "$$ &Changed $$ &Deleted") % f, 0) == 0:
1555 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1555 actionbyfile[f] = ('g', (flags, False), "prompt recreating")
1556 else:
1556 else:
1557 del actionbyfile[f]
1557 del actionbyfile[f]
1558
1558
1559 # Convert to dictionary-of-lists format
1559 # Convert to dictionary-of-lists format
1560 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1560 actions = dict((m, []) for m in 'a am f g cd dc r dm dg m e k'.split())
1561 for f, (m, args, msg) in actionbyfile.iteritems():
1561 for f, (m, args, msg) in actionbyfile.iteritems():
1562 if m not in actions:
1562 if m not in actions:
1563 actions[m] = []
1563 actions[m] = []
1564 actions[m].append((f, args, msg))
1564 actions[m].append((f, args, msg))
1565
1565
1566 if not util.checkcase(repo.path):
1566 if not util.checkcase(repo.path):
1567 # check collision between files only in p2 for clean update
1567 # check collision between files only in p2 for clean update
1568 if (not branchmerge and
1568 if (not branchmerge and
1569 (force or not wc.dirty(missing=True, branch=False))):
1569 (force or not wc.dirty(missing=True, branch=False))):
1570 _checkcollision(repo, p2.manifest(), None)
1570 _checkcollision(repo, p2.manifest(), None)
1571 else:
1571 else:
1572 _checkcollision(repo, wc.manifest(), actions)
1572 _checkcollision(repo, wc.manifest(), actions)
1573
1573
1574 # divergent renames
1574 # divergent renames
1575 for f, fl in sorted(diverge.iteritems()):
1575 for f, fl in sorted(diverge.iteritems()):
1576 repo.ui.warn(_("note: possible conflict - %s was renamed "
1576 repo.ui.warn(_("note: possible conflict - %s was renamed "
1577 "multiple times to:\n") % f)
1577 "multiple times to:\n") % f)
1578 for nf in fl:
1578 for nf in fl:
1579 repo.ui.warn(" %s\n" % nf)
1579 repo.ui.warn(" %s\n" % nf)
1580
1580
1581 # rename and delete
1581 # rename and delete
1582 for f, fl in sorted(renamedelete.iteritems()):
1582 for f, fl in sorted(renamedelete.iteritems()):
1583 repo.ui.warn(_("note: possible conflict - %s was deleted "
1583 repo.ui.warn(_("note: possible conflict - %s was deleted "
1584 "and renamed to:\n") % f)
1584 "and renamed to:\n") % f)
1585 for nf in fl:
1585 for nf in fl:
1586 repo.ui.warn(" %s\n" % nf)
1586 repo.ui.warn(" %s\n" % nf)
1587
1587
1588 ### apply phase
1588 ### apply phase
1589 if not branchmerge: # just jump to the new rev
1589 if not branchmerge: # just jump to the new rev
1590 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1590 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1591 if not partial:
1591 if not partial:
1592 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1592 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1593 # note that we're in the middle of an update
1593 # note that we're in the middle of an update
1594 repo.vfs.write('updatestate', p2.hex())
1594 repo.vfs.write('updatestate', p2.hex())
1595
1595
1596 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1596 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1597
1597
1598 if not partial:
1598 if not partial:
1599 repo.dirstate.beginparentchange()
1599 repo.dirstate.beginparentchange()
1600 repo.setparents(fp1, fp2)
1600 repo.setparents(fp1, fp2)
1601 recordupdates(repo, actions, branchmerge)
1601 recordupdates(repo, actions, branchmerge)
1602 # update completed, clear state
1602 # update completed, clear state
1603 util.unlink(repo.join('updatestate'))
1603 util.unlink(repo.join('updatestate'))
1604
1604
1605 if not branchmerge:
1605 if not branchmerge:
1606 repo.dirstate.setbranch(p2.branch())
1606 repo.dirstate.setbranch(p2.branch())
1607 repo.dirstate.endparentchange()
1607 repo.dirstate.endparentchange()
1608
1608
1609 if not partial:
1609 if not partial:
1610 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1610 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1611 return stats
1611 return stats
1612
1612
1613 def graft(repo, ctx, pctx, labels, keepparent=False):
1613 def graft(repo, ctx, pctx, labels, keepparent=False):
1614 """Do a graft-like merge.
1614 """Do a graft-like merge.
1615
1615
1616 This is a merge where the merge ancestor is chosen such that one
1616 This is a merge where the merge ancestor is chosen such that one
1617 or more changesets are grafted onto the current changeset. In
1617 or more changesets are grafted onto the current changeset. In
1618 addition to the merge, this fixes up the dirstate to include only
1618 addition to the merge, this fixes up the dirstate to include only
1619 a single parent (if keepparent is False) and tries to duplicate any
1619 a single parent (if keepparent is False) and tries to duplicate any
1620 renames/copies appropriately.
1620 renames/copies appropriately.
1621
1621
1622 ctx - changeset to rebase
1622 ctx - changeset to rebase
1623 pctx - merge base, usually ctx.p1()
1623 pctx - merge base, usually ctx.p1()
1624 labels - merge labels eg ['local', 'graft']
1624 labels - merge labels eg ['local', 'graft']
1625 keepparent - keep second parent if any
1625 keepparent - keep second parent if any
1626
1626
1627 """
1627 """
1628 # If we're grafting a descendant onto an ancestor, be sure to pass
1628 # If we're grafting a descendant onto an ancestor, be sure to pass
1629 # mergeancestor=True to update. This does two things: 1) allows the merge if
1629 # mergeancestor=True to update. This does two things: 1) allows the merge if
1630 # the destination is the same as the parent of the ctx (so we can use graft
1630 # the destination is the same as the parent of the ctx (so we can use graft
1631 # to copy commits), and 2) informs update that the incoming changes are
1631 # to copy commits), and 2) informs update that the incoming changes are
1632 # newer than the destination so it doesn't prompt about "remote changed foo
1632 # newer than the destination so it doesn't prompt about "remote changed foo
1633 # which local deleted".
1633 # which local deleted".
1634 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1634 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
1635
1635
1636 stats = update(repo, ctx.node(), True, True, pctx.node(),
1636 stats = update(repo, ctx.node(), True, True, pctx.node(),
1637 mergeancestor=mergeancestor, labels=labels)
1637 mergeancestor=mergeancestor, labels=labels)
1638
1638
1639 pother = nullid
1639 pother = nullid
1640 parents = ctx.parents()
1640 parents = ctx.parents()
1641 if keepparent and len(parents) == 2 and pctx in parents:
1641 if keepparent and len(parents) == 2 and pctx in parents:
1642 parents.remove(pctx)
1642 parents.remove(pctx)
1643 pother = parents[0].node()
1643 pother = parents[0].node()
1644
1644
1645 repo.dirstate.beginparentchange()
1645 repo.dirstate.beginparentchange()
1646 repo.setparents(repo['.'].node(), pother)
1646 repo.setparents(repo['.'].node(), pother)
1647 repo.dirstate.write(repo.currenttransaction())
1647 repo.dirstate.write(repo.currenttransaction())
1648 # fix up dirstate for copies and renames
1648 # fix up dirstate for copies and renames
1649 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1649 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1650 repo.dirstate.endparentchange()
1650 repo.dirstate.endparentchange()
1651 return stats
1651 return stats
@@ -1,55 +1,74 b''
1 Test update logic when there are renames or weird same-name cases between dirs
1 Test update logic when there are renames or weird same-name cases between dirs
2 and files
2 and files
3
3
4 Update with local changes across a file rename
4 Update with local changes across a file rename
5
5
6 $ hg init r1 && cd r1
6 $ hg init r1 && cd r1
7
7
8 $ echo a > a
8 $ echo a > a
9 $ hg add a
9 $ hg add a
10 $ hg ci -m a
10 $ hg ci -m a
11
11
12 $ hg mv a b
12 $ hg mv a b
13 $ hg ci -m rename
13 $ hg ci -m rename
14
14
15 $ echo b > b
15 $ echo b > b
16 $ hg ci -m change
16 $ hg ci -m change
17
17
18 $ hg up -q 0
18 $ hg up -q 0
19
19
20 $ echo c > a
20 $ echo c > a
21
21
22 $ hg up
22 $ hg up
23 merging a and b to b
23 merging a and b to b
24 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
24 warning: conflicts while merging b! (edit, then use 'hg resolve --mark')
25 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
25 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
26 use 'hg resolve' to retry unresolved file merges
26 use 'hg resolve' to retry unresolved file merges
27 [1]
27 [1]
28
28
29 Test update when local untracked directory exists with the same name as a
29 Test update when local untracked directory exists with the same name as a
30 tracked file in a commit we are updating to
30 tracked file in a commit we are updating to
31 $ hg init r2 && cd r2
31 $ hg init r2 && cd r2
32 $ echo root > root && hg ci -Am root # rev 0
32 $ echo root > root && hg ci -Am root # rev 0
33 adding root
33 adding root
34 $ echo text > name && hg ci -Am "name is a file" # rev 1
34 $ echo text > name && hg ci -Am "name is a file" # rev 1
35 adding name
35 adding name
36 $ hg up 0
36 $ hg up 0
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
37 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
38 $ mkdir name
38 $ mkdir name
39 $ hg up 1
39 $ hg up 1
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
40 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
41
41
42 Test update when local untracked directory exists with some files in it and has
42 Test update when local untracked directory exists with some files in it and has
43 the same name a tracked file in a commit we are updating to. In future this
43 the same name a tracked file in a commit we are updating to. In future this
44 should be updated to give an friendlier error message, but now we should just
44 should be updated to give an friendlier error message, but now we should just
45 make sure that this does not erase untracked data
45 make sure that this does not erase untracked data
46 $ hg up 0
46 $ hg up 0
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
47 0 files updated, 0 files merged, 1 files removed, 0 files unresolved
48 $ mkdir name
48 $ mkdir name
49 $ echo text > name/file
49 $ echo text > name/file
50 $ hg st
50 $ hg st
51 ? name/file
51 ? name/file
52 $ hg up 1
52 $ hg up 1
53 abort: *: '$TESTTMP/r1/r2/name' (glob)
53 abort: *: '$TESTTMP/r1/r2/name' (glob)
54 [255]
54 [255]
55 $ cd ..
55 $ cd ..
56
57 #if symlink
58
59 Test update when two commits have symlinks that point to different folders
60 $ hg init r3 && cd r3
61 $ echo root > root && hg ci -Am root
62 adding root
63 $ mkdir folder1 && mkdir folder2
64 $ ln -s folder1 folder
65 $ hg ci -Am "symlink to folder1"
66 adding folder
67 $ rm folder
68 $ ln -s folder2 folder
69 $ hg ci -Am "symlink to folder2"
70 $ hg up 1
71 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
72 $ cd ..
73
74 #endif
General Comments 0
You need to be logged in to leave comments. Login now