##// END OF EJS Templates
i18n: use UTF-8 string to lower filename for case collision check...
FUJIWARA Katsunori -
r14980:28e98a8b stable
parent child Browse files
Show More
@@ -0,0 +1,43
1 run only on case-insensitive filesystems, because collision check at
2 "hg update" is done only on case-insensitive filesystems
3
4 $ "$TESTDIR/hghave" icasefs || exit 80
5
6 setup repository, and target files
7
8 $ HGENCODING=cp932
9 $ export HGENCODING
10 $ hg init t
11 $ cd t
12 $ python << EOF
13 > names = ["\x83\x41", # cp932(0x83, 0x41='A'), UNICODE(0x30a2)
14 > "\x83\x5A", # cp932(0x83, 0x5A='Z'), UNICODE(0x30bb)
15 > "\x83\x61", # cp932(0x83, 0x61='a'), UNICODE(0x30c2)
16 > "\x83\x7A", # cp932(0x83, 0x7A='z'), UNICODE(0x30db)
17 > ]
18 > for num, name in zip(range(len(names)), names):
19 > # file for getting target filename of "hg add"
20 > f = file(str(num), 'w'); f.write(name); f.close()
21 > # target file of "hg add"
22 > f = file(name, 'w'); f.write(name); f.close()
23 > EOF
24
25 test filename collison check at "hg add"
26
27 $ hg add --config ui.portablefilenames=abort `cat 0`
28 $ hg add --config ui.portablefilenames=abort `cat 1`
29 $ hg add --config ui.portablefilenames=abort `cat 2`
30 $ hg add --config ui.portablefilenames=abort `cat 3`
31 $ hg status -a
32 A \x83A (esc)
33 A \x83Z (esc)
34 A \x83a (esc)
35 A \x83z (esc)
36
37 test filename collision check at "hg update"
38
39 $ hg commit -m 'revision 0'
40 $ hg update null
41 0 files updated, 0 files merged, 4 files removed, 0 files unresolved
42 $ hg update tip
43 4 files updated, 0 files merged, 0 files removed, 0 files unresolved
@@ -1,566 +1,566
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
10 import scmutil, util, filemerge, copies, subrepo, encoding
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):
84 def _checkunknown(wctx, mctx):
85 "check for collisions between unknown files and files in mctx"
85 "check for collisions between unknown files and files in mctx"
86 for f in wctx.unknown():
86 for f in wctx.unknown():
87 if f in mctx and mctx[f].cmp(wctx[f]):
87 if f in mctx and mctx[f].cmp(wctx[f]):
88 raise util.Abort(_("untracked file in working directory differs"
88 raise util.Abort(_("untracked file in working directory differs"
89 " from file in requested revision: '%s'") % f)
89 " from file in requested revision: '%s'") % f)
90
90
91 def _checkcollision(mctx):
91 def _checkcollision(mctx):
92 "check for case folding collisions in the destination context"
92 "check for case folding collisions in the destination context"
93 folded = {}
93 folded = {}
94 for fn in mctx:
94 for fn in mctx:
95 fold = fn.lower()
95 fold = encoding.lower(fn)
96 if fold in folded:
96 if fold in folded:
97 raise util.Abort(_("case-folding collision between %s and %s")
97 raise util.Abort(_("case-folding collision between %s and %s")
98 % (fn, folded[fold]))
98 % (fn, folded[fold]))
99 folded[fold] = fn
99 folded[fold] = fn
100
100
101 def _forgetremoved(wctx, mctx, branchmerge):
101 def _forgetremoved(wctx, mctx, branchmerge):
102 """
102 """
103 Forget removed files
103 Forget removed files
104
104
105 If we're jumping between revisions (as opposed to merging), and if
105 If we're jumping between revisions (as opposed to merging), and if
106 neither the working directory nor the target rev has the file,
106 neither the working directory nor the target rev has the file,
107 then we need to remove it from the dirstate, to prevent the
107 then we need to remove it from the dirstate, to prevent the
108 dirstate from listing the file when it is no longer in the
108 dirstate from listing the file when it is no longer in the
109 manifest.
109 manifest.
110
110
111 If we're merging, and the other revision has removed a file
111 If we're merging, and the other revision has removed a file
112 that is not present in the working directory, we need to mark it
112 that is not present in the working directory, we need to mark it
113 as removed.
113 as removed.
114 """
114 """
115
115
116 action = []
116 action = []
117 state = branchmerge and 'r' or 'f'
117 state = branchmerge and 'r' or 'f'
118 for f in wctx.deleted():
118 for f in wctx.deleted():
119 if f not in mctx:
119 if f not in mctx:
120 action.append((f, state))
120 action.append((f, state))
121
121
122 if not branchmerge:
122 if not branchmerge:
123 for f in wctx.removed():
123 for f in wctx.removed():
124 if f not in mctx:
124 if f not in mctx:
125 action.append((f, "f"))
125 action.append((f, "f"))
126
126
127 return action
127 return action
128
128
129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
129 def manifestmerge(repo, p1, p2, pa, overwrite, partial):
130 """
130 """
131 Merge p1 and p2 with ancestor pa and generate merge action list
131 Merge p1 and p2 with ancestor pa and generate merge action list
132
132
133 overwrite = whether we clobber working files
133 overwrite = whether we clobber working files
134 partial = function to filter file lists
134 partial = function to filter file lists
135 """
135 """
136
136
137 def fmerge(f, f2, fa):
137 def fmerge(f, f2, fa):
138 """merge flags"""
138 """merge flags"""
139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
139 a, m, n = ma.flags(fa), m1.flags(f), m2.flags(f2)
140 if m == n: # flags agree
140 if m == n: # flags agree
141 return m # unchanged
141 return m # unchanged
142 if m and n and not a: # flags set, don't agree, differ from parent
142 if m and n and not a: # flags set, don't agree, differ from parent
143 r = repo.ui.promptchoice(
143 r = repo.ui.promptchoice(
144 _(" conflicting flags for %s\n"
144 _(" conflicting flags for %s\n"
145 "(n)one, e(x)ec or sym(l)ink?") % f,
145 "(n)one, e(x)ec or sym(l)ink?") % f,
146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
146 (_("&None"), _("E&xec"), _("Sym&link")), 0)
147 if r == 1:
147 if r == 1:
148 return "x" # Exec
148 return "x" # Exec
149 if r == 2:
149 if r == 2:
150 return "l" # Symlink
150 return "l" # Symlink
151 return ""
151 return ""
152 if m and m != a: # changed from a to m
152 if m and m != a: # changed from a to m
153 return m
153 return m
154 if n and n != a: # changed from a to n
154 if n and n != a: # changed from a to n
155 return n
155 return n
156 return '' # flag was cleared
156 return '' # flag was cleared
157
157
158 def act(msg, m, f, *args):
158 def act(msg, m, f, *args):
159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
159 repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m))
160 action.append((f, m) + args)
160 action.append((f, m) + args)
161
161
162 action, copy = [], {}
162 action, copy = [], {}
163
163
164 if overwrite:
164 if overwrite:
165 pa = p1
165 pa = p1
166 elif pa == p2: # backwards
166 elif pa == p2: # backwards
167 pa = p1.p1()
167 pa = p1.p1()
168 elif pa and repo.ui.configbool("merge", "followcopies", True):
168 elif pa and repo.ui.configbool("merge", "followcopies", True):
169 dirs = repo.ui.configbool("merge", "followdirs", True)
169 dirs = repo.ui.configbool("merge", "followdirs", True)
170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
170 copy, diverge = copies.copies(repo, p1, p2, pa, dirs)
171 for of, fl in diverge.iteritems():
171 for of, fl in diverge.iteritems():
172 act("divergent renames", "dr", of, fl)
172 act("divergent renames", "dr", of, fl)
173
173
174 repo.ui.note(_("resolving manifests\n"))
174 repo.ui.note(_("resolving manifests\n"))
175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
175 repo.ui.debug(" overwrite %s partial %s\n" % (overwrite, bool(partial)))
176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
176 repo.ui.debug(" ancestor %s local %s remote %s\n" % (pa, p1, p2))
177
177
178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
178 m1, m2, ma = p1.manifest(), p2.manifest(), pa.manifest()
179 copied = set(copy.values())
179 copied = set(copy.values())
180
180
181 if '.hgsubstate' in m1:
181 if '.hgsubstate' in m1:
182 # check whether sub state is modified
182 # check whether sub state is modified
183 for s in p1.substate:
183 for s in p1.substate:
184 if p1.sub(s).dirty():
184 if p1.sub(s).dirty():
185 m1['.hgsubstate'] += "+"
185 m1['.hgsubstate'] += "+"
186 break
186 break
187
187
188 # Compare manifests
188 # Compare manifests
189 for f, n in m1.iteritems():
189 for f, n in m1.iteritems():
190 if partial and not partial(f):
190 if partial and not partial(f):
191 continue
191 continue
192 if f in m2:
192 if f in m2:
193 rflags = fmerge(f, f, f)
193 rflags = fmerge(f, f, f)
194 a = ma.get(f, nullid)
194 a = ma.get(f, nullid)
195 if n == m2[f] or m2[f] == a: # same or local newer
195 if n == m2[f] or m2[f] == a: # same or local newer
196 # is file locally modified or flags need changing?
196 # is file locally modified or flags need changing?
197 # dirstate flags may need to be made current
197 # dirstate flags may need to be made current
198 if m1.flags(f) != rflags or n[20:]:
198 if m1.flags(f) != rflags or n[20:]:
199 act("update permissions", "e", f, rflags)
199 act("update permissions", "e", f, rflags)
200 elif n == a: # remote newer
200 elif n == a: # remote newer
201 act("remote is newer", "g", f, rflags)
201 act("remote is newer", "g", f, rflags)
202 else: # both changed
202 else: # both changed
203 act("versions differ", "m", f, f, f, rflags, False)
203 act("versions differ", "m", f, f, f, rflags, False)
204 elif f in copied: # files we'll deal with on m2 side
204 elif f in copied: # files we'll deal with on m2 side
205 pass
205 pass
206 elif f in copy:
206 elif f in copy:
207 f2 = copy[f]
207 f2 = copy[f]
208 if f2 not in m2: # directory rename
208 if f2 not in m2: # directory rename
209 act("remote renamed directory to " + f2, "d",
209 act("remote renamed directory to " + f2, "d",
210 f, None, f2, m1.flags(f))
210 f, None, f2, m1.flags(f))
211 else: # case 2 A,B/B/B or case 4,21 A/B/B
211 else: # case 2 A,B/B/B or case 4,21 A/B/B
212 act("local copied/moved to " + f2, "m",
212 act("local copied/moved to " + f2, "m",
213 f, f2, f, fmerge(f, f2, f2), False)
213 f, f2, f, fmerge(f, f2, f2), False)
214 elif f in ma: # clean, a different, no remote
214 elif f in ma: # clean, a different, no remote
215 if n != ma[f]:
215 if n != ma[f]:
216 if repo.ui.promptchoice(
216 if repo.ui.promptchoice(
217 _(" local changed %s which remote deleted\n"
217 _(" local changed %s which remote deleted\n"
218 "use (c)hanged version or (d)elete?") % f,
218 "use (c)hanged version or (d)elete?") % f,
219 (_("&Changed"), _("&Delete")), 0):
219 (_("&Changed"), _("&Delete")), 0):
220 act("prompt delete", "r", f)
220 act("prompt delete", "r", f)
221 else:
221 else:
222 act("prompt keep", "a", f)
222 act("prompt keep", "a", f)
223 elif n[20:] == "a": # added, no remote
223 elif n[20:] == "a": # added, no remote
224 act("remote deleted", "f", f)
224 act("remote deleted", "f", f)
225 elif n[20:] != "u":
225 elif n[20:] != "u":
226 act("other deleted", "r", f)
226 act("other deleted", "r", f)
227
227
228 for f, n in m2.iteritems():
228 for f, n in m2.iteritems():
229 if partial and not partial(f):
229 if partial and not partial(f):
230 continue
230 continue
231 if f in m1 or f in copied: # files already visited
231 if f in m1 or f in copied: # files already visited
232 continue
232 continue
233 if f in copy:
233 if f in copy:
234 f2 = copy[f]
234 f2 = copy[f]
235 if f2 not in m1: # directory rename
235 if f2 not in m1: # directory rename
236 act("local renamed directory to " + f2, "d",
236 act("local renamed directory to " + f2, "d",
237 None, f, f2, m2.flags(f))
237 None, f, f2, m2.flags(f))
238 elif f2 in m2: # rename case 1, A/A,B/A
238 elif f2 in m2: # rename case 1, A/A,B/A
239 act("remote copied to " + f, "m",
239 act("remote copied to " + f, "m",
240 f2, f, f, fmerge(f2, f, f2), False)
240 f2, f, f, fmerge(f2, f, f2), False)
241 else: # case 3,20 A/B/A
241 else: # case 3,20 A/B/A
242 act("remote moved to " + f, "m",
242 act("remote moved to " + f, "m",
243 f2, f, f, fmerge(f2, f, f2), True)
243 f2, f, f, fmerge(f2, f, f2), True)
244 elif f not in ma:
244 elif f not in ma:
245 act("remote created", "g", f, m2.flags(f))
245 act("remote created", "g", f, m2.flags(f))
246 elif n != ma[f]:
246 elif n != ma[f]:
247 if repo.ui.promptchoice(
247 if repo.ui.promptchoice(
248 _("remote changed %s which local deleted\n"
248 _("remote changed %s which local deleted\n"
249 "use (c)hanged version or leave (d)eleted?") % f,
249 "use (c)hanged version or leave (d)eleted?") % f,
250 (_("&Changed"), _("&Deleted")), 0) == 0:
250 (_("&Changed"), _("&Deleted")), 0) == 0:
251 act("prompt recreating", "g", f, m2.flags(f))
251 act("prompt recreating", "g", f, m2.flags(f))
252
252
253 return action
253 return action
254
254
255 def actionkey(a):
255 def actionkey(a):
256 return a[1] == 'r' and -1 or 0, a
256 return a[1] == 'r' and -1 or 0, a
257
257
258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
258 def applyupdates(repo, action, wctx, mctx, actx, overwrite):
259 """apply the merge action list to the working directory
259 """apply the merge action list to the working directory
260
260
261 wctx is the working copy context
261 wctx is the working copy context
262 mctx is the context to be merged into the working copy
262 mctx is the context to be merged into the working copy
263 actx is the context of the common ancestor
263 actx is the context of the common ancestor
264
264
265 Return a tuple of counts (updated, merged, removed, unresolved) that
265 Return a tuple of counts (updated, merged, removed, unresolved) that
266 describes how many files were affected by the update.
266 describes how many files were affected by the update.
267 """
267 """
268
268
269 updated, merged, removed, unresolved = 0, 0, 0, 0
269 updated, merged, removed, unresolved = 0, 0, 0, 0
270 ms = mergestate(repo)
270 ms = mergestate(repo)
271 ms.reset(wctx.p1().node())
271 ms.reset(wctx.p1().node())
272 moves = []
272 moves = []
273 action.sort(key=actionkey)
273 action.sort(key=actionkey)
274
274
275 # prescan for merges
275 # prescan for merges
276 u = repo.ui
276 u = repo.ui
277 for a in action:
277 for a in action:
278 f, m = a[:2]
278 f, m = a[:2]
279 if m == 'm': # merge
279 if m == 'm': # merge
280 f2, fd, flags, move = a[2:]
280 f2, fd, flags, move = a[2:]
281 if f == '.hgsubstate': # merged internally
281 if f == '.hgsubstate': # merged internally
282 continue
282 continue
283 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
283 repo.ui.debug("preserving %s for resolve of %s\n" % (f, fd))
284 fcl = wctx[f]
284 fcl = wctx[f]
285 fco = mctx[f2]
285 fco = mctx[f2]
286 if mctx == actx: # backwards, use working dir parent as ancestor
286 if mctx == actx: # backwards, use working dir parent as ancestor
287 if fcl.parents():
287 if fcl.parents():
288 fca = fcl.p1()
288 fca = fcl.p1()
289 else:
289 else:
290 fca = repo.filectx(f, fileid=nullrev)
290 fca = repo.filectx(f, fileid=nullrev)
291 else:
291 else:
292 fca = fcl.ancestor(fco, actx)
292 fca = fcl.ancestor(fco, actx)
293 if not fca:
293 if not fca:
294 fca = repo.filectx(f, fileid=nullrev)
294 fca = repo.filectx(f, fileid=nullrev)
295 ms.add(fcl, fco, fca, fd, flags)
295 ms.add(fcl, fco, fca, fd, flags)
296 if f != fd and move:
296 if f != fd and move:
297 moves.append(f)
297 moves.append(f)
298
298
299 audit = scmutil.pathauditor(repo.root)
299 audit = scmutil.pathauditor(repo.root)
300
300
301 # remove renamed files after safely stored
301 # remove renamed files after safely stored
302 for f in moves:
302 for f in moves:
303 if os.path.lexists(repo.wjoin(f)):
303 if os.path.lexists(repo.wjoin(f)):
304 repo.ui.debug("removing %s\n" % f)
304 repo.ui.debug("removing %s\n" % f)
305 audit(f)
305 audit(f)
306 os.unlink(repo.wjoin(f))
306 os.unlink(repo.wjoin(f))
307
307
308 numupdates = len(action)
308 numupdates = len(action)
309 for i, a in enumerate(action):
309 for i, a in enumerate(action):
310 f, m = a[:2]
310 f, m = a[:2]
311 u.progress(_('updating'), i + 1, item=f, total=numupdates,
311 u.progress(_('updating'), i + 1, item=f, total=numupdates,
312 unit=_('files'))
312 unit=_('files'))
313 if f and f[0] == "/":
313 if f and f[0] == "/":
314 continue
314 continue
315 if m == "r": # remove
315 if m == "r": # remove
316 repo.ui.note(_("removing %s\n") % f)
316 repo.ui.note(_("removing %s\n") % f)
317 audit(f)
317 audit(f)
318 if f == '.hgsubstate': # subrepo states need updating
318 if f == '.hgsubstate': # subrepo states need updating
319 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
319 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
320 try:
320 try:
321 util.unlinkpath(repo.wjoin(f))
321 util.unlinkpath(repo.wjoin(f))
322 except OSError, inst:
322 except OSError, inst:
323 if inst.errno != errno.ENOENT:
323 if inst.errno != errno.ENOENT:
324 repo.ui.warn(_("update failed to remove %s: %s!\n") %
324 repo.ui.warn(_("update failed to remove %s: %s!\n") %
325 (f, inst.strerror))
325 (f, inst.strerror))
326 removed += 1
326 removed += 1
327 elif m == "m": # merge
327 elif m == "m": # merge
328 if f == '.hgsubstate': # subrepo states need updating
328 if f == '.hgsubstate': # subrepo states need updating
329 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
329 subrepo.submerge(repo, wctx, mctx, wctx.ancestor(mctx), overwrite)
330 continue
330 continue
331 f2, fd, flags, move = a[2:]
331 f2, fd, flags, move = a[2:]
332 repo.wopener.audit(fd)
332 repo.wopener.audit(fd)
333 r = ms.resolve(fd, wctx, mctx)
333 r = ms.resolve(fd, wctx, mctx)
334 if r is not None and r > 0:
334 if r is not None and r > 0:
335 unresolved += 1
335 unresolved += 1
336 else:
336 else:
337 if r is None:
337 if r is None:
338 updated += 1
338 updated += 1
339 else:
339 else:
340 merged += 1
340 merged += 1
341 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
341 util.setflags(repo.wjoin(fd), 'l' in flags, 'x' in flags)
342 if (move and repo.dirstate.normalize(fd) != f
342 if (move and repo.dirstate.normalize(fd) != f
343 and os.path.lexists(repo.wjoin(f))):
343 and os.path.lexists(repo.wjoin(f))):
344 repo.ui.debug("removing %s\n" % f)
344 repo.ui.debug("removing %s\n" % f)
345 audit(f)
345 audit(f)
346 os.unlink(repo.wjoin(f))
346 os.unlink(repo.wjoin(f))
347 elif m == "g": # get
347 elif m == "g": # get
348 flags = a[2]
348 flags = a[2]
349 repo.ui.note(_("getting %s\n") % f)
349 repo.ui.note(_("getting %s\n") % f)
350 t = mctx.filectx(f).data()
350 t = mctx.filectx(f).data()
351 repo.wwrite(f, t, flags)
351 repo.wwrite(f, t, flags)
352 t = None
352 t = None
353 updated += 1
353 updated += 1
354 if f == '.hgsubstate': # subrepo states need updating
354 if f == '.hgsubstate': # subrepo states need updating
355 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
355 subrepo.submerge(repo, wctx, mctx, wctx, overwrite)
356 elif m == "d": # directory rename
356 elif m == "d": # directory rename
357 f2, fd, flags = a[2:]
357 f2, fd, flags = a[2:]
358 if f:
358 if f:
359 repo.ui.note(_("moving %s to %s\n") % (f, fd))
359 repo.ui.note(_("moving %s to %s\n") % (f, fd))
360 audit(f)
360 audit(f)
361 t = wctx.filectx(f).data()
361 t = wctx.filectx(f).data()
362 repo.wwrite(fd, t, flags)
362 repo.wwrite(fd, t, flags)
363 util.unlinkpath(repo.wjoin(f))
363 util.unlinkpath(repo.wjoin(f))
364 if f2:
364 if f2:
365 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
365 repo.ui.note(_("getting %s to %s\n") % (f2, fd))
366 t = mctx.filectx(f2).data()
366 t = mctx.filectx(f2).data()
367 repo.wwrite(fd, t, flags)
367 repo.wwrite(fd, t, flags)
368 updated += 1
368 updated += 1
369 elif m == "dr": # divergent renames
369 elif m == "dr": # divergent renames
370 fl = a[2]
370 fl = a[2]
371 repo.ui.warn(_("note: possible conflict - %s was renamed "
371 repo.ui.warn(_("note: possible conflict - %s was renamed "
372 "multiple times to:\n") % f)
372 "multiple times to:\n") % f)
373 for nf in fl:
373 for nf in fl:
374 repo.ui.warn(" %s\n" % nf)
374 repo.ui.warn(" %s\n" % nf)
375 elif m == "e": # exec
375 elif m == "e": # exec
376 flags = a[2]
376 flags = a[2]
377 repo.wopener.audit(f)
377 repo.wopener.audit(f)
378 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
378 util.setflags(repo.wjoin(f), 'l' in flags, 'x' in flags)
379 ms.commit()
379 ms.commit()
380 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
380 u.progress(_('updating'), None, total=numupdates, unit=_('files'))
381
381
382 return updated, merged, removed, unresolved
382 return updated, merged, removed, unresolved
383
383
384 def recordupdates(repo, action, branchmerge):
384 def recordupdates(repo, action, branchmerge):
385 "record merge actions to the dirstate"
385 "record merge actions to the dirstate"
386
386
387 for a in action:
387 for a in action:
388 f, m = a[:2]
388 f, m = a[:2]
389 if m == "r": # remove
389 if m == "r": # remove
390 if branchmerge:
390 if branchmerge:
391 repo.dirstate.remove(f)
391 repo.dirstate.remove(f)
392 else:
392 else:
393 repo.dirstate.drop(f)
393 repo.dirstate.drop(f)
394 elif m == "a": # re-add
394 elif m == "a": # re-add
395 if not branchmerge:
395 if not branchmerge:
396 repo.dirstate.add(f)
396 repo.dirstate.add(f)
397 elif m == "f": # forget
397 elif m == "f": # forget
398 repo.dirstate.drop(f)
398 repo.dirstate.drop(f)
399 elif m == "e": # exec change
399 elif m == "e": # exec change
400 repo.dirstate.normallookup(f)
400 repo.dirstate.normallookup(f)
401 elif m == "g": # get
401 elif m == "g": # get
402 if branchmerge:
402 if branchmerge:
403 repo.dirstate.otherparent(f)
403 repo.dirstate.otherparent(f)
404 else:
404 else:
405 repo.dirstate.normal(f)
405 repo.dirstate.normal(f)
406 elif m == "m": # merge
406 elif m == "m": # merge
407 f2, fd, flag, move = a[2:]
407 f2, fd, flag, move = a[2:]
408 if branchmerge:
408 if branchmerge:
409 # We've done a branch merge, mark this file as merged
409 # We've done a branch merge, mark this file as merged
410 # so that we properly record the merger later
410 # so that we properly record the merger later
411 repo.dirstate.merge(fd)
411 repo.dirstate.merge(fd)
412 if f != f2: # copy/rename
412 if f != f2: # copy/rename
413 if move:
413 if move:
414 repo.dirstate.remove(f)
414 repo.dirstate.remove(f)
415 if f != fd:
415 if f != fd:
416 repo.dirstate.copy(f, fd)
416 repo.dirstate.copy(f, fd)
417 else:
417 else:
418 repo.dirstate.copy(f2, fd)
418 repo.dirstate.copy(f2, fd)
419 else:
419 else:
420 # We've update-merged a locally modified file, so
420 # We've update-merged a locally modified file, so
421 # we set the dirstate to emulate a normal checkout
421 # we set the dirstate to emulate a normal checkout
422 # of that file some time in the past. Thus our
422 # of that file some time in the past. Thus our
423 # merge will appear as a normal local file
423 # merge will appear as a normal local file
424 # modification.
424 # modification.
425 if f2 == fd: # file not locally copied/moved
425 if f2 == fd: # file not locally copied/moved
426 repo.dirstate.normallookup(fd)
426 repo.dirstate.normallookup(fd)
427 if move:
427 if move:
428 repo.dirstate.drop(f)
428 repo.dirstate.drop(f)
429 elif m == "d": # directory rename
429 elif m == "d": # directory rename
430 f2, fd, flag = a[2:]
430 f2, fd, flag = a[2:]
431 if not f2 and f not in repo.dirstate:
431 if not f2 and f not in repo.dirstate:
432 # untracked file moved
432 # untracked file moved
433 continue
433 continue
434 if branchmerge:
434 if branchmerge:
435 repo.dirstate.add(fd)
435 repo.dirstate.add(fd)
436 if f:
436 if f:
437 repo.dirstate.remove(f)
437 repo.dirstate.remove(f)
438 repo.dirstate.copy(f, fd)
438 repo.dirstate.copy(f, fd)
439 if f2:
439 if f2:
440 repo.dirstate.copy(f2, fd)
440 repo.dirstate.copy(f2, fd)
441 else:
441 else:
442 repo.dirstate.normal(fd)
442 repo.dirstate.normal(fd)
443 if f:
443 if f:
444 repo.dirstate.drop(f)
444 repo.dirstate.drop(f)
445
445
446 def update(repo, node, branchmerge, force, partial, ancestor=None):
446 def update(repo, node, branchmerge, force, partial, ancestor=None):
447 """
447 """
448 Perform a merge between the working directory and the given node
448 Perform a merge between the working directory and the given node
449
449
450 node = the node to update to, or None if unspecified
450 node = the node to update to, or None if unspecified
451 branchmerge = whether to merge between branches
451 branchmerge = whether to merge between branches
452 force = whether to force branch merging or file overwriting
452 force = whether to force branch merging or file overwriting
453 partial = a function to filter file lists (dirstate not updated)
453 partial = a function to filter file lists (dirstate not updated)
454
454
455 The table below shows all the behaviors of the update command
455 The table below shows all the behaviors of the update command
456 given the -c and -C or no options, whether the working directory
456 given the -c and -C or no options, whether the working directory
457 is dirty, whether a revision is specified, and the relationship of
457 is dirty, whether a revision is specified, and the relationship of
458 the parent rev to the target rev (linear, on the same named
458 the parent rev to the target rev (linear, on the same named
459 branch, or on another named branch).
459 branch, or on another named branch).
460
460
461 This logic is tested by test-update-branches.t.
461 This logic is tested by test-update-branches.t.
462
462
463 -c -C dirty rev | linear same cross
463 -c -C dirty rev | linear same cross
464 n n n n | ok (1) x
464 n n n n | ok (1) x
465 n n n y | ok ok ok
465 n n n y | ok ok ok
466 n n y * | merge (2) (2)
466 n n y * | merge (2) (2)
467 n y * * | --- discard ---
467 n y * * | --- discard ---
468 y n y * | --- (3) ---
468 y n y * | --- (3) ---
469 y n n * | --- ok ---
469 y n n * | --- ok ---
470 y y * * | --- (4) ---
470 y y * * | --- (4) ---
471
471
472 x = can't happen
472 x = can't happen
473 * = don't-care
473 * = don't-care
474 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
474 1 = abort: crosses branches (use 'hg merge' or 'hg update -c')
475 2 = abort: crosses branches (use 'hg merge' to merge or
475 2 = abort: crosses branches (use 'hg merge' to merge or
476 use 'hg update -C' to discard changes)
476 use 'hg update -C' to discard changes)
477 3 = abort: uncommitted local changes
477 3 = abort: uncommitted local changes
478 4 = incompatible options (checked in commands.py)
478 4 = incompatible options (checked in commands.py)
479
479
480 Return the same tuple as applyupdates().
480 Return the same tuple as applyupdates().
481 """
481 """
482
482
483 onode = node
483 onode = node
484 wlock = repo.wlock()
484 wlock = repo.wlock()
485 try:
485 try:
486 wc = repo[None]
486 wc = repo[None]
487 if node is None:
487 if node is None:
488 # tip of current branch
488 # tip of current branch
489 try:
489 try:
490 node = repo.branchtags()[wc.branch()]
490 node = repo.branchtags()[wc.branch()]
491 except KeyError:
491 except KeyError:
492 if wc.branch() == "default": # no default branch!
492 if wc.branch() == "default": # no default branch!
493 node = repo.lookup("tip") # update to tip
493 node = repo.lookup("tip") # update to tip
494 else:
494 else:
495 raise util.Abort(_("branch %s not found") % wc.branch())
495 raise util.Abort(_("branch %s not found") % wc.branch())
496 overwrite = force and not branchmerge
496 overwrite = force and not branchmerge
497 pl = wc.parents()
497 pl = wc.parents()
498 p1, p2 = pl[0], repo[node]
498 p1, p2 = pl[0], repo[node]
499 if ancestor:
499 if ancestor:
500 pa = repo[ancestor]
500 pa = repo[ancestor]
501 else:
501 else:
502 pa = p1.ancestor(p2)
502 pa = p1.ancestor(p2)
503
503
504 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
504 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), str(p1), str(p2)
505
505
506 ### check phase
506 ### check phase
507 if not overwrite and len(pl) > 1:
507 if not overwrite and len(pl) > 1:
508 raise util.Abort(_("outstanding uncommitted merges"))
508 raise util.Abort(_("outstanding uncommitted merges"))
509 if branchmerge:
509 if branchmerge:
510 if pa == p2:
510 if pa == p2:
511 raise util.Abort(_("merging with a working directory ancestor"
511 raise util.Abort(_("merging with a working directory ancestor"
512 " has no effect"))
512 " has no effect"))
513 elif pa == p1:
513 elif pa == p1:
514 if p1.branch() == p2.branch():
514 if p1.branch() == p2.branch():
515 raise util.Abort(_("nothing to merge (use 'hg update'"
515 raise util.Abort(_("nothing to merge (use 'hg update'"
516 " or check 'hg heads')"))
516 " or check 'hg heads')"))
517 if not force and (wc.files() or wc.deleted()):
517 if not force and (wc.files() or wc.deleted()):
518 raise util.Abort(_("outstanding uncommitted changes "
518 raise util.Abort(_("outstanding uncommitted changes "
519 "(use 'hg status' to list changes)"))
519 "(use 'hg status' to list changes)"))
520 for s in wc.substate:
520 for s in wc.substate:
521 if wc.sub(s).dirty():
521 if wc.sub(s).dirty():
522 raise util.Abort(_("outstanding uncommitted changes in "
522 raise util.Abort(_("outstanding uncommitted changes in "
523 "subrepository '%s'") % s)
523 "subrepository '%s'") % s)
524
524
525 elif not overwrite:
525 elif not overwrite:
526 if pa == p1 or pa == p2: # linear
526 if pa == p1 or pa == p2: # linear
527 pass # all good
527 pass # all good
528 elif wc.dirty(missing=True):
528 elif wc.dirty(missing=True):
529 raise util.Abort(_("crosses branches (merge branches or use"
529 raise util.Abort(_("crosses branches (merge branches or use"
530 " --clean to discard changes)"))
530 " --clean to discard changes)"))
531 elif onode is None:
531 elif onode is None:
532 raise util.Abort(_("crosses branches (merge branches or update"
532 raise util.Abort(_("crosses branches (merge branches or update"
533 " --check to force update)"))
533 " --check to force update)"))
534 else:
534 else:
535 # Allow jumping branches if clean and specific rev given
535 # Allow jumping branches if clean and specific rev given
536 overwrite = True
536 overwrite = True
537
537
538 ### calculate phase
538 ### calculate phase
539 action = []
539 action = []
540 wc.status(unknown=True) # prime cache
540 wc.status(unknown=True) # prime cache
541 if not force:
541 if not force:
542 _checkunknown(wc, p2)
542 _checkunknown(wc, p2)
543 if not util.checkcase(repo.path):
543 if not util.checkcase(repo.path):
544 _checkcollision(p2)
544 _checkcollision(p2)
545 action += _forgetremoved(wc, p2, branchmerge)
545 action += _forgetremoved(wc, p2, branchmerge)
546 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
546 action += manifestmerge(repo, wc, p2, pa, overwrite, partial)
547
547
548 ### apply phase
548 ### apply phase
549 if not branchmerge: # just jump to the new rev
549 if not branchmerge: # just jump to the new rev
550 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
550 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
551 if not partial:
551 if not partial:
552 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
552 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
553
553
554 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
554 stats = applyupdates(repo, action, wc, p2, pa, overwrite)
555
555
556 if not partial:
556 if not partial:
557 repo.dirstate.setparents(fp1, fp2)
557 repo.dirstate.setparents(fp1, fp2)
558 recordupdates(repo, action, branchmerge)
558 recordupdates(repo, action, branchmerge)
559 if not branchmerge:
559 if not branchmerge:
560 repo.dirstate.setbranch(p2.branch())
560 repo.dirstate.setbranch(p2.branch())
561 finally:
561 finally:
562 wlock.release()
562 wlock.release()
563
563
564 if not partial:
564 if not partial:
565 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
565 repo.hook('update', parent1=xp1, parent2=xp2, error=stats[3])
566 return stats
566 return stats
@@ -1,711 +1,711
1 # scmutil.py - Mercurial core utility functions
1 # scmutil.py - Mercurial core utility functions
2 #
2 #
3 # Copyright Matt Mackall <mpm@selenic.com>
3 # Copyright 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 i18n import _
8 from i18n import _
9 import util, error, osutil, revset, similar
9 import util, error, osutil, revset, similar, encoding
10 import match as matchmod
10 import match as matchmod
11 import os, errno, re, stat, sys, glob
11 import os, errno, re, stat, sys, glob
12
12
13 def checkfilename(f):
13 def checkfilename(f):
14 '''Check that the filename f is an acceptable filename for a tracked file'''
14 '''Check that the filename f is an acceptable filename for a tracked file'''
15 if '\r' in f or '\n' in f:
15 if '\r' in f or '\n' in f:
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
17
17
18 def checkportable(ui, f):
18 def checkportable(ui, f):
19 '''Check if filename f is portable and warn or abort depending on config'''
19 '''Check if filename f is portable and warn or abort depending on config'''
20 checkfilename(f)
20 checkfilename(f)
21 abort, warn = checkportabilityalert(ui)
21 abort, warn = checkportabilityalert(ui)
22 if abort or warn:
22 if abort or warn:
23 msg = util.checkwinfilename(f)
23 msg = util.checkwinfilename(f)
24 if msg:
24 if msg:
25 msg = "%s: %r" % (msg, f)
25 msg = "%s: %r" % (msg, f)
26 if abort:
26 if abort:
27 raise util.Abort(msg)
27 raise util.Abort(msg)
28 ui.warn(_("warning: %s\n") % msg)
28 ui.warn(_("warning: %s\n") % msg)
29
29
30 def checkportabilityalert(ui):
30 def checkportabilityalert(ui):
31 '''check if the user's config requests nothing, a warning, or abort for
31 '''check if the user's config requests nothing, a warning, or abort for
32 non-portable filenames'''
32 non-portable filenames'''
33 val = ui.config('ui', 'portablefilenames', 'warn')
33 val = ui.config('ui', 'portablefilenames', 'warn')
34 lval = val.lower()
34 lval = val.lower()
35 bval = util.parsebool(val)
35 bval = util.parsebool(val)
36 abort = os.name == 'nt' or lval == 'abort'
36 abort = os.name == 'nt' or lval == 'abort'
37 warn = bval or lval == 'warn'
37 warn = bval or lval == 'warn'
38 if bval is None and not (warn or abort or lval == 'ignore'):
38 if bval is None and not (warn or abort or lval == 'ignore'):
39 raise error.ConfigError(
39 raise error.ConfigError(
40 _("ui.portablefilenames value is invalid ('%s')") % val)
40 _("ui.portablefilenames value is invalid ('%s')") % val)
41 return abort, warn
41 return abort, warn
42
42
43 class casecollisionauditor(object):
43 class casecollisionauditor(object):
44 def __init__(self, ui, abort, existingiter):
44 def __init__(self, ui, abort, existingiter):
45 self._ui = ui
45 self._ui = ui
46 self._abort = abort
46 self._abort = abort
47 self._map = {}
47 self._map = {}
48 for f in existingiter:
48 for f in existingiter:
49 self._map[f.lower()] = f
49 self._map[encoding.lower(f)] = f
50
50
51 def __call__(self, f):
51 def __call__(self, f):
52 fl = f.lower()
52 fl = encoding.lower(f)
53 map = self._map
53 map = self._map
54 if fl in map and map[fl] != f:
54 if fl in map and map[fl] != f:
55 msg = _('possible case-folding collision for %s') % f
55 msg = _('possible case-folding collision for %s') % f
56 if self._abort:
56 if self._abort:
57 raise util.Abort(msg)
57 raise util.Abort(msg)
58 self._ui.warn(_("warning: %s\n") % msg)
58 self._ui.warn(_("warning: %s\n") % msg)
59 map[fl] = f
59 map[fl] = f
60
60
61 class pathauditor(object):
61 class pathauditor(object):
62 '''ensure that a filesystem path contains no banned components.
62 '''ensure that a filesystem path contains no banned components.
63 the following properties of a path are checked:
63 the following properties of a path are checked:
64
64
65 - ends with a directory separator
65 - ends with a directory separator
66 - under top-level .hg
66 - under top-level .hg
67 - starts at the root of a windows drive
67 - starts at the root of a windows drive
68 - contains ".."
68 - contains ".."
69 - traverses a symlink (e.g. a/symlink_here/b)
69 - traverses a symlink (e.g. a/symlink_here/b)
70 - inside a nested repository (a callback can be used to approve
70 - inside a nested repository (a callback can be used to approve
71 some nested repositories, e.g., subrepositories)
71 some nested repositories, e.g., subrepositories)
72 '''
72 '''
73
73
74 def __init__(self, root, callback=None):
74 def __init__(self, root, callback=None):
75 self.audited = set()
75 self.audited = set()
76 self.auditeddir = set()
76 self.auditeddir = set()
77 self.root = root
77 self.root = root
78 self.callback = callback
78 self.callback = callback
79
79
80 def __call__(self, path):
80 def __call__(self, path):
81 '''Check the relative path.
81 '''Check the relative path.
82 path may contain a pattern (e.g. foodir/**.txt)'''
82 path may contain a pattern (e.g. foodir/**.txt)'''
83
83
84 if path in self.audited:
84 if path in self.audited:
85 return
85 return
86 # AIX ignores "/" at end of path, others raise EISDIR.
86 # AIX ignores "/" at end of path, others raise EISDIR.
87 if util.endswithsep(path):
87 if util.endswithsep(path):
88 raise util.Abort(_("path ends in directory separator: %s") % path)
88 raise util.Abort(_("path ends in directory separator: %s") % path)
89 normpath = os.path.normcase(path)
89 normpath = os.path.normcase(path)
90 parts = util.splitpath(normpath)
90 parts = util.splitpath(normpath)
91 if (os.path.splitdrive(path)[0]
91 if (os.path.splitdrive(path)[0]
92 or parts[0].lower() in ('.hg', '.hg.', '')
92 or parts[0].lower() in ('.hg', '.hg.', '')
93 or os.pardir in parts):
93 or os.pardir in parts):
94 raise util.Abort(_("path contains illegal component: %s") % path)
94 raise util.Abort(_("path contains illegal component: %s") % path)
95 if '.hg' in path.lower():
95 if '.hg' in path.lower():
96 lparts = [p.lower() for p in parts]
96 lparts = [p.lower() for p in parts]
97 for p in '.hg', '.hg.':
97 for p in '.hg', '.hg.':
98 if p in lparts[1:]:
98 if p in lparts[1:]:
99 pos = lparts.index(p)
99 pos = lparts.index(p)
100 base = os.path.join(*parts[:pos])
100 base = os.path.join(*parts[:pos])
101 raise util.Abort(_('path %r is inside nested repo %r')
101 raise util.Abort(_('path %r is inside nested repo %r')
102 % (path, base))
102 % (path, base))
103
103
104 parts.pop()
104 parts.pop()
105 prefixes = []
105 prefixes = []
106 while parts:
106 while parts:
107 prefix = os.sep.join(parts)
107 prefix = os.sep.join(parts)
108 if prefix in self.auditeddir:
108 if prefix in self.auditeddir:
109 break
109 break
110 curpath = os.path.join(self.root, prefix)
110 curpath = os.path.join(self.root, prefix)
111 try:
111 try:
112 st = os.lstat(curpath)
112 st = os.lstat(curpath)
113 except OSError, err:
113 except OSError, err:
114 # EINVAL can be raised as invalid path syntax under win32.
114 # EINVAL can be raised as invalid path syntax under win32.
115 # They must be ignored for patterns can be checked too.
115 # They must be ignored for patterns can be checked too.
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
116 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
117 raise
117 raise
118 else:
118 else:
119 if stat.S_ISLNK(st.st_mode):
119 if stat.S_ISLNK(st.st_mode):
120 raise util.Abort(
120 raise util.Abort(
121 _('path %r traverses symbolic link %r')
121 _('path %r traverses symbolic link %r')
122 % (path, prefix))
122 % (path, prefix))
123 elif (stat.S_ISDIR(st.st_mode) and
123 elif (stat.S_ISDIR(st.st_mode) and
124 os.path.isdir(os.path.join(curpath, '.hg'))):
124 os.path.isdir(os.path.join(curpath, '.hg'))):
125 if not self.callback or not self.callback(curpath):
125 if not self.callback or not self.callback(curpath):
126 raise util.Abort(_('path %r is inside nested repo %r') %
126 raise util.Abort(_('path %r is inside nested repo %r') %
127 (path, prefix))
127 (path, prefix))
128 prefixes.append(prefix)
128 prefixes.append(prefix)
129 parts.pop()
129 parts.pop()
130
130
131 self.audited.add(path)
131 self.audited.add(path)
132 # only add prefixes to the cache after checking everything: we don't
132 # only add prefixes to the cache after checking everything: we don't
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
133 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
134 self.auditeddir.update(prefixes)
134 self.auditeddir.update(prefixes)
135
135
136 class abstractopener(object):
136 class abstractopener(object):
137 """Abstract base class; cannot be instantiated"""
137 """Abstract base class; cannot be instantiated"""
138
138
139 def __init__(self, *args, **kwargs):
139 def __init__(self, *args, **kwargs):
140 '''Prevent instantiation; don't call this from subclasses.'''
140 '''Prevent instantiation; don't call this from subclasses.'''
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
141 raise NotImplementedError('attempted instantiating ' + str(type(self)))
142
142
143 def read(self, path):
143 def read(self, path):
144 fp = self(path, 'rb')
144 fp = self(path, 'rb')
145 try:
145 try:
146 return fp.read()
146 return fp.read()
147 finally:
147 finally:
148 fp.close()
148 fp.close()
149
149
150 def write(self, path, data):
150 def write(self, path, data):
151 fp = self(path, 'wb')
151 fp = self(path, 'wb')
152 try:
152 try:
153 return fp.write(data)
153 return fp.write(data)
154 finally:
154 finally:
155 fp.close()
155 fp.close()
156
156
157 def append(self, path, data):
157 def append(self, path, data):
158 fp = self(path, 'ab')
158 fp = self(path, 'ab')
159 try:
159 try:
160 return fp.write(data)
160 return fp.write(data)
161 finally:
161 finally:
162 fp.close()
162 fp.close()
163
163
164 class opener(abstractopener):
164 class opener(abstractopener):
165 '''Open files relative to a base directory
165 '''Open files relative to a base directory
166
166
167 This class is used to hide the details of COW semantics and
167 This class is used to hide the details of COW semantics and
168 remote file access from higher level code.
168 remote file access from higher level code.
169 '''
169 '''
170 def __init__(self, base, audit=True):
170 def __init__(self, base, audit=True):
171 self.base = base
171 self.base = base
172 self._audit = audit
172 self._audit = audit
173 if audit:
173 if audit:
174 self.auditor = pathauditor(base)
174 self.auditor = pathauditor(base)
175 else:
175 else:
176 self.auditor = util.always
176 self.auditor = util.always
177 self.createmode = None
177 self.createmode = None
178 self._trustnlink = None
178 self._trustnlink = None
179
179
180 @util.propertycache
180 @util.propertycache
181 def _cansymlink(self):
181 def _cansymlink(self):
182 return util.checklink(self.base)
182 return util.checklink(self.base)
183
183
184 def _fixfilemode(self, name):
184 def _fixfilemode(self, name):
185 if self.createmode is None:
185 if self.createmode is None:
186 return
186 return
187 os.chmod(name, self.createmode & 0666)
187 os.chmod(name, self.createmode & 0666)
188
188
189 def __call__(self, path, mode="r", text=False, atomictemp=False):
189 def __call__(self, path, mode="r", text=False, atomictemp=False):
190 if self._audit:
190 if self._audit:
191 r = util.checkosfilename(path)
191 r = util.checkosfilename(path)
192 if r:
192 if r:
193 raise util.Abort("%s: %r" % (r, path))
193 raise util.Abort("%s: %r" % (r, path))
194 self.auditor(path)
194 self.auditor(path)
195 f = os.path.join(self.base, path)
195 f = os.path.join(self.base, path)
196
196
197 if not text and "b" not in mode:
197 if not text and "b" not in mode:
198 mode += "b" # for that other OS
198 mode += "b" # for that other OS
199
199
200 nlink = -1
200 nlink = -1
201 dirname, basename = os.path.split(f)
201 dirname, basename = os.path.split(f)
202 # If basename is empty, then the path is malformed because it points
202 # If basename is empty, then the path is malformed because it points
203 # to a directory. Let the posixfile() call below raise IOError.
203 # to a directory. Let the posixfile() call below raise IOError.
204 if basename and mode not in ('r', 'rb'):
204 if basename and mode not in ('r', 'rb'):
205 if atomictemp:
205 if atomictemp:
206 if not os.path.isdir(dirname):
206 if not os.path.isdir(dirname):
207 util.makedirs(dirname, self.createmode)
207 util.makedirs(dirname, self.createmode)
208 return util.atomictempfile(f, mode, self.createmode)
208 return util.atomictempfile(f, mode, self.createmode)
209 try:
209 try:
210 if 'w' in mode:
210 if 'w' in mode:
211 util.unlink(f)
211 util.unlink(f)
212 nlink = 0
212 nlink = 0
213 else:
213 else:
214 # nlinks() may behave differently for files on Windows
214 # nlinks() may behave differently for files on Windows
215 # shares if the file is open.
215 # shares if the file is open.
216 fd = util.posixfile(f)
216 fd = util.posixfile(f)
217 nlink = util.nlinks(f)
217 nlink = util.nlinks(f)
218 if nlink < 1:
218 if nlink < 1:
219 nlink = 2 # force mktempcopy (issue1922)
219 nlink = 2 # force mktempcopy (issue1922)
220 fd.close()
220 fd.close()
221 except (OSError, IOError), e:
221 except (OSError, IOError), e:
222 if e.errno != errno.ENOENT:
222 if e.errno != errno.ENOENT:
223 raise
223 raise
224 nlink = 0
224 nlink = 0
225 if not os.path.isdir(dirname):
225 if not os.path.isdir(dirname):
226 util.makedirs(dirname, self.createmode)
226 util.makedirs(dirname, self.createmode)
227 if nlink > 0:
227 if nlink > 0:
228 if self._trustnlink is None:
228 if self._trustnlink is None:
229 self._trustnlink = nlink > 1 or util.checknlink(f)
229 self._trustnlink = nlink > 1 or util.checknlink(f)
230 if nlink > 1 or not self._trustnlink:
230 if nlink > 1 or not self._trustnlink:
231 util.rename(util.mktempcopy(f), f)
231 util.rename(util.mktempcopy(f), f)
232 fp = util.posixfile(f, mode)
232 fp = util.posixfile(f, mode)
233 if nlink == 0:
233 if nlink == 0:
234 self._fixfilemode(f)
234 self._fixfilemode(f)
235 return fp
235 return fp
236
236
237 def symlink(self, src, dst):
237 def symlink(self, src, dst):
238 self.auditor(dst)
238 self.auditor(dst)
239 linkname = os.path.join(self.base, dst)
239 linkname = os.path.join(self.base, dst)
240 try:
240 try:
241 os.unlink(linkname)
241 os.unlink(linkname)
242 except OSError:
242 except OSError:
243 pass
243 pass
244
244
245 dirname = os.path.dirname(linkname)
245 dirname = os.path.dirname(linkname)
246 if not os.path.exists(dirname):
246 if not os.path.exists(dirname):
247 util.makedirs(dirname, self.createmode)
247 util.makedirs(dirname, self.createmode)
248
248
249 if self._cansymlink:
249 if self._cansymlink:
250 try:
250 try:
251 os.symlink(src, linkname)
251 os.symlink(src, linkname)
252 except OSError, err:
252 except OSError, err:
253 raise OSError(err.errno, _('could not symlink to %r: %s') %
253 raise OSError(err.errno, _('could not symlink to %r: %s') %
254 (src, err.strerror), linkname)
254 (src, err.strerror), linkname)
255 else:
255 else:
256 f = self(dst, "w")
256 f = self(dst, "w")
257 f.write(src)
257 f.write(src)
258 f.close()
258 f.close()
259 self._fixfilemode(dst)
259 self._fixfilemode(dst)
260
260
261 def audit(self, path):
261 def audit(self, path):
262 self.auditor(path)
262 self.auditor(path)
263
263
264 class filteropener(abstractopener):
264 class filteropener(abstractopener):
265 '''Wrapper opener for filtering filenames with a function.'''
265 '''Wrapper opener for filtering filenames with a function.'''
266
266
267 def __init__(self, opener, filter):
267 def __init__(self, opener, filter):
268 self._filter = filter
268 self._filter = filter
269 self._orig = opener
269 self._orig = opener
270
270
271 def __call__(self, path, *args, **kwargs):
271 def __call__(self, path, *args, **kwargs):
272 return self._orig(self._filter(path), *args, **kwargs)
272 return self._orig(self._filter(path), *args, **kwargs)
273
273
274 def canonpath(root, cwd, myname, auditor=None):
274 def canonpath(root, cwd, myname, auditor=None):
275 '''return the canonical path of myname, given cwd and root'''
275 '''return the canonical path of myname, given cwd and root'''
276 if util.endswithsep(root):
276 if util.endswithsep(root):
277 rootsep = root
277 rootsep = root
278 else:
278 else:
279 rootsep = root + os.sep
279 rootsep = root + os.sep
280 name = myname
280 name = myname
281 if not os.path.isabs(name):
281 if not os.path.isabs(name):
282 name = os.path.join(root, cwd, name)
282 name = os.path.join(root, cwd, name)
283 name = os.path.normpath(name)
283 name = os.path.normpath(name)
284 if auditor is None:
284 if auditor is None:
285 auditor = pathauditor(root)
285 auditor = pathauditor(root)
286 if name != rootsep and name.startswith(rootsep):
286 if name != rootsep and name.startswith(rootsep):
287 name = name[len(rootsep):]
287 name = name[len(rootsep):]
288 auditor(name)
288 auditor(name)
289 return util.pconvert(name)
289 return util.pconvert(name)
290 elif name == root:
290 elif name == root:
291 return ''
291 return ''
292 else:
292 else:
293 # Determine whether `name' is in the hierarchy at or beneath `root',
293 # Determine whether `name' is in the hierarchy at or beneath `root',
294 # by iterating name=dirname(name) until that causes no change (can't
294 # by iterating name=dirname(name) until that causes no change (can't
295 # check name == '/', because that doesn't work on windows). For each
295 # check name == '/', because that doesn't work on windows). For each
296 # `name', compare dev/inode numbers. If they match, the list `rel'
296 # `name', compare dev/inode numbers. If they match, the list `rel'
297 # holds the reversed list of components making up the relative file
297 # holds the reversed list of components making up the relative file
298 # name we want.
298 # name we want.
299 root_st = os.stat(root)
299 root_st = os.stat(root)
300 rel = []
300 rel = []
301 while True:
301 while True:
302 try:
302 try:
303 name_st = os.stat(name)
303 name_st = os.stat(name)
304 except OSError:
304 except OSError:
305 break
305 break
306 if util.samestat(name_st, root_st):
306 if util.samestat(name_st, root_st):
307 if not rel:
307 if not rel:
308 # name was actually the same as root (maybe a symlink)
308 # name was actually the same as root (maybe a symlink)
309 return ''
309 return ''
310 rel.reverse()
310 rel.reverse()
311 name = os.path.join(*rel)
311 name = os.path.join(*rel)
312 auditor(name)
312 auditor(name)
313 return util.pconvert(name)
313 return util.pconvert(name)
314 dirname, basename = os.path.split(name)
314 dirname, basename = os.path.split(name)
315 rel.append(basename)
315 rel.append(basename)
316 if dirname == name:
316 if dirname == name:
317 break
317 break
318 name = dirname
318 name = dirname
319
319
320 raise util.Abort('%s not under root' % myname)
320 raise util.Abort('%s not under root' % myname)
321
321
322 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
322 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
323 '''yield every hg repository under path, recursively.'''
323 '''yield every hg repository under path, recursively.'''
324 def errhandler(err):
324 def errhandler(err):
325 if err.filename == path:
325 if err.filename == path:
326 raise err
326 raise err
327 if followsym and hasattr(os.path, 'samestat'):
327 if followsym and hasattr(os.path, 'samestat'):
328 def adddir(dirlst, dirname):
328 def adddir(dirlst, dirname):
329 match = False
329 match = False
330 samestat = os.path.samestat
330 samestat = os.path.samestat
331 dirstat = os.stat(dirname)
331 dirstat = os.stat(dirname)
332 for lstdirstat in dirlst:
332 for lstdirstat in dirlst:
333 if samestat(dirstat, lstdirstat):
333 if samestat(dirstat, lstdirstat):
334 match = True
334 match = True
335 break
335 break
336 if not match:
336 if not match:
337 dirlst.append(dirstat)
337 dirlst.append(dirstat)
338 return not match
338 return not match
339 else:
339 else:
340 followsym = False
340 followsym = False
341
341
342 if (seen_dirs is None) and followsym:
342 if (seen_dirs is None) and followsym:
343 seen_dirs = []
343 seen_dirs = []
344 adddir(seen_dirs, path)
344 adddir(seen_dirs, path)
345 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
345 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
346 dirs.sort()
346 dirs.sort()
347 if '.hg' in dirs:
347 if '.hg' in dirs:
348 yield root # found a repository
348 yield root # found a repository
349 qroot = os.path.join(root, '.hg', 'patches')
349 qroot = os.path.join(root, '.hg', 'patches')
350 if os.path.isdir(os.path.join(qroot, '.hg')):
350 if os.path.isdir(os.path.join(qroot, '.hg')):
351 yield qroot # we have a patch queue repo here
351 yield qroot # we have a patch queue repo here
352 if recurse:
352 if recurse:
353 # avoid recursing inside the .hg directory
353 # avoid recursing inside the .hg directory
354 dirs.remove('.hg')
354 dirs.remove('.hg')
355 else:
355 else:
356 dirs[:] = [] # don't descend further
356 dirs[:] = [] # don't descend further
357 elif followsym:
357 elif followsym:
358 newdirs = []
358 newdirs = []
359 for d in dirs:
359 for d in dirs:
360 fname = os.path.join(root, d)
360 fname = os.path.join(root, d)
361 if adddir(seen_dirs, fname):
361 if adddir(seen_dirs, fname):
362 if os.path.islink(fname):
362 if os.path.islink(fname):
363 for hgname in walkrepos(fname, True, seen_dirs):
363 for hgname in walkrepos(fname, True, seen_dirs):
364 yield hgname
364 yield hgname
365 else:
365 else:
366 newdirs.append(d)
366 newdirs.append(d)
367 dirs[:] = newdirs
367 dirs[:] = newdirs
368
368
369 def osrcpath():
369 def osrcpath():
370 '''return default os-specific hgrc search path'''
370 '''return default os-specific hgrc search path'''
371 path = systemrcpath()
371 path = systemrcpath()
372 path.extend(userrcpath())
372 path.extend(userrcpath())
373 path = [os.path.normpath(f) for f in path]
373 path = [os.path.normpath(f) for f in path]
374 return path
374 return path
375
375
376 _rcpath = None
376 _rcpath = None
377
377
378 def rcpath():
378 def rcpath():
379 '''return hgrc search path. if env var HGRCPATH is set, use it.
379 '''return hgrc search path. if env var HGRCPATH is set, use it.
380 for each item in path, if directory, use files ending in .rc,
380 for each item in path, if directory, use files ending in .rc,
381 else use item.
381 else use item.
382 make HGRCPATH empty to only look in .hg/hgrc of current repo.
382 make HGRCPATH empty to only look in .hg/hgrc of current repo.
383 if no HGRCPATH, use default os-specific path.'''
383 if no HGRCPATH, use default os-specific path.'''
384 global _rcpath
384 global _rcpath
385 if _rcpath is None:
385 if _rcpath is None:
386 if 'HGRCPATH' in os.environ:
386 if 'HGRCPATH' in os.environ:
387 _rcpath = []
387 _rcpath = []
388 for p in os.environ['HGRCPATH'].split(os.pathsep):
388 for p in os.environ['HGRCPATH'].split(os.pathsep):
389 if not p:
389 if not p:
390 continue
390 continue
391 p = util.expandpath(p)
391 p = util.expandpath(p)
392 if os.path.isdir(p):
392 if os.path.isdir(p):
393 for f, kind in osutil.listdir(p):
393 for f, kind in osutil.listdir(p):
394 if f.endswith('.rc'):
394 if f.endswith('.rc'):
395 _rcpath.append(os.path.join(p, f))
395 _rcpath.append(os.path.join(p, f))
396 else:
396 else:
397 _rcpath.append(p)
397 _rcpath.append(p)
398 else:
398 else:
399 _rcpath = osrcpath()
399 _rcpath = osrcpath()
400 return _rcpath
400 return _rcpath
401
401
402 if os.name != 'nt':
402 if os.name != 'nt':
403
403
404 def rcfiles(path):
404 def rcfiles(path):
405 rcs = [os.path.join(path, 'hgrc')]
405 rcs = [os.path.join(path, 'hgrc')]
406 rcdir = os.path.join(path, 'hgrc.d')
406 rcdir = os.path.join(path, 'hgrc.d')
407 try:
407 try:
408 rcs.extend([os.path.join(rcdir, f)
408 rcs.extend([os.path.join(rcdir, f)
409 for f, kind in osutil.listdir(rcdir)
409 for f, kind in osutil.listdir(rcdir)
410 if f.endswith(".rc")])
410 if f.endswith(".rc")])
411 except OSError:
411 except OSError:
412 pass
412 pass
413 return rcs
413 return rcs
414
414
415 def systemrcpath():
415 def systemrcpath():
416 path = []
416 path = []
417 # old mod_python does not set sys.argv
417 # old mod_python does not set sys.argv
418 if len(getattr(sys, 'argv', [])) > 0:
418 if len(getattr(sys, 'argv', [])) > 0:
419 p = os.path.dirname(os.path.dirname(sys.argv[0]))
419 p = os.path.dirname(os.path.dirname(sys.argv[0]))
420 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
420 path.extend(rcfiles(os.path.join(p, 'etc/mercurial')))
421 path.extend(rcfiles('/etc/mercurial'))
421 path.extend(rcfiles('/etc/mercurial'))
422 return path
422 return path
423
423
424 def userrcpath():
424 def userrcpath():
425 return [os.path.expanduser('~/.hgrc')]
425 return [os.path.expanduser('~/.hgrc')]
426
426
427 else:
427 else:
428
428
429 _HKEY_LOCAL_MACHINE = 0x80000002L
429 _HKEY_LOCAL_MACHINE = 0x80000002L
430
430
431 def systemrcpath():
431 def systemrcpath():
432 '''return default os-specific hgrc search path'''
432 '''return default os-specific hgrc search path'''
433 rcpath = []
433 rcpath = []
434 filename = util.executablepath()
434 filename = util.executablepath()
435 # Use mercurial.ini found in directory with hg.exe
435 # Use mercurial.ini found in directory with hg.exe
436 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
436 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
437 if os.path.isfile(progrc):
437 if os.path.isfile(progrc):
438 rcpath.append(progrc)
438 rcpath.append(progrc)
439 return rcpath
439 return rcpath
440 # Use hgrc.d found in directory with hg.exe
440 # Use hgrc.d found in directory with hg.exe
441 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
441 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
442 if os.path.isdir(progrcd):
442 if os.path.isdir(progrcd):
443 for f, kind in osutil.listdir(progrcd):
443 for f, kind in osutil.listdir(progrcd):
444 if f.endswith('.rc'):
444 if f.endswith('.rc'):
445 rcpath.append(os.path.join(progrcd, f))
445 rcpath.append(os.path.join(progrcd, f))
446 return rcpath
446 return rcpath
447 # else look for a system rcpath in the registry
447 # else look for a system rcpath in the registry
448 value = util.lookupreg('SOFTWARE\\Mercurial', None,
448 value = util.lookupreg('SOFTWARE\\Mercurial', None,
449 _HKEY_LOCAL_MACHINE)
449 _HKEY_LOCAL_MACHINE)
450 if not isinstance(value, str) or not value:
450 if not isinstance(value, str) or not value:
451 return rcpath
451 return rcpath
452 value = value.replace('/', os.sep)
452 value = value.replace('/', os.sep)
453 for p in value.split(os.pathsep):
453 for p in value.split(os.pathsep):
454 if p.lower().endswith('mercurial.ini'):
454 if p.lower().endswith('mercurial.ini'):
455 rcpath.append(p)
455 rcpath.append(p)
456 elif os.path.isdir(p):
456 elif os.path.isdir(p):
457 for f, kind in osutil.listdir(p):
457 for f, kind in osutil.listdir(p):
458 if f.endswith('.rc'):
458 if f.endswith('.rc'):
459 rcpath.append(os.path.join(p, f))
459 rcpath.append(os.path.join(p, f))
460 return rcpath
460 return rcpath
461
461
462 def userrcpath():
462 def userrcpath():
463 '''return os-specific hgrc search path to the user dir'''
463 '''return os-specific hgrc search path to the user dir'''
464 home = os.path.expanduser('~')
464 home = os.path.expanduser('~')
465 path = [os.path.join(home, 'mercurial.ini'),
465 path = [os.path.join(home, 'mercurial.ini'),
466 os.path.join(home, '.hgrc')]
466 os.path.join(home, '.hgrc')]
467 userprofile = os.environ.get('USERPROFILE')
467 userprofile = os.environ.get('USERPROFILE')
468 if userprofile:
468 if userprofile:
469 path.append(os.path.join(userprofile, 'mercurial.ini'))
469 path.append(os.path.join(userprofile, 'mercurial.ini'))
470 path.append(os.path.join(userprofile, '.hgrc'))
470 path.append(os.path.join(userprofile, '.hgrc'))
471 return path
471 return path
472
472
473 def revsingle(repo, revspec, default='.'):
473 def revsingle(repo, revspec, default='.'):
474 if not revspec:
474 if not revspec:
475 return repo[default]
475 return repo[default]
476
476
477 l = revrange(repo, [revspec])
477 l = revrange(repo, [revspec])
478 if len(l) < 1:
478 if len(l) < 1:
479 raise util.Abort(_('empty revision set'))
479 raise util.Abort(_('empty revision set'))
480 return repo[l[-1]]
480 return repo[l[-1]]
481
481
482 def revpair(repo, revs):
482 def revpair(repo, revs):
483 if not revs:
483 if not revs:
484 return repo.dirstate.p1(), None
484 return repo.dirstate.p1(), None
485
485
486 l = revrange(repo, revs)
486 l = revrange(repo, revs)
487
487
488 if len(l) == 0:
488 if len(l) == 0:
489 return repo.dirstate.p1(), None
489 return repo.dirstate.p1(), None
490
490
491 if len(l) == 1:
491 if len(l) == 1:
492 return repo.lookup(l[0]), None
492 return repo.lookup(l[0]), None
493
493
494 return repo.lookup(l[0]), repo.lookup(l[-1])
494 return repo.lookup(l[0]), repo.lookup(l[-1])
495
495
496 _revrangesep = ':'
496 _revrangesep = ':'
497
497
498 def revrange(repo, revs):
498 def revrange(repo, revs):
499 """Yield revision as strings from a list of revision specifications."""
499 """Yield revision as strings from a list of revision specifications."""
500
500
501 def revfix(repo, val, defval):
501 def revfix(repo, val, defval):
502 if not val and val != 0 and defval is not None:
502 if not val and val != 0 and defval is not None:
503 return defval
503 return defval
504 return repo.changelog.rev(repo.lookup(val))
504 return repo.changelog.rev(repo.lookup(val))
505
505
506 seen, l = set(), []
506 seen, l = set(), []
507 for spec in revs:
507 for spec in revs:
508 # attempt to parse old-style ranges first to deal with
508 # attempt to parse old-style ranges first to deal with
509 # things like old-tag which contain query metacharacters
509 # things like old-tag which contain query metacharacters
510 try:
510 try:
511 if isinstance(spec, int):
511 if isinstance(spec, int):
512 seen.add(spec)
512 seen.add(spec)
513 l.append(spec)
513 l.append(spec)
514 continue
514 continue
515
515
516 if _revrangesep in spec:
516 if _revrangesep in spec:
517 start, end = spec.split(_revrangesep, 1)
517 start, end = spec.split(_revrangesep, 1)
518 start = revfix(repo, start, 0)
518 start = revfix(repo, start, 0)
519 end = revfix(repo, end, len(repo) - 1)
519 end = revfix(repo, end, len(repo) - 1)
520 step = start > end and -1 or 1
520 step = start > end and -1 or 1
521 for rev in xrange(start, end + step, step):
521 for rev in xrange(start, end + step, step):
522 if rev in seen:
522 if rev in seen:
523 continue
523 continue
524 seen.add(rev)
524 seen.add(rev)
525 l.append(rev)
525 l.append(rev)
526 continue
526 continue
527 elif spec and spec in repo: # single unquoted rev
527 elif spec and spec in repo: # single unquoted rev
528 rev = revfix(repo, spec, None)
528 rev = revfix(repo, spec, None)
529 if rev in seen:
529 if rev in seen:
530 continue
530 continue
531 seen.add(rev)
531 seen.add(rev)
532 l.append(rev)
532 l.append(rev)
533 continue
533 continue
534 except error.RepoLookupError:
534 except error.RepoLookupError:
535 pass
535 pass
536
536
537 # fall through to new-style queries if old-style fails
537 # fall through to new-style queries if old-style fails
538 m = revset.match(repo.ui, spec)
538 m = revset.match(repo.ui, spec)
539 for r in m(repo, range(len(repo))):
539 for r in m(repo, range(len(repo))):
540 if r not in seen:
540 if r not in seen:
541 l.append(r)
541 l.append(r)
542 seen.update(l)
542 seen.update(l)
543
543
544 return l
544 return l
545
545
546 def expandpats(pats):
546 def expandpats(pats):
547 if not util.expandglobs:
547 if not util.expandglobs:
548 return list(pats)
548 return list(pats)
549 ret = []
549 ret = []
550 for p in pats:
550 for p in pats:
551 kind, name = matchmod._patsplit(p, None)
551 kind, name = matchmod._patsplit(p, None)
552 if kind is None:
552 if kind is None:
553 try:
553 try:
554 globbed = glob.glob(name)
554 globbed = glob.glob(name)
555 except re.error:
555 except re.error:
556 globbed = [name]
556 globbed = [name]
557 if globbed:
557 if globbed:
558 ret.extend(globbed)
558 ret.extend(globbed)
559 continue
559 continue
560 ret.append(p)
560 ret.append(p)
561 return ret
561 return ret
562
562
563 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
563 def match(ctx, pats=[], opts={}, globbed=False, default='relpath'):
564 if pats == ("",):
564 if pats == ("",):
565 pats = []
565 pats = []
566 if not globbed and default == 'relpath':
566 if not globbed and default == 'relpath':
567 pats = expandpats(pats or [])
567 pats = expandpats(pats or [])
568
568
569 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
569 m = ctx.match(pats, opts.get('include'), opts.get('exclude'),
570 default)
570 default)
571 def badfn(f, msg):
571 def badfn(f, msg):
572 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
572 ctx._repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
573 m.bad = badfn
573 m.bad = badfn
574 return m
574 return m
575
575
576 def matchall(repo):
576 def matchall(repo):
577 return matchmod.always(repo.root, repo.getcwd())
577 return matchmod.always(repo.root, repo.getcwd())
578
578
579 def matchfiles(repo, files):
579 def matchfiles(repo, files):
580 return matchmod.exact(repo.root, repo.getcwd(), files)
580 return matchmod.exact(repo.root, repo.getcwd(), files)
581
581
582 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
582 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
583 if dry_run is None:
583 if dry_run is None:
584 dry_run = opts.get('dry_run')
584 dry_run = opts.get('dry_run')
585 if similarity is None:
585 if similarity is None:
586 similarity = float(opts.get('similarity') or 0)
586 similarity = float(opts.get('similarity') or 0)
587 # we'd use status here, except handling of symlinks and ignore is tricky
587 # we'd use status here, except handling of symlinks and ignore is tricky
588 added, unknown, deleted, removed = [], [], [], []
588 added, unknown, deleted, removed = [], [], [], []
589 audit_path = pathauditor(repo.root)
589 audit_path = pathauditor(repo.root)
590 m = match(repo[None], pats, opts)
590 m = match(repo[None], pats, opts)
591 for abs in repo.walk(m):
591 for abs in repo.walk(m):
592 target = repo.wjoin(abs)
592 target = repo.wjoin(abs)
593 good = True
593 good = True
594 try:
594 try:
595 audit_path(abs)
595 audit_path(abs)
596 except (OSError, util.Abort):
596 except (OSError, util.Abort):
597 good = False
597 good = False
598 rel = m.rel(abs)
598 rel = m.rel(abs)
599 exact = m.exact(abs)
599 exact = m.exact(abs)
600 if good and abs not in repo.dirstate:
600 if good and abs not in repo.dirstate:
601 unknown.append(abs)
601 unknown.append(abs)
602 if repo.ui.verbose or not exact:
602 if repo.ui.verbose or not exact:
603 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
603 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
604 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
604 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
605 or (os.path.isdir(target) and not os.path.islink(target))):
605 or (os.path.isdir(target) and not os.path.islink(target))):
606 deleted.append(abs)
606 deleted.append(abs)
607 if repo.ui.verbose or not exact:
607 if repo.ui.verbose or not exact:
608 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
608 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
609 # for finding renames
609 # for finding renames
610 elif repo.dirstate[abs] == 'r':
610 elif repo.dirstate[abs] == 'r':
611 removed.append(abs)
611 removed.append(abs)
612 elif repo.dirstate[abs] == 'a':
612 elif repo.dirstate[abs] == 'a':
613 added.append(abs)
613 added.append(abs)
614 copies = {}
614 copies = {}
615 if similarity > 0:
615 if similarity > 0:
616 for old, new, score in similar.findrenames(repo,
616 for old, new, score in similar.findrenames(repo,
617 added + unknown, removed + deleted, similarity):
617 added + unknown, removed + deleted, similarity):
618 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
618 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
619 repo.ui.status(_('recording removal of %s as rename to %s '
619 repo.ui.status(_('recording removal of %s as rename to %s '
620 '(%d%% similar)\n') %
620 '(%d%% similar)\n') %
621 (m.rel(old), m.rel(new), score * 100))
621 (m.rel(old), m.rel(new), score * 100))
622 copies[new] = old
622 copies[new] = old
623
623
624 if not dry_run:
624 if not dry_run:
625 wctx = repo[None]
625 wctx = repo[None]
626 wlock = repo.wlock()
626 wlock = repo.wlock()
627 try:
627 try:
628 wctx.forget(deleted)
628 wctx.forget(deleted)
629 wctx.add(unknown)
629 wctx.add(unknown)
630 for new, old in copies.iteritems():
630 for new, old in copies.iteritems():
631 wctx.copy(old, new)
631 wctx.copy(old, new)
632 finally:
632 finally:
633 wlock.release()
633 wlock.release()
634
634
635 def updatedir(ui, repo, patches, similarity=0):
635 def updatedir(ui, repo, patches, similarity=0):
636 '''Update dirstate after patch application according to metadata'''
636 '''Update dirstate after patch application according to metadata'''
637 if not patches:
637 if not patches:
638 return []
638 return []
639 copies = []
639 copies = []
640 removes = set()
640 removes = set()
641 cfiles = patches.keys()
641 cfiles = patches.keys()
642 cwd = repo.getcwd()
642 cwd = repo.getcwd()
643 if cwd:
643 if cwd:
644 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
644 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
645 for f in patches:
645 for f in patches:
646 gp = patches[f]
646 gp = patches[f]
647 if not gp:
647 if not gp:
648 continue
648 continue
649 if gp.op == 'RENAME':
649 if gp.op == 'RENAME':
650 copies.append((gp.oldpath, gp.path))
650 copies.append((gp.oldpath, gp.path))
651 removes.add(gp.oldpath)
651 removes.add(gp.oldpath)
652 elif gp.op == 'COPY':
652 elif gp.op == 'COPY':
653 copies.append((gp.oldpath, gp.path))
653 copies.append((gp.oldpath, gp.path))
654 elif gp.op == 'DELETE':
654 elif gp.op == 'DELETE':
655 removes.add(gp.path)
655 removes.add(gp.path)
656
656
657 wctx = repo[None]
657 wctx = repo[None]
658 for src, dst in copies:
658 for src, dst in copies:
659 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
659 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
660 if (not similarity) and removes:
660 if (not similarity) and removes:
661 wctx.remove(sorted(removes), True)
661 wctx.remove(sorted(removes), True)
662
662
663 for f in patches:
663 for f in patches:
664 gp = patches[f]
664 gp = patches[f]
665 if gp and gp.mode:
665 if gp and gp.mode:
666 islink, isexec = gp.mode
666 islink, isexec = gp.mode
667 dst = repo.wjoin(gp.path)
667 dst = repo.wjoin(gp.path)
668 # patch won't create empty files
668 # patch won't create empty files
669 if gp.op == 'ADD' and not os.path.lexists(dst):
669 if gp.op == 'ADD' and not os.path.lexists(dst):
670 flags = (isexec and 'x' or '') + (islink and 'l' or '')
670 flags = (isexec and 'x' or '') + (islink and 'l' or '')
671 repo.wwrite(gp.path, '', flags)
671 repo.wwrite(gp.path, '', flags)
672 util.setflags(dst, islink, isexec)
672 util.setflags(dst, islink, isexec)
673 addremove(repo, cfiles, similarity=similarity)
673 addremove(repo, cfiles, similarity=similarity)
674 files = patches.keys()
674 files = patches.keys()
675 files.extend([r for r in removes if r not in files])
675 files.extend([r for r in removes if r not in files])
676 return sorted(files)
676 return sorted(files)
677
677
678 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
678 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
679 """Update the dirstate to reflect the intent of copying src to dst. For
679 """Update the dirstate to reflect the intent of copying src to dst. For
680 different reasons it might not end with dst being marked as copied from src.
680 different reasons it might not end with dst being marked as copied from src.
681 """
681 """
682 origsrc = repo.dirstate.copied(src) or src
682 origsrc = repo.dirstate.copied(src) or src
683 if dst == origsrc: # copying back a copy?
683 if dst == origsrc: # copying back a copy?
684 if repo.dirstate[dst] not in 'mn' and not dryrun:
684 if repo.dirstate[dst] not in 'mn' and not dryrun:
685 repo.dirstate.normallookup(dst)
685 repo.dirstate.normallookup(dst)
686 else:
686 else:
687 if repo.dirstate[origsrc] == 'a' and origsrc == src:
687 if repo.dirstate[origsrc] == 'a' and origsrc == src:
688 if not ui.quiet:
688 if not ui.quiet:
689 ui.warn(_("%s has not been committed yet, so no copy "
689 ui.warn(_("%s has not been committed yet, so no copy "
690 "data will be stored for %s.\n")
690 "data will be stored for %s.\n")
691 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
691 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
692 if repo.dirstate[dst] in '?r' and not dryrun:
692 if repo.dirstate[dst] in '?r' and not dryrun:
693 wctx.add([dst])
693 wctx.add([dst])
694 elif not dryrun:
694 elif not dryrun:
695 wctx.copy(origsrc, dst)
695 wctx.copy(origsrc, dst)
696
696
697 def readrequires(opener, supported):
697 def readrequires(opener, supported):
698 '''Reads and parses .hg/requires and checks if all entries found
698 '''Reads and parses .hg/requires and checks if all entries found
699 are in the list of supported features.'''
699 are in the list of supported features.'''
700 requirements = set(opener.read("requires").splitlines())
700 requirements = set(opener.read("requires").splitlines())
701 missings = []
701 missings = []
702 for r in requirements:
702 for r in requirements:
703 if r not in supported:
703 if r not in supported:
704 if not r or not r[0].isalnum():
704 if not r or not r[0].isalnum():
705 raise error.RequirementError(_(".hg/requires file is corrupt"))
705 raise error.RequirementError(_(".hg/requires file is corrupt"))
706 missings.append(r)
706 missings.append(r)
707 missings.sort()
707 missings.sort()
708 if missings:
708 if missings:
709 raise error.RequirementError(_("unknown repository format: "
709 raise error.RequirementError(_("unknown repository format: "
710 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
710 "requires features '%s' (upgrade Mercurial)") % "', '".join(missings))
711 return requirements
711 return requirements
General Comments 0
You need to be logged in to leave comments. Login now