##// END OF EJS Templates
merge with stable
Matt Mackall -
r15655:5402fd9d merge default
parent child Browse files
Show More
@@ -1,576 +1,576 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 of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from node import nullid, nullrev, hex, bin
9 9 from i18n import _
10 import scmutil, util, filemerge, copies, subrepo, encoding
10 import scmutil, util, filemerge, copies, subrepo
11 11 import errno, os, shutil
12 12
13 13 class mergestate(object):
14 14 '''track 3-way merge state of individual files'''
15 15 def __init__(self, repo):
16 16 self._repo = repo
17 17 self._dirty = False
18 18 self._read()
19 19 def reset(self, node=None):
20 20 self._state = {}
21 21 if node:
22 22 self._local = node
23 23 shutil.rmtree(self._repo.join("merge"), True)
24 24 self._dirty = False
25 25 def _read(self):
26 26 self._state = {}
27 27 try:
28 28 f = self._repo.opener("merge/state")
29 29 for i, l in enumerate(f):
30 30 if i == 0:
31 31 self._local = bin(l[:-1])
32 32 else:
33 33 bits = l[:-1].split("\0")
34 34 self._state[bits[0]] = bits[1:]
35 35 f.close()
36 36 except IOError, err:
37 37 if err.errno != errno.ENOENT:
38 38 raise
39 39 self._dirty = False
40 40 def commit(self):
41 41 if self._dirty:
42 42 f = self._repo.opener("merge/state", "w")
43 43 f.write(hex(self._local) + "\n")
44 44 for d, v in self._state.iteritems():
45 45 f.write("\0".join([d] + v) + "\n")
46 46 f.close()
47 47 self._dirty = False
48 48 def add(self, fcl, fco, fca, fd, flags):
49 49 hash = util.sha1(fcl.path()).hexdigest()
50 50 self._repo.opener.write("merge/" + hash, fcl.data())
51 51 self._state[fd] = ['u', hash, fcl.path(), fca.path(),
52 52 hex(fca.filenode()), fco.path(), flags]
53 53 self._dirty = True
54 54 def __contains__(self, dfile):
55 55 return dfile in self._state
56 56 def __getitem__(self, dfile):
57 57 return self._state[dfile][0]
58 58 def __iter__(self):
59 59 l = self._state.keys()
60 60 l.sort()
61 61 for f in l:
62 62 yield f
63 63 def mark(self, dfile, state):
64 64 self._state[dfile][0] = state
65 65 self._dirty = True
66 66 def resolve(self, dfile, wctx, octx):
67 67 if self[dfile] == 'r':
68 68 return 0
69 69 state, hash, lfile, afile, anode, ofile, flags = self._state[dfile]
70 70 f = self._repo.opener("merge/" + hash)
71 71 self._repo.wwrite(dfile, f.read(), flags)
72 72 f.close()
73 73 fcd = wctx[dfile]
74 74 fco = octx[ofile]
75 75 fca = self._repo.filectx(afile, fileid=anode)
76 76 r = filemerge.filemerge(self._repo, self._local, lfile, fcd, fco, fca)
77 77 if r is None:
78 78 # no real conflict
79 79 del self._state[dfile]
80 80 elif not r:
81 81 self.mark(dfile, 'r')
82 82 return r
83 83
84 84 def _checkunknown(wctx, mctx, folding):
85 85 "check for collisions between unknown files and files in mctx"
86 86 if folding:
87 87 foldf = util.normcase
88 88 else:
89 89 foldf = lambda fn: fn
90 90 folded = {}
91 91 for fn in mctx:
92 92 folded[foldf(fn)] = fn
93 93 for fn in wctx.unknown():
94 94 f = foldf(fn)
95 95 if f in folded and mctx[folded[f]].cmp(wctx[f]):
96 96 raise util.Abort(_("untracked file in working directory differs"
97 97 " from file in requested revision: '%s'") % fn)
98 98
99 99 def _checkcollision(mctx):
100 100 "check for case folding collisions in the destination context"
101 101 folded = {}
102 102 for fn in mctx:
103 fold = encoding.lower(fn)
103 fold = util.normcase(fn)
104 104 if fold in folded:
105 105 raise util.Abort(_("case-folding collision between %s and %s")
106 106 % (fn, folded[fold]))
107 107 folded[fold] = fn
108 108
109 109 def _forgetremoved(wctx, mctx, branchmerge):
110 110 """
111 111 Forget removed files
112 112
113 113 If we're jumping between revisions (as opposed to merging), and if
114 114 neither the working directory nor the target rev has the file,
115 115 then we need to remove it from the dirstate, to prevent the
116 116 dirstate from listing the file when it is no longer in the
117 117 manifest.
118 118
119 119 If we're merging, and the other revision has removed a file
120 120 that is not present in the working directory, we need to mark it
121 121 as removed.
122 122 """
123 123
124 124 action = []
125 125 state = branchmerge and 'r' or 'f'
126 126 for f in wctx.deleted():
127 127 if f not in mctx:
128 128 action.append((f, state))
129 129
130 130 if not branchmerge:
131 131 for f in wctx.removed():
132 132 if f not in mctx:
133 133 action.append((f, "f"))
134 134
135 135 return action
136 136
137 137 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
138 138 """
139 139 Merge p1 and p2 with ancestor pa and generate merge action list
140 140
141 141 overwrite = whether we clobber working files
142 142 partial = function to filter file lists
143 143 """
144 144
145 145 def fmerge(f, f2, fa):
146 146 """merge flags"""
147 147 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
148 148 if m == n: # flags agree
149 149 return m # unchanged
150 150 if m and n and not a: # flags set, don't agree, differ from parent
151 151 r = repo.ui.promptchoice(
152 152 _(" conflicting flags for %s\n"
153 153 "(n)one, e(x)ec or sym(l)ink?") % f,
154 154 (_("&None"), _("E&xec"), _("Sym&link")), 0)
155 155 if r == 1:
156 156 return "x" # Exec
157 157 if r == 2:
158 158 return "l" # Symlink
159 159 return ""
160 160 if m and m != a: # changed from a to m
161 161 return m
162 162 if n and n != a: # changed from a to n
163 163 return n
164 164 return '' # flag was cleared
165 165
166 166 def act(msg, m, f, *args):
167 167 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
168 168 action.append((f, m) + args)
169 169
170 170 action, copy = [], {}
171 171
172 172 if overwrite:
173 173 pa = p1
174 174 elif pa == p2: # backwards
175 175 pa = p1.p1()
176 176 elif pa and repo.ui.configbool("merge", "followcopies", True):
177 177 dirs = repo.ui.configbool("merge", "followdirs", True)
178 178 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
179 179 for of, fl in diverge.iteritems():
180 180 act("divergent renames", "dr", of, fl)
181 181
182 182 repo.ui.note(_("resolving manifests\n"))
183 183 repo.ui.debug(" overwrite: %s, partial: %s\n"
184 184 % (bool(overwrite), bool(partial)))
185 185 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, p1, p2))
186 186
187 187 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
188 188 copied = set(copy.values())
189 189
190 190 if '.hgsubstate' in m1:
191 191 # check whether sub state is modified
192 192 for s in p1.substate:
193 193 if p1.sub(s).dirty():
194 194 m1['.hgsubstate'] += "+"
195 195 break
196 196
197 197 # Compare manifests
198 198 for f, n in m1.iteritems():
199 199 if partial and not partial(f):
200 200 continue
201 201 if f in m2:
202 202 rflags = fmerge(f, f, f)
203 203 a = ma.get(f, nullid)
204 204 if n == m2[f] or m2[f] == a: # same or local newer
205 205 # is file locally modified or flags need changing?
206 206 # dirstate flags may need to be made current
207 207 if m1.flags(f) != rflags or n[20:]:
208 208 act("update permissions", "e", f, rflags)
209 209 elif n == a: # remote newer
210 210 act("remote is newer", "g", f, rflags)
211 211 else: # both changed
212 212 act("versions differ", "m", f, f, f, rflags, False)
213 213 elif f in copied: # files we'll deal with on m2 side
214 214 pass
215 215 elif f in copy:
216 216 f2 = copy[f]
217 217 if f2 not in m2: # directory rename
218 218 act("remote renamed directory to " + f2, "d",
219 219 f, None, f2, m1.flags(f))
220 220 else: # case 2 A,B/B/B or case 4,21 A/B/B
221 221 act("local copied/moved to " + f2, "m",
222 222 f, f2, f, fmerge(f, f2, f2), False)
223 223 elif f in ma: # clean, a different, no remote
224 224 if n != ma[f]:
225 225 if repo.ui.promptchoice(
226 226 _(" local changed %s which remote deleted\n"
227 227 "use (c)hanged version or (d)elete?") % f,
228 228 (_("&Changed"), _("&Delete")), 0):
229 229 act("prompt delete", "r", f)
230 230 else:
231 231 act("prompt keep", "a", f)
232 232 elif n[20:] == "a": # added, no remote
233 233 act("remote deleted", "f", f)
234 234 elif n[20:] != "u":
235 235 act("other deleted", "r", f)
236 236
237 237 for f, n in m2.iteritems():
238 238 if partial and not partial(f):
239 239 continue
240 240 if f in m1 or f in copied: # files already visited
241 241 continue
242 242 if f in copy:
243 243 f2 = copy[f]
244 244 if f2 not in m1: # directory rename
245 245 act("local renamed directory to " + f2, "d",
246 246 None, f, f2, m2.flags(f))
247 247 elif f2 in m2: # rename case 1, A/A,B/A
248 248 act("remote copied to " + f, "m",
249 249 f2, f, f, fmerge(f2, f, f2), False)
250 250 else: # case 3,20 A/B/A
251 251 act("remote moved to " + f, "m",
252 252 f2, f, f, fmerge(f2, f, f2), True)
253 253 elif f not in ma:
254 254 act("remote created", "g", f, m2.flags(f))
255 255 elif n != ma[f]:
256 256 if repo.ui.promptchoice(
257 257 _("remote changed %s which local deleted\n"
258 258 "use (c)hanged version or leave (d)eleted?") % f,
259 259 (_("&Changed"), _("&Deleted")), 0) == 0:
260 260 act("prompt recreating", "g", f, m2.flags(f))
261 261
262 262 return action
263 263
264 264 def actionkey(a):
265 265 return a[1] == 'r' and -1 or 0, a
266 266
267 267 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
268 268 """apply the merge action list to the working directory
269 269
270 270 wctx is the working copy context
271 271 mctx is the context to be merged into the working copy
272 272 actx is the context of the common ancestor
273 273
274 274 Return a tuple of counts (updated, merged, removed, unresolved) that
275 275 describes how many files were affected by the update.
276 276 """
277 277
278 278 updated, merged, removed, unresolved = 0, 0, 0, 0
279 279 ms = mergestate(repo)
280 280 ms.reset(wctx.p1().node())
281 281 moves = []
282 282 action.sort(key=actionkey)
283 283
284 284 # prescan for merges
285 285 for a in action:
286 286 f, m = a[:2]
287 287 if m == 'm': # merge
288 288 f2, fd, flags, move = a[2:]
289 289 if f == '.hgsubstate': # merged internally
290 290 continue
291 291 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
292 292 fcl = wctx[f]
293 293 fco = mctx[f2]
294 294 if mctx == actx: # backwards, use working dir parent as ancestor
295 295 if fcl.parents():
296 296 fca = fcl.p1()
297 297 else:
298 298 fca = repo.filectx(f, fileid=nullrev)
299 299 else:
300 300 fca = fcl.ancestor(fco, actx)
301 301 if not fca:
302 302 fca = repo.filectx(f, fileid=nullrev)
303 303 ms.add(fcl, fco, fca, fd, flags)
304 304 if f != fd and move:
305 305 moves.append(f)
306 306
307 307 audit = scmutil.pathauditor(repo.root)
308 308
309 309 # remove renamed files after safely stored
310 310 for f in moves:
311 311 if os.path.lexists(repo.wjoin(f)):
312 312 repo.ui.debug("removing %s\n" % f)
313 313 audit(f)
314 314 os.unlink(repo.wjoin(f))
315 315
316 316 numupdates = len(action)
317 317 for i, a in enumerate(action):
318 318 f, m = a[:2]
319 319 repo.ui.progress(_('updating'), i + 1, item=f, total=numupdates,
320 320 unit=_('files'))
321 321 if f and f[0] == "/":
322 322 continue
323 323 if m == "r": # remove
324 324 repo.ui.note(_("removing %s\n") % f)
325 325 audit(f)
326 326 if f == '.hgsubstate': # subrepo states need updating
327 327 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
328 328 try:
329 329 util.unlinkpath(repo.wjoin(f))
330 330 except OSError, inst:
331 331 if inst.errno != errno.ENOENT:
332 332 repo.ui.warn(_("update failed to remove %s: %s!\n") %
333 333 (f, inst.strerror))
334 334 removed += 1
335 335 elif m == "m": # merge
336 336 if f == '.hgsubstate': # subrepo states need updating
337 337 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
338 338 continue
339 339 f2, fd, flags, move = a[2:]
340 340 repo.wopener.audit(fd)
341 341 r = ms.resolve(fd, wctx, mctx)
342 342 if r is not None and r > 0:
343 343 unresolved += 1
344 344 else:
345 345 if r is None:
346 346 updated += 1
347 347 else:
348 348 merged += 1
349 349 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
350 350 if (move and repo.dirstate.normalize(fd) != f
351 351 and os.path.lexists(repo.wjoin(f))):
352 352 repo.ui.debug("removing %s\n" % f)
353 353 audit(f)
354 354 os.unlink(repo.wjoin(f))
355 355 elif m == "g": # get
356 356 flags = a[2]
357 357 repo.ui.note(_("getting %s\n") % f)
358 358 t = mctx.filectx(f).data()
359 359 repo.wwrite(f, t, flags)
360 360 t = None
361 361 updated += 1
362 362 if f == '.hgsubstate': # subrepo states need updating
363 363 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
364 364 elif m == "d": # directory rename
365 365 f2, fd, flags = a[2:]
366 366 if f:
367 367 repo.ui.note(_("moving %s to %s\n") % (f, fd))
368 368 audit(f)
369 369 t = wctx.filectx(f).data()
370 370 repo.wwrite(fd, t, flags)
371 371 util.unlinkpath(repo.wjoin(f))
372 372 if f2:
373 373 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
374 374 t = mctx.filectx(f2).data()
375 375 repo.wwrite(fd, t, flags)
376 376 updated += 1
377 377 elif m == "dr": # divergent renames
378 378 fl = a[2]
379 379 repo.ui.warn(_("note: possible conflict - %s was renamed "
380 380 "multiple times to:\n") % f)
381 381 for nf in fl:
382 382 repo.ui.warn(" %s\n" % nf)
383 383 elif m == "e": # exec
384 384 flags = a[2]
385 385 repo.wopener.audit(f)
386 386 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
387 387 ms.commit()
388 388 repo.ui.progress(_('updating'), None, total=numupdates, unit=_('files'))
389 389
390 390 return updated, merged, removed, unresolved
391 391
392 392 def recordupdates(repo, action, branchmerge):
393 393 "record merge actions to the dirstate"
394 394
395 395 for a in action:
396 396 f, m = a[:2]
397 397 if m == "r": # remove
398 398 if branchmerge:
399 399 repo.dirstate.remove(f)
400 400 else:
401 401 repo.dirstate.drop(f)
402 402 elif m == "a": # re-add
403 403 if not branchmerge:
404 404 repo.dirstate.add(f)
405 405 elif m == "f": # forget
406 406 repo.dirstate.drop(f)
407 407 elif m == "e": # exec change
408 408 repo.dirstate.normallookup(f)
409 409 elif m == "g": # get
410 410 if branchmerge:
411 411 repo.dirstate.otherparent(f)
412 412 else:
413 413 repo.dirstate.normal(f)
414 414 elif m == "m": # merge
415 415 f2, fd, flag, move = a[2:]
416 416 if branchmerge:
417 417 # We've done a branch merge, mark this file as merged
418 418 # so that we properly record the merger later
419 419 repo.dirstate.merge(fd)
420 420 if f != f2: # copy/rename
421 421 if move:
422 422 repo.dirstate.remove(f)
423 423 if f != fd:
424 424 repo.dirstate.copy(f, fd)
425 425 else:
426 426 repo.dirstate.copy(f2, fd)
427 427 else:
428 428 # We've update-merged a locally modified file, so
429 429 # we set the dirstate to emulate a normal checkout
430 430 # of that file some time in the past. Thus our
431 431 # merge will appear as a normal local file
432 432 # modification.
433 433 if f2 == fd: # file not locally copied/moved
434 434 repo.dirstate.normallookup(fd)
435 435 if move:
436 436 repo.dirstate.drop(f)
437 437 elif m == "d": # directory rename
438 438 f2, fd, flag = a[2:]
439 439 if not f2 and f not in repo.dirstate:
440 440 # untracked file moved
441 441 continue
442 442 if branchmerge:
443 443 repo.dirstate.add(fd)
444 444 if f:
445 445 repo.dirstate.remove(f)
446 446 repo.dirstate.copy(f, fd)
447 447 if f2:
448 448 repo.dirstate.copy(f2, fd)
449 449 else:
450 450 repo.dirstate.normal(fd)
451 451 if f:
452 452 repo.dirstate.drop(f)
453 453
454 454 def update(repo, node, branchmerge, force, partial, ancestor=None):
455 455 """
456 456 Perform a merge between the working directory and the given node
457 457
458 458 node = the node to update to, or None if unspecified
459 459 branchmerge = whether to merge between branches
460 460 force = whether to force branch merging or file overwriting
461 461 partial = a function to filter file lists (dirstate not updated)
462 462
463 463 The table below shows all the behaviors of the update command
464 464 given the -c and -C or no options, whether the working directory
465 465 is dirty, whether a revision is specified, and the relationship of
466 466 the parent rev to the target rev (linear, on the same named
467 467 branch, or on another named branch).
468 468
469 469 This logic is tested by test-update-branches.t.
470 470
471 471 -c -C dirty rev | linear same cross
472 472 n n n n | ok (1) x
473 473 n n n y | ok ok ok
474 474 n n y * | merge (2) (2)
475 475 n y * * | --- discard ---
476 476 y n y * | --- (3) ---
477 477 y n n * | --- ok ---
478 478 y y * * | --- (4) ---
479 479
480 480 x = can't happen
481 481 * = don't-care
482 482 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
483 483 2 = abort: crosses branches (use 'hg merge' to merge or
484 484 use 'hg update -C' to discard changes)
485 485 3 = abort: uncommitted local changes
486 486 4 = incompatible options (checked in commands.py)
487 487
488 488 Return the same tuple as applyupdates().
489 489 """
490 490
491 491 onode = node
492 492 wlock = repo.wlock()
493 493 try:
494 494 wc = repo[None]
495 495 if node is None:
496 496 # tip of current branch
497 497 try:
498 498 node = repo.branchtags()[wc.branch()]
499 499 except KeyError:
500 500 if wc.branch() == "default": # no default branch!
501 501 node = repo.lookup("tip") # update to tip
502 502 else:
503 503 raise util.Abort(_("branch %s not found") % wc.branch())
504 504 overwrite = force and not branchmerge
505 505 pl = wc.parents()
506 506 p1, p2 = pl[0], repo[node]
507 507 if ancestor:
508 508 pa = repo[ancestor]
509 509 else:
510 510 pa = p1.ancestor(p2)
511 511
512 512 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
513 513
514 514 ### check phase
515 515 if not overwrite and len(pl) > 1:
516 516 raise util.Abort(_("outstanding uncommitted merges"))
517 517 if branchmerge:
518 518 if pa == p2:
519 519 raise util.Abort(_("merging with a working directory ancestor"
520 520 " has no effect"))
521 521 elif pa == p1:
522 522 if p1.branch() == p2.branch():
523 523 raise util.Abort(_("nothing to merge"),
524 524 hint=_("use 'hg update' "
525 525 "or check 'hg heads'"))
526 526 if not force and (wc.files() or wc.deleted()):
527 527 raise util.Abort(_("outstanding uncommitted changes"),
528 528 hint=_("use 'hg status' to list changes"))
529 529 for s in wc.substate:
530 530 if wc.sub(s).dirty():
531 531 raise util.Abort(_("outstanding uncommitted changes in "
532 532 "subrepository '%s'") % s)
533 533
534 534 elif not overwrite:
535 535 if pa == p1 or pa == p2: # linear
536 536 pass # all good
537 537 elif wc.dirty(missing=True):
538 538 raise util.Abort(_("crosses branches (merge branches or use"
539 539 " --clean to discard changes)"))
540 540 elif onode is None:
541 541 raise util.Abort(_("crosses branches (merge branches or update"
542 542 " --check to force update)"))
543 543 else:
544 544 # Allow jumping branches if clean and specific rev given
545 545 overwrite = True
546 546
547 547 ### calculate phase
548 548 action = []
549 549 wc.status(unknown=True) # prime cache
550 550 folding = not util.checkcase(repo.path)
551 551 if not force:
552 552 _checkunknown(wc, p2, folding)
553 553 if folding:
554 554 _checkcollision(p2)
555 555 action += _forgetremoved(wc, p2, branchmerge)
556 556 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
557 557
558 558 ### apply phase
559 559 if not branchmerge: # just jump to the new rev
560 560 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
561 561 if not partial:
562 562 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
563 563
564 564 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
565 565
566 566 if not partial:
567 567 repo.dirstate.setparents(fp1, fp2)
568 568 recordupdates(repo, action, branchmerge)
569 569 if not branchmerge:
570 570 repo.dirstate.setbranch(p2.branch())
571 571 finally:
572 572 wlock.release()
573 573
574 574 if not partial:
575 575 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
576 576 return stats
@@ -1,316 +1,315 b''
1 1 # windows.py - Windows utility function implementations for Mercurial
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import osutil
10 10 import errno, msvcrt, os, re, sys
11 11
12 12 import win32
13 13 executablepath = win32.executablepath
14 14 getuser = win32.getuser
15 15 hidewindow = win32.hidewindow
16 16 lookupreg = win32.lookupreg
17 17 makedir = win32.makedir
18 18 nlinks = win32.nlinks
19 19 oslink = win32.oslink
20 20 samedevice = win32.samedevice
21 21 samefile = win32.samefile
22 22 setsignalhandler = win32.setsignalhandler
23 23 spawndetached = win32.spawndetached
24 24 termwidth = win32.termwidth
25 25 testpid = win32.testpid
26 26 unlink = win32.unlink
27 27
28 28 nulldev = 'NUL:'
29 29 umask = 0022
30 30
31 31 # wrap osutil.posixfile to provide friendlier exceptions
32 32 def posixfile(name, mode='r', buffering=-1):
33 33 try:
34 34 return osutil.posixfile(name, mode, buffering)
35 35 except WindowsError, err:
36 36 raise IOError(err.errno, '%s: %s' % (name, err.strerror))
37 37 posixfile.__doc__ = osutil.posixfile.__doc__
38 38
39 39 class winstdout(object):
40 40 '''stdout on windows misbehaves if sent through a pipe'''
41 41
42 42 def __init__(self, fp):
43 43 self.fp = fp
44 44
45 45 def __getattr__(self, key):
46 46 return getattr(self.fp, key)
47 47
48 48 def close(self):
49 49 try:
50 50 self.fp.close()
51 51 except IOError:
52 52 pass
53 53
54 54 def write(self, s):
55 55 try:
56 56 # This is workaround for "Not enough space" error on
57 57 # writing large size of data to console.
58 58 limit = 16000
59 59 l = len(s)
60 60 start = 0
61 61 self.softspace = 0
62 62 while start < l:
63 63 end = start + limit
64 64 self.fp.write(s[start:end])
65 65 start = end
66 66 except IOError, inst:
67 67 if inst.errno != 0:
68 68 raise
69 69 self.close()
70 70 raise IOError(errno.EPIPE, 'Broken pipe')
71 71
72 72 def flush(self):
73 73 try:
74 74 return self.fp.flush()
75 75 except IOError, inst:
76 76 if inst.errno != errno.EINVAL:
77 77 raise
78 78 self.close()
79 79 raise IOError(errno.EPIPE, 'Broken pipe')
80 80
81 81 sys.__stdout__ = sys.stdout = winstdout(sys.stdout)
82 82
83 83 def _is_win_9x():
84 84 '''return true if run on windows 95, 98 or me.'''
85 85 try:
86 86 return sys.getwindowsversion()[3] == 1
87 87 except AttributeError:
88 88 return 'command' in os.environ.get('comspec', '')
89 89
90 90 def openhardlinks():
91 91 return not _is_win_9x()
92 92
93 93 def parsepatchoutput(output_line):
94 94 """parses the output produced by patch and returns the filename"""
95 95 pf = output_line[14:]
96 96 if pf[0] == '`':
97 97 pf = pf[1:-1] # Remove the quotes
98 98 return pf
99 99
100 100 def sshargs(sshcmd, host, user, port):
101 101 '''Build argument list for ssh or Plink'''
102 102 pflag = 'plink' in sshcmd.lower() and '-P' or '-p'
103 103 args = user and ("%s@%s" % (user, host)) or host
104 104 return port and ("%s %s %s" % (args, pflag, port)) or args
105 105
106 106 def setflags(f, l, x):
107 107 pass
108 108
109 109 def copymode(src, dst, mode=None):
110 110 pass
111 111
112 112 def checkexec(path):
113 113 return False
114 114
115 115 def checklink(path):
116 116 return False
117 117
118 118 def setbinary(fd):
119 119 # When run without console, pipes may expose invalid
120 120 # fileno(), usually set to -1.
121 121 fno = getattr(fd, 'fileno', None)
122 122 if fno is not None and fno() >= 0:
123 123 msvcrt.setmode(fno(), os.O_BINARY)
124 124
125 125 def pconvert(path):
126 126 return '/'.join(path.split(os.sep))
127 127
128 128 def localpath(path):
129 129 return path.replace('/', '\\')
130 130
131 131 def normpath(path):
132 132 return pconvert(os.path.normpath(path))
133 133
134 134 normcase = os.path.normcase
135 135
136 136 def realpath(path):
137 137 '''
138 138 Returns the true, canonical file system path equivalent to the given
139 139 path.
140 140 '''
141 141 # TODO: There may be a more clever way to do this that also handles other,
142 142 # less common file systems.
143 return os.path.normpath(os.path.normcase(os.path.realpath(path)))
143 return os.path.normpath(normcase(os.path.realpath(path)))
144 144
145 145 def samestat(s1, s2):
146 146 return False
147 147
148 148 # A sequence of backslashes is special iff it precedes a double quote:
149 149 # - if there's an even number of backslashes, the double quote is not
150 150 # quoted (i.e. it ends the quoted region)
151 151 # - if there's an odd number of backslashes, the double quote is quoted
152 152 # - in both cases, every pair of backslashes is unquoted into a single
153 153 # backslash
154 154 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
155 155 # So, to quote a string, we must surround it in double quotes, double
156 156 # the number of backslashes that preceed double quotes and add another
157 157 # backslash before every double quote (being careful with the double
158 158 # quote we've appended to the end)
159 159 _quotere = None
160 160 def shellquote(s):
161 161 global _quotere
162 162 if _quotere is None:
163 163 _quotere = re.compile(r'(\\*)("|\\$)')
164 164 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
165 165
166 166 def quotecommand(cmd):
167 167 """Build a command string suitable for os.popen* calls."""
168 168 if sys.version_info < (2, 7, 1):
169 169 # Python versions since 2.7.1 do this extra quoting themselves
170 170 return '"' + cmd + '"'
171 171 return cmd
172 172
173 173 def popen(command, mode='r'):
174 174 # Work around "popen spawned process may not write to stdout
175 175 # under windows"
176 176 # http://bugs.python.org/issue1366
177 177 command += " 2> %s" % nulldev
178 178 return os.popen(quotecommand(command), mode)
179 179
180 180 def explainexit(code):
181 181 return _("exited with status %d") % code, code
182 182
183 183 # if you change this stub into a real check, please try to implement the
184 184 # username and groupname functions above, too.
185 185 def isowner(st):
186 186 return True
187 187
188 188 def findexe(command):
189 189 '''Find executable for command searching like cmd.exe does.
190 190 If command is a basename then PATH is searched for command.
191 191 PATH isn't searched if command is an absolute or relative path.
192 192 An extension from PATHEXT is found and added if not present.
193 193 If command isn't found None is returned.'''
194 194 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
195 195 pathexts = [ext for ext in pathext.lower().split(os.pathsep)]
196 196 if os.path.splitext(command)[1].lower() in pathexts:
197 197 pathexts = ['']
198 198
199 199 def findexisting(pathcommand):
200 200 'Will append extension (if needed) and return existing file'
201 201 for ext in pathexts:
202 202 executable = pathcommand + ext
203 203 if os.path.exists(executable):
204 204 return executable
205 205 return None
206 206
207 207 if os.sep in command:
208 208 return findexisting(command)
209 209
210 210 for path in os.environ.get('PATH', '').split(os.pathsep):
211 211 executable = findexisting(os.path.join(path, command))
212 212 if executable is not None:
213 213 return executable
214 214 return findexisting(os.path.expanduser(os.path.expandvars(command)))
215 215
216 216 def statfiles(files):
217 217 '''Stat each file in files and yield stat or None if file does not exist.
218 218 Cluster and cache stat per directory to minimize number of OS stat calls.'''
219 ncase = os.path.normcase
220 219 dircache = {} # dirname -> filename -> status | None if file does not exist
221 220 for nf in files:
222 nf = ncase(nf)
221 nf = normcase(nf)
223 222 dir, base = os.path.split(nf)
224 223 if not dir:
225 224 dir = '.'
226 225 cache = dircache.get(dir, None)
227 226 if cache is None:
228 227 try:
229 dmap = dict([(ncase(n), s)
228 dmap = dict([(normcase(n), s)
230 229 for n, k, s in osutil.listdir(dir, True)])
231 230 except OSError, err:
232 231 # handle directory not found in Python version prior to 2.5
233 232 # Python <= 2.4 returns native Windows code 3 in errno
234 233 # Python >= 2.5 returns ENOENT and adds winerror field
235 234 # EINVAL is raised if dir is not a directory.
236 235 if err.errno not in (3, errno.ENOENT, errno.EINVAL,
237 236 errno.ENOTDIR):
238 237 raise
239 238 dmap = {}
240 239 cache = dircache.setdefault(dir, dmap)
241 240 yield cache.get(base, None)
242 241
243 242 def username(uid=None):
244 243 """Return the name of the user with the given uid.
245 244
246 245 If uid is None, return the name of the current user."""
247 246 return None
248 247
249 248 def groupname(gid=None):
250 249 """Return the name of the group with the given gid.
251 250
252 251 If gid is None, return the name of the current group."""
253 252 return None
254 253
255 254 def _removedirs(name):
256 255 """special version of os.removedirs that does not remove symlinked
257 256 directories or junction points if they actually contain files"""
258 257 if osutil.listdir(name):
259 258 return
260 259 os.rmdir(name)
261 260 head, tail = os.path.split(name)
262 261 if not tail:
263 262 head, tail = os.path.split(head)
264 263 while head and tail:
265 264 try:
266 265 if osutil.listdir(head):
267 266 return
268 267 os.rmdir(head)
269 268 except (ValueError, OSError):
270 269 break
271 270 head, tail = os.path.split(head)
272 271
273 272 def unlinkpath(f):
274 273 """unlink and remove the directory if it is empty"""
275 274 unlink(f)
276 275 # try removing directories that might now be empty
277 276 try:
278 277 _removedirs(os.path.dirname(f))
279 278 except OSError:
280 279 pass
281 280
282 281 def rename(src, dst):
283 282 '''atomically rename file src to dst, replacing dst if it exists'''
284 283 try:
285 284 os.rename(src, dst)
286 285 except OSError, e:
287 286 if e.errno != errno.EEXIST:
288 287 raise
289 288 unlink(dst)
290 289 os.rename(src, dst)
291 290
292 291 def gethgcmd():
293 292 return [sys.executable] + sys.argv[:1]
294 293
295 294 def termwidth():
296 295 # cmd.exe does not handle CR like a unix console, the CR is
297 296 # counted in the line length. On 80 columns consoles, if 80
298 297 # characters are written, the following CR won't apply on the
299 298 # current line but on the new one. Keep room for it.
300 299 return 79
301 300
302 301 def groupmembers(name):
303 302 # Don't support groups on Windows for now
304 303 raise KeyError()
305 304
306 305 def isexec(f):
307 306 return False
308 307
309 308 class cachestat(object):
310 309 def __init__(self, path):
311 310 pass
312 311
313 312 def cacheable(self):
314 313 return False
315 314
316 315 expandglobs = True
General Comments 0
You need to be logged in to leave comments. Login now