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