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