##// END OF EJS Templates
update: don't overwrite untracked ignored files on update...
Martin von Zweigbergk -
r23478:30b60216 stable
parent child Browse files
Show More
@@ -1,1166 +1,1165 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 import struct
8 import struct
9
9
10 from node import nullid, nullrev, hex, bin
10 from node import nullid, nullrev, hex, bin
11 from i18n import _
11 from i18n import _
12 from mercurial import obsolete
12 from mercurial import obsolete
13 import error as errormod, util, filemerge, copies, subrepo, worker
13 import error as errormod, util, filemerge, copies, subrepo, worker
14 import errno, os, shutil
14 import errno, os, shutil
15
15
16 _pack = struct.pack
16 _pack = struct.pack
17 _unpack = struct.unpack
17 _unpack = struct.unpack
18
18
19 def _droponode(data):
19 def _droponode(data):
20 # used for compatibility for v1
20 # used for compatibility for v1
21 bits = data.split("\0")
21 bits = data.split("\0")
22 bits = bits[:-2] + bits[-1:]
22 bits = bits[:-2] + bits[-1:]
23 return "\0".join(bits)
23 return "\0".join(bits)
24
24
25 class mergestate(object):
25 class mergestate(object):
26 '''track 3-way merge state of individual files
26 '''track 3-way merge state of individual files
27
27
28 it is stored on disk when needed. Two file are used, one with an old
28 it is stored on disk when needed. Two file are used, one with an old
29 format, one with a new format. Both contains similar data, but the new
29 format, one with a new format. Both contains similar data, but the new
30 format can store new kind of field.
30 format can store new kind of field.
31
31
32 Current new format is a list of arbitrary record of the form:
32 Current new format is a list of arbitrary record of the form:
33
33
34 [type][length][content]
34 [type][length][content]
35
35
36 Type is a single character, length is a 4 bytes integer, content is an
36 Type is a single character, length is a 4 bytes integer, content is an
37 arbitrary suites of bytes of length `length`.
37 arbitrary suites of bytes of length `length`.
38
38
39 Type should be a letter. Capital letter are mandatory record, Mercurial
39 Type should be a letter. Capital letter are mandatory record, Mercurial
40 should abort if they are unknown. lower case record can be safely ignored.
40 should abort if they are unknown. lower case record can be safely ignored.
41
41
42 Currently known record:
42 Currently known record:
43
43
44 L: the node of the "local" part of the merge (hexified version)
44 L: the node of the "local" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
45 O: the node of the "other" part of the merge (hexified version)
46 F: a file to be merged entry
46 F: a file to be merged entry
47 '''
47 '''
48 statepathv1 = "merge/state"
48 statepathv1 = "merge/state"
49 statepathv2 = "merge/state2"
49 statepathv2 = "merge/state2"
50
50
51 def __init__(self, repo):
51 def __init__(self, repo):
52 self._repo = repo
52 self._repo = repo
53 self._dirty = False
53 self._dirty = False
54 self._read()
54 self._read()
55
55
56 def reset(self, node=None, other=None):
56 def reset(self, node=None, other=None):
57 self._state = {}
57 self._state = {}
58 self._local = None
58 self._local = None
59 self._other = None
59 self._other = None
60 if node:
60 if node:
61 self._local = node
61 self._local = node
62 self._other = other
62 self._other = other
63 shutil.rmtree(self._repo.join("merge"), True)
63 shutil.rmtree(self._repo.join("merge"), True)
64 self._dirty = False
64 self._dirty = False
65
65
66 def _read(self):
66 def _read(self):
67 """Analyse each record content to restore a serialized state from disk
67 """Analyse each record content to restore a serialized state from disk
68
68
69 This function process "record" entry produced by the de-serialization
69 This function process "record" entry produced by the de-serialization
70 of on disk file.
70 of on disk file.
71 """
71 """
72 self._state = {}
72 self._state = {}
73 self._local = None
73 self._local = None
74 self._other = None
74 self._other = None
75 records = self._readrecords()
75 records = self._readrecords()
76 for rtype, record in records:
76 for rtype, record in records:
77 if rtype == 'L':
77 if rtype == 'L':
78 self._local = bin(record)
78 self._local = bin(record)
79 elif rtype == 'O':
79 elif rtype == 'O':
80 self._other = bin(record)
80 self._other = bin(record)
81 elif rtype == "F":
81 elif rtype == "F":
82 bits = record.split("\0")
82 bits = record.split("\0")
83 self._state[bits[0]] = bits[1:]
83 self._state[bits[0]] = bits[1:]
84 elif not rtype.islower():
84 elif not rtype.islower():
85 raise util.Abort(_('unsupported merge state record: %s')
85 raise util.Abort(_('unsupported merge state record: %s')
86 % rtype)
86 % rtype)
87 self._dirty = False
87 self._dirty = False
88
88
89 def _readrecords(self):
89 def _readrecords(self):
90 """Read merge state from disk and return a list of record (TYPE, data)
90 """Read merge state from disk and return a list of record (TYPE, data)
91
91
92 We read data from both v1 and v2 files and decide which one to use.
92 We read data from both v1 and v2 files and decide which one to use.
93
93
94 V1 has been used by version prior to 2.9.1 and contains less data than
94 V1 has been used by version prior to 2.9.1 and contains less data than
95 v2. We read both versions and check if no data in v2 contradicts
95 v2. We read both versions and check if no data in v2 contradicts
96 v1. If there is not contradiction we can safely assume that both v1
96 v1. If there is not contradiction we can safely assume that both v1
97 and v2 were written at the same time and use the extract data in v2. If
97 and v2 were written at the same time and use the extract data in v2. If
98 there is contradiction we ignore v2 content as we assume an old version
98 there is contradiction we ignore v2 content as we assume an old version
99 of Mercurial has overwritten the mergestate file and left an old v2
99 of Mercurial has overwritten the mergestate file and left an old v2
100 file around.
100 file around.
101
101
102 returns list of record [(TYPE, data), ...]"""
102 returns list of record [(TYPE, data), ...]"""
103 v1records = self._readrecordsv1()
103 v1records = self._readrecordsv1()
104 v2records = self._readrecordsv2()
104 v2records = self._readrecordsv2()
105 oldv2 = set() # old format version of v2 record
105 oldv2 = set() # old format version of v2 record
106 for rec in v2records:
106 for rec in v2records:
107 if rec[0] == 'L':
107 if rec[0] == 'L':
108 oldv2.add(rec)
108 oldv2.add(rec)
109 elif rec[0] == 'F':
109 elif rec[0] == 'F':
110 # drop the onode data (not contained in v1)
110 # drop the onode data (not contained in v1)
111 oldv2.add(('F', _droponode(rec[1])))
111 oldv2.add(('F', _droponode(rec[1])))
112 for rec in v1records:
112 for rec in v1records:
113 if rec not in oldv2:
113 if rec not in oldv2:
114 # v1 file is newer than v2 file, use it
114 # v1 file is newer than v2 file, use it
115 # we have to infer the "other" changeset of the merge
115 # we have to infer the "other" changeset of the merge
116 # we cannot do better than that with v1 of the format
116 # we cannot do better than that with v1 of the format
117 mctx = self._repo[None].parents()[-1]
117 mctx = self._repo[None].parents()[-1]
118 v1records.append(('O', mctx.hex()))
118 v1records.append(('O', mctx.hex()))
119 # add place holder "other" file node information
119 # add place holder "other" file node information
120 # nobody is using it yet so we do no need to fetch the data
120 # nobody is using it yet so we do no need to fetch the data
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
121 # if mctx was wrong `mctx[bits[-2]]` may fails.
122 for idx, r in enumerate(v1records):
122 for idx, r in enumerate(v1records):
123 if r[0] == 'F':
123 if r[0] == 'F':
124 bits = r[1].split("\0")
124 bits = r[1].split("\0")
125 bits.insert(-2, '')
125 bits.insert(-2, '')
126 v1records[idx] = (r[0], "\0".join(bits))
126 v1records[idx] = (r[0], "\0".join(bits))
127 return v1records
127 return v1records
128 else:
128 else:
129 return v2records
129 return v2records
130
130
131 def _readrecordsv1(self):
131 def _readrecordsv1(self):
132 """read on disk merge state for version 1 file
132 """read on disk merge state for version 1 file
133
133
134 returns list of record [(TYPE, data), ...]
134 returns list of record [(TYPE, data), ...]
135
135
136 Note: the "F" data from this file are one entry short
136 Note: the "F" data from this file are one entry short
137 (no "other file node" entry)
137 (no "other file node" entry)
138 """
138 """
139 records = []
139 records = []
140 try:
140 try:
141 f = self._repo.opener(self.statepathv1)
141 f = self._repo.opener(self.statepathv1)
142 for i, l in enumerate(f):
142 for i, l in enumerate(f):
143 if i == 0:
143 if i == 0:
144 records.append(('L', l[:-1]))
144 records.append(('L', l[:-1]))
145 else:
145 else:
146 records.append(('F', l[:-1]))
146 records.append(('F', l[:-1]))
147 f.close()
147 f.close()
148 except IOError, err:
148 except IOError, err:
149 if err.errno != errno.ENOENT:
149 if err.errno != errno.ENOENT:
150 raise
150 raise
151 return records
151 return records
152
152
153 def _readrecordsv2(self):
153 def _readrecordsv2(self):
154 """read on disk merge state for version 2 file
154 """read on disk merge state for version 2 file
155
155
156 returns list of record [(TYPE, data), ...]
156 returns list of record [(TYPE, data), ...]
157 """
157 """
158 records = []
158 records = []
159 try:
159 try:
160 f = self._repo.opener(self.statepathv2)
160 f = self._repo.opener(self.statepathv2)
161 data = f.read()
161 data = f.read()
162 off = 0
162 off = 0
163 end = len(data)
163 end = len(data)
164 while off < end:
164 while off < end:
165 rtype = data[off]
165 rtype = data[off]
166 off += 1
166 off += 1
167 length = _unpack('>I', data[off:(off + 4)])[0]
167 length = _unpack('>I', data[off:(off + 4)])[0]
168 off += 4
168 off += 4
169 record = data[off:(off + length)]
169 record = data[off:(off + length)]
170 off += length
170 off += length
171 records.append((rtype, record))
171 records.append((rtype, record))
172 f.close()
172 f.close()
173 except IOError, err:
173 except IOError, err:
174 if err.errno != errno.ENOENT:
174 if err.errno != errno.ENOENT:
175 raise
175 raise
176 return records
176 return records
177
177
178 def active(self):
178 def active(self):
179 """Whether mergestate is active.
179 """Whether mergestate is active.
180
180
181 Returns True if there appears to be mergestate. This is a rough proxy
181 Returns True if there appears to be mergestate. This is a rough proxy
182 for "is a merge in progress."
182 for "is a merge in progress."
183 """
183 """
184 # Check local variables before looking at filesystem for performance
184 # Check local variables before looking at filesystem for performance
185 # reasons.
185 # reasons.
186 return bool(self._local) or bool(self._state) or \
186 return bool(self._local) or bool(self._state) or \
187 self._repo.opener.exists(self.statepathv1) or \
187 self._repo.opener.exists(self.statepathv1) or \
188 self._repo.opener.exists(self.statepathv2)
188 self._repo.opener.exists(self.statepathv2)
189
189
190 def commit(self):
190 def commit(self):
191 """Write current state on disk (if necessary)"""
191 """Write current state on disk (if necessary)"""
192 if self._dirty:
192 if self._dirty:
193 records = []
193 records = []
194 records.append(("L", hex(self._local)))
194 records.append(("L", hex(self._local)))
195 records.append(("O", hex(self._other)))
195 records.append(("O", hex(self._other)))
196 for d, v in self._state.iteritems():
196 for d, v in self._state.iteritems():
197 records.append(("F", "\0".join([d] + v)))
197 records.append(("F", "\0".join([d] + v)))
198 self._writerecords(records)
198 self._writerecords(records)
199 self._dirty = False
199 self._dirty = False
200
200
201 def _writerecords(self, records):
201 def _writerecords(self, records):
202 """Write current state on disk (both v1 and v2)"""
202 """Write current state on disk (both v1 and v2)"""
203 self._writerecordsv1(records)
203 self._writerecordsv1(records)
204 self._writerecordsv2(records)
204 self._writerecordsv2(records)
205
205
206 def _writerecordsv1(self, records):
206 def _writerecordsv1(self, records):
207 """Write current state on disk in a version 1 file"""
207 """Write current state on disk in a version 1 file"""
208 f = self._repo.opener(self.statepathv1, "w")
208 f = self._repo.opener(self.statepathv1, "w")
209 irecords = iter(records)
209 irecords = iter(records)
210 lrecords = irecords.next()
210 lrecords = irecords.next()
211 assert lrecords[0] == 'L'
211 assert lrecords[0] == 'L'
212 f.write(hex(self._local) + "\n")
212 f.write(hex(self._local) + "\n")
213 for rtype, data in irecords:
213 for rtype, data in irecords:
214 if rtype == "F":
214 if rtype == "F":
215 f.write("%s\n" % _droponode(data))
215 f.write("%s\n" % _droponode(data))
216 f.close()
216 f.close()
217
217
218 def _writerecordsv2(self, records):
218 def _writerecordsv2(self, records):
219 """Write current state on disk in a version 2 file"""
219 """Write current state on disk in a version 2 file"""
220 f = self._repo.opener(self.statepathv2, "w")
220 f = self._repo.opener(self.statepathv2, "w")
221 for key, data in records:
221 for key, data in records:
222 assert len(key) == 1
222 assert len(key) == 1
223 format = ">sI%is" % len(data)
223 format = ">sI%is" % len(data)
224 f.write(_pack(format, key, len(data), data))
224 f.write(_pack(format, key, len(data), data))
225 f.close()
225 f.close()
226
226
227 def add(self, fcl, fco, fca, fd):
227 def add(self, fcl, fco, fca, fd):
228 """add a new (potentially?) conflicting file the merge state
228 """add a new (potentially?) conflicting file the merge state
229 fcl: file context for local,
229 fcl: file context for local,
230 fco: file context for remote,
230 fco: file context for remote,
231 fca: file context for ancestors,
231 fca: file context for ancestors,
232 fd: file path of the resulting merge.
232 fd: file path of the resulting merge.
233
233
234 note: also write the local version to the `.hg/merge` directory.
234 note: also write the local version to the `.hg/merge` directory.
235 """
235 """
236 hash = util.sha1(fcl.path()).hexdigest()
236 hash = util.sha1(fcl.path()).hexdigest()
237 self._repo.opener.write("merge/" + hash, fcl.data())
237 self._repo.opener.write("merge/" + hash, fcl.data())
238 self._state[fd] = ['u', hash, fcl.path(),
238 self._state[fd] = ['u', hash, fcl.path(),
239 fca.path(), hex(fca.filenode()),
239 fca.path(), hex(fca.filenode()),
240 fco.path(), hex(fco.filenode()),
240 fco.path(), hex(fco.filenode()),
241 fcl.flags()]
241 fcl.flags()]
242 self._dirty = True
242 self._dirty = True
243
243
244 def __contains__(self, dfile):
244 def __contains__(self, dfile):
245 return dfile in self._state
245 return dfile in self._state
246
246
247 def __getitem__(self, dfile):
247 def __getitem__(self, dfile):
248 return self._state[dfile][0]
248 return self._state[dfile][0]
249
249
250 def __iter__(self):
250 def __iter__(self):
251 return iter(sorted(self._state))
251 return iter(sorted(self._state))
252
252
253 def files(self):
253 def files(self):
254 return self._state.keys()
254 return self._state.keys()
255
255
256 def mark(self, dfile, state):
256 def mark(self, dfile, state):
257 self._state[dfile][0] = state
257 self._state[dfile][0] = state
258 self._dirty = True
258 self._dirty = True
259
259
260 def unresolved(self):
260 def unresolved(self):
261 """Obtain the paths of unresolved files."""
261 """Obtain the paths of unresolved files."""
262
262
263 for f, entry in self._state.items():
263 for f, entry in self._state.items():
264 if entry[0] == 'u':
264 if entry[0] == 'u':
265 yield f
265 yield f
266
266
267 def resolve(self, dfile, wctx, labels=None):
267 def resolve(self, dfile, wctx, labels=None):
268 """rerun merge process for file path `dfile`"""
268 """rerun merge process for file path `dfile`"""
269 if self[dfile] == 'r':
269 if self[dfile] == 'r':
270 return 0
270 return 0
271 stateentry = self._state[dfile]
271 stateentry = self._state[dfile]
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
272 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
273 octx = self._repo[self._other]
273 octx = self._repo[self._other]
274 fcd = wctx[dfile]
274 fcd = wctx[dfile]
275 fco = octx[ofile]
275 fco = octx[ofile]
276 fca = self._repo.filectx(afile, fileid=anode)
276 fca = self._repo.filectx(afile, fileid=anode)
277 # "premerge" x flags
277 # "premerge" x flags
278 flo = fco.flags()
278 flo = fco.flags()
279 fla = fca.flags()
279 fla = fca.flags()
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
280 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
281 if fca.node() == nullid:
281 if fca.node() == nullid:
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
282 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
283 afile)
283 afile)
284 elif flags == fla:
284 elif flags == fla:
285 flags = flo
285 flags = flo
286 # restore local
286 # restore local
287 f = self._repo.opener("merge/" + hash)
287 f = self._repo.opener("merge/" + hash)
288 self._repo.wwrite(dfile, f.read(), flags)
288 self._repo.wwrite(dfile, f.read(), flags)
289 f.close()
289 f.close()
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
290 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca,
291 labels=labels)
291 labels=labels)
292 if r is None:
292 if r is None:
293 # no real conflict
293 # no real conflict
294 del self._state[dfile]
294 del self._state[dfile]
295 self._dirty = True
295 self._dirty = True
296 elif not r:
296 elif not r:
297 self.mark(dfile, 'r')
297 self.mark(dfile, 'r')
298 return r
298 return r
299
299
300 def _checkunknownfile(repo, wctx, mctx, f):
300 def _checkunknownfile(repo, wctx, mctx, f):
301 return (not repo.dirstate._ignore(f)
301 return (os.path.isfile(repo.wjoin(f))
302 and os.path.isfile(repo.wjoin(f))
303 and repo.wopener.audit.check(f)
302 and repo.wopener.audit.check(f)
304 and repo.dirstate.normalize(f) not in repo.dirstate
303 and repo.dirstate.normalize(f) not in repo.dirstate
305 and mctx[f].cmp(wctx[f]))
304 and mctx[f].cmp(wctx[f]))
306
305
307 def _checkunknown(repo, wctx, mctx):
306 def _checkunknown(repo, wctx, mctx):
308 "check for collisions between unknown files and files in mctx"
307 "check for collisions between unknown files and files in mctx"
309
308
310 error = False
309 error = False
311 for f in mctx:
310 for f in mctx:
312 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
311 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
313 error = True
312 error = True
314 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
313 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
315 if error:
314 if error:
316 raise util.Abort(_("untracked files in working directory differ "
315 raise util.Abort(_("untracked files in working directory differ "
317 "from files in requested revision"))
316 "from files in requested revision"))
318
317
319 def _forgetremoved(wctx, mctx, branchmerge):
318 def _forgetremoved(wctx, mctx, branchmerge):
320 """
319 """
321 Forget removed files
320 Forget removed files
322
321
323 If we're jumping between revisions (as opposed to merging), and if
322 If we're jumping between revisions (as opposed to merging), and if
324 neither the working directory nor the target rev has the file,
323 neither the working directory nor the target rev has the file,
325 then we need to remove it from the dirstate, to prevent the
324 then we need to remove it from the dirstate, to prevent the
326 dirstate from listing the file when it is no longer in the
325 dirstate from listing the file when it is no longer in the
327 manifest.
326 manifest.
328
327
329 If we're merging, and the other revision has removed a file
328 If we're merging, and the other revision has removed a file
330 that is not present in the working directory, we need to mark it
329 that is not present in the working directory, we need to mark it
331 as removed.
330 as removed.
332 """
331 """
333
332
334 ractions = []
333 ractions = []
335 factions = xactions = []
334 factions = xactions = []
336 if branchmerge:
335 if branchmerge:
337 xactions = ractions
336 xactions = ractions
338 for f in wctx.deleted():
337 for f in wctx.deleted():
339 if f not in mctx:
338 if f not in mctx:
340 xactions.append((f, None, "forget deleted"))
339 xactions.append((f, None, "forget deleted"))
341
340
342 if not branchmerge:
341 if not branchmerge:
343 for f in wctx.removed():
342 for f in wctx.removed():
344 if f not in mctx:
343 if f not in mctx:
345 factions.append((f, None, "forget removed"))
344 factions.append((f, None, "forget removed"))
346
345
347 return ractions, factions
346 return ractions, factions
348
347
349 def _checkcollision(repo, wmf, actions):
348 def _checkcollision(repo, wmf, actions):
350 # build provisional merged manifest up
349 # build provisional merged manifest up
351 pmmf = set(wmf)
350 pmmf = set(wmf)
352
351
353 if actions:
352 if actions:
354 # k, dr, e and rd are no-op
353 # k, dr, e and rd are no-op
355 for m in 'a', 'f', 'g', 'cd', 'dc':
354 for m in 'a', 'f', 'g', 'cd', 'dc':
356 for f, args, msg in actions[m]:
355 for f, args, msg in actions[m]:
357 pmmf.add(f)
356 pmmf.add(f)
358 for f, args, msg in actions['r']:
357 for f, args, msg in actions['r']:
359 pmmf.discard(f)
358 pmmf.discard(f)
360 for f, args, msg in actions['dm']:
359 for f, args, msg in actions['dm']:
361 f2, flags = args
360 f2, flags = args
362 pmmf.discard(f2)
361 pmmf.discard(f2)
363 pmmf.add(f)
362 pmmf.add(f)
364 for f, args, msg in actions['dg']:
363 for f, args, msg in actions['dg']:
365 f2, flags = args
364 f2, flags = args
366 pmmf.add(f)
365 pmmf.add(f)
367 for f, args, msg in actions['m']:
366 for f, args, msg in actions['m']:
368 f1, f2, fa, move, anc = args
367 f1, f2, fa, move, anc = args
369 if move:
368 if move:
370 pmmf.discard(f1)
369 pmmf.discard(f1)
371 pmmf.add(f)
370 pmmf.add(f)
372
371
373 # check case-folding collision in provisional merged manifest
372 # check case-folding collision in provisional merged manifest
374 foldmap = {}
373 foldmap = {}
375 for f in sorted(pmmf):
374 for f in sorted(pmmf):
376 fold = util.normcase(f)
375 fold = util.normcase(f)
377 if fold in foldmap:
376 if fold in foldmap:
378 raise util.Abort(_("case-folding collision between %s and %s")
377 raise util.Abort(_("case-folding collision between %s and %s")
379 % (f, foldmap[fold]))
378 % (f, foldmap[fold]))
380 foldmap[fold] = f
379 foldmap[fold] = f
381
380
382 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
381 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, partial,
383 acceptremote, followcopies):
382 acceptremote, followcopies):
384 """
383 """
385 Merge p1 and p2 with ancestor pa and generate merge action list
384 Merge p1 and p2 with ancestor pa and generate merge action list
386
385
387 branchmerge and force are as passed in to update
386 branchmerge and force are as passed in to update
388 partial = function to filter file lists
387 partial = function to filter file lists
389 acceptremote = accept the incoming changes without prompting
388 acceptremote = accept the incoming changes without prompting
390 """
389 """
391
390
392 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
391 actions = dict((m, []) for m in 'a f g cd dc r dm dg m dr e rd k'.split())
393 copy, movewithdir = {}, {}
392 copy, movewithdir = {}, {}
394
393
395 # manifests fetched in order are going to be faster, so prime the caches
394 # manifests fetched in order are going to be faster, so prime the caches
396 [x.manifest() for x in
395 [x.manifest() for x in
397 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
396 sorted(wctx.parents() + [p2, pa], key=lambda x: x.rev())]
398
397
399 if followcopies:
398 if followcopies:
400 ret = copies.mergecopies(repo, wctx, p2, pa)
399 ret = copies.mergecopies(repo, wctx, p2, pa)
401 copy, movewithdir, diverge, renamedelete = ret
400 copy, movewithdir, diverge, renamedelete = ret
402 for of, fl in diverge.iteritems():
401 for of, fl in diverge.iteritems():
403 actions['dr'].append((of, (fl,), "divergent renames"))
402 actions['dr'].append((of, (fl,), "divergent renames"))
404 for of, fl in renamedelete.iteritems():
403 for of, fl in renamedelete.iteritems():
405 actions['rd'].append((of, (fl,), "rename and delete"))
404 actions['rd'].append((of, (fl,), "rename and delete"))
406
405
407 repo.ui.note(_("resolving manifests\n"))
406 repo.ui.note(_("resolving manifests\n"))
408 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
407 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
409 % (bool(branchmerge), bool(force), bool(partial)))
408 % (bool(branchmerge), bool(force), bool(partial)))
410 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
409 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
411
410
412 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
411 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
413 copied = set(copy.values())
412 copied = set(copy.values())
414 copied.update(movewithdir.values())
413 copied.update(movewithdir.values())
415
414
416 if '.hgsubstate' in m1:
415 if '.hgsubstate' in m1:
417 # check whether sub state is modified
416 # check whether sub state is modified
418 for s in sorted(wctx.substate):
417 for s in sorted(wctx.substate):
419 if wctx.sub(s).dirty():
418 if wctx.sub(s).dirty():
420 m1['.hgsubstate'] += "+"
419 m1['.hgsubstate'] += "+"
421 break
420 break
422
421
423 aborts = []
422 aborts = []
424 # Compare manifests
423 # Compare manifests
425 diff = m1.diff(m2)
424 diff = m1.diff(m2)
426
425
427 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
426 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
428 if partial and not partial(f):
427 if partial and not partial(f):
429 continue
428 continue
430 if n1 and n2:
429 if n1 and n2:
431 fa = f
430 fa = f
432 a = ma.get(f, nullid)
431 a = ma.get(f, nullid)
433 if a == nullid:
432 if a == nullid:
434 fa = copy.get(f, f)
433 fa = copy.get(f, f)
435 # Note: f as default is wrong - we can't really make a 3-way
434 # Note: f as default is wrong - we can't really make a 3-way
436 # merge without an ancestor file.
435 # merge without an ancestor file.
437 fla = ma.flags(fa)
436 fla = ma.flags(fa)
438 nol = 'l' not in fl1 + fl2 + fla
437 nol = 'l' not in fl1 + fl2 + fla
439 if n2 == a and fl2 == fla:
438 if n2 == a and fl2 == fla:
440 actions['k'].append((f, (), "keep")) # remote unchanged
439 actions['k'].append((f, (), "keep")) # remote unchanged
441 elif n1 == a and fl1 == fla: # local unchanged - use remote
440 elif n1 == a and fl1 == fla: # local unchanged - use remote
442 if n1 == n2: # optimization: keep local content
441 if n1 == n2: # optimization: keep local content
443 actions['e'].append((f, (fl2,), "update permissions"))
442 actions['e'].append((f, (fl2,), "update permissions"))
444 else:
443 else:
445 actions['g'].append((f, (fl2,), "remote is newer"))
444 actions['g'].append((f, (fl2,), "remote is newer"))
446 elif nol and n2 == a: # remote only changed 'x'
445 elif nol and n2 == a: # remote only changed 'x'
447 actions['e'].append((f, (fl2,), "update permissions"))
446 actions['e'].append((f, (fl2,), "update permissions"))
448 elif nol and n1 == a: # local only changed 'x'
447 elif nol and n1 == a: # local only changed 'x'
449 actions['g'].append((f, (fl1,), "remote is newer"))
448 actions['g'].append((f, (fl1,), "remote is newer"))
450 else: # both changed something
449 else: # both changed something
451 actions['m'].append((f, (f, f, fa, False, pa.node()),
450 actions['m'].append((f, (f, f, fa, False, pa.node()),
452 "versions differ"))
451 "versions differ"))
453 elif f in copied: # files we'll deal with on m2 side
452 elif f in copied: # files we'll deal with on m2 side
454 pass
453 pass
455 elif n1 and f in movewithdir: # directory rename, move local
454 elif n1 and f in movewithdir: # directory rename, move local
456 f2 = movewithdir[f]
455 f2 = movewithdir[f]
457 actions['dm'].append((f2, (f, fl1),
456 actions['dm'].append((f2, (f, fl1),
458 "remote directory rename - move from " + f))
457 "remote directory rename - move from " + f))
459 elif n1 and f in copy:
458 elif n1 and f in copy:
460 f2 = copy[f]
459 f2 = copy[f]
461 actions['m'].append((f, (f, f2, f2, False, pa.node()),
460 actions['m'].append((f, (f, f2, f2, False, pa.node()),
462 "local copied/moved from " + f2))
461 "local copied/moved from " + f2))
463 elif n1 and f in ma: # clean, a different, no remote
462 elif n1 and f in ma: # clean, a different, no remote
464 if n1 != ma[f]:
463 if n1 != ma[f]:
465 if acceptremote:
464 if acceptremote:
466 actions['r'].append((f, None, "remote delete"))
465 actions['r'].append((f, None, "remote delete"))
467 else:
466 else:
468 actions['cd'].append((f, None, "prompt changed/deleted"))
467 actions['cd'].append((f, None, "prompt changed/deleted"))
469 elif n1[20:] == "a": # added, no remote
468 elif n1[20:] == "a": # added, no remote
470 actions['f'].append((f, None, "remote deleted"))
469 actions['f'].append((f, None, "remote deleted"))
471 else:
470 else:
472 actions['r'].append((f, None, "other deleted"))
471 actions['r'].append((f, None, "other deleted"))
473 elif n2 and f in movewithdir:
472 elif n2 and f in movewithdir:
474 f2 = movewithdir[f]
473 f2 = movewithdir[f]
475 actions['dg'].append((f2, (f, fl2),
474 actions['dg'].append((f2, (f, fl2),
476 "local directory rename - get from " + f))
475 "local directory rename - get from " + f))
477 elif n2 and f in copy:
476 elif n2 and f in copy:
478 f2 = copy[f]
477 f2 = copy[f]
479 if f2 in m2:
478 if f2 in m2:
480 actions['m'].append((f, (f2, f, f2, False, pa.node()),
479 actions['m'].append((f, (f2, f, f2, False, pa.node()),
481 "remote copied from " + f2))
480 "remote copied from " + f2))
482 else:
481 else:
483 actions['m'].append((f, (f2, f, f2, True, pa.node()),
482 actions['m'].append((f, (f2, f, f2, True, pa.node()),
484 "remote moved from " + f2))
483 "remote moved from " + f2))
485 elif n2 and f not in ma:
484 elif n2 and f not in ma:
486 # local unknown, remote created: the logic is described by the
485 # local unknown, remote created: the logic is described by the
487 # following table:
486 # following table:
488 #
487 #
489 # force branchmerge different | action
488 # force branchmerge different | action
490 # n * n | get
489 # n * n | get
491 # n * y | abort
490 # n * y | abort
492 # y n * | get
491 # y n * | get
493 # y y n | get
492 # y y n | get
494 # y y y | merge
493 # y y y | merge
495 #
494 #
496 # Checking whether the files are different is expensive, so we
495 # Checking whether the files are different is expensive, so we
497 # don't do that when we can avoid it.
496 # don't do that when we can avoid it.
498 if force and not branchmerge:
497 if force and not branchmerge:
499 actions['g'].append((f, (fl2,), "remote created"))
498 actions['g'].append((f, (fl2,), "remote created"))
500 else:
499 else:
501 different = _checkunknownfile(repo, wctx, p2, f)
500 different = _checkunknownfile(repo, wctx, p2, f)
502 if force and branchmerge and different:
501 if force and branchmerge and different:
503 # FIXME: This is wrong - f is not in ma ...
502 # FIXME: This is wrong - f is not in ma ...
504 actions['m'].append((f, (f, f, f, False, pa.node()),
503 actions['m'].append((f, (f, f, f, False, pa.node()),
505 "remote differs from untracked local"))
504 "remote differs from untracked local"))
506 elif not force and different:
505 elif not force and different:
507 aborts.append((f, "ud"))
506 aborts.append((f, "ud"))
508 else:
507 else:
509 actions['g'].append((f, (fl2,), "remote created"))
508 actions['g'].append((f, (fl2,), "remote created"))
510 elif n2 and n2 != ma[f]:
509 elif n2 and n2 != ma[f]:
511 different = _checkunknownfile(repo, wctx, p2, f)
510 different = _checkunknownfile(repo, wctx, p2, f)
512 if not force and different:
511 if not force and different:
513 aborts.append((f, "ud"))
512 aborts.append((f, "ud"))
514 else:
513 else:
515 # if different: old untracked f may be overwritten and lost
514 # if different: old untracked f may be overwritten and lost
516 if acceptremote:
515 if acceptremote:
517 actions['g'].append((f, (m2.flags(f),),
516 actions['g'].append((f, (m2.flags(f),),
518 "remote recreating"))
517 "remote recreating"))
519 else:
518 else:
520 actions['dc'].append((f, (m2.flags(f),),
519 actions['dc'].append((f, (m2.flags(f),),
521 "prompt deleted/changed"))
520 "prompt deleted/changed"))
522
521
523 for f, m in sorted(aborts):
522 for f, m in sorted(aborts):
524 if m == "ud":
523 if m == "ud":
525 repo.ui.warn(_("%s: untracked file differs\n") % f)
524 repo.ui.warn(_("%s: untracked file differs\n") % f)
526 else: assert False, m
525 else: assert False, m
527 if aborts:
526 if aborts:
528 raise util.Abort(_("untracked files in working directory differ "
527 raise util.Abort(_("untracked files in working directory differ "
529 "from files in requested revision"))
528 "from files in requested revision"))
530
529
531 if not util.checkcase(repo.path):
530 if not util.checkcase(repo.path):
532 # check collision between files only in p2 for clean update
531 # check collision between files only in p2 for clean update
533 if (not branchmerge and
532 if (not branchmerge and
534 (force or not wctx.dirty(missing=True, branch=False))):
533 (force or not wctx.dirty(missing=True, branch=False))):
535 _checkcollision(repo, m2, None)
534 _checkcollision(repo, m2, None)
536 else:
535 else:
537 _checkcollision(repo, m1, actions)
536 _checkcollision(repo, m1, actions)
538
537
539 return actions
538 return actions
540
539
541 def batchremove(repo, actions):
540 def batchremove(repo, actions):
542 """apply removes to the working directory
541 """apply removes to the working directory
543
542
544 yields tuples for progress updates
543 yields tuples for progress updates
545 """
544 """
546 verbose = repo.ui.verbose
545 verbose = repo.ui.verbose
547 unlink = util.unlinkpath
546 unlink = util.unlinkpath
548 wjoin = repo.wjoin
547 wjoin = repo.wjoin
549 audit = repo.wopener.audit
548 audit = repo.wopener.audit
550 i = 0
549 i = 0
551 for f, args, msg in actions:
550 for f, args, msg in actions:
552 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
551 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
553 if verbose:
552 if verbose:
554 repo.ui.note(_("removing %s\n") % f)
553 repo.ui.note(_("removing %s\n") % f)
555 audit(f)
554 audit(f)
556 try:
555 try:
557 unlink(wjoin(f), ignoremissing=True)
556 unlink(wjoin(f), ignoremissing=True)
558 except OSError, inst:
557 except OSError, inst:
559 repo.ui.warn(_("update failed to remove %s: %s!\n") %
558 repo.ui.warn(_("update failed to remove %s: %s!\n") %
560 (f, inst.strerror))
559 (f, inst.strerror))
561 if i == 100:
560 if i == 100:
562 yield i, f
561 yield i, f
563 i = 0
562 i = 0
564 i += 1
563 i += 1
565 if i > 0:
564 if i > 0:
566 yield i, f
565 yield i, f
567
566
568 def batchget(repo, mctx, actions):
567 def batchget(repo, mctx, actions):
569 """apply gets to the working directory
568 """apply gets to the working directory
570
569
571 mctx is the context to get from
570 mctx is the context to get from
572
571
573 yields tuples for progress updates
572 yields tuples for progress updates
574 """
573 """
575 verbose = repo.ui.verbose
574 verbose = repo.ui.verbose
576 fctx = mctx.filectx
575 fctx = mctx.filectx
577 wwrite = repo.wwrite
576 wwrite = repo.wwrite
578 i = 0
577 i = 0
579 for f, args, msg in actions:
578 for f, args, msg in actions:
580 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
579 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
581 if verbose:
580 if verbose:
582 repo.ui.note(_("getting %s\n") % f)
581 repo.ui.note(_("getting %s\n") % f)
583 wwrite(f, fctx(f).data(), args[0])
582 wwrite(f, fctx(f).data(), args[0])
584 if i == 100:
583 if i == 100:
585 yield i, f
584 yield i, f
586 i = 0
585 i = 0
587 i += 1
586 i += 1
588 if i > 0:
587 if i > 0:
589 yield i, f
588 yield i, f
590
589
591 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
590 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
592 """apply the merge action list to the working directory
591 """apply the merge action list to the working directory
593
592
594 wctx is the working copy context
593 wctx is the working copy context
595 mctx is the context to be merged into the working copy
594 mctx is the context to be merged into the working copy
596
595
597 Return a tuple of counts (updated, merged, removed, unresolved) that
596 Return a tuple of counts (updated, merged, removed, unresolved) that
598 describes how many files were affected by the update.
597 describes how many files were affected by the update.
599 """
598 """
600
599
601 updated, merged, removed, unresolved = 0, 0, 0, 0
600 updated, merged, removed, unresolved = 0, 0, 0, 0
602 ms = mergestate(repo)
601 ms = mergestate(repo)
603 ms.reset(wctx.p1().node(), mctx.node())
602 ms.reset(wctx.p1().node(), mctx.node())
604 moves = []
603 moves = []
605 for m, l in actions.items():
604 for m, l in actions.items():
606 l.sort()
605 l.sort()
607
606
608 # prescan for merges
607 # prescan for merges
609 for f, args, msg in actions['m']:
608 for f, args, msg in actions['m']:
610 f1, f2, fa, move, anc = args
609 f1, f2, fa, move, anc = args
611 if f == '.hgsubstate': # merged internally
610 if f == '.hgsubstate': # merged internally
612 continue
611 continue
613 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
612 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
614 fcl = wctx[f1]
613 fcl = wctx[f1]
615 fco = mctx[f2]
614 fco = mctx[f2]
616 actx = repo[anc]
615 actx = repo[anc]
617 if fa in actx:
616 if fa in actx:
618 fca = actx[fa]
617 fca = actx[fa]
619 else:
618 else:
620 fca = repo.filectx(f1, fileid=nullrev)
619 fca = repo.filectx(f1, fileid=nullrev)
621 ms.add(fcl, fco, fca, f)
620 ms.add(fcl, fco, fca, f)
622 if f1 != f and move:
621 if f1 != f and move:
623 moves.append(f1)
622 moves.append(f1)
624
623
625 audit = repo.wopener.audit
624 audit = repo.wopener.audit
626 _updating = _('updating')
625 _updating = _('updating')
627 _files = _('files')
626 _files = _('files')
628 progress = repo.ui.progress
627 progress = repo.ui.progress
629
628
630 # remove renamed files after safely stored
629 # remove renamed files after safely stored
631 for f in moves:
630 for f in moves:
632 if os.path.lexists(repo.wjoin(f)):
631 if os.path.lexists(repo.wjoin(f)):
633 repo.ui.debug("removing %s\n" % f)
632 repo.ui.debug("removing %s\n" % f)
634 audit(f)
633 audit(f)
635 util.unlinkpath(repo.wjoin(f))
634 util.unlinkpath(repo.wjoin(f))
636
635
637 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
636 numupdates = sum(len(l) for m, l in actions.items() if m != 'k')
638
637
639 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
638 if [a for a in actions['r'] if a[0] == '.hgsubstate']:
640 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
639 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
641
640
642 # remove in parallel (must come first)
641 # remove in parallel (must come first)
643 z = 0
642 z = 0
644 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
643 prog = worker.worker(repo.ui, 0.001, batchremove, (repo,), actions['r'])
645 for i, item in prog:
644 for i, item in prog:
646 z += i
645 z += i
647 progress(_updating, z, item=item, total=numupdates, unit=_files)
646 progress(_updating, z, item=item, total=numupdates, unit=_files)
648 removed = len(actions['r'])
647 removed = len(actions['r'])
649
648
650 # get in parallel
649 # get in parallel
651 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
650 prog = worker.worker(repo.ui, 0.001, batchget, (repo, mctx), actions['g'])
652 for i, item in prog:
651 for i, item in prog:
653 z += i
652 z += i
654 progress(_updating, z, item=item, total=numupdates, unit=_files)
653 progress(_updating, z, item=item, total=numupdates, unit=_files)
655 updated = len(actions['g'])
654 updated = len(actions['g'])
656
655
657 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
656 if [a for a in actions['g'] if a[0] == '.hgsubstate']:
658 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
657 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
659
658
660 # forget (manifest only, just log it) (must come first)
659 # forget (manifest only, just log it) (must come first)
661 for f, args, msg in actions['f']:
660 for f, args, msg in actions['f']:
662 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
661 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
663 z += 1
662 z += 1
664 progress(_updating, z, item=f, total=numupdates, unit=_files)
663 progress(_updating, z, item=f, total=numupdates, unit=_files)
665
664
666 # re-add (manifest only, just log it)
665 # re-add (manifest only, just log it)
667 for f, args, msg in actions['a']:
666 for f, args, msg in actions['a']:
668 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
667 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
669 z += 1
668 z += 1
670 progress(_updating, z, item=f, total=numupdates, unit=_files)
669 progress(_updating, z, item=f, total=numupdates, unit=_files)
671
670
672 # keep (noop, just log it)
671 # keep (noop, just log it)
673 for f, args, msg in actions['k']:
672 for f, args, msg in actions['k']:
674 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
673 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
675 # no progress
674 # no progress
676
675
677 # merge
676 # merge
678 for f, args, msg in actions['m']:
677 for f, args, msg in actions['m']:
679 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
678 repo.ui.debug(" %s: %s -> m\n" % (f, msg))
680 z += 1
679 z += 1
681 progress(_updating, z, item=f, total=numupdates, unit=_files)
680 progress(_updating, z, item=f, total=numupdates, unit=_files)
682 f1, f2, fa, move, anc = args
681 f1, f2, fa, move, anc = args
683 if f == '.hgsubstate': # subrepo states need updating
682 if f == '.hgsubstate': # subrepo states need updating
684 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
683 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
685 overwrite)
684 overwrite)
686 continue
685 continue
687 audit(f)
686 audit(f)
688 r = ms.resolve(f, wctx, labels=labels)
687 r = ms.resolve(f, wctx, labels=labels)
689 if r is not None and r > 0:
688 if r is not None and r > 0:
690 unresolved += 1
689 unresolved += 1
691 else:
690 else:
692 if r is None:
691 if r is None:
693 updated += 1
692 updated += 1
694 else:
693 else:
695 merged += 1
694 merged += 1
696
695
697 # directory rename, move local
696 # directory rename, move local
698 for f, args, msg in actions['dm']:
697 for f, args, msg in actions['dm']:
699 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
698 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
700 z += 1
699 z += 1
701 progress(_updating, z, item=f, total=numupdates, unit=_files)
700 progress(_updating, z, item=f, total=numupdates, unit=_files)
702 f0, flags = args
701 f0, flags = args
703 repo.ui.note(_("moving %s to %s\n") % (f0, f))
702 repo.ui.note(_("moving %s to %s\n") % (f0, f))
704 audit(f)
703 audit(f)
705 repo.wwrite(f, wctx.filectx(f0).data(), flags)
704 repo.wwrite(f, wctx.filectx(f0).data(), flags)
706 util.unlinkpath(repo.wjoin(f0))
705 util.unlinkpath(repo.wjoin(f0))
707 updated += 1
706 updated += 1
708
707
709 # local directory rename, get
708 # local directory rename, get
710 for f, args, msg in actions['dg']:
709 for f, args, msg in actions['dg']:
711 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
710 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
712 z += 1
711 z += 1
713 progress(_updating, z, item=f, total=numupdates, unit=_files)
712 progress(_updating, z, item=f, total=numupdates, unit=_files)
714 f0, flags = args
713 f0, flags = args
715 repo.ui.note(_("getting %s to %s\n") % (f0, f))
714 repo.ui.note(_("getting %s to %s\n") % (f0, f))
716 repo.wwrite(f, mctx.filectx(f0).data(), flags)
715 repo.wwrite(f, mctx.filectx(f0).data(), flags)
717 updated += 1
716 updated += 1
718
717
719 # divergent renames
718 # divergent renames
720 for f, args, msg in actions['dr']:
719 for f, args, msg in actions['dr']:
721 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
720 repo.ui.debug(" %s: %s -> dr\n" % (f, msg))
722 z += 1
721 z += 1
723 progress(_updating, z, item=f, total=numupdates, unit=_files)
722 progress(_updating, z, item=f, total=numupdates, unit=_files)
724 fl, = args
723 fl, = args
725 repo.ui.warn(_("note: possible conflict - %s was renamed "
724 repo.ui.warn(_("note: possible conflict - %s was renamed "
726 "multiple times to:\n") % f)
725 "multiple times to:\n") % f)
727 for nf in fl:
726 for nf in fl:
728 repo.ui.warn(" %s\n" % nf)
727 repo.ui.warn(" %s\n" % nf)
729
728
730 # rename and delete
729 # rename and delete
731 for f, args, msg in actions['rd']:
730 for f, args, msg in actions['rd']:
732 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
731 repo.ui.debug(" %s: %s -> rd\n" % (f, msg))
733 z += 1
732 z += 1
734 progress(_updating, z, item=f, total=numupdates, unit=_files)
733 progress(_updating, z, item=f, total=numupdates, unit=_files)
735 fl, = args
734 fl, = args
736 repo.ui.warn(_("note: possible conflict - %s was deleted "
735 repo.ui.warn(_("note: possible conflict - %s was deleted "
737 "and renamed to:\n") % f)
736 "and renamed to:\n") % f)
738 for nf in fl:
737 for nf in fl:
739 repo.ui.warn(" %s\n" % nf)
738 repo.ui.warn(" %s\n" % nf)
740
739
741 # exec
740 # exec
742 for f, args, msg in actions['e']:
741 for f, args, msg in actions['e']:
743 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
742 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
744 z += 1
743 z += 1
745 progress(_updating, z, item=f, total=numupdates, unit=_files)
744 progress(_updating, z, item=f, total=numupdates, unit=_files)
746 flags, = args
745 flags, = args
747 audit(f)
746 audit(f)
748 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
747 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
749 updated += 1
748 updated += 1
750
749
751 ms.commit()
750 ms.commit()
752 progress(_updating, None, total=numupdates, unit=_files)
751 progress(_updating, None, total=numupdates, unit=_files)
753
752
754 return updated, merged, removed, unresolved
753 return updated, merged, removed, unresolved
755
754
756 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
755 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force, partial,
757 acceptremote, followcopies):
756 acceptremote, followcopies):
758 "Calculate the actions needed to merge mctx into wctx using ancestors"
757 "Calculate the actions needed to merge mctx into wctx using ancestors"
759
758
760 if len(ancestors) == 1: # default
759 if len(ancestors) == 1: # default
761 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
760 actions = manifestmerge(repo, wctx, mctx, ancestors[0],
762 branchmerge, force,
761 branchmerge, force,
763 partial, acceptremote, followcopies)
762 partial, acceptremote, followcopies)
764
763
765 else: # only when merge.preferancestor=* - the default
764 else: # only when merge.preferancestor=* - the default
766 repo.ui.note(
765 repo.ui.note(
767 _("note: merging %s and %s using bids from ancestors %s\n") %
766 _("note: merging %s and %s using bids from ancestors %s\n") %
768 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
767 (wctx, mctx, _(' and ').join(str(anc) for anc in ancestors)))
769
768
770 # Call for bids
769 # Call for bids
771 fbids = {} # mapping filename to bids (action method to list af actions)
770 fbids = {} # mapping filename to bids (action method to list af actions)
772 for ancestor in ancestors:
771 for ancestor in ancestors:
773 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
772 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
774 actions = manifestmerge(repo, wctx, mctx, ancestor,
773 actions = manifestmerge(repo, wctx, mctx, ancestor,
775 branchmerge, force,
774 branchmerge, force,
776 partial, acceptremote, followcopies)
775 partial, acceptremote, followcopies)
777 for m, l in sorted(actions.items()):
776 for m, l in sorted(actions.items()):
778 for a in l:
777 for a in l:
779 f, args, msg = a
778 f, args, msg = a
780 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
779 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
781 if f in fbids:
780 if f in fbids:
782 d = fbids[f]
781 d = fbids[f]
783 if m in d:
782 if m in d:
784 d[m].append(a)
783 d[m].append(a)
785 else:
784 else:
786 d[m] = [a]
785 d[m] = [a]
787 else:
786 else:
788 fbids[f] = {m: [a]}
787 fbids[f] = {m: [a]}
789
788
790 # Pick the best bid for each file
789 # Pick the best bid for each file
791 repo.ui.note(_('\nauction for merging merge bids\n'))
790 repo.ui.note(_('\nauction for merging merge bids\n'))
792 actions = dict((m, []) for m in actions.keys())
791 actions = dict((m, []) for m in actions.keys())
793 for f, bids in sorted(fbids.items()):
792 for f, bids in sorted(fbids.items()):
794 # bids is a mapping from action method to list af actions
793 # bids is a mapping from action method to list af actions
795 # Consensus?
794 # Consensus?
796 if len(bids) == 1: # all bids are the same kind of method
795 if len(bids) == 1: # all bids are the same kind of method
797 m, l = bids.items()[0]
796 m, l = bids.items()[0]
798 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
797 if util.all(a == l[0] for a in l[1:]): # len(bids) is > 1
799 repo.ui.note(" %s: consensus for %s\n" % (f, m))
798 repo.ui.note(" %s: consensus for %s\n" % (f, m))
800 actions[m].append(l[0])
799 actions[m].append(l[0])
801 continue
800 continue
802 # If keep is an option, just do it.
801 # If keep is an option, just do it.
803 if "k" in bids:
802 if "k" in bids:
804 repo.ui.note(" %s: picking 'keep' action\n" % f)
803 repo.ui.note(" %s: picking 'keep' action\n" % f)
805 actions['k'].append(bids["k"][0])
804 actions['k'].append(bids["k"][0])
806 continue
805 continue
807 # If there are gets and they all agree [how could they not?], do it.
806 # If there are gets and they all agree [how could they not?], do it.
808 if "g" in bids:
807 if "g" in bids:
809 ga0 = bids["g"][0]
808 ga0 = bids["g"][0]
810 if util.all(a == ga0 for a in bids["g"][1:]):
809 if util.all(a == ga0 for a in bids["g"][1:]):
811 repo.ui.note(" %s: picking 'get' action\n" % f)
810 repo.ui.note(" %s: picking 'get' action\n" % f)
812 actions['g'].append(ga0)
811 actions['g'].append(ga0)
813 continue
812 continue
814 # TODO: Consider other simple actions such as mode changes
813 # TODO: Consider other simple actions such as mode changes
815 # Handle inefficient democrazy.
814 # Handle inefficient democrazy.
816 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
815 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
817 for m, l in sorted(bids.items()):
816 for m, l in sorted(bids.items()):
818 for _f, args, msg in l:
817 for _f, args, msg in l:
819 repo.ui.note(' %s -> %s\n' % (msg, m))
818 repo.ui.note(' %s -> %s\n' % (msg, m))
820 # Pick random action. TODO: Instead, prompt user when resolving
819 # Pick random action. TODO: Instead, prompt user when resolving
821 m, l = bids.items()[0]
820 m, l = bids.items()[0]
822 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
821 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
823 (f, m))
822 (f, m))
824 actions[m].append(l[0])
823 actions[m].append(l[0])
825 continue
824 continue
826 repo.ui.note(_('end of auction\n\n'))
825 repo.ui.note(_('end of auction\n\n'))
827
826
828 # Prompt and create actions. TODO: Move this towards resolve phase.
827 # Prompt and create actions. TODO: Move this towards resolve phase.
829 for f, args, msg in actions['cd']:
828 for f, args, msg in actions['cd']:
830 if f in ancestors[0] and not wctx[f].cmp(ancestors[0][f]):
829 if f in ancestors[0] and not wctx[f].cmp(ancestors[0][f]):
831 # local did change but ended up with same content
830 # local did change but ended up with same content
832 actions['r'].append((f, None, "prompt same"))
831 actions['r'].append((f, None, "prompt same"))
833 elif repo.ui.promptchoice(
832 elif repo.ui.promptchoice(
834 _("local changed %s which remote deleted\n"
833 _("local changed %s which remote deleted\n"
835 "use (c)hanged version or (d)elete?"
834 "use (c)hanged version or (d)elete?"
836 "$$ &Changed $$ &Delete") % f, 0):
835 "$$ &Changed $$ &Delete") % f, 0):
837 actions['r'].append((f, None, "prompt delete"))
836 actions['r'].append((f, None, "prompt delete"))
838 else:
837 else:
839 actions['a'].append((f, None, "prompt keep"))
838 actions['a'].append((f, None, "prompt keep"))
840 del actions['cd'][:]
839 del actions['cd'][:]
841
840
842 for f, args, msg in actions['dc']:
841 for f, args, msg in actions['dc']:
843 flags, = args
842 flags, = args
844 if f in ancestors[0] and not mctx[f].cmp(ancestors[0][f]):
843 if f in ancestors[0] and not mctx[f].cmp(ancestors[0][f]):
845 # remote did change but ended up with same content
844 # remote did change but ended up with same content
846 pass # don't get = keep local deleted
845 pass # don't get = keep local deleted
847 elif repo.ui.promptchoice(
846 elif repo.ui.promptchoice(
848 _("remote changed %s which local deleted\n"
847 _("remote changed %s which local deleted\n"
849 "use (c)hanged version or leave (d)eleted?"
848 "use (c)hanged version or leave (d)eleted?"
850 "$$ &Changed $$ &Deleted") % f, 0) == 0:
849 "$$ &Changed $$ &Deleted") % f, 0) == 0:
851 actions['g'].append((f, (flags,), "prompt recreating"))
850 actions['g'].append((f, (flags,), "prompt recreating"))
852 del actions['dc'][:]
851 del actions['dc'][:]
853
852
854 if wctx.rev() is None:
853 if wctx.rev() is None:
855 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
854 ractions, factions = _forgetremoved(wctx, mctx, branchmerge)
856 actions['r'].extend(ractions)
855 actions['r'].extend(ractions)
857 actions['f'].extend(factions)
856 actions['f'].extend(factions)
858
857
859 return actions
858 return actions
860
859
861 def recordupdates(repo, actions, branchmerge):
860 def recordupdates(repo, actions, branchmerge):
862 "record merge actions to the dirstate"
861 "record merge actions to the dirstate"
863 # remove (must come first)
862 # remove (must come first)
864 for f, args, msg in actions['r']:
863 for f, args, msg in actions['r']:
865 if branchmerge:
864 if branchmerge:
866 repo.dirstate.remove(f)
865 repo.dirstate.remove(f)
867 else:
866 else:
868 repo.dirstate.drop(f)
867 repo.dirstate.drop(f)
869
868
870 # forget (must come first)
869 # forget (must come first)
871 for f, args, msg in actions['f']:
870 for f, args, msg in actions['f']:
872 repo.dirstate.drop(f)
871 repo.dirstate.drop(f)
873
872
874 # re-add
873 # re-add
875 for f, args, msg in actions['a']:
874 for f, args, msg in actions['a']:
876 if not branchmerge:
875 if not branchmerge:
877 repo.dirstate.add(f)
876 repo.dirstate.add(f)
878
877
879 # exec change
878 # exec change
880 for f, args, msg in actions['e']:
879 for f, args, msg in actions['e']:
881 repo.dirstate.normallookup(f)
880 repo.dirstate.normallookup(f)
882
881
883 # keep
882 # keep
884 for f, args, msg in actions['k']:
883 for f, args, msg in actions['k']:
885 pass
884 pass
886
885
887 # get
886 # get
888 for f, args, msg in actions['g']:
887 for f, args, msg in actions['g']:
889 if branchmerge:
888 if branchmerge:
890 repo.dirstate.otherparent(f)
889 repo.dirstate.otherparent(f)
891 else:
890 else:
892 repo.dirstate.normal(f)
891 repo.dirstate.normal(f)
893
892
894 # merge
893 # merge
895 for f, args, msg in actions['m']:
894 for f, args, msg in actions['m']:
896 f1, f2, fa, move, anc = args
895 f1, f2, fa, move, anc = args
897 if branchmerge:
896 if branchmerge:
898 # We've done a branch merge, mark this file as merged
897 # We've done a branch merge, mark this file as merged
899 # so that we properly record the merger later
898 # so that we properly record the merger later
900 repo.dirstate.merge(f)
899 repo.dirstate.merge(f)
901 if f1 != f2: # copy/rename
900 if f1 != f2: # copy/rename
902 if move:
901 if move:
903 repo.dirstate.remove(f1)
902 repo.dirstate.remove(f1)
904 if f1 != f:
903 if f1 != f:
905 repo.dirstate.copy(f1, f)
904 repo.dirstate.copy(f1, f)
906 else:
905 else:
907 repo.dirstate.copy(f2, f)
906 repo.dirstate.copy(f2, f)
908 else:
907 else:
909 # We've update-merged a locally modified file, so
908 # We've update-merged a locally modified file, so
910 # we set the dirstate to emulate a normal checkout
909 # we set the dirstate to emulate a normal checkout
911 # of that file some time in the past. Thus our
910 # of that file some time in the past. Thus our
912 # merge will appear as a normal local file
911 # merge will appear as a normal local file
913 # modification.
912 # modification.
914 if f2 == f: # file not locally copied/moved
913 if f2 == f: # file not locally copied/moved
915 repo.dirstate.normallookup(f)
914 repo.dirstate.normallookup(f)
916 if move:
915 if move:
917 repo.dirstate.drop(f1)
916 repo.dirstate.drop(f1)
918
917
919 # directory rename, move local
918 # directory rename, move local
920 for f, args, msg in actions['dm']:
919 for f, args, msg in actions['dm']:
921 f0, flag = args
920 f0, flag = args
922 if f0 not in repo.dirstate:
921 if f0 not in repo.dirstate:
923 # untracked file moved
922 # untracked file moved
924 continue
923 continue
925 if branchmerge:
924 if branchmerge:
926 repo.dirstate.add(f)
925 repo.dirstate.add(f)
927 repo.dirstate.remove(f0)
926 repo.dirstate.remove(f0)
928 repo.dirstate.copy(f0, f)
927 repo.dirstate.copy(f0, f)
929 else:
928 else:
930 repo.dirstate.normal(f)
929 repo.dirstate.normal(f)
931 repo.dirstate.drop(f0)
930 repo.dirstate.drop(f0)
932
931
933 # directory rename, get
932 # directory rename, get
934 for f, args, msg in actions['dg']:
933 for f, args, msg in actions['dg']:
935 f0, flag = args
934 f0, flag = args
936 if branchmerge:
935 if branchmerge:
937 repo.dirstate.add(f)
936 repo.dirstate.add(f)
938 repo.dirstate.copy(f0, f)
937 repo.dirstate.copy(f0, f)
939 else:
938 else:
940 repo.dirstate.normal(f)
939 repo.dirstate.normal(f)
941
940
942 def update(repo, node, branchmerge, force, partial, ancestor=None,
941 def update(repo, node, branchmerge, force, partial, ancestor=None,
943 mergeancestor=False, labels=None):
942 mergeancestor=False, labels=None):
944 """
943 """
945 Perform a merge between the working directory and the given node
944 Perform a merge between the working directory and the given node
946
945
947 node = the node to update to, or None if unspecified
946 node = the node to update to, or None if unspecified
948 branchmerge = whether to merge between branches
947 branchmerge = whether to merge between branches
949 force = whether to force branch merging or file overwriting
948 force = whether to force branch merging or file overwriting
950 partial = a function to filter file lists (dirstate not updated)
949 partial = a function to filter file lists (dirstate not updated)
951 mergeancestor = whether it is merging with an ancestor. If true,
950 mergeancestor = whether it is merging with an ancestor. If true,
952 we should accept the incoming changes for any prompts that occur.
951 we should accept the incoming changes for any prompts that occur.
953 If false, merging with an ancestor (fast-forward) is only allowed
952 If false, merging with an ancestor (fast-forward) is only allowed
954 between different named branches. This flag is used by rebase extension
953 between different named branches. This flag is used by rebase extension
955 as a temporary fix and should be avoided in general.
954 as a temporary fix and should be avoided in general.
956
955
957 The table below shows all the behaviors of the update command
956 The table below shows all the behaviors of the update command
958 given the -c and -C or no options, whether the working directory
957 given the -c and -C or no options, whether the working directory
959 is dirty, whether a revision is specified, and the relationship of
958 is dirty, whether a revision is specified, and the relationship of
960 the parent rev to the target rev (linear, on the same named
959 the parent rev to the target rev (linear, on the same named
961 branch, or on another named branch).
960 branch, or on another named branch).
962
961
963 This logic is tested by test-update-branches.t.
962 This logic is tested by test-update-branches.t.
964
963
965 -c -C dirty rev | linear same cross
964 -c -C dirty rev | linear same cross
966 n n n n | ok (1) x
965 n n n n | ok (1) x
967 n n n y | ok ok ok
966 n n n y | ok ok ok
968 n n y n | merge (2) (2)
967 n n y n | merge (2) (2)
969 n n y y | merge (3) (3)
968 n n y y | merge (3) (3)
970 n y * * | --- discard ---
969 n y * * | --- discard ---
971 y n y * | --- (4) ---
970 y n y * | --- (4) ---
972 y n n * | --- ok ---
971 y n n * | --- ok ---
973 y y * * | --- (5) ---
972 y y * * | --- (5) ---
974
973
975 x = can't happen
974 x = can't happen
976 * = don't-care
975 * = don't-care
977 1 = abort: not a linear update (merge or update --check to force update)
976 1 = abort: not a linear update (merge or update --check to force update)
978 2 = abort: uncommitted changes (commit and merge, or update --clean to
977 2 = abort: uncommitted changes (commit and merge, or update --clean to
979 discard changes)
978 discard changes)
980 3 = abort: uncommitted changes (commit or update --clean to discard changes)
979 3 = abort: uncommitted changes (commit or update --clean to discard changes)
981 4 = abort: uncommitted changes (checked in commands.py)
980 4 = abort: uncommitted changes (checked in commands.py)
982 5 = incompatible options (checked in commands.py)
981 5 = incompatible options (checked in commands.py)
983
982
984 Return the same tuple as applyupdates().
983 Return the same tuple as applyupdates().
985 """
984 """
986
985
987 onode = node
986 onode = node
988 wlock = repo.wlock()
987 wlock = repo.wlock()
989 try:
988 try:
990 wc = repo[None]
989 wc = repo[None]
991 pl = wc.parents()
990 pl = wc.parents()
992 p1 = pl[0]
991 p1 = pl[0]
993 pas = [None]
992 pas = [None]
994 if ancestor:
993 if ancestor:
995 pas = [repo[ancestor]]
994 pas = [repo[ancestor]]
996
995
997 if node is None:
996 if node is None:
998 # Here is where we should consider bookmarks, divergent bookmarks,
997 # Here is where we should consider bookmarks, divergent bookmarks,
999 # foreground changesets (successors), and tip of current branch;
998 # foreground changesets (successors), and tip of current branch;
1000 # but currently we are only checking the branch tips.
999 # but currently we are only checking the branch tips.
1001 try:
1000 try:
1002 node = repo.branchtip(wc.branch())
1001 node = repo.branchtip(wc.branch())
1003 except errormod.RepoLookupError:
1002 except errormod.RepoLookupError:
1004 if wc.branch() == "default": # no default branch!
1003 if wc.branch() == "default": # no default branch!
1005 node = repo.lookup("tip") # update to tip
1004 node = repo.lookup("tip") # update to tip
1006 else:
1005 else:
1007 raise util.Abort(_("branch %s not found") % wc.branch())
1006 raise util.Abort(_("branch %s not found") % wc.branch())
1008
1007
1009 if p1.obsolete() and not p1.children():
1008 if p1.obsolete() and not p1.children():
1010 # allow updating to successors
1009 # allow updating to successors
1011 successors = obsolete.successorssets(repo, p1.node())
1010 successors = obsolete.successorssets(repo, p1.node())
1012
1011
1013 # behavior of certain cases is as follows,
1012 # behavior of certain cases is as follows,
1014 #
1013 #
1015 # divergent changesets: update to highest rev, similar to what
1014 # divergent changesets: update to highest rev, similar to what
1016 # is currently done when there are more than one head
1015 # is currently done when there are more than one head
1017 # (i.e. 'tip')
1016 # (i.e. 'tip')
1018 #
1017 #
1019 # replaced changesets: same as divergent except we know there
1018 # replaced changesets: same as divergent except we know there
1020 # is no conflict
1019 # is no conflict
1021 #
1020 #
1022 # pruned changeset: no update is done; though, we could
1021 # pruned changeset: no update is done; though, we could
1023 # consider updating to the first non-obsolete parent,
1022 # consider updating to the first non-obsolete parent,
1024 # similar to what is current done for 'hg prune'
1023 # similar to what is current done for 'hg prune'
1025
1024
1026 if successors:
1025 if successors:
1027 # flatten the list here handles both divergent (len > 1)
1026 # flatten the list here handles both divergent (len > 1)
1028 # and the usual case (len = 1)
1027 # and the usual case (len = 1)
1029 successors = [n for sub in successors for n in sub]
1028 successors = [n for sub in successors for n in sub]
1030
1029
1031 # get the max revision for the given successors set,
1030 # get the max revision for the given successors set,
1032 # i.e. the 'tip' of a set
1031 # i.e. the 'tip' of a set
1033 node = repo.revs("max(%ln)", successors).first()
1032 node = repo.revs("max(%ln)", successors).first()
1034 pas = [p1]
1033 pas = [p1]
1035
1034
1036 overwrite = force and not branchmerge
1035 overwrite = force and not branchmerge
1037
1036
1038 p2 = repo[node]
1037 p2 = repo[node]
1039 if pas[0] is None:
1038 if pas[0] is None:
1040 if repo.ui.config("merge", "preferancestor", '*') == '*':
1039 if repo.ui.config("merge", "preferancestor", '*') == '*':
1041 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1040 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1042 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1041 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1043 else:
1042 else:
1044 pas = [p1.ancestor(p2, warn=branchmerge)]
1043 pas = [p1.ancestor(p2, warn=branchmerge)]
1045
1044
1046 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1045 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
1047
1046
1048 ### check phase
1047 ### check phase
1049 if not overwrite and len(pl) > 1:
1048 if not overwrite and len(pl) > 1:
1050 raise util.Abort(_("outstanding uncommitted merge"))
1049 raise util.Abort(_("outstanding uncommitted merge"))
1051 if branchmerge:
1050 if branchmerge:
1052 if pas == [p2]:
1051 if pas == [p2]:
1053 raise util.Abort(_("merging with a working directory ancestor"
1052 raise util.Abort(_("merging with a working directory ancestor"
1054 " has no effect"))
1053 " has no effect"))
1055 elif pas == [p1]:
1054 elif pas == [p1]:
1056 if not mergeancestor and p1.branch() == p2.branch():
1055 if not mergeancestor and p1.branch() == p2.branch():
1057 raise util.Abort(_("nothing to merge"),
1056 raise util.Abort(_("nothing to merge"),
1058 hint=_("use 'hg update' "
1057 hint=_("use 'hg update' "
1059 "or check 'hg heads'"))
1058 "or check 'hg heads'"))
1060 if not force and (wc.files() or wc.deleted()):
1059 if not force and (wc.files() or wc.deleted()):
1061 raise util.Abort(_("uncommitted changes"),
1060 raise util.Abort(_("uncommitted changes"),
1062 hint=_("use 'hg status' to list changes"))
1061 hint=_("use 'hg status' to list changes"))
1063 for s in sorted(wc.substate):
1062 for s in sorted(wc.substate):
1064 if wc.sub(s).dirty():
1063 if wc.sub(s).dirty():
1065 raise util.Abort(_("uncommitted changes in "
1064 raise util.Abort(_("uncommitted changes in "
1066 "subrepository '%s'") % s)
1065 "subrepository '%s'") % s)
1067
1066
1068 elif not overwrite:
1067 elif not overwrite:
1069 if p1 == p2: # no-op update
1068 if p1 == p2: # no-op update
1070 # call the hooks and exit early
1069 # call the hooks and exit early
1071 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1070 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
1072 repo.hook('update', parent1=xp2, parent2='', error=0)
1071 repo.hook('update', parent1=xp2, parent2='', error=0)
1073 return 0, 0, 0, 0
1072 return 0, 0, 0, 0
1074
1073
1075 if pas not in ([p1], [p2]): # nonlinear
1074 if pas not in ([p1], [p2]): # nonlinear
1076 dirty = wc.dirty(missing=True)
1075 dirty = wc.dirty(missing=True)
1077 if dirty or onode is None:
1076 if dirty or onode is None:
1078 # Branching is a bit strange to ensure we do the minimal
1077 # Branching is a bit strange to ensure we do the minimal
1079 # amount of call to obsolete.background.
1078 # amount of call to obsolete.background.
1080 foreground = obsolete.foreground(repo, [p1.node()])
1079 foreground = obsolete.foreground(repo, [p1.node()])
1081 # note: the <node> variable contains a random identifier
1080 # note: the <node> variable contains a random identifier
1082 if repo[node].node() in foreground:
1081 if repo[node].node() in foreground:
1083 pas = [p1] # allow updating to successors
1082 pas = [p1] # allow updating to successors
1084 elif dirty:
1083 elif dirty:
1085 msg = _("uncommitted changes")
1084 msg = _("uncommitted changes")
1086 if onode is None:
1085 if onode is None:
1087 hint = _("commit and merge, or update --clean to"
1086 hint = _("commit and merge, or update --clean to"
1088 " discard changes")
1087 " discard changes")
1089 else:
1088 else:
1090 hint = _("commit or update --clean to discard"
1089 hint = _("commit or update --clean to discard"
1091 " changes")
1090 " changes")
1092 raise util.Abort(msg, hint=hint)
1091 raise util.Abort(msg, hint=hint)
1093 else: # node is none
1092 else: # node is none
1094 msg = _("not a linear update")
1093 msg = _("not a linear update")
1095 hint = _("merge or update --check to force update")
1094 hint = _("merge or update --check to force update")
1096 raise util.Abort(msg, hint=hint)
1095 raise util.Abort(msg, hint=hint)
1097 else:
1096 else:
1098 # Allow jumping branches if clean and specific rev given
1097 # Allow jumping branches if clean and specific rev given
1099 pas = [p1]
1098 pas = [p1]
1100
1099
1101 followcopies = False
1100 followcopies = False
1102 if overwrite:
1101 if overwrite:
1103 pas = [wc]
1102 pas = [wc]
1104 elif pas == [p2]: # backwards
1103 elif pas == [p2]: # backwards
1105 pas = [wc.p1()]
1104 pas = [wc.p1()]
1106 elif not branchmerge and not wc.dirty(missing=True):
1105 elif not branchmerge and not wc.dirty(missing=True):
1107 pass
1106 pass
1108 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1107 elif pas[0] and repo.ui.configbool("merge", "followcopies", True):
1109 followcopies = True
1108 followcopies = True
1110
1109
1111 ### calculate phase
1110 ### calculate phase
1112 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1111 actions = calculateupdates(repo, wc, p2, pas, branchmerge, force,
1113 partial, mergeancestor, followcopies)
1112 partial, mergeancestor, followcopies)
1114
1113
1115 ### apply phase
1114 ### apply phase
1116 if not branchmerge: # just jump to the new rev
1115 if not branchmerge: # just jump to the new rev
1117 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1116 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
1118 if not partial:
1117 if not partial:
1119 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1118 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
1120 # note that we're in the middle of an update
1119 # note that we're in the middle of an update
1121 repo.vfs.write('updatestate', p2.hex())
1120 repo.vfs.write('updatestate', p2.hex())
1122
1121
1123 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1122 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
1124
1123
1125 if not partial:
1124 if not partial:
1126 repo.dirstate.beginparentchange()
1125 repo.dirstate.beginparentchange()
1127 repo.setparents(fp1, fp2)
1126 repo.setparents(fp1, fp2)
1128 recordupdates(repo, actions, branchmerge)
1127 recordupdates(repo, actions, branchmerge)
1129 # update completed, clear state
1128 # update completed, clear state
1130 util.unlink(repo.join('updatestate'))
1129 util.unlink(repo.join('updatestate'))
1131
1130
1132 if not branchmerge:
1131 if not branchmerge:
1133 repo.dirstate.setbranch(p2.branch())
1132 repo.dirstate.setbranch(p2.branch())
1134 repo.dirstate.endparentchange()
1133 repo.dirstate.endparentchange()
1135 finally:
1134 finally:
1136 wlock.release()
1135 wlock.release()
1137
1136
1138 if not partial:
1137 if not partial:
1139 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1138 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
1140 return stats
1139 return stats
1141
1140
1142 def graft(repo, ctx, pctx, labels):
1141 def graft(repo, ctx, pctx, labels):
1143 """Do a graft-like merge.
1142 """Do a graft-like merge.
1144
1143
1145 This is a merge where the merge ancestor is chosen such that one
1144 This is a merge where the merge ancestor is chosen such that one
1146 or more changesets are grafted onto the current changeset. In
1145 or more changesets are grafted onto the current changeset. In
1147 addition to the merge, this fixes up the dirstate to include only
1146 addition to the merge, this fixes up the dirstate to include only
1148 a single parent and tries to duplicate any renames/copies
1147 a single parent and tries to duplicate any renames/copies
1149 appropriately.
1148 appropriately.
1150
1149
1151 ctx - changeset to rebase
1150 ctx - changeset to rebase
1152 pctx - merge base, usually ctx.p1()
1151 pctx - merge base, usually ctx.p1()
1153 labels - merge labels eg ['local', 'graft']
1152 labels - merge labels eg ['local', 'graft']
1154
1153
1155 """
1154 """
1156
1155
1157 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1156 stats = update(repo, ctx.node(), True, True, False, pctx.node(),
1158 labels=labels)
1157 labels=labels)
1159 # drop the second merge parent
1158 # drop the second merge parent
1160 repo.dirstate.beginparentchange()
1159 repo.dirstate.beginparentchange()
1161 repo.setparents(repo['.'].node(), nullid)
1160 repo.setparents(repo['.'].node(), nullid)
1162 repo.dirstate.write()
1161 repo.dirstate.write()
1163 # fix up dirstate for copies and renames
1162 # fix up dirstate for copies and renames
1164 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1163 copies.duplicatecopies(repo, ctx.rev(), pctx.rev())
1165 repo.dirstate.endparentchange()
1164 repo.dirstate.endparentchange()
1166 return stats
1165 return stats
@@ -1,274 +1,272 b''
1 $ HGMERGE=true; export HGMERGE
1 $ HGMERGE=true; export HGMERGE
2
2
3 $ hg init r1
3 $ hg init r1
4 $ cd r1
4 $ cd r1
5 $ echo a > a
5 $ echo a > a
6 $ hg addremove
6 $ hg addremove
7 adding a
7 adding a
8 $ hg commit -m "1"
8 $ hg commit -m "1"
9
9
10 $ hg clone . ../r2
10 $ hg clone . ../r2
11 updating to branch default
11 updating to branch default
12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
12 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
13 $ cd ../r2
13 $ cd ../r2
14 $ hg up
14 $ hg up
15 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
15 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
16 $ echo abc > a
16 $ echo abc > a
17 $ hg diff --nodates
17 $ hg diff --nodates
18 diff -r c19d34741b0a a
18 diff -r c19d34741b0a a
19 --- a/a
19 --- a/a
20 +++ b/a
20 +++ b/a
21 @@ -1,1 +1,1 @@
21 @@ -1,1 +1,1 @@
22 -a
22 -a
23 +abc
23 +abc
24
24
25 $ cd ../r1
25 $ cd ../r1
26 $ echo b > b
26 $ echo b > b
27 $ echo a2 > a
27 $ echo a2 > a
28 $ hg addremove
28 $ hg addremove
29 adding b
29 adding b
30 $ hg commit -m "2"
30 $ hg commit -m "2"
31
31
32 $ cd ../r2
32 $ cd ../r2
33 $ hg -q pull ../r1
33 $ hg -q pull ../r1
34 $ hg status
34 $ hg status
35 M a
35 M a
36 $ hg parents
36 $ hg parents
37 changeset: 0:c19d34741b0a
37 changeset: 0:c19d34741b0a
38 user: test
38 user: test
39 date: Thu Jan 01 00:00:00 1970 +0000
39 date: Thu Jan 01 00:00:00 1970 +0000
40 summary: 1
40 summary: 1
41
41
42 $ hg --debug up
42 $ hg --debug up
43 searching for copies back to rev 1
43 searching for copies back to rev 1
44 unmatched files in other:
44 unmatched files in other:
45 b
45 b
46 resolving manifests
46 resolving manifests
47 branchmerge: False, force: False, partial: False
47 branchmerge: False, force: False, partial: False
48 ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
48 ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
49 preserving a for resolve of a
49 preserving a for resolve of a
50 b: remote created -> g
50 b: remote created -> g
51 getting b
51 getting b
52 updating: b 1/2 files (50.00%)
52 updating: b 1/2 files (50.00%)
53 a: versions differ -> m
53 a: versions differ -> m
54 updating: a 2/2 files (100.00%)
54 updating: a 2/2 files (100.00%)
55 picked tool 'true' for a (binary False symlink False)
55 picked tool 'true' for a (binary False symlink False)
56 merging a
56 merging a
57 my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
57 my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
58 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
58 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
59 $ hg parents
59 $ hg parents
60 changeset: 1:1e71731e6fbb
60 changeset: 1:1e71731e6fbb
61 tag: tip
61 tag: tip
62 user: test
62 user: test
63 date: Thu Jan 01 00:00:00 1970 +0000
63 date: Thu Jan 01 00:00:00 1970 +0000
64 summary: 2
64 summary: 2
65
65
66 $ hg --debug up 0
66 $ hg --debug up 0
67 resolving manifests
67 resolving manifests
68 branchmerge: False, force: False, partial: False
68 branchmerge: False, force: False, partial: False
69 ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
69 ancestor: 1e71731e6fbb, local: 1e71731e6fbb+, remote: c19d34741b0a
70 preserving a for resolve of a
70 preserving a for resolve of a
71 b: other deleted -> r
71 b: other deleted -> r
72 removing b
72 removing b
73 updating: b 1/2 files (50.00%)
73 updating: b 1/2 files (50.00%)
74 a: versions differ -> m
74 a: versions differ -> m
75 updating: a 2/2 files (100.00%)
75 updating: a 2/2 files (100.00%)
76 picked tool 'true' for a (binary False symlink False)
76 picked tool 'true' for a (binary False symlink False)
77 merging a
77 merging a
78 my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb
78 my a@1e71731e6fbb+ other a@c19d34741b0a ancestor a@1e71731e6fbb
79 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
79 0 files updated, 1 files merged, 1 files removed, 0 files unresolved
80 $ hg parents
80 $ hg parents
81 changeset: 0:c19d34741b0a
81 changeset: 0:c19d34741b0a
82 user: test
82 user: test
83 date: Thu Jan 01 00:00:00 1970 +0000
83 date: Thu Jan 01 00:00:00 1970 +0000
84 summary: 1
84 summary: 1
85
85
86 $ hg --debug merge
86 $ hg --debug merge
87 abort: nothing to merge
87 abort: nothing to merge
88 (use 'hg update' instead)
88 (use 'hg update' instead)
89 [255]
89 [255]
90 $ hg parents
90 $ hg parents
91 changeset: 0:c19d34741b0a
91 changeset: 0:c19d34741b0a
92 user: test
92 user: test
93 date: Thu Jan 01 00:00:00 1970 +0000
93 date: Thu Jan 01 00:00:00 1970 +0000
94 summary: 1
94 summary: 1
95
95
96 $ hg --debug up
96 $ hg --debug up
97 searching for copies back to rev 1
97 searching for copies back to rev 1
98 unmatched files in other:
98 unmatched files in other:
99 b
99 b
100 resolving manifests
100 resolving manifests
101 branchmerge: False, force: False, partial: False
101 branchmerge: False, force: False, partial: False
102 ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
102 ancestor: c19d34741b0a, local: c19d34741b0a+, remote: 1e71731e6fbb
103 preserving a for resolve of a
103 preserving a for resolve of a
104 b: remote created -> g
104 b: remote created -> g
105 getting b
105 getting b
106 updating: b 1/2 files (50.00%)
106 updating: b 1/2 files (50.00%)
107 a: versions differ -> m
107 a: versions differ -> m
108 updating: a 2/2 files (100.00%)
108 updating: a 2/2 files (100.00%)
109 picked tool 'true' for a (binary False symlink False)
109 picked tool 'true' for a (binary False symlink False)
110 merging a
110 merging a
111 my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
111 my a@c19d34741b0a+ other a@1e71731e6fbb ancestor a@c19d34741b0a
112 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
112 1 files updated, 1 files merged, 0 files removed, 0 files unresolved
113 $ hg parents
113 $ hg parents
114 changeset: 1:1e71731e6fbb
114 changeset: 1:1e71731e6fbb
115 tag: tip
115 tag: tip
116 user: test
116 user: test
117 date: Thu Jan 01 00:00:00 1970 +0000
117 date: Thu Jan 01 00:00:00 1970 +0000
118 summary: 2
118 summary: 2
119
119
120 $ hg -v history
120 $ hg -v history
121 changeset: 1:1e71731e6fbb
121 changeset: 1:1e71731e6fbb
122 tag: tip
122 tag: tip
123 user: test
123 user: test
124 date: Thu Jan 01 00:00:00 1970 +0000
124 date: Thu Jan 01 00:00:00 1970 +0000
125 files: a b
125 files: a b
126 description:
126 description:
127 2
127 2
128
128
129
129
130 changeset: 0:c19d34741b0a
130 changeset: 0:c19d34741b0a
131 user: test
131 user: test
132 date: Thu Jan 01 00:00:00 1970 +0000
132 date: Thu Jan 01 00:00:00 1970 +0000
133 files: a
133 files: a
134 description:
134 description:
135 1
135 1
136
136
137
137
138 $ hg diff --nodates
138 $ hg diff --nodates
139 diff -r 1e71731e6fbb a
139 diff -r 1e71731e6fbb a
140 --- a/a
140 --- a/a
141 +++ b/a
141 +++ b/a
142 @@ -1,1 +1,1 @@
142 @@ -1,1 +1,1 @@
143 -a2
143 -a2
144 +abc
144 +abc
145
145
146
146
147 create a second head
147 create a second head
148
148
149 $ cd ../r1
149 $ cd ../r1
150 $ hg up 0
150 $ hg up 0
151 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
151 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
152 $ echo b2 > b
152 $ echo b2 > b
153 $ echo a3 > a
153 $ echo a3 > a
154 $ hg addremove
154 $ hg addremove
155 adding b
155 adding b
156 $ hg commit -m "3"
156 $ hg commit -m "3"
157 created new head
157 created new head
158
158
159 $ cd ../r2
159 $ cd ../r2
160 $ hg -q pull ../r1
160 $ hg -q pull ../r1
161 $ hg status
161 $ hg status
162 M a
162 M a
163 $ hg parents
163 $ hg parents
164 changeset: 1:1e71731e6fbb
164 changeset: 1:1e71731e6fbb
165 user: test
165 user: test
166 date: Thu Jan 01 00:00:00 1970 +0000
166 date: Thu Jan 01 00:00:00 1970 +0000
167 summary: 2
167 summary: 2
168
168
169 $ hg --debug up
169 $ hg --debug up
170 abort: uncommitted changes
170 abort: uncommitted changes
171 (commit and merge, or update --clean to discard changes)
171 (commit and merge, or update --clean to discard changes)
172 [255]
172 [255]
173 $ hg --debug merge
173 $ hg --debug merge
174 abort: uncommitted changes
174 abort: uncommitted changes
175 (use 'hg status' to list changes)
175 (use 'hg status' to list changes)
176 [255]
176 [255]
177 $ hg --debug merge -f
177 $ hg --debug merge -f
178 searching for copies back to rev 1
178 searching for copies back to rev 1
179 unmatched files new in both:
179 unmatched files new in both:
180 b
180 b
181 resolving manifests
181 resolving manifests
182 branchmerge: True, force: True, partial: False
182 branchmerge: True, force: True, partial: False
183 ancestor: c19d34741b0a, local: 1e71731e6fbb+, remote: 83c51d0caff4
183 ancestor: c19d34741b0a, local: 1e71731e6fbb+, remote: 83c51d0caff4
184 preserving a for resolve of a
184 preserving a for resolve of a
185 preserving b for resolve of b
185 preserving b for resolve of b
186 a: versions differ -> m
186 a: versions differ -> m
187 updating: a 1/2 files (50.00%)
187 updating: a 1/2 files (50.00%)
188 picked tool 'true' for a (binary False symlink False)
188 picked tool 'true' for a (binary False symlink False)
189 merging a
189 merging a
190 my a@1e71731e6fbb+ other a@83c51d0caff4 ancestor a@c19d34741b0a
190 my a@1e71731e6fbb+ other a@83c51d0caff4 ancestor a@c19d34741b0a
191 b: versions differ -> m
191 b: versions differ -> m
192 updating: b 2/2 files (100.00%)
192 updating: b 2/2 files (100.00%)
193 picked tool 'true' for b (binary False symlink False)
193 picked tool 'true' for b (binary False symlink False)
194 merging b
194 merging b
195 my b@1e71731e6fbb+ other b@83c51d0caff4 ancestor b@000000000000
195 my b@1e71731e6fbb+ other b@83c51d0caff4 ancestor b@000000000000
196 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
196 0 files updated, 2 files merged, 0 files removed, 0 files unresolved
197 (branch merge, don't forget to commit)
197 (branch merge, don't forget to commit)
198 $ hg parents
198 $ hg parents
199 changeset: 1:1e71731e6fbb
199 changeset: 1:1e71731e6fbb
200 user: test
200 user: test
201 date: Thu Jan 01 00:00:00 1970 +0000
201 date: Thu Jan 01 00:00:00 1970 +0000
202 summary: 2
202 summary: 2
203
203
204 changeset: 2:83c51d0caff4
204 changeset: 2:83c51d0caff4
205 tag: tip
205 tag: tip
206 parent: 0:c19d34741b0a
206 parent: 0:c19d34741b0a
207 user: test
207 user: test
208 date: Thu Jan 01 00:00:00 1970 +0000
208 date: Thu Jan 01 00:00:00 1970 +0000
209 summary: 3
209 summary: 3
210
210
211 $ hg diff --nodates
211 $ hg diff --nodates
212 diff -r 1e71731e6fbb a
212 diff -r 1e71731e6fbb a
213 --- a/a
213 --- a/a
214 +++ b/a
214 +++ b/a
215 @@ -1,1 +1,1 @@
215 @@ -1,1 +1,1 @@
216 -a2
216 -a2
217 +abc
217 +abc
218
218
219
219
220 test conflicting untracked files
220 test conflicting untracked files
221
221
222 $ hg up -qC 0
222 $ hg up -qC 0
223 $ echo untracked > b
223 $ echo untracked > b
224 $ hg st
224 $ hg st
225 ? b
225 ? b
226 $ hg up 1
226 $ hg up 1
227 b: untracked file differs
227 b: untracked file differs
228 abort: untracked files in working directory differ from files in requested revision
228 abort: untracked files in working directory differ from files in requested revision
229 [255]
229 [255]
230 $ rm b
230 $ rm b
231
231
232 test conflicting untracked ignored file
232 test conflicting untracked ignored file
233
233
234 BROKEN: the ignored file gets overwritten by the remote version
235
236 $ hg up -qC 0
234 $ hg up -qC 0
237 $ echo ignored > .hgignore
235 $ echo ignored > .hgignore
238 $ hg add .hgignore
236 $ hg add .hgignore
239 $ hg ci -m 'add .hgignore'
237 $ hg ci -m 'add .hgignore'
240 created new head
238 created new head
241 $ echo ignored > ignored
239 $ echo ignored > ignored
242 $ hg add ignored
240 $ hg add ignored
243 $ hg ci -m 'add ignored file'
241 $ hg ci -m 'add ignored file'
244
242
245 $ hg up -q 'desc("add .hgignore")'
243 $ hg up -q 'desc("add .hgignore")'
246 $ echo untracked > ignored
244 $ echo untracked > ignored
247 $ hg st
245 $ hg st
248 $ hg up 'desc("add ignored file")'
246 $ hg up 'desc("add ignored file")'
249 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
247 ignored: untracked file differs
250 $ cat ignored
248 abort: untracked files in working directory differ from files in requested revision
251 ignored
249 [255]
252
250
253 test a local add
251 test a local add
254
252
255 $ cd ..
253 $ cd ..
256 $ hg init a
254 $ hg init a
257 $ hg init b
255 $ hg init b
258 $ echo a > a/a
256 $ echo a > a/a
259 $ echo a > b/a
257 $ echo a > b/a
260 $ hg --cwd a commit -A -m a
258 $ hg --cwd a commit -A -m a
261 adding a
259 adding a
262 $ cd b
260 $ cd b
263 $ hg add a
261 $ hg add a
264 $ hg pull -u ../a
262 $ hg pull -u ../a
265 pulling from ../a
263 pulling from ../a
266 requesting all changes
264 requesting all changes
267 adding changesets
265 adding changesets
268 adding manifests
266 adding manifests
269 adding file changes
267 adding file changes
270 added 1 changesets with 1 changes to 1 files
268 added 1 changesets with 1 changes to 1 files
271 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
269 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
272 $ hg st
270 $ hg st
273
271
274 $ cd ..
272 $ cd ..
General Comments 0
You need to be logged in to leave comments. Login now