##// END OF EJS Templates
Fix missing space in one of the new update messages.
Thomas Arendsen Hein -
r6381:0b89315d default
parent child Browse files
Show More
@@ -1,411 +1,411 b''
1 1 # merge.py - directory-level update/merge handling for Mercurial
2 2 #
3 3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from node import nullid, nullrev
9 9 from i18n import _
10 10 import errno, util, os, filemerge, copies
11 11
12 12 def _checkunknown(wctx, mctx):
13 13 "check for collisions between unknown files and files in mctx"
14 14 for f in wctx.unknown():
15 15 if f in mctx and mctx[f].cmp(wctx[f].data()):
16 16 raise util.Abort(_("untracked file in working directory differs"
17 17 " from file in requested revision: '%s'") % f)
18 18
19 19 def _checkcollision(mctx):
20 20 "check for case folding collisions in the destination context"
21 21 folded = {}
22 22 for fn in mctx:
23 23 fold = fn.lower()
24 24 if fold in folded:
25 25 raise util.Abort(_("case-folding collision between %s and %s")
26 26 % (fn, folded[fold]))
27 27 folded[fold] = fn
28 28
29 29 def _forgetremoved(wctx, mctx, branchmerge):
30 30 """
31 31 Forget removed files
32 32
33 33 If we're jumping between revisions (as opposed to merging), and if
34 34 neither the working directory nor the target rev has the file,
35 35 then we need to remove it from the dirstate, to prevent the
36 36 dirstate from listing the file when it is no longer in the
37 37 manifest.
38 38
39 39 If we're merging, and the other revision has removed a file
40 40 that is not present in the working directory, we need to mark it
41 41 as removed.
42 42 """
43 43
44 44 action = []
45 45 state = branchmerge and 'r' or 'f'
46 46 for f in wctx.deleted():
47 47 if f not in mctx:
48 48 action.append((f, state))
49 49
50 50 if not branchmerge:
51 51 for f in wctx.removed():
52 52 if f not in mctx:
53 53 action.append((f, "f"))
54 54
55 55 return action
56 56
57 57 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
58 58 """
59 59 Merge p1 and p2 with ancestor ma and generate merge action list
60 60
61 61 overwrite = whether we clobber working files
62 62 partial = function to filter file lists
63 63 """
64 64
65 65 repo.ui.note(_("resolving manifests\n"))
66 66 repo.ui.debug(_(" overwrite %s partial %s\n") % (overwrite, bool(partial)))
67 67 repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (pa, p1, p2))
68 68
69 69 m1 = p1.manifest()
70 70 m2 = p2.manifest()
71 71 ma = pa.manifest()
72 72 backwards = (pa == p2)
73 73 action = []
74 74 copy, copied, diverge = {}, {}, {}
75 75
76 76 def fmerge(f, f2=None, fa=None):
77 77 """merge flags"""
78 78 if not f2:
79 79 f2 = f
80 80 fa = f
81 81 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
82 82 if m == n: # flags agree
83 83 return m # unchanged
84 84 if m and n: # flags are set but don't agree
85 85 if not a: # both differ from parent
86 86 r = repo.ui.prompt(
87 87 _(" conflicting flags for %s\n"
88 88 "(n)one, e(x)ec or sym(l)ink?") % f, "[nxl]", "n")
89 89 return r != "n" and r or ''
90 90 if m == a:
91 91 return n # changed from m to n
92 92 return m # changed from n to m
93 93 if m and m != a: # changed from a to m
94 94 return m
95 95 if n and n != a: # changed from a to n
96 96 return n
97 97 return '' # flag was cleared
98 98
99 99 def act(msg, m, f, *args):
100 100 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
101 101 action.append((f, m) + args)
102 102
103 103 if pa and not (backwards or overwrite):
104 104 copy, diverge = copies.copies(repo, p1, p2, pa)
105 105 copied = dict.fromkeys(copy.values())
106 106 for of, fl in diverge.items():
107 107 act("divergent renames", "dr", of, fl)
108 108
109 109 # Compare manifests
110 110 for f, n in m1.iteritems():
111 111 if partial and not partial(f):
112 112 continue
113 113 if f in m2:
114 114 if overwrite or backwards:
115 115 rflags = m2.flags(f)
116 116 else:
117 117 rflags = fmerge(f)
118 118 # are files different?
119 119 if n != m2[f]:
120 120 a = ma.get(f, nullid)
121 121 # are we clobbering?
122 122 if overwrite:
123 123 act("clobbering", "g", f, rflags)
124 124 # or are we going back in time and clean?
125 125 elif backwards and not n[20:]:
126 126 act("reverting", "g", f, rflags)
127 127 # are both different from the ancestor?
128 128 elif n != a and m2[f] != a:
129 129 act("versions differ", "m", f, f, f, rflags, False)
130 130 # is remote's version newer?
131 131 elif m2[f] != a:
132 132 act("remote is newer", "g", f, rflags)
133 133 # local is newer, not overwrite, check mode bits
134 134 elif m1.flags(f) != rflags:
135 135 act("update permissions", "e", f, rflags)
136 136 # contents same, check mode bits
137 137 elif m1.flags(f) != rflags:
138 138 act("update permissions", "e", f, rflags)
139 139 elif f in copied:
140 140 continue
141 141 elif f in copy:
142 142 f2 = copy[f]
143 143 if f2 not in m2: # directory rename
144 144 act("remote renamed directory to " + f2, "d",
145 145 f, None, f2, m1.flags(f))
146 146 elif f2 in m1: # case 2 A,B/B/B
147 147 act("local copied to " + f2, "m",
148 148 f, f2, f, fmerge(f, f2, f2), False)
149 149 else: # case 4,21 A/B/B
150 150 act("local moved to " + f2, "m",
151 151 f, f2, f, fmerge(f, f2, f2), False)
152 152 elif f in ma:
153 153 if n != ma[f] and not overwrite:
154 154 if repo.ui.prompt(
155 155 _(" local changed %s which remote deleted\n"
156 156 "use (c)hanged version or (d)elete?") % f,
157 157 _("[cd]"), _("c")) == _("d"):
158 158 act("prompt delete", "r", f)
159 159 else:
160 160 act("other deleted", "r", f)
161 161 else:
162 162 # file is created on branch or in working directory
163 163 if (overwrite and n[20:] != "u") or (backwards and not n[20:]):
164 164 act("remote deleted", "r", f)
165 165
166 166 for f, n in m2.iteritems():
167 167 if partial and not partial(f):
168 168 continue
169 169 if f in m1:
170 170 continue
171 171 if f in copied:
172 172 continue
173 173 if f in copy:
174 174 f2 = copy[f]
175 175 if f2 not in m1: # directory rename
176 176 act("local renamed directory to " + f2, "d",
177 177 None, f, f2, m2.flags(f))
178 178 elif f2 in m2: # rename case 1, A/A,B/A
179 179 act("remote copied to " + f, "m",
180 180 f2, f, f, fmerge(f2, f, f2), False)
181 181 else: # case 3,20 A/B/A
182 182 act("remote moved to " + f, "m",
183 183 f2, f, f, fmerge(f2, f, f2), True)
184 184 elif f in ma:
185 185 if overwrite or backwards:
186 186 act("recreating", "g", f, m2.flags(f))
187 187 elif n != ma[f]:
188 188 if repo.ui.prompt(
189 189 _("remote changed %s which local deleted\n"
190 190 "use (c)hanged version or leave (d)eleted?") % f,
191 191 _("[cd]"), _("c")) == _("c"):
192 192 act("prompt recreating", "g", f, m2.flags(f))
193 193 else:
194 194 act("remote created", "g", f, m2.flags(f))
195 195
196 196 return action
197 197
198 198 def applyupdates(repo, action, wctx, mctx):
199 199 "apply the merge action list to the working directory"
200 200
201 201 updated, merged, removed, unresolved = 0, 0, 0, 0
202 202 action.sort()
203 203 # prescan for copy/renames
204 204 for a in action:
205 205 f, m = a[:2]
206 206 if m == 'm': # merge
207 207 f2, fd, flags, move = a[2:]
208 208 if f != fd:
209 209 repo.ui.debug(_("copying %s to %s\n") % (f, fd))
210 210 repo.wwrite(fd, repo.wread(f), flags)
211 211
212 212 audit_path = util.path_auditor(repo.root)
213 213
214 214 for a in action:
215 215 f, m = a[:2]
216 216 if f and f[0] == "/":
217 217 continue
218 218 if m == "r": # remove
219 219 repo.ui.note(_("removing %s\n") % f)
220 220 audit_path(f)
221 221 try:
222 222 util.unlink(repo.wjoin(f))
223 223 except OSError, inst:
224 224 if inst.errno != errno.ENOENT:
225 225 repo.ui.warn(_("update failed to remove %s: %s!\n") %
226 226 (f, inst.strerror))
227 227 removed += 1
228 228 elif m == "m": # merge
229 229 f2, fd, flags, move = a[2:]
230 230 r = filemerge.filemerge(repo, f, fd, f2, wctx, mctx)
231 231 if r > 0:
232 232 unresolved += 1
233 233 else:
234 234 if r is None:
235 235 updated += 1
236 236 else:
237 237 merged += 1
238 238 util.set_flags(repo.wjoin(fd), flags)
239 239 if f != fd and move and util.lexists(repo.wjoin(f)):
240 240 repo.ui.debug(_("removing %s\n") % f)
241 241 os.unlink(repo.wjoin(f))
242 242 elif m == "g": # get
243 243 flags = a[2]
244 244 repo.ui.note(_("getting %s\n") % f)
245 245 t = mctx.filectx(f).data()
246 246 repo.wwrite(f, t, flags)
247 247 updated += 1
248 248 elif m == "d": # directory rename
249 249 f2, fd, flags = a[2:]
250 250 if f:
251 251 repo.ui.note(_("moving %s to %s\n") % (f, fd))
252 252 t = wctx.filectx(f).data()
253 253 repo.wwrite(fd, t, flags)
254 254 util.unlink(repo.wjoin(f))
255 255 if f2:
256 256 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
257 257 t = mctx.filectx(f2).data()
258 258 repo.wwrite(fd, t, flags)
259 259 updated += 1
260 260 elif m == "dr": # divergent renames
261 261 fl = a[2]
262 262 repo.ui.warn("warning: detected divergent renames of %s to:\n" % f)
263 263 for nf in fl:
264 264 repo.ui.warn(" %s\n" % nf)
265 265 elif m == "e": # exec
266 266 flags = a[2]
267 267 util.set_flags(repo.wjoin(f), flags)
268 268
269 269 return updated, merged, removed, unresolved
270 270
271 271 def recordupdates(repo, action, branchmerge):
272 272 "record merge actions to the dirstate"
273 273
274 274 for a in action:
275 275 f, m = a[:2]
276 276 if m == "r": # remove
277 277 if branchmerge:
278 278 repo.dirstate.remove(f)
279 279 else:
280 280 repo.dirstate.forget(f)
281 281 elif m == "f": # forget
282 282 repo.dirstate.forget(f)
283 283 elif m in "ge": # get or exec change
284 284 if branchmerge:
285 285 repo.dirstate.normaldirty(f)
286 286 else:
287 287 repo.dirstate.normal(f)
288 288 elif m == "m": # merge
289 289 f2, fd, flag, move = a[2:]
290 290 if branchmerge:
291 291 # We've done a branch merge, mark this file as merged
292 292 # so that we properly record the merger later
293 293 repo.dirstate.merge(fd)
294 294 if f != f2: # copy/rename
295 295 if move:
296 296 repo.dirstate.remove(f)
297 297 if f != fd:
298 298 repo.dirstate.copy(f, fd)
299 299 else:
300 300 repo.dirstate.copy(f2, fd)
301 301 else:
302 302 # We've update-merged a locally modified file, so
303 303 # we set the dirstate to emulate a normal checkout
304 304 # of that file some time in the past. Thus our
305 305 # merge will appear as a normal local file
306 306 # modification.
307 307 repo.dirstate.normallookup(fd)
308 308 if move:
309 309 repo.dirstate.forget(f)
310 310 elif m == "d": # directory rename
311 311 f2, fd, flag = a[2:]
312 312 if not f2 and f not in repo.dirstate:
313 313 # untracked file moved
314 314 continue
315 315 if branchmerge:
316 316 repo.dirstate.add(fd)
317 317 if f:
318 318 repo.dirstate.remove(f)
319 319 repo.dirstate.copy(f, fd)
320 320 if f2:
321 321 repo.dirstate.copy(f2, fd)
322 322 else:
323 323 repo.dirstate.normal(fd)
324 324 if f:
325 325 repo.dirstate.forget(f)
326 326
327 327 def update(repo, node, branchmerge, force, partial):
328 328 """
329 329 Perform a merge between the working directory and the given node
330 330
331 331 branchmerge = whether to merge between branches
332 332 force = whether to force branch merging or file overwriting
333 333 partial = a function to filter file lists (dirstate not updated)
334 334 """
335 335
336 336 wlock = repo.wlock()
337 337 try:
338 338 wc = repo.workingctx()
339 339 if node is None:
340 340 # tip of current branch
341 341 try:
342 342 node = repo.branchtags()[wc.branch()]
343 343 except KeyError:
344 344 if wc.branch() == "default": # no default branch!
345 345 node = repo.lookup("tip") # update to tip
346 346 else:
347 347 raise util.Abort(_("branch %s not found") % wc.branch())
348 348 overwrite = force and not branchmerge
349 349 pl = wc.parents()
350 350 p1, p2 = pl[0], repo.changectx(node)
351 351 pa = p1.ancestor(p2)
352 352 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
353 353 fastforward = False
354 354
355 355 ### check phase
356 356 if not overwrite and len(pl) > 1:
357 357 raise util.Abort(_("outstanding uncommitted merges"))
358 358 if branchmerge:
359 359 if pa == p2:
360 360 raise util.Abort(_("can't merge with ancestor"))
361 361 elif pa == p1:
362 362 if p1.branch() != p2.branch():
363 363 fastforward = True
364 364 else:
365 365 raise util.Abort(_("nothing to merge (use 'hg update'"
366 366 " or check 'hg heads')"))
367 367 if not force and (wc.files() or wc.deleted()):
368 368 raise util.Abort(_("outstanding uncommitted changes"))
369 369 elif not overwrite:
370 370 if pa == p1 or pa == p2: # linear
371 371 pass # all good
372 372 elif p1.branch() == p2.branch():
373 373 if wc.files() or wc.deleted():
374 374 raise util.Abort(_("crosses branches (use 'hg merge' or "
375 375 "'hg update -C' to discard changes)"))
376 raise util.Abort(_("crosses branches (use 'hg merge'"
376 raise util.Abort(_("crosses branches (use 'hg merge' "
377 377 "or 'hg update -C')"))
378 378 elif wc.files() or wc.deleted():
379 379 raise util.Abort(_("crosses named branches (use "
380 380 "'hg update -C' to discard changes)"))
381 381 else:
382 382 # Allow jumping branches if there are no changes
383 383 overwrite = True
384 384
385 385 ### calculate phase
386 386 action = []
387 387 if not force:
388 388 _checkunknown(wc, p2)
389 389 if not util.checkfolding(repo.path):
390 390 _checkcollision(p2)
391 391 action += _forgetremoved(wc, p2, branchmerge)
392 392 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
393 393
394 394 ### apply phase
395 395 if not branchmerge: # just jump to the new rev
396 396 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
397 397 if not partial:
398 398 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
399 399
400 400 stats = applyupdates(repo, action, wc, p2)
401 401
402 402 if not partial:
403 403 recordupdates(repo, action, branchmerge)
404 404 repo.dirstate.setparents(fp1, fp2)
405 405 if not branchmerge and not fastforward:
406 406 repo.dirstate.setbranch(p2.branch())
407 407 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
408 408
409 409 return stats
410 410 finally:
411 411 del wlock
@@ -1,7 +1,7 b''
1 1 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
2 2 removing b
3 3 created new head
4 4 % should abort
5 5 abort: crosses branches (use 'hg merge' or 'hg update -C' to discard changes)
6 6 % should succeed
7 abort: crosses branches (use 'hg merge'or 'hg update -C')
7 abort: crosses branches (use 'hg merge' or 'hg update -C')
General Comments 0
You need to be logged in to leave comments. Login now