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