##// END OF EJS Templates
merge: only sort manifests in debug mode (issue3769)
Matt Mackall -
r18456:8a811fa9 stable
parent child Browse files
Show More
@@ -1,636 +1,642 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from node import nullid, nullrev, hex, bin
8 from node import nullid, nullrev, hex, bin
9 from i18n import _
9 from i18n import _
10 import error, util, filemerge, copies, subrepo
10 import error, util, filemerge, copies, subrepo
11 import errno, os, shutil
11 import errno, os, shutil
12
12
13 class mergestate(object):
13 class mergestate(object):
14 '''track 3-way merge state of individual files'''
14 '''track 3-way merge state of individual files'''
15 def __init__(self, repo):
15 def __init__(self, repo):
16 self._repo = repo
16 self._repo = repo
17 self._dirty = False
17 self._dirty = False
18 self._read()
18 self._read()
19 def reset(self, node=None):
19 def reset(self, node=None):
20 self._state = {}
20 self._state = {}
21 if node:
21 if node:
22 self._local = node
22 self._local = node
23 shutil.rmtree(self._repo.join("merge"), True)
23 shutil.rmtree(self._repo.join("merge"), True)
24 self._dirty = False
24 self._dirty = False
25 def _read(self):
25 def _read(self):
26 self._state = {}
26 self._state = {}
27 try:
27 try:
28 f = self._repo.opener("merge/state")
28 f = self._repo.opener("merge/state")
29 for i, l in enumerate(f):
29 for i, l in enumerate(f):
30 if i == 0:
30 if i == 0:
31 self._local = bin(l[:-1])
31 self._local = bin(l[:-1])
32 else:
32 else:
33 bits = l[:-1].split("\0")
33 bits = l[:-1].split("\0")
34 self._state[bits[0]] = bits[1:]
34 self._state[bits[0]] = bits[1:]
35 f.close()
35 f.close()
36 except IOError, err:
36 except IOError, err:
37 if err.errno != errno.ENOENT:
37 if err.errno != errno.ENOENT:
38 raise
38 raise
39 self._dirty = False
39 self._dirty = False
40 def commit(self):
40 def commit(self):
41 if self._dirty:
41 if self._dirty:
42 f = self._repo.opener("merge/state", "w")
42 f = self._repo.opener("merge/state", "w")
43 f.write(hex(self._local) + "\n")
43 f.write(hex(self._local) + "\n")
44 for d, v in self._state.iteritems():
44 for d, v in self._state.iteritems():
45 f.write("\0".join([d] + v) + "\n")
45 f.write("\0".join([d] + v) + "\n")
46 f.close()
46 f.close()
47 self._dirty = False
47 self._dirty = False
48 def add(self, fcl, fco, fca, fd):
48 def add(self, fcl, fco, fca, fd):
49 hash = util.sha1(fcl.path()).hexdigest()
49 hash = util.sha1(fcl.path()).hexdigest()
50 self._repo.opener.write("merge/" + hash, fcl.data())
50 self._repo.opener.write("merge/" + hash, fcl.data())
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 hex(fca.filenode()), fco.path(), fcl.flags()]
52 hex(fca.filenode()), fco.path(), fcl.flags()]
53 self._dirty = True
53 self._dirty = True
54 def __contains__(self, dfile):
54 def __contains__(self, dfile):
55 return dfile in self._state
55 return dfile in self._state
56 def __getitem__(self, dfile):
56 def __getitem__(self, dfile):
57 return self._state[dfile][0]
57 return self._state[dfile][0]
58 def __iter__(self):
58 def __iter__(self):
59 l = self._state.keys()
59 l = self._state.keys()
60 l.sort()
60 l.sort()
61 for f in l:
61 for f in l:
62 yield f
62 yield f
63 def mark(self, dfile, state):
63 def mark(self, dfile, state):
64 self._state[dfile][0] = state
64 self._state[dfile][0] = state
65 self._dirty = True
65 self._dirty = True
66 def resolve(self, dfile, wctx, octx):
66 def resolve(self, dfile, wctx, octx):
67 if self[dfile] == 'r':
67 if self[dfile] == 'r':
68 return 0
68 return 0
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 fcd = wctx[dfile]
70 fcd = wctx[dfile]
71 fco = octx[ofile]
71 fco = octx[ofile]
72 fca = self._repo.filectx(afile, fileid=anode)
72 fca = self._repo.filectx(afile, fileid=anode)
73 # "premerge" x flags
73 # "premerge" x flags
74 flo = fco.flags()
74 flo = fco.flags()
75 fla = fca.flags()
75 fla = fca.flags()
76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
76 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
77 if fca.node() == nullid:
77 if fca.node() == nullid:
78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
78 self._repo.ui.warn(_('warning: cannot merge flags for %s\n') %
79 afile)
79 afile)
80 elif flags == fla:
80 elif flags == fla:
81 flags = flo
81 flags = flo
82 # restore local
82 # restore local
83 f = self._repo.opener("merge/" + hash)
83 f = self._repo.opener("merge/" + hash)
84 self._repo.wwrite(dfile, f.read(), flags)
84 self._repo.wwrite(dfile, f.read(), flags)
85 f.close()
85 f.close()
86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
86 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
87 if r is None:
87 if r is None:
88 # no real conflict
88 # no real conflict
89 del self._state[dfile]
89 del self._state[dfile]
90 elif not r:
90 elif not r:
91 self.mark(dfile, 'r')
91 self.mark(dfile, 'r')
92 return r
92 return r
93
93
94 def _checkunknownfile(repo, wctx, mctx, f):
94 def _checkunknownfile(repo, wctx, mctx, f):
95 return (not repo.dirstate._ignore(f)
95 return (not repo.dirstate._ignore(f)
96 and os.path.isfile(repo.wjoin(f))
96 and os.path.isfile(repo.wjoin(f))
97 and repo.dirstate.normalize(f) not in repo.dirstate
97 and repo.dirstate.normalize(f) not in repo.dirstate
98 and mctx[f].cmp(wctx[f]))
98 and mctx[f].cmp(wctx[f]))
99
99
100 def _checkunknown(repo, wctx, mctx):
100 def _checkunknown(repo, wctx, mctx):
101 "check for collisions between unknown files and files in mctx"
101 "check for collisions between unknown files and files in mctx"
102
102
103 error = False
103 error = False
104 for f in mctx:
104 for f in mctx:
105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
105 if f not in wctx and _checkunknownfile(repo, wctx, mctx, f):
106 error = True
106 error = True
107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
107 wctx._repo.ui.warn(_("%s: untracked file differs\n") % f)
108 if error:
108 if error:
109 raise util.Abort(_("untracked files in working directory differ "
109 raise util.Abort(_("untracked files in working directory differ "
110 "from files in requested revision"))
110 "from files in requested revision"))
111
111
112 def _remains(f, m, ma, workingctx=False):
112 def _remains(f, m, ma, workingctx=False):
113 """check whether specified file remains after merge.
113 """check whether specified file remains after merge.
114
114
115 It is assumed that specified file is not contained in the manifest
115 It is assumed that specified file is not contained in the manifest
116 of the other context.
116 of the other context.
117 """
117 """
118 if f in ma:
118 if f in ma:
119 n = m[f]
119 n = m[f]
120 if n != ma[f]:
120 if n != ma[f]:
121 return True # because it is changed locally
121 return True # because it is changed locally
122 # even though it doesn't remain, if "remote deleted" is
122 # even though it doesn't remain, if "remote deleted" is
123 # chosen in manifestmerge()
123 # chosen in manifestmerge()
124 elif workingctx and n[20:] == "a":
124 elif workingctx and n[20:] == "a":
125 return True # because it is added locally (linear merge specific)
125 return True # because it is added locally (linear merge specific)
126 else:
126 else:
127 return False # because it is removed remotely
127 return False # because it is removed remotely
128 else:
128 else:
129 return True # because it is added locally
129 return True # because it is added locally
130
130
131 def _checkcollision(mctx, extractxs):
131 def _checkcollision(mctx, extractxs):
132 "check for case folding collisions in the destination context"
132 "check for case folding collisions in the destination context"
133 folded = {}
133 folded = {}
134 for fn in mctx:
134 for fn in mctx:
135 fold = util.normcase(fn)
135 fold = util.normcase(fn)
136 if fold in folded:
136 if fold in folded:
137 raise util.Abort(_("case-folding collision between %s and %s")
137 raise util.Abort(_("case-folding collision between %s and %s")
138 % (fn, folded[fold]))
138 % (fn, folded[fold]))
139 folded[fold] = fn
139 folded[fold] = fn
140
140
141 if extractxs:
141 if extractxs:
142 wctx, actx = extractxs
142 wctx, actx = extractxs
143 # class to delay looking up copy mapping
143 # class to delay looking up copy mapping
144 class pathcopies(object):
144 class pathcopies(object):
145 @util.propertycache
145 @util.propertycache
146 def map(self):
146 def map(self):
147 # {dst@mctx: src@wctx} copy mapping
147 # {dst@mctx: src@wctx} copy mapping
148 return copies.pathcopies(wctx, mctx)
148 return copies.pathcopies(wctx, mctx)
149 pc = pathcopies()
149 pc = pathcopies()
150
150
151 for fn in wctx:
151 for fn in wctx:
152 fold = util.normcase(fn)
152 fold = util.normcase(fn)
153 mfn = folded.get(fold, None)
153 mfn = folded.get(fold, None)
154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
154 if (mfn and mfn != fn and pc.map.get(mfn) != fn and
155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
155 _remains(fn, wctx.manifest(), actx.manifest(), True) and
156 _remains(mfn, mctx.manifest(), actx.manifest())):
156 _remains(mfn, mctx.manifest(), actx.manifest())):
157 raise util.Abort(_("case-folding collision between %s and %s")
157 raise util.Abort(_("case-folding collision between %s and %s")
158 % (mfn, fn))
158 % (mfn, fn))
159
159
160 def _forgetremoved(wctx, mctx, branchmerge):
160 def _forgetremoved(wctx, mctx, branchmerge):
161 """
161 """
162 Forget removed files
162 Forget removed files
163
163
164 If we're jumping between revisions (as opposed to merging), and if
164 If we're jumping between revisions (as opposed to merging), and if
165 neither the working directory nor the target rev has the file,
165 neither the working directory nor the target rev has the file,
166 then we need to remove it from the dirstate, to prevent the
166 then we need to remove it from the dirstate, to prevent the
167 dirstate from listing the file when it is no longer in the
167 dirstate from listing the file when it is no longer in the
168 manifest.
168 manifest.
169
169
170 If we're merging, and the other revision has removed a file
170 If we're merging, and the other revision has removed a file
171 that is not present in the working directory, we need to mark it
171 that is not present in the working directory, we need to mark it
172 as removed.
172 as removed.
173 """
173 """
174
174
175 actions = []
175 actions = []
176 state = branchmerge and 'r' or 'f'
176 state = branchmerge and 'r' or 'f'
177 for f in wctx.deleted():
177 for f in wctx.deleted():
178 if f not in mctx:
178 if f not in mctx:
179 actions.append((f, state))
179 actions.append((f, state))
180
180
181 if not branchmerge:
181 if not branchmerge:
182 for f in wctx.removed():
182 for f in wctx.removed():
183 if f not in mctx:
183 if f not in mctx:
184 actions.append((f, "f"))
184 actions.append((f, "f"))
185
185
186 return actions
186 return actions
187
187
188 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
188 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
189 """
189 """
190 Merge p1 and p2 with ancestor pa and generate merge action list
190 Merge p1 and p2 with ancestor pa and generate merge action list
191
191
192 overwrite = whether we clobber working files
192 overwrite = whether we clobber working files
193 partial = function to filter file lists
193 partial = function to filter file lists
194 """
194 """
195
195
196 def act(msg, m, f, *args):
196 def act(msg, m, f, *args):
197 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
197 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
198 actions.append((f, m) + args)
198 actions.append((f, m) + args)
199
199
200 actions, copy, movewithdir = [], {}, {}
200 actions, copy, movewithdir = [], {}, {}
201
201
202 if overwrite:
202 if overwrite:
203 pa = p1
203 pa = p1
204 elif pa == p2: # backwards
204 elif pa == p2: # backwards
205 pa = p1.p1()
205 pa = p1.p1()
206 elif pa and repo.ui.configbool("merge", "followcopies", True):
206 elif pa and repo.ui.configbool("merge", "followcopies", True):
207 ret = copies.mergecopies(repo, p1, p2, pa)
207 ret = copies.mergecopies(repo, p1, p2, pa)
208 copy, movewithdir, diverge, renamedelete = ret
208 copy, movewithdir, diverge, renamedelete = ret
209 for of, fl in diverge.iteritems():
209 for of, fl in diverge.iteritems():
210 act("divergent renames", "dr", of, fl)
210 act("divergent renames", "dr", of, fl)
211 for of, fl in renamedelete.iteritems():
211 for of, fl in renamedelete.iteritems():
212 act("rename and delete", "rd", of, fl)
212 act("rename and delete", "rd", of, fl)
213
213
214 repo.ui.note(_("resolving manifests\n"))
214 repo.ui.note(_("resolving manifests\n"))
215 repo.ui.debug(" overwrite: %s, partial: %s\n"
215 repo.ui.debug(" overwrite: %s, partial: %s\n"
216 % (bool(overwrite), bool(partial)))
216 % (bool(overwrite), bool(partial)))
217 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
217 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
218
218
219 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
219 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
220 copied = set(copy.values())
220 copied = set(copy.values())
221 copied.update(movewithdir.values())
221 copied.update(movewithdir.values())
222
222
223 if '.hgsubstate' in m1:
223 if '.hgsubstate' in m1:
224 # check whether sub state is modified
224 # check whether sub state is modified
225 for s in sorted(p1.substate):
225 for s in sorted(p1.substate):
226 if p1.sub(s).dirty():
226 if p1.sub(s).dirty():
227 m1['.hgsubstate'] += "+"
227 m1['.hgsubstate'] += "+"
228 break
228 break
229
229
230 # Compare manifests
230 # Compare manifests
231 for f, n in sorted(m1.iteritems()):
231 visit = m1.iteritems()
232 if repo.ui.debugflag:
233 visit = sorted(visit)
234 for f, n in visit:
232 if partial and not partial(f):
235 if partial and not partial(f):
233 continue
236 continue
234 if f in m2:
237 if f in m2:
235 n2 = m2[f]
238 n2 = m2[f]
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
239 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 nol = 'l' not in fl1 + fl2 + fla
240 nol = 'l' not in fl1 + fl2 + fla
238 a = ma.get(f, nullid)
241 a = ma.get(f, nullid)
239 if n == n2 and fl1 == fl2:
242 if n == n2 and fl1 == fl2:
240 pass # same - keep local
243 pass # same - keep local
241 elif n2 == a and fl2 == fla:
244 elif n2 == a and fl2 == fla:
242 pass # remote unchanged - keep local
245 pass # remote unchanged - keep local
243 elif n == a and fl1 == fla: # local unchanged - use remote
246 elif n == a and fl1 == fla: # local unchanged - use remote
244 if n == n2: # optimization: keep local content
247 if n == n2: # optimization: keep local content
245 act("update permissions", "e", f, fl2)
248 act("update permissions", "e", f, fl2)
246 else:
249 else:
247 act("remote is newer", "g", f, fl2)
250 act("remote is newer", "g", f, fl2)
248 elif nol and n2 == a: # remote only changed 'x'
251 elif nol and n2 == a: # remote only changed 'x'
249 act("update permissions", "e", f, fl2)
252 act("update permissions", "e", f, fl2)
250 elif nol and n == a: # local only changed 'x'
253 elif nol and n == a: # local only changed 'x'
251 act("remote is newer", "g", f, fl)
254 act("remote is newer", "g", f, fl)
252 else: # both changed something
255 else: # both changed something
253 act("versions differ", "m", f, f, f, False)
256 act("versions differ", "m", f, f, f, False)
254 elif f in copied: # files we'll deal with on m2 side
257 elif f in copied: # files we'll deal with on m2 side
255 pass
258 pass
256 elif f in movewithdir: # directory rename
259 elif f in movewithdir: # directory rename
257 f2 = movewithdir[f]
260 f2 = movewithdir[f]
258 act("remote renamed directory to " + f2, "d", f, None, f2,
261 act("remote renamed directory to " + f2, "d", f, None, f2,
259 m1.flags(f))
262 m1.flags(f))
260 elif f in copy:
263 elif f in copy:
261 f2 = copy[f]
264 f2 = copy[f]
262 act("local copied/moved to " + f2, "m", f, f2, f, False)
265 act("local copied/moved to " + f2, "m", f, f2, f, False)
263 elif f in ma: # clean, a different, no remote
266 elif f in ma: # clean, a different, no remote
264 if n != ma[f]:
267 if n != ma[f]:
265 if repo.ui.promptchoice(
268 if repo.ui.promptchoice(
266 _(" local changed %s which remote deleted\n"
269 _(" local changed %s which remote deleted\n"
267 "use (c)hanged version or (d)elete?") % f,
270 "use (c)hanged version or (d)elete?") % f,
268 (_("&Changed"), _("&Delete")), 0):
271 (_("&Changed"), _("&Delete")), 0):
269 act("prompt delete", "r", f)
272 act("prompt delete", "r", f)
270 else:
273 else:
271 act("prompt keep", "a", f)
274 act("prompt keep", "a", f)
272 elif n[20:] == "a": # added, no remote
275 elif n[20:] == "a": # added, no remote
273 act("remote deleted", "f", f)
276 act("remote deleted", "f", f)
274 else:
277 else:
275 act("other deleted", "r", f)
278 act("other deleted", "r", f)
276
279
277 for f, n in sorted(m2.iteritems()):
280 visit = m2.iteritems()
281 if repo.ui.debugflag:
282 visit = sorted(visit)
283 for f, n in visit:
278 if partial and not partial(f):
284 if partial and not partial(f):
279 continue
285 continue
280 if f in m1 or f in copied: # files already visited
286 if f in m1 or f in copied: # files already visited
281 continue
287 continue
282 if f in movewithdir:
288 if f in movewithdir:
283 f2 = movewithdir[f]
289 f2 = movewithdir[f]
284 act("local renamed directory to " + f2, "d", None, f, f2,
290 act("local renamed directory to " + f2, "d", None, f, f2,
285 m2.flags(f))
291 m2.flags(f))
286 elif f in copy:
292 elif f in copy:
287 f2 = copy[f]
293 f2 = copy[f]
288 if f2 in m2:
294 if f2 in m2:
289 act("remote copied to " + f, "m",
295 act("remote copied to " + f, "m",
290 f2, f, f, False)
296 f2, f, f, False)
291 else:
297 else:
292 act("remote moved to " + f, "m",
298 act("remote moved to " + f, "m",
293 f2, f, f, True)
299 f2, f, f, True)
294 elif f not in ma:
300 elif f not in ma:
295 if (not overwrite
301 if (not overwrite
296 and _checkunknownfile(repo, p1, p2, f)):
302 and _checkunknownfile(repo, p1, p2, f)):
297 act("remote differs from untracked local",
303 act("remote differs from untracked local",
298 "m", f, f, f, False)
304 "m", f, f, f, False)
299 else:
305 else:
300 act("remote created", "g", f, m2.flags(f))
306 act("remote created", "g", f, m2.flags(f))
301 elif n != ma[f]:
307 elif n != ma[f]:
302 if repo.ui.promptchoice(
308 if repo.ui.promptchoice(
303 _("remote changed %s which local deleted\n"
309 _("remote changed %s which local deleted\n"
304 "use (c)hanged version or leave (d)eleted?") % f,
310 "use (c)hanged version or leave (d)eleted?") % f,
305 (_("&Changed"), _("&Deleted")), 0) == 0:
311 (_("&Changed"), _("&Deleted")), 0) == 0:
306 act("prompt recreating", "g", f, m2.flags(f))
312 act("prompt recreating", "g", f, m2.flags(f))
307
313
308 return actions
314 return actions
309
315
310 def actionkey(a):
316 def actionkey(a):
311 return a[1] == "r" and -1 or 0, a
317 return a[1] == "r" and -1 or 0, a
312
318
313 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
319 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
314 """apply the merge action list to the working directory
320 """apply the merge action list to the working directory
315
321
316 wctx is the working copy context
322 wctx is the working copy context
317 mctx is the context to be merged into the working copy
323 mctx is the context to be merged into the working copy
318 actx is the context of the common ancestor
324 actx is the context of the common ancestor
319
325
320 Return a tuple of counts (updated, merged, removed, unresolved) that
326 Return a tuple of counts (updated, merged, removed, unresolved) that
321 describes how many files were affected by the update.
327 describes how many files were affected by the update.
322 """
328 """
323
329
324 updated, merged, removed, unresolved = 0, 0, 0, 0
330 updated, merged, removed, unresolved = 0, 0, 0, 0
325 ms = mergestate(repo)
331 ms = mergestate(repo)
326 ms.reset(wctx.p1().node())
332 ms.reset(wctx.p1().node())
327 moves = []
333 moves = []
328 actions.sort(key=actionkey)
334 actions.sort(key=actionkey)
329
335
330 # prescan for merges
336 # prescan for merges
331 for a in actions:
337 for a in actions:
332 f, m = a[:2]
338 f, m = a[:2]
333 if m == "m": # merge
339 if m == "m": # merge
334 f2, fd, move = a[2:]
340 f2, fd, move = a[2:]
335 if fd == '.hgsubstate': # merged internally
341 if fd == '.hgsubstate': # merged internally
336 continue
342 continue
337 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
343 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
338 fcl = wctx[f]
344 fcl = wctx[f]
339 fco = mctx[f2]
345 fco = mctx[f2]
340 if mctx == actx: # backwards, use working dir parent as ancestor
346 if mctx == actx: # backwards, use working dir parent as ancestor
341 if fcl.parents():
347 if fcl.parents():
342 fca = fcl.p1()
348 fca = fcl.p1()
343 else:
349 else:
344 fca = repo.filectx(f, fileid=nullrev)
350 fca = repo.filectx(f, fileid=nullrev)
345 else:
351 else:
346 fca = fcl.ancestor(fco, actx)
352 fca = fcl.ancestor(fco, actx)
347 if not fca:
353 if not fca:
348 fca = repo.filectx(f, fileid=nullrev)
354 fca = repo.filectx(f, fileid=nullrev)
349 ms.add(fcl, fco, fca, fd)
355 ms.add(fcl, fco, fca, fd)
350 if f != fd and move:
356 if f != fd and move:
351 moves.append(f)
357 moves.append(f)
352
358
353 audit = repo.wopener.audit
359 audit = repo.wopener.audit
354
360
355 # remove renamed files after safely stored
361 # remove renamed files after safely stored
356 for f in moves:
362 for f in moves:
357 if os.path.lexists(repo.wjoin(f)):
363 if os.path.lexists(repo.wjoin(f)):
358 repo.ui.debug("removing %s\n" % f)
364 repo.ui.debug("removing %s\n" % f)
359 audit(f)
365 audit(f)
360 util.unlinkpath(repo.wjoin(f))
366 util.unlinkpath(repo.wjoin(f))
361
367
362 numupdates = len(actions)
368 numupdates = len(actions)
363 for i, a in enumerate(actions):
369 for i, a in enumerate(actions):
364 f, m = a[:2]
370 f, m = a[:2]
365 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
371 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
366 unit=_('files'))
372 unit=_('files'))
367 if m == "r": # remove
373 if m == "r": # remove
368 repo.ui.note(_("removing %s\n") % f)
374 repo.ui.note(_("removing %s\n") % f)
369 audit(f)
375 audit(f)
370 if f == '.hgsubstate': # subrepo states need updating
376 if f == '.hgsubstate': # subrepo states need updating
371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
377 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
372 try:
378 try:
373 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
379 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
374 except OSError, inst:
380 except OSError, inst:
375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
381 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 (f, inst.strerror))
382 (f, inst.strerror))
377 removed += 1
383 removed += 1
378 elif m == "m": # merge
384 elif m == "m": # merge
379 if fd == '.hgsubstate': # subrepo states need updating
385 if fd == '.hgsubstate': # subrepo states need updating
380 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
386 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
381 overwrite)
387 overwrite)
382 continue
388 continue
383 f2, fd, move = a[2:]
389 f2, fd, move = a[2:]
384 audit(fd)
390 audit(fd)
385 r = ms.resolve(fd, wctx, mctx)
391 r = ms.resolve(fd, wctx, mctx)
386 if r is not None and r > 0:
392 if r is not None and r > 0:
387 unresolved += 1
393 unresolved += 1
388 else:
394 else:
389 if r is None:
395 if r is None:
390 updated += 1
396 updated += 1
391 else:
397 else:
392 merged += 1
398 merged += 1
393 elif m == "g": # get
399 elif m == "g": # get
394 flags = a[2]
400 flags = a[2]
395 repo.ui.note(_("getting %s\n") % f)
401 repo.ui.note(_("getting %s\n") % f)
396 repo.wwrite(f, mctx.filectx(f).data(), flags)
402 repo.wwrite(f, mctx.filectx(f).data(), flags)
397 updated += 1
403 updated += 1
398 if f == '.hgsubstate': # subrepo states need updating
404 if f == '.hgsubstate': # subrepo states need updating
399 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
405 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
400 elif m == "d": # directory rename
406 elif m == "d": # directory rename
401 f2, fd, flags = a[2:]
407 f2, fd, flags = a[2:]
402 if f:
408 if f:
403 repo.ui.note(_("moving %s to %s\n") % (f, fd))
409 repo.ui.note(_("moving %s to %s\n") % (f, fd))
404 audit(f)
410 audit(f)
405 repo.wwrite(fd, wctx.filectx(f).data(), flags)
411 repo.wwrite(fd, wctx.filectx(f).data(), flags)
406 util.unlinkpath(repo.wjoin(f))
412 util.unlinkpath(repo.wjoin(f))
407 if f2:
413 if f2:
408 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
414 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
409 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
415 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
410 updated += 1
416 updated += 1
411 elif m == "dr": # divergent renames
417 elif m == "dr": # divergent renames
412 fl = a[2]
418 fl = a[2]
413 repo.ui.warn(_("note: possible conflict - %s was renamed "
419 repo.ui.warn(_("note: possible conflict - %s was renamed "
414 "multiple times to:\n") % f)
420 "multiple times to:\n") % f)
415 for nf in fl:
421 for nf in fl:
416 repo.ui.warn(" %s\n" % nf)
422 repo.ui.warn(" %s\n" % nf)
417 elif m == "rd": # rename and delete
423 elif m == "rd": # rename and delete
418 fl = a[2]
424 fl = a[2]
419 repo.ui.warn(_("note: possible conflict - %s was deleted "
425 repo.ui.warn(_("note: possible conflict - %s was deleted "
420 "and renamed to:\n") % f)
426 "and renamed to:\n") % f)
421 for nf in fl:
427 for nf in fl:
422 repo.ui.warn(" %s\n" % nf)
428 repo.ui.warn(" %s\n" % nf)
423 elif m == "e": # exec
429 elif m == "e": # exec
424 flags = a[2]
430 flags = a[2]
425 audit(f)
431 audit(f)
426 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
432 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
427 updated += 1
433 updated += 1
428 ms.commit()
434 ms.commit()
429 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
435 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
430
436
431 return updated, merged, removed, unresolved
437 return updated, merged, removed, unresolved
432
438
433 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
439 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
434 "Calculate the actions needed to merge mctx into tctx"
440 "Calculate the actions needed to merge mctx into tctx"
435 actions = []
441 actions = []
436 folding = not util.checkcase(repo.path)
442 folding = not util.checkcase(repo.path)
437 if folding:
443 if folding:
438 # collision check is not needed for clean update
444 # collision check is not needed for clean update
439 if (not branchmerge and
445 if (not branchmerge and
440 (force or not tctx.dirty(missing=True, branch=False))):
446 (force or not tctx.dirty(missing=True, branch=False))):
441 _checkcollision(mctx, None)
447 _checkcollision(mctx, None)
442 else:
448 else:
443 _checkcollision(mctx, (tctx, ancestor))
449 _checkcollision(mctx, (tctx, ancestor))
444 if not force:
450 if not force:
445 _checkunknown(repo, tctx, mctx)
451 _checkunknown(repo, tctx, mctx)
446 if tctx.rev() is None:
452 if tctx.rev() is None:
447 actions += _forgetremoved(tctx, mctx, branchmerge)
453 actions += _forgetremoved(tctx, mctx, branchmerge)
448 actions += manifestmerge(repo, tctx, mctx,
454 actions += manifestmerge(repo, tctx, mctx,
449 ancestor,
455 ancestor,
450 force and not branchmerge,
456 force and not branchmerge,
451 partial)
457 partial)
452 return actions
458 return actions
453
459
454 def recordupdates(repo, actions, branchmerge):
460 def recordupdates(repo, actions, branchmerge):
455 "record merge actions to the dirstate"
461 "record merge actions to the dirstate"
456
462
457 for a in actions:
463 for a in actions:
458 f, m = a[:2]
464 f, m = a[:2]
459 if m == "r": # remove
465 if m == "r": # remove
460 if branchmerge:
466 if branchmerge:
461 repo.dirstate.remove(f)
467 repo.dirstate.remove(f)
462 else:
468 else:
463 repo.dirstate.drop(f)
469 repo.dirstate.drop(f)
464 elif m == "a": # re-add
470 elif m == "a": # re-add
465 if not branchmerge:
471 if not branchmerge:
466 repo.dirstate.add(f)
472 repo.dirstate.add(f)
467 elif m == "f": # forget
473 elif m == "f": # forget
468 repo.dirstate.drop(f)
474 repo.dirstate.drop(f)
469 elif m == "e": # exec change
475 elif m == "e": # exec change
470 repo.dirstate.normallookup(f)
476 repo.dirstate.normallookup(f)
471 elif m == "g": # get
477 elif m == "g": # get
472 if branchmerge:
478 if branchmerge:
473 repo.dirstate.otherparent(f)
479 repo.dirstate.otherparent(f)
474 else:
480 else:
475 repo.dirstate.normal(f)
481 repo.dirstate.normal(f)
476 elif m == "m": # merge
482 elif m == "m": # merge
477 f2, fd, move = a[2:]
483 f2, fd, move = a[2:]
478 if branchmerge:
484 if branchmerge:
479 # We've done a branch merge, mark this file as merged
485 # We've done a branch merge, mark this file as merged
480 # so that we properly record the merger later
486 # so that we properly record the merger later
481 repo.dirstate.merge(fd)
487 repo.dirstate.merge(fd)
482 if f != f2: # copy/rename
488 if f != f2: # copy/rename
483 if move:
489 if move:
484 repo.dirstate.remove(f)
490 repo.dirstate.remove(f)
485 if f != fd:
491 if f != fd:
486 repo.dirstate.copy(f, fd)
492 repo.dirstate.copy(f, fd)
487 else:
493 else:
488 repo.dirstate.copy(f2, fd)
494 repo.dirstate.copy(f2, fd)
489 else:
495 else:
490 # We've update-merged a locally modified file, so
496 # We've update-merged a locally modified file, so
491 # we set the dirstate to emulate a normal checkout
497 # we set the dirstate to emulate a normal checkout
492 # of that file some time in the past. Thus our
498 # of that file some time in the past. Thus our
493 # merge will appear as a normal local file
499 # merge will appear as a normal local file
494 # modification.
500 # modification.
495 if f2 == fd: # file not locally copied/moved
501 if f2 == fd: # file not locally copied/moved
496 repo.dirstate.normallookup(fd)
502 repo.dirstate.normallookup(fd)
497 if move:
503 if move:
498 repo.dirstate.drop(f)
504 repo.dirstate.drop(f)
499 elif m == "d": # directory rename
505 elif m == "d": # directory rename
500 f2, fd, flag = a[2:]
506 f2, fd, flag = a[2:]
501 if not f2 and f not in repo.dirstate:
507 if not f2 and f not in repo.dirstate:
502 # untracked file moved
508 # untracked file moved
503 continue
509 continue
504 if branchmerge:
510 if branchmerge:
505 repo.dirstate.add(fd)
511 repo.dirstate.add(fd)
506 if f:
512 if f:
507 repo.dirstate.remove(f)
513 repo.dirstate.remove(f)
508 repo.dirstate.copy(f, fd)
514 repo.dirstate.copy(f, fd)
509 if f2:
515 if f2:
510 repo.dirstate.copy(f2, fd)
516 repo.dirstate.copy(f2, fd)
511 else:
517 else:
512 repo.dirstate.normal(fd)
518 repo.dirstate.normal(fd)
513 if f:
519 if f:
514 repo.dirstate.drop(f)
520 repo.dirstate.drop(f)
515
521
516 def update(repo, node, branchmerge, force, partial, ancestor=None,
522 def update(repo, node, branchmerge, force, partial, ancestor=None,
517 mergeancestor=False):
523 mergeancestor=False):
518 """
524 """
519 Perform a merge between the working directory and the given node
525 Perform a merge between the working directory and the given node
520
526
521 node = the node to update to, or None if unspecified
527 node = the node to update to, or None if unspecified
522 branchmerge = whether to merge between branches
528 branchmerge = whether to merge between branches
523 force = whether to force branch merging or file overwriting
529 force = whether to force branch merging or file overwriting
524 partial = a function to filter file lists (dirstate not updated)
530 partial = a function to filter file lists (dirstate not updated)
525 mergeancestor = if false, merging with an ancestor (fast-forward)
531 mergeancestor = if false, merging with an ancestor (fast-forward)
526 is only allowed between different named branches. This flag
532 is only allowed between different named branches. This flag
527 is used by rebase extension as a temporary fix and should be
533 is used by rebase extension as a temporary fix and should be
528 avoided in general.
534 avoided in general.
529
535
530 The table below shows all the behaviors of the update command
536 The table below shows all the behaviors of the update command
531 given the -c and -C or no options, whether the working directory
537 given the -c and -C or no options, whether the working directory
532 is dirty, whether a revision is specified, and the relationship of
538 is dirty, whether a revision is specified, and the relationship of
533 the parent rev to the target rev (linear, on the same named
539 the parent rev to the target rev (linear, on the same named
534 branch, or on another named branch).
540 branch, or on another named branch).
535
541
536 This logic is tested by test-update-branches.t.
542 This logic is tested by test-update-branches.t.
537
543
538 -c -C dirty rev | linear same cross
544 -c -C dirty rev | linear same cross
539 n n n n | ok (1) x
545 n n n n | ok (1) x
540 n n n y | ok ok ok
546 n n n y | ok ok ok
541 n n y * | merge (2) (2)
547 n n y * | merge (2) (2)
542 n y * * | --- discard ---
548 n y * * | --- discard ---
543 y n y * | --- (3) ---
549 y n y * | --- (3) ---
544 y n n * | --- ok ---
550 y n n * | --- ok ---
545 y y * * | --- (4) ---
551 y y * * | --- (4) ---
546
552
547 x = can't happen
553 x = can't happen
548 * = don't-care
554 * = don't-care
549 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
555 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
550 2 = abort: crosses branches (use 'hg merge' to merge or
556 2 = abort: crosses branches (use 'hg merge' to merge or
551 use 'hg update -C' to discard changes)
557 use 'hg update -C' to discard changes)
552 3 = abort: uncommitted local changes
558 3 = abort: uncommitted local changes
553 4 = incompatible options (checked in commands.py)
559 4 = incompatible options (checked in commands.py)
554
560
555 Return the same tuple as applyupdates().
561 Return the same tuple as applyupdates().
556 """
562 """
557
563
558 onode = node
564 onode = node
559 wlock = repo.wlock()
565 wlock = repo.wlock()
560 try:
566 try:
561 wc = repo[None]
567 wc = repo[None]
562 if node is None:
568 if node is None:
563 # tip of current branch
569 # tip of current branch
564 try:
570 try:
565 node = repo.branchtip(wc.branch())
571 node = repo.branchtip(wc.branch())
566 except error.RepoLookupError:
572 except error.RepoLookupError:
567 if wc.branch() == "default": # no default branch!
573 if wc.branch() == "default": # no default branch!
568 node = repo.lookup("tip") # update to tip
574 node = repo.lookup("tip") # update to tip
569 else:
575 else:
570 raise util.Abort(_("branch %s not found") % wc.branch())
576 raise util.Abort(_("branch %s not found") % wc.branch())
571 overwrite = force and not branchmerge
577 overwrite = force and not branchmerge
572 pl = wc.parents()
578 pl = wc.parents()
573 p1, p2 = pl[0], repo[node]
579 p1, p2 = pl[0], repo[node]
574 if ancestor:
580 if ancestor:
575 pa = repo[ancestor]
581 pa = repo[ancestor]
576 else:
582 else:
577 pa = p1.ancestor(p2)
583 pa = p1.ancestor(p2)
578
584
579 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
585 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
580
586
581 ### check phase
587 ### check phase
582 if not overwrite and len(pl) > 1:
588 if not overwrite and len(pl) > 1:
583 raise util.Abort(_("outstanding uncommitted merges"))
589 raise util.Abort(_("outstanding uncommitted merges"))
584 if branchmerge:
590 if branchmerge:
585 if pa == p2:
591 if pa == p2:
586 raise util.Abort(_("merging with a working directory ancestor"
592 raise util.Abort(_("merging with a working directory ancestor"
587 " has no effect"))
593 " has no effect"))
588 elif pa == p1:
594 elif pa == p1:
589 if not mergeancestor and p1.branch() == p2.branch():
595 if not mergeancestor and p1.branch() == p2.branch():
590 raise util.Abort(_("nothing to merge"),
596 raise util.Abort(_("nothing to merge"),
591 hint=_("use 'hg update' "
597 hint=_("use 'hg update' "
592 "or check 'hg heads'"))
598 "or check 'hg heads'"))
593 if not force and (wc.files() or wc.deleted()):
599 if not force and (wc.files() or wc.deleted()):
594 raise util.Abort(_("outstanding uncommitted changes"),
600 raise util.Abort(_("outstanding uncommitted changes"),
595 hint=_("use 'hg status' to list changes"))
601 hint=_("use 'hg status' to list changes"))
596 for s in sorted(wc.substate):
602 for s in sorted(wc.substate):
597 if wc.sub(s).dirty():
603 if wc.sub(s).dirty():
598 raise util.Abort(_("outstanding uncommitted changes in "
604 raise util.Abort(_("outstanding uncommitted changes in "
599 "subrepository '%s'") % s)
605 "subrepository '%s'") % s)
600
606
601 elif not overwrite:
607 elif not overwrite:
602 if pa == p1 or pa == p2: # linear
608 if pa == p1 or pa == p2: # linear
603 pass # all good
609 pass # all good
604 elif wc.dirty(missing=True):
610 elif wc.dirty(missing=True):
605 raise util.Abort(_("crosses branches (merge branches or use"
611 raise util.Abort(_("crosses branches (merge branches or use"
606 " --clean to discard changes)"))
612 " --clean to discard changes)"))
607 elif onode is None:
613 elif onode is None:
608 raise util.Abort(_("crosses branches (merge branches or update"
614 raise util.Abort(_("crosses branches (merge branches or update"
609 " --check to force update)"))
615 " --check to force update)"))
610 else:
616 else:
611 # Allow jumping branches if clean and specific rev given
617 # Allow jumping branches if clean and specific rev given
612 pa = p1
618 pa = p1
613
619
614 ### calculate phase
620 ### calculate phase
615 actions = calculateupdates(repo, wc, p2, pa,
621 actions = calculateupdates(repo, wc, p2, pa,
616 branchmerge, force, partial)
622 branchmerge, force, partial)
617
623
618 ### apply phase
624 ### apply phase
619 if not branchmerge: # just jump to the new rev
625 if not branchmerge: # just jump to the new rev
620 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
626 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
621 if not partial:
627 if not partial:
622 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
628 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
623
629
624 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
630 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
625
631
626 if not partial:
632 if not partial:
627 repo.setparents(fp1, fp2)
633 repo.setparents(fp1, fp2)
628 recordupdates(repo, actions, branchmerge)
634 recordupdates(repo, actions, branchmerge)
629 if not branchmerge:
635 if not branchmerge:
630 repo.dirstate.setbranch(p2.branch())
636 repo.dirstate.setbranch(p2.branch())
631 finally:
637 finally:
632 wlock.release()
638 wlock.release()
633
639
634 if not partial:
640 if not partial:
635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
641 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
636 return stats
642 return stats
General Comments 0
You need to be logged in to leave comments. Login now