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