##// END OF EJS Templates
merge: remove "case" comments...
Mads Kiilerich -
r18339:aadefcee default
parent child Browse files
Show More
@@ -1,636 +1,636 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 p1.substate:
225 for s in 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 m1.iteritems():
231 for f, n in m1.iteritems():
232 if partial and not partial(f):
232 if partial and not partial(f):
233 continue
233 continue
234 if f in m2:
234 if f in m2:
235 n2 = m2[f]
235 n2 = m2[f]
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
236 fl1, fl2, fla = m1.flags(f), m2.flags(f), ma.flags(f)
237 nol = 'l' not in fl1 + fl2 + fla
237 nol = 'l' not in fl1 + fl2 + fla
238 a = ma.get(f, nullid)
238 a = ma.get(f, nullid)
239 if n == n2 and fl1 == fl2:
239 if n == n2 and fl1 == fl2:
240 pass # same - keep local
240 pass # same - keep local
241 elif n2 == a and fl2 == fla:
241 elif n2 == a and fl2 == fla:
242 pass # remote unchanged - keep local
242 pass # remote unchanged - keep local
243 elif n == a and fl1 == fla: # local unchanged - use remote
243 elif n == a and fl1 == fla: # local unchanged - use remote
244 if n == n2: # optimization: keep local content
244 if n == n2: # optimization: keep local content
245 act("update permissions", "e", f, fl2)
245 act("update permissions", "e", f, fl2)
246 else:
246 else:
247 act("remote is newer", "g", f, fl2)
247 act("remote is newer", "g", f, fl2)
248 elif nol and n2 == a: # remote only changed 'x'
248 elif nol and n2 == a: # remote only changed 'x'
249 act("update permissions", "e", f, fl2)
249 act("update permissions", "e", f, fl2)
250 elif nol and n == a: # local only changed 'x'
250 elif nol and n == a: # local only changed 'x'
251 act("remote is newer", "g", f, fl)
251 act("remote is newer", "g", f, fl)
252 else: # both changed something
252 else: # both changed something
253 act("versions differ", "m", f, f, f, False)
253 act("versions differ", "m", f, f, f, False)
254 elif f in copied: # files we'll deal with on m2 side
254 elif f in copied: # files we'll deal with on m2 side
255 pass
255 pass
256 elif f in movewithdir: # directory rename
256 elif f in movewithdir: # directory rename
257 f2 = movewithdir[f]
257 f2 = movewithdir[f]
258 act("remote renamed directory to " + f2, "d", f, None, f2,
258 act("remote renamed directory to " + f2, "d", f, None, f2,
259 m1.flags(f))
259 m1.flags(f))
260 elif f in copy: # case 2 A,B/B/B or case 4,21 A/B/B
260 elif f in copy:
261 f2 = copy[f]
261 f2 = copy[f]
262 act("local copied/moved to " + f2, "m", f, f2, f, False)
262 act("local copied/moved to " + f2, "m", f, f2, f, False)
263 elif f in ma: # clean, a different, no remote
263 elif f in ma: # clean, a different, no remote
264 if n != ma[f]:
264 if n != ma[f]:
265 if repo.ui.promptchoice(
265 if repo.ui.promptchoice(
266 _(" local changed %s which remote deleted\n"
266 _(" local changed %s which remote deleted\n"
267 "use (c)hanged version or (d)elete?") % f,
267 "use (c)hanged version or (d)elete?") % f,
268 (_("&Changed"), _("&Delete")), 0):
268 (_("&Changed"), _("&Delete")), 0):
269 act("prompt delete", "r", f)
269 act("prompt delete", "r", f)
270 else:
270 else:
271 act("prompt keep", "a", f)
271 act("prompt keep", "a", f)
272 elif n[20:] == "a": # added, no remote
272 elif n[20:] == "a": # added, no remote
273 act("remote deleted", "f", f)
273 act("remote deleted", "f", f)
274 else:
274 else:
275 act("other deleted", "r", f)
275 act("other deleted", "r", f)
276
276
277 for f, n in m2.iteritems():
277 for f, n in m2.iteritems():
278 if partial and not partial(f):
278 if partial and not partial(f):
279 continue
279 continue
280 if f in m1 or f in copied: # files already visited
280 if f in m1 or f in copied: # files already visited
281 continue
281 continue
282 if f in movewithdir:
282 if f in movewithdir:
283 f2 = movewithdir[f]
283 f2 = movewithdir[f]
284 act("local renamed directory to " + f2, "d", None, f, f2,
284 act("local renamed directory to " + f2, "d", None, f, f2,
285 m2.flags(f))
285 m2.flags(f))
286 elif f in copy:
286 elif f in copy:
287 f2 = copy[f]
287 f2 = copy[f]
288 if f2 in m2: # rename case 1, A/A,B/A
288 if f2 in m2:
289 act("remote copied to " + f, "m",
289 act("remote copied to " + f, "m",
290 f2, f, f, False)
290 f2, f, f, False)
291 else: # case 3,20 A/B/A
291 else:
292 act("remote moved to " + f, "m",
292 act("remote moved to " + f, "m",
293 f2, f, f, True)
293 f2, f, f, True)
294 elif f not in ma:
294 elif f not in ma:
295 if (not overwrite
295 if (not overwrite
296 and _checkunknownfile(repo, p1, p2, f)):
296 and _checkunknownfile(repo, p1, p2, f)):
297 act("remote differs from untracked local",
297 act("remote differs from untracked local",
298 "m", f, f, f, False)
298 "m", f, f, f, False)
299 else:
299 else:
300 act("remote created", "g", f, m2.flags(f))
300 act("remote created", "g", f, m2.flags(f))
301 elif n != ma[f]:
301 elif n != ma[f]:
302 if repo.ui.promptchoice(
302 if repo.ui.promptchoice(
303 _("remote changed %s which local deleted\n"
303 _("remote changed %s which local deleted\n"
304 "use (c)hanged version or leave (d)eleted?") % f,
304 "use (c)hanged version or leave (d)eleted?") % f,
305 (_("&Changed"), _("&Deleted")), 0) == 0:
305 (_("&Changed"), _("&Deleted")), 0) == 0:
306 act("prompt recreating", "g", f, m2.flags(f))
306 act("prompt recreating", "g", f, m2.flags(f))
307
307
308 return actions
308 return actions
309
309
310 def actionkey(a):
310 def actionkey(a):
311 return a[1] == "r" and -1 or 0, a
311 return a[1] == "r" and -1 or 0, a
312
312
313 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
313 def applyupdates(repo, actions, wctx, mctx, actx, overwrite):
314 """apply the merge action list to the working directory
314 """apply the merge action list to the working directory
315
315
316 wctx is the working copy context
316 wctx is the working copy context
317 mctx is the context to be merged into the working copy
317 mctx is the context to be merged into the working copy
318 actx is the context of the common ancestor
318 actx is the context of the common ancestor
319
319
320 Return a tuple of counts (updated, merged, removed, unresolved) that
320 Return a tuple of counts (updated, merged, removed, unresolved) that
321 describes how many files were affected by the update.
321 describes how many files were affected by the update.
322 """
322 """
323
323
324 updated, merged, removed, unresolved = 0, 0, 0, 0
324 updated, merged, removed, unresolved = 0, 0, 0, 0
325 ms = mergestate(repo)
325 ms = mergestate(repo)
326 ms.reset(wctx.p1().node())
326 ms.reset(wctx.p1().node())
327 moves = []
327 moves = []
328 actions.sort(key=actionkey)
328 actions.sort(key=actionkey)
329
329
330 # prescan for merges
330 # prescan for merges
331 for a in actions:
331 for a in actions:
332 f, m = a[:2]
332 f, m = a[:2]
333 if m == "m": # merge
333 if m == "m": # merge
334 f2, fd, move = a[2:]
334 f2, fd, move = a[2:]
335 if fd == '.hgsubstate': # merged internally
335 if fd == '.hgsubstate': # merged internally
336 continue
336 continue
337 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
337 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
338 fcl = wctx[f]
338 fcl = wctx[f]
339 fco = mctx[f2]
339 fco = mctx[f2]
340 if mctx == actx: # backwards, use working dir parent as ancestor
340 if mctx == actx: # backwards, use working dir parent as ancestor
341 if fcl.parents():
341 if fcl.parents():
342 fca = fcl.p1()
342 fca = fcl.p1()
343 else:
343 else:
344 fca = repo.filectx(f, fileid=nullrev)
344 fca = repo.filectx(f, fileid=nullrev)
345 else:
345 else:
346 fca = fcl.ancestor(fco, actx)
346 fca = fcl.ancestor(fco, actx)
347 if not fca:
347 if not fca:
348 fca = repo.filectx(f, fileid=nullrev)
348 fca = repo.filectx(f, fileid=nullrev)
349 ms.add(fcl, fco, fca, fd)
349 ms.add(fcl, fco, fca, fd)
350 if f != fd and move:
350 if f != fd and move:
351 moves.append(f)
351 moves.append(f)
352
352
353 audit = repo.wopener.audit
353 audit = repo.wopener.audit
354
354
355 # remove renamed files after safely stored
355 # remove renamed files after safely stored
356 for f in moves:
356 for f in moves:
357 if os.path.lexists(repo.wjoin(f)):
357 if os.path.lexists(repo.wjoin(f)):
358 repo.ui.debug("removing %s\n" % f)
358 repo.ui.debug("removing %s\n" % f)
359 audit(f)
359 audit(f)
360 util.unlinkpath(repo.wjoin(f))
360 util.unlinkpath(repo.wjoin(f))
361
361
362 numupdates = len(actions)
362 numupdates = len(actions)
363 for i, a in enumerate(actions):
363 for i, a in enumerate(actions):
364 f, m = a[:2]
364 f, m = a[:2]
365 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
365 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
366 unit=_('files'))
366 unit=_('files'))
367 if m == "r": # remove
367 if m == "r": # remove
368 repo.ui.note(_("removing %s\n") % f)
368 repo.ui.note(_("removing %s\n") % f)
369 audit(f)
369 audit(f)
370 if f == '.hgsubstate': # subrepo states need updating
370 if f == '.hgsubstate': # subrepo states need updating
371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
371 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
372 try:
372 try:
373 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
373 util.unlinkpath(repo.wjoin(f), ignoremissing=True)
374 except OSError, inst:
374 except OSError, inst:
375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
375 repo.ui.warn(_("update failed to remove %s: %s!\n") %
376 (f, inst.strerror))
376 (f, inst.strerror))
377 removed += 1
377 removed += 1
378 elif m == "m": # merge
378 elif m == "m": # merge
379 if fd == '.hgsubstate': # subrepo states need updating
379 if fd == '.hgsubstate': # subrepo states need updating
380 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
380 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
381 overwrite)
381 overwrite)
382 continue
382 continue
383 f2, fd, move = a[2:]
383 f2, fd, move = a[2:]
384 audit(fd)
384 audit(fd)
385 r = ms.resolve(fd, wctx, mctx)
385 r = ms.resolve(fd, wctx, mctx)
386 if r is not None and r > 0:
386 if r is not None and r > 0:
387 unresolved += 1
387 unresolved += 1
388 else:
388 else:
389 if r is None:
389 if r is None:
390 updated += 1
390 updated += 1
391 else:
391 else:
392 merged += 1
392 merged += 1
393 elif m == "g": # get
393 elif m == "g": # get
394 flags = a[2]
394 flags = a[2]
395 repo.ui.note(_("getting %s\n") % f)
395 repo.ui.note(_("getting %s\n") % f)
396 repo.wwrite(f, mctx.filectx(f).data(), flags)
396 repo.wwrite(f, mctx.filectx(f).data(), flags)
397 updated += 1
397 updated += 1
398 if f == '.hgsubstate': # subrepo states need updating
398 if f == '.hgsubstate': # subrepo states need updating
399 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
399 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
400 elif m == "d": # directory rename
400 elif m == "d": # directory rename
401 f2, fd, flags = a[2:]
401 f2, fd, flags = a[2:]
402 if f:
402 if f:
403 repo.ui.note(_("moving %s to %s\n") % (f, fd))
403 repo.ui.note(_("moving %s to %s\n") % (f, fd))
404 audit(f)
404 audit(f)
405 repo.wwrite(fd, wctx.filectx(f).data(), flags)
405 repo.wwrite(fd, wctx.filectx(f).data(), flags)
406 util.unlinkpath(repo.wjoin(f))
406 util.unlinkpath(repo.wjoin(f))
407 if f2:
407 if f2:
408 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
408 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
409 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
409 repo.wwrite(fd, mctx.filectx(f2).data(), flags)
410 updated += 1
410 updated += 1
411 elif m == "dr": # divergent renames
411 elif m == "dr": # divergent renames
412 fl = a[2]
412 fl = a[2]
413 repo.ui.warn(_("note: possible conflict - %s was renamed "
413 repo.ui.warn(_("note: possible conflict - %s was renamed "
414 "multiple times to:\n") % f)
414 "multiple times to:\n") % f)
415 for nf in fl:
415 for nf in fl:
416 repo.ui.warn(" %s\n" % nf)
416 repo.ui.warn(" %s\n" % nf)
417 elif m == "rd": # rename and delete
417 elif m == "rd": # rename and delete
418 fl = a[2]
418 fl = a[2]
419 repo.ui.warn(_("note: possible conflict - %s was deleted "
419 repo.ui.warn(_("note: possible conflict - %s was deleted "
420 "and renamed to:\n") % f)
420 "and renamed to:\n") % f)
421 for nf in fl:
421 for nf in fl:
422 repo.ui.warn(" %s\n" % nf)
422 repo.ui.warn(" %s\n" % nf)
423 elif m == "e": # exec
423 elif m == "e": # exec
424 flags = a[2]
424 flags = a[2]
425 audit(f)
425 audit(f)
426 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
426 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
427 updated += 1
427 updated += 1
428 ms.commit()
428 ms.commit()
429 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
429 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
430
430
431 return updated, merged, removed, unresolved
431 return updated, merged, removed, unresolved
432
432
433 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
433 def calculateupdates(repo, tctx, mctx, ancestor, branchmerge, force, partial):
434 "Calculate the actions needed to merge mctx into tctx"
434 "Calculate the actions needed to merge mctx into tctx"
435 actions = []
435 actions = []
436 folding = not util.checkcase(repo.path)
436 folding = not util.checkcase(repo.path)
437 if folding:
437 if folding:
438 # collision check is not needed for clean update
438 # collision check is not needed for clean update
439 if (not branchmerge and
439 if (not branchmerge and
440 (force or not tctx.dirty(missing=True, branch=False))):
440 (force or not tctx.dirty(missing=True, branch=False))):
441 _checkcollision(mctx, None)
441 _checkcollision(mctx, None)
442 else:
442 else:
443 _checkcollision(mctx, (tctx, ancestor))
443 _checkcollision(mctx, (tctx, ancestor))
444 if not force:
444 if not force:
445 _checkunknown(repo, tctx, mctx)
445 _checkunknown(repo, tctx, mctx)
446 if tctx.rev() is None:
446 if tctx.rev() is None:
447 actions += _forgetremoved(tctx, mctx, branchmerge)
447 actions += _forgetremoved(tctx, mctx, branchmerge)
448 actions += manifestmerge(repo, tctx, mctx,
448 actions += manifestmerge(repo, tctx, mctx,
449 ancestor,
449 ancestor,
450 force and not branchmerge,
450 force and not branchmerge,
451 partial)
451 partial)
452 return actions
452 return actions
453
453
454 def recordupdates(repo, actions, branchmerge):
454 def recordupdates(repo, actions, branchmerge):
455 "record merge actions to the dirstate"
455 "record merge actions to the dirstate"
456
456
457 for a in actions:
457 for a in actions:
458 f, m = a[:2]
458 f, m = a[:2]
459 if m == "r": # remove
459 if m == "r": # remove
460 if branchmerge:
460 if branchmerge:
461 repo.dirstate.remove(f)
461 repo.dirstate.remove(f)
462 else:
462 else:
463 repo.dirstate.drop(f)
463 repo.dirstate.drop(f)
464 elif m == "a": # re-add
464 elif m == "a": # re-add
465 if not branchmerge:
465 if not branchmerge:
466 repo.dirstate.add(f)
466 repo.dirstate.add(f)
467 elif m == "f": # forget
467 elif m == "f": # forget
468 repo.dirstate.drop(f)
468 repo.dirstate.drop(f)
469 elif m == "e": # exec change
469 elif m == "e": # exec change
470 repo.dirstate.normallookup(f)
470 repo.dirstate.normallookup(f)
471 elif m == "g": # get
471 elif m == "g": # get
472 if branchmerge:
472 if branchmerge:
473 repo.dirstate.otherparent(f)
473 repo.dirstate.otherparent(f)
474 else:
474 else:
475 repo.dirstate.normal(f)
475 repo.dirstate.normal(f)
476 elif m == "m": # merge
476 elif m == "m": # merge
477 f2, fd, move = a[2:]
477 f2, fd, move = a[2:]
478 if branchmerge:
478 if branchmerge:
479 # We've done a branch merge, mark this file as merged
479 # We've done a branch merge, mark this file as merged
480 # so that we properly record the merger later
480 # so that we properly record the merger later
481 repo.dirstate.merge(fd)
481 repo.dirstate.merge(fd)
482 if f != f2: # copy/rename
482 if f != f2: # copy/rename
483 if move:
483 if move:
484 repo.dirstate.remove(f)
484 repo.dirstate.remove(f)
485 if f != fd:
485 if f != fd:
486 repo.dirstate.copy(f, fd)
486 repo.dirstate.copy(f, fd)
487 else:
487 else:
488 repo.dirstate.copy(f2, fd)
488 repo.dirstate.copy(f2, fd)
489 else:
489 else:
490 # We've update-merged a locally modified file, so
490 # We've update-merged a locally modified file, so
491 # we set the dirstate to emulate a normal checkout
491 # we set the dirstate to emulate a normal checkout
492 # of that file some time in the past. Thus our
492 # of that file some time in the past. Thus our
493 # merge will appear as a normal local file
493 # merge will appear as a normal local file
494 # modification.
494 # modification.
495 if f2 == fd: # file not locally copied/moved
495 if f2 == fd: # file not locally copied/moved
496 repo.dirstate.normallookup(fd)
496 repo.dirstate.normallookup(fd)
497 if move:
497 if move:
498 repo.dirstate.drop(f)
498 repo.dirstate.drop(f)
499 elif m == "d": # directory rename
499 elif m == "d": # directory rename
500 f2, fd, flag = a[2:]
500 f2, fd, flag = a[2:]
501 if not f2 and f not in repo.dirstate:
501 if not f2 and f not in repo.dirstate:
502 # untracked file moved
502 # untracked file moved
503 continue
503 continue
504 if branchmerge:
504 if branchmerge:
505 repo.dirstate.add(fd)
505 repo.dirstate.add(fd)
506 if f:
506 if f:
507 repo.dirstate.remove(f)
507 repo.dirstate.remove(f)
508 repo.dirstate.copy(f, fd)
508 repo.dirstate.copy(f, fd)
509 if f2:
509 if f2:
510 repo.dirstate.copy(f2, fd)
510 repo.dirstate.copy(f2, fd)
511 else:
511 else:
512 repo.dirstate.normal(fd)
512 repo.dirstate.normal(fd)
513 if f:
513 if f:
514 repo.dirstate.drop(f)
514 repo.dirstate.drop(f)
515
515
516 def update(repo, node, branchmerge, force, partial, ancestor=None,
516 def update(repo, node, branchmerge, force, partial, ancestor=None,
517 mergeancestor=False):
517 mergeancestor=False):
518 """
518 """
519 Perform a merge between the working directory and the given node
519 Perform a merge between the working directory and the given node
520
520
521 node = the node to update to, or None if unspecified
521 node = the node to update to, or None if unspecified
522 branchmerge = whether to merge between branches
522 branchmerge = whether to merge between branches
523 force = whether to force branch merging or file overwriting
523 force = whether to force branch merging or file overwriting
524 partial = a function to filter file lists (dirstate not updated)
524 partial = a function to filter file lists (dirstate not updated)
525 mergeancestor = if false, merging with an ancestor (fast-forward)
525 mergeancestor = if false, merging with an ancestor (fast-forward)
526 is only allowed between different named branches. This flag
526 is only allowed between different named branches. This flag
527 is used by rebase extension as a temporary fix and should be
527 is used by rebase extension as a temporary fix and should be
528 avoided in general.
528 avoided in general.
529
529
530 The table below shows all the behaviors of the update command
530 The table below shows all the behaviors of the update command
531 given the -c and -C or no options, whether the working directory
531 given the -c and -C or no options, whether the working directory
532 is dirty, whether a revision is specified, and the relationship of
532 is dirty, whether a revision is specified, and the relationship of
533 the parent rev to the target rev (linear, on the same named
533 the parent rev to the target rev (linear, on the same named
534 branch, or on another named branch).
534 branch, or on another named branch).
535
535
536 This logic is tested by test-update-branches.t.
536 This logic is tested by test-update-branches.t.
537
537
538 -c -C dirty rev | linear same cross
538 -c -C dirty rev | linear same cross
539 n n n n | ok (1) x
539 n n n n | ok (1) x
540 n n n y | ok ok ok
540 n n n y | ok ok ok
541 n n y * | merge (2) (2)
541 n n y * | merge (2) (2)
542 n y * * | --- discard ---
542 n y * * | --- discard ---
543 y n y * | --- (3) ---
543 y n y * | --- (3) ---
544 y n n * | --- ok ---
544 y n n * | --- ok ---
545 y y * * | --- (4) ---
545 y y * * | --- (4) ---
546
546
547 x = can't happen
547 x = can't happen
548 * = don't-care
548 * = don't-care
549 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
549 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
550 2 = abort: crosses branches (use 'hg merge' to merge or
550 2 = abort: crosses branches (use 'hg merge' to merge or
551 use 'hg update -C' to discard changes)
551 use 'hg update -C' to discard changes)
552 3 = abort: uncommitted local changes
552 3 = abort: uncommitted local changes
553 4 = incompatible options (checked in commands.py)
553 4 = incompatible options (checked in commands.py)
554
554
555 Return the same tuple as applyupdates().
555 Return the same tuple as applyupdates().
556 """
556 """
557
557
558 onode = node
558 onode = node
559 wlock = repo.wlock()
559 wlock = repo.wlock()
560 try:
560 try:
561 wc = repo[None]
561 wc = repo[None]
562 if node is None:
562 if node is None:
563 # tip of current branch
563 # tip of current branch
564 try:
564 try:
565 node = repo.branchtip(wc.branch())
565 node = repo.branchtip(wc.branch())
566 except error.RepoLookupError:
566 except error.RepoLookupError:
567 if wc.branch() == "default": # no default branch!
567 if wc.branch() == "default": # no default branch!
568 node = repo.lookup("tip") # update to tip
568 node = repo.lookup("tip") # update to tip
569 else:
569 else:
570 raise util.Abort(_("branch %s not found") % wc.branch())
570 raise util.Abort(_("branch %s not found") % wc.branch())
571 overwrite = force and not branchmerge
571 overwrite = force and not branchmerge
572 pl = wc.parents()
572 pl = wc.parents()
573 p1, p2 = pl[0], repo[node]
573 p1, p2 = pl[0], repo[node]
574 if ancestor:
574 if ancestor:
575 pa = repo[ancestor]
575 pa = repo[ancestor]
576 else:
576 else:
577 pa = p1.ancestor(p2)
577 pa = p1.ancestor(p2)
578
578
579 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
579 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
580
580
581 ### check phase
581 ### check phase
582 if not overwrite and len(pl) > 1:
582 if not overwrite and len(pl) > 1:
583 raise util.Abort(_("outstanding uncommitted merges"))
583 raise util.Abort(_("outstanding uncommitted merges"))
584 if branchmerge:
584 if branchmerge:
585 if pa == p2:
585 if pa == p2:
586 raise util.Abort(_("merging with a working directory ancestor"
586 raise util.Abort(_("merging with a working directory ancestor"
587 " has no effect"))
587 " has no effect"))
588 elif pa == p1:
588 elif pa == p1:
589 if not mergeancestor and p1.branch() == p2.branch():
589 if not mergeancestor and p1.branch() == p2.branch():
590 raise util.Abort(_("nothing to merge"),
590 raise util.Abort(_("nothing to merge"),
591 hint=_("use 'hg update' "
591 hint=_("use 'hg update' "
592 "or check 'hg heads'"))
592 "or check 'hg heads'"))
593 if not force and (wc.files() or wc.deleted()):
593 if not force and (wc.files() or wc.deleted()):
594 raise util.Abort(_("outstanding uncommitted changes"),
594 raise util.Abort(_("outstanding uncommitted changes"),
595 hint=_("use 'hg status' to list changes"))
595 hint=_("use 'hg status' to list changes"))
596 for s in wc.substate:
596 for s in wc.substate:
597 if wc.sub(s).dirty():
597 if wc.sub(s).dirty():
598 raise util.Abort(_("outstanding uncommitted changes in "
598 raise util.Abort(_("outstanding uncommitted changes in "
599 "subrepository '%s'") % s)
599 "subrepository '%s'") % s)
600
600
601 elif not overwrite:
601 elif not overwrite:
602 if pa == p1 or pa == p2: # linear
602 if pa == p1 or pa == p2: # linear
603 pass # all good
603 pass # all good
604 elif wc.dirty(missing=True):
604 elif wc.dirty(missing=True):
605 raise util.Abort(_("crosses branches (merge branches or use"
605 raise util.Abort(_("crosses branches (merge branches or use"
606 " --clean to discard changes)"))
606 " --clean to discard changes)"))
607 elif onode is None:
607 elif onode is None:
608 raise util.Abort(_("crosses branches (merge branches or update"
608 raise util.Abort(_("crosses branches (merge branches or update"
609 " --check to force update)"))
609 " --check to force update)"))
610 else:
610 else:
611 # Allow jumping branches if clean and specific rev given
611 # Allow jumping branches if clean and specific rev given
612 pa = p1
612 pa = p1
613
613
614 ### calculate phase
614 ### calculate phase
615 actions = calculateupdates(repo, wc, p2, pa,
615 actions = calculateupdates(repo, wc, p2, pa,
616 branchmerge, force, partial)
616 branchmerge, force, partial)
617
617
618 ### apply phase
618 ### apply phase
619 if not branchmerge: # just jump to the new rev
619 if not branchmerge: # just jump to the new rev
620 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
620 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
621 if not partial:
621 if not partial:
622 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
622 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
623
623
624 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
624 stats = applyupdates(repo, actions, wc, p2, pa, overwrite)
625
625
626 if not partial:
626 if not partial:
627 repo.setparents(fp1, fp2)
627 repo.setparents(fp1, fp2)
628 recordupdates(repo, actions, branchmerge)
628 recordupdates(repo, actions, branchmerge)
629 if not branchmerge:
629 if not branchmerge:
630 repo.dirstate.setbranch(p2.branch())
630 repo.dirstate.setbranch(p2.branch())
631 finally:
631 finally:
632 wlock.release()
632 wlock.release()
633
633
634 if not partial:
634 if not partial:
635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
635 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
636 return stats
636 return stats
General Comments 0
You need to be logged in to leave comments. Login now