##// END OF EJS Templates
add: notify when adding a file that would cause a case-folding collision...
Kevin Gessner -
r14068:04ce8fa1 default
parent child Browse files
Show More
@@ -0,0 +1,34 b''
1 run only on case-sensitive filesystems
2
3 $ "$TESTDIR/hghave" no-icasefs || exit 80
4
5 test file addition with colliding case
6
7 $ hg init repo1
8 $ cd repo1
9 $ echo a > a
10 $ echo A > A
11 $ hg add a
12 $ hg st
13 A a
14 ? A
15 $ hg add --config ui.portablefilenames=abort A
16 abort: possible case-folding collision for A
17 [255]
18 $ hg st
19 A a
20 ? A
21 $ hg add A
22 warning: possible case-folding collision for A
23 $ hg st
24 A A
25 A a
26 $ hg forget A
27 $ hg st
28 A a
29 ? A
30 $ hg add --config ui.portablefilenames=no A
31 $ hg st
32 A A
33 A a
34 $ cd ..
@@ -1,1391 +1,1398 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-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 hex, nullid, nullrev, short
8 from node import hex, nullid, nullrev, short
9 from i18n import _
9 from i18n import _
10 import os, sys, errno, re, glob, tempfile
10 import os, sys, errno, re, glob, tempfile
11 import util, scmutil, templater, patch, error, templatekw
11 import util, scmutil, templater, patch, error, templatekw
12 import match as matchmod
12 import match as matchmod
13 import similar, revset, subrepo
13 import similar, revset, subrepo
14
14
15 revrangesep = ':'
15 revrangesep = ':'
16
16
17 def parsealiases(cmd):
17 def parsealiases(cmd):
18 return cmd.lstrip("^").split("|")
18 return cmd.lstrip("^").split("|")
19
19
20 def findpossible(cmd, table, strict=False):
20 def findpossible(cmd, table, strict=False):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = parsealiases(e)
29 aliases = parsealiases(e)
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not strict:
33 elif not strict:
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(cmd, table, strict=True):
49 def findcmd(cmd, table, strict=True):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(cmd, table, strict)
51 choice = findpossible(cmd, table, strict)
52
52
53 if cmd in choice:
53 if cmd in choice:
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise error.AmbiguousCommand(cmd, clist)
59 raise error.AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise error.UnknownCommand(cmd)
64 raise error.UnknownCommand(cmd)
65
65
66 def findrepo(p):
66 def findrepo(p):
67 while not os.path.isdir(os.path.join(p, ".hg")):
67 while not os.path.isdir(os.path.join(p, ".hg")):
68 oldp, p = p, os.path.dirname(p)
68 oldp, p = p, os.path.dirname(p)
69 if p == oldp:
69 if p == oldp:
70 return None
70 return None
71
71
72 return p
72 return p
73
73
74 def bail_if_changed(repo):
74 def bail_if_changed(repo):
75 if repo.dirstate.p2() != nullid:
75 if repo.dirstate.p2() != nullid:
76 raise util.Abort(_('outstanding uncommitted merge'))
76 raise util.Abort(_('outstanding uncommitted merge'))
77 modified, added, removed, deleted = repo.status()[:4]
77 modified, added, removed, deleted = repo.status()[:4]
78 if modified or added or removed or deleted:
78 if modified or added or removed or deleted:
79 raise util.Abort(_("outstanding uncommitted changes"))
79 raise util.Abort(_("outstanding uncommitted changes"))
80
80
81 def logmessage(opts):
81 def logmessage(opts):
82 """ get the log message according to -m and -l option """
82 """ get the log message according to -m and -l option """
83 message = opts.get('message')
83 message = opts.get('message')
84 logfile = opts.get('logfile')
84 logfile = opts.get('logfile')
85
85
86 if message and logfile:
86 if message and logfile:
87 raise util.Abort(_('options --message and --logfile are mutually '
87 raise util.Abort(_('options --message and --logfile are mutually '
88 'exclusive'))
88 'exclusive'))
89 if not message and logfile:
89 if not message and logfile:
90 try:
90 try:
91 if logfile == '-':
91 if logfile == '-':
92 message = sys.stdin.read()
92 message = sys.stdin.read()
93 else:
93 else:
94 message = open(logfile).read()
94 message = open(logfile).read()
95 except IOError, inst:
95 except IOError, inst:
96 raise util.Abort(_("can't read commit message '%s': %s") %
96 raise util.Abort(_("can't read commit message '%s': %s") %
97 (logfile, inst.strerror))
97 (logfile, inst.strerror))
98 return message
98 return message
99
99
100 def loglimit(opts):
100 def loglimit(opts):
101 """get the log limit according to option -l/--limit"""
101 """get the log limit according to option -l/--limit"""
102 limit = opts.get('limit')
102 limit = opts.get('limit')
103 if limit:
103 if limit:
104 try:
104 try:
105 limit = int(limit)
105 limit = int(limit)
106 except ValueError:
106 except ValueError:
107 raise util.Abort(_('limit must be a positive integer'))
107 raise util.Abort(_('limit must be a positive integer'))
108 if limit <= 0:
108 if limit <= 0:
109 raise util.Abort(_('limit must be positive'))
109 raise util.Abort(_('limit must be positive'))
110 else:
110 else:
111 limit = None
111 limit = None
112 return limit
112 return limit
113
113
114 def revsingle(repo, revspec, default='.'):
114 def revsingle(repo, revspec, default='.'):
115 if not revspec:
115 if not revspec:
116 return repo[default]
116 return repo[default]
117
117
118 l = revrange(repo, [revspec])
118 l = revrange(repo, [revspec])
119 if len(l) < 1:
119 if len(l) < 1:
120 raise util.Abort(_('empty revision set'))
120 raise util.Abort(_('empty revision set'))
121 return repo[l[-1]]
121 return repo[l[-1]]
122
122
123 def revpair(repo, revs):
123 def revpair(repo, revs):
124 if not revs:
124 if not revs:
125 return repo.dirstate.p1(), None
125 return repo.dirstate.p1(), None
126
126
127 l = revrange(repo, revs)
127 l = revrange(repo, revs)
128
128
129 if len(l) == 0:
129 if len(l) == 0:
130 return repo.dirstate.p1(), None
130 return repo.dirstate.p1(), None
131
131
132 if len(l) == 1:
132 if len(l) == 1:
133 return repo.lookup(l[0]), None
133 return repo.lookup(l[0]), None
134
134
135 return repo.lookup(l[0]), repo.lookup(l[-1])
135 return repo.lookup(l[0]), repo.lookup(l[-1])
136
136
137 def revrange(repo, revs):
137 def revrange(repo, revs):
138 """Yield revision as strings from a list of revision specifications."""
138 """Yield revision as strings from a list of revision specifications."""
139
139
140 def revfix(repo, val, defval):
140 def revfix(repo, val, defval):
141 if not val and val != 0 and defval is not None:
141 if not val and val != 0 and defval is not None:
142 return defval
142 return defval
143 return repo.changelog.rev(repo.lookup(val))
143 return repo.changelog.rev(repo.lookup(val))
144
144
145 seen, l = set(), []
145 seen, l = set(), []
146 for spec in revs:
146 for spec in revs:
147 # attempt to parse old-style ranges first to deal with
147 # attempt to parse old-style ranges first to deal with
148 # things like old-tag which contain query metacharacters
148 # things like old-tag which contain query metacharacters
149 try:
149 try:
150 if isinstance(spec, int):
150 if isinstance(spec, int):
151 seen.add(spec)
151 seen.add(spec)
152 l.append(spec)
152 l.append(spec)
153 continue
153 continue
154
154
155 if revrangesep in spec:
155 if revrangesep in spec:
156 start, end = spec.split(revrangesep, 1)
156 start, end = spec.split(revrangesep, 1)
157 start = revfix(repo, start, 0)
157 start = revfix(repo, start, 0)
158 end = revfix(repo, end, len(repo) - 1)
158 end = revfix(repo, end, len(repo) - 1)
159 step = start > end and -1 or 1
159 step = start > end and -1 or 1
160 for rev in xrange(start, end + step, step):
160 for rev in xrange(start, end + step, step):
161 if rev in seen:
161 if rev in seen:
162 continue
162 continue
163 seen.add(rev)
163 seen.add(rev)
164 l.append(rev)
164 l.append(rev)
165 continue
165 continue
166 elif spec and spec in repo: # single unquoted rev
166 elif spec and spec in repo: # single unquoted rev
167 rev = revfix(repo, spec, None)
167 rev = revfix(repo, spec, None)
168 if rev in seen:
168 if rev in seen:
169 continue
169 continue
170 seen.add(rev)
170 seen.add(rev)
171 l.append(rev)
171 l.append(rev)
172 continue
172 continue
173 except error.RepoLookupError:
173 except error.RepoLookupError:
174 pass
174 pass
175
175
176 # fall through to new-style queries if old-style fails
176 # fall through to new-style queries if old-style fails
177 m = revset.match(spec)
177 m = revset.match(spec)
178 for r in m(repo, range(len(repo))):
178 for r in m(repo, range(len(repo))):
179 if r not in seen:
179 if r not in seen:
180 l.append(r)
180 l.append(r)
181 seen.update(l)
181 seen.update(l)
182
182
183 return l
183 return l
184
184
185 def make_filename(repo, pat, node,
185 def make_filename(repo, pat, node,
186 total=None, seqno=None, revwidth=None, pathname=None):
186 total=None, seqno=None, revwidth=None, pathname=None):
187 node_expander = {
187 node_expander = {
188 'H': lambda: hex(node),
188 'H': lambda: hex(node),
189 'R': lambda: str(repo.changelog.rev(node)),
189 'R': lambda: str(repo.changelog.rev(node)),
190 'h': lambda: short(node),
190 'h': lambda: short(node),
191 }
191 }
192 expander = {
192 expander = {
193 '%': lambda: '%',
193 '%': lambda: '%',
194 'b': lambda: os.path.basename(repo.root),
194 'b': lambda: os.path.basename(repo.root),
195 }
195 }
196
196
197 try:
197 try:
198 if node:
198 if node:
199 expander.update(node_expander)
199 expander.update(node_expander)
200 if node:
200 if node:
201 expander['r'] = (lambda:
201 expander['r'] = (lambda:
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
202 str(repo.changelog.rev(node)).zfill(revwidth or 0))
203 if total is not None:
203 if total is not None:
204 expander['N'] = lambda: str(total)
204 expander['N'] = lambda: str(total)
205 if seqno is not None:
205 if seqno is not None:
206 expander['n'] = lambda: str(seqno)
206 expander['n'] = lambda: str(seqno)
207 if total is not None and seqno is not None:
207 if total is not None and seqno is not None:
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
208 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
209 if pathname is not None:
209 if pathname is not None:
210 expander['s'] = lambda: os.path.basename(pathname)
210 expander['s'] = lambda: os.path.basename(pathname)
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
211 expander['d'] = lambda: os.path.dirname(pathname) or '.'
212 expander['p'] = lambda: pathname
212 expander['p'] = lambda: pathname
213
213
214 newname = []
214 newname = []
215 patlen = len(pat)
215 patlen = len(pat)
216 i = 0
216 i = 0
217 while i < patlen:
217 while i < patlen:
218 c = pat[i]
218 c = pat[i]
219 if c == '%':
219 if c == '%':
220 i += 1
220 i += 1
221 c = pat[i]
221 c = pat[i]
222 c = expander[c]()
222 c = expander[c]()
223 newname.append(c)
223 newname.append(c)
224 i += 1
224 i += 1
225 return ''.join(newname)
225 return ''.join(newname)
226 except KeyError, inst:
226 except KeyError, inst:
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
227 raise util.Abort(_("invalid format spec '%%%s' in output filename") %
228 inst.args[0])
228 inst.args[0])
229
229
230 def make_file(repo, pat, node=None,
230 def make_file(repo, pat, node=None,
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
231 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
232
232
233 writable = mode not in ('r', 'rb')
233 writable = mode not in ('r', 'rb')
234
234
235 if not pat or pat == '-':
235 if not pat or pat == '-':
236 fp = writable and sys.stdout or sys.stdin
236 fp = writable and sys.stdout or sys.stdin
237 return os.fdopen(os.dup(fp.fileno()), mode)
237 return os.fdopen(os.dup(fp.fileno()), mode)
238 if hasattr(pat, 'write') and writable:
238 if hasattr(pat, 'write') and writable:
239 return pat
239 return pat
240 if hasattr(pat, 'read') and 'r' in mode:
240 if hasattr(pat, 'read') and 'r' in mode:
241 return pat
241 return pat
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
242 return open(make_filename(repo, pat, node, total, seqno, revwidth,
243 pathname),
243 pathname),
244 mode)
244 mode)
245
245
246 def expandpats(pats):
246 def expandpats(pats):
247 if not util.expandglobs:
247 if not util.expandglobs:
248 return list(pats)
248 return list(pats)
249 ret = []
249 ret = []
250 for p in pats:
250 for p in pats:
251 kind, name = matchmod._patsplit(p, None)
251 kind, name = matchmod._patsplit(p, None)
252 if kind is None:
252 if kind is None:
253 try:
253 try:
254 globbed = glob.glob(name)
254 globbed = glob.glob(name)
255 except re.error:
255 except re.error:
256 globbed = [name]
256 globbed = [name]
257 if globbed:
257 if globbed:
258 ret.extend(globbed)
258 ret.extend(globbed)
259 continue
259 continue
260 ret.append(p)
260 ret.append(p)
261 return ret
261 return ret
262
262
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
263 def match(repo, pats=[], opts={}, globbed=False, default='relpath'):
264 if pats == ("",):
264 if pats == ("",):
265 pats = []
265 pats = []
266 if not globbed and default == 'relpath':
266 if not globbed and default == 'relpath':
267 pats = expandpats(pats or [])
267 pats = expandpats(pats or [])
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
268 m = matchmod.match(repo.root, repo.getcwd(), pats,
269 opts.get('include'), opts.get('exclude'), default,
269 opts.get('include'), opts.get('exclude'), default,
270 auditor=repo.auditor)
270 auditor=repo.auditor)
271 def badfn(f, msg):
271 def badfn(f, msg):
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
272 repo.ui.warn("%s: %s\n" % (m.rel(f), msg))
273 m.bad = badfn
273 m.bad = badfn
274 return m
274 return m
275
275
276 def matchall(repo):
276 def matchall(repo):
277 return matchmod.always(repo.root, repo.getcwd())
277 return matchmod.always(repo.root, repo.getcwd())
278
278
279 def matchfiles(repo, files):
279 def matchfiles(repo, files):
280 return matchmod.exact(repo.root, repo.getcwd(), files)
280 return matchmod.exact(repo.root, repo.getcwd(), files)
281
281
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
282 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
283 if dry_run is None:
283 if dry_run is None:
284 dry_run = opts.get('dry_run')
284 dry_run = opts.get('dry_run')
285 if similarity is None:
285 if similarity is None:
286 similarity = float(opts.get('similarity') or 0)
286 similarity = float(opts.get('similarity') or 0)
287 # we'd use status here, except handling of symlinks and ignore is tricky
287 # we'd use status here, except handling of symlinks and ignore is tricky
288 added, unknown, deleted, removed = [], [], [], []
288 added, unknown, deleted, removed = [], [], [], []
289 audit_path = scmutil.path_auditor(repo.root)
289 audit_path = scmutil.path_auditor(repo.root)
290 m = match(repo, pats, opts)
290 m = match(repo, pats, opts)
291 for abs in repo.walk(m):
291 for abs in repo.walk(m):
292 target = repo.wjoin(abs)
292 target = repo.wjoin(abs)
293 good = True
293 good = True
294 try:
294 try:
295 audit_path(abs)
295 audit_path(abs)
296 except (OSError, util.Abort):
296 except (OSError, util.Abort):
297 good = False
297 good = False
298 rel = m.rel(abs)
298 rel = m.rel(abs)
299 exact = m.exact(abs)
299 exact = m.exact(abs)
300 if good and abs not in repo.dirstate:
300 if good and abs not in repo.dirstate:
301 unknown.append(abs)
301 unknown.append(abs)
302 if repo.ui.verbose or not exact:
302 if repo.ui.verbose or not exact:
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
303 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
304 elif repo.dirstate[abs] != 'r' and (not good or not os.path.lexists(target)
305 or (os.path.isdir(target) and not os.path.islink(target))):
305 or (os.path.isdir(target) and not os.path.islink(target))):
306 deleted.append(abs)
306 deleted.append(abs)
307 if repo.ui.verbose or not exact:
307 if repo.ui.verbose or not exact:
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
308 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
309 # for finding renames
309 # for finding renames
310 elif repo.dirstate[abs] == 'r':
310 elif repo.dirstate[abs] == 'r':
311 removed.append(abs)
311 removed.append(abs)
312 elif repo.dirstate[abs] == 'a':
312 elif repo.dirstate[abs] == 'a':
313 added.append(abs)
313 added.append(abs)
314 copies = {}
314 copies = {}
315 if similarity > 0:
315 if similarity > 0:
316 for old, new, score in similar.findrenames(repo,
316 for old, new, score in similar.findrenames(repo,
317 added + unknown, removed + deleted, similarity):
317 added + unknown, removed + deleted, similarity):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
318 if repo.ui.verbose or not m.exact(old) or not m.exact(new):
319 repo.ui.status(_('recording removal of %s as rename to %s '
319 repo.ui.status(_('recording removal of %s as rename to %s '
320 '(%d%% similar)\n') %
320 '(%d%% similar)\n') %
321 (m.rel(old), m.rel(new), score * 100))
321 (m.rel(old), m.rel(new), score * 100))
322 copies[new] = old
322 copies[new] = old
323
323
324 if not dry_run:
324 if not dry_run:
325 wctx = repo[None]
325 wctx = repo[None]
326 wlock = repo.wlock()
326 wlock = repo.wlock()
327 try:
327 try:
328 wctx.remove(deleted)
328 wctx.remove(deleted)
329 wctx.add(unknown)
329 wctx.add(unknown)
330 for new, old in copies.iteritems():
330 for new, old in copies.iteritems():
331 wctx.copy(old, new)
331 wctx.copy(old, new)
332 finally:
332 finally:
333 wlock.release()
333 wlock.release()
334
334
335 def updatedir(ui, repo, patches, similarity=0):
335 def updatedir(ui, repo, patches, similarity=0):
336 '''Update dirstate after patch application according to metadata'''
336 '''Update dirstate after patch application according to metadata'''
337 if not patches:
337 if not patches:
338 return
338 return
339 copies = []
339 copies = []
340 removes = set()
340 removes = set()
341 cfiles = patches.keys()
341 cfiles = patches.keys()
342 cwd = repo.getcwd()
342 cwd = repo.getcwd()
343 if cwd:
343 if cwd:
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
344 cfiles = [util.pathto(repo.root, cwd, f) for f in patches.keys()]
345 for f in patches:
345 for f in patches:
346 gp = patches[f]
346 gp = patches[f]
347 if not gp:
347 if not gp:
348 continue
348 continue
349 if gp.op == 'RENAME':
349 if gp.op == 'RENAME':
350 copies.append((gp.oldpath, gp.path))
350 copies.append((gp.oldpath, gp.path))
351 removes.add(gp.oldpath)
351 removes.add(gp.oldpath)
352 elif gp.op == 'COPY':
352 elif gp.op == 'COPY':
353 copies.append((gp.oldpath, gp.path))
353 copies.append((gp.oldpath, gp.path))
354 elif gp.op == 'DELETE':
354 elif gp.op == 'DELETE':
355 removes.add(gp.path)
355 removes.add(gp.path)
356
356
357 wctx = repo[None]
357 wctx = repo[None]
358 for src, dst in copies:
358 for src, dst in copies:
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
359 dirstatecopy(ui, repo, wctx, src, dst, cwd=cwd)
360 if (not similarity) and removes:
360 if (not similarity) and removes:
361 wctx.remove(sorted(removes), True)
361 wctx.remove(sorted(removes), True)
362
362
363 for f in patches:
363 for f in patches:
364 gp = patches[f]
364 gp = patches[f]
365 if gp and gp.mode:
365 if gp and gp.mode:
366 islink, isexec = gp.mode
366 islink, isexec = gp.mode
367 dst = repo.wjoin(gp.path)
367 dst = repo.wjoin(gp.path)
368 # patch won't create empty files
368 # patch won't create empty files
369 if gp.op == 'ADD' and not os.path.lexists(dst):
369 if gp.op == 'ADD' and not os.path.lexists(dst):
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
370 flags = (isexec and 'x' or '') + (islink and 'l' or '')
371 repo.wwrite(gp.path, '', flags)
371 repo.wwrite(gp.path, '', flags)
372 util.set_flags(dst, islink, isexec)
372 util.set_flags(dst, islink, isexec)
373 addremove(repo, cfiles, similarity=similarity)
373 addremove(repo, cfiles, similarity=similarity)
374 files = patches.keys()
374 files = patches.keys()
375 files.extend([r for r in removes if r not in files])
375 files.extend([r for r in removes if r not in files])
376 return sorted(files)
376 return sorted(files)
377
377
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
378 def dirstatecopy(ui, repo, wctx, src, dst, dryrun=False, cwd=None):
379 """Update the dirstate to reflect the intent of copying src to dst. For
379 """Update the dirstate to reflect the intent of copying src to dst. For
380 different reasons it might not end with dst being marked as copied from src.
380 different reasons it might not end with dst being marked as copied from src.
381 """
381 """
382 origsrc = repo.dirstate.copied(src) or src
382 origsrc = repo.dirstate.copied(src) or src
383 if dst == origsrc: # copying back a copy?
383 if dst == origsrc: # copying back a copy?
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
384 if repo.dirstate[dst] not in 'mn' and not dryrun:
385 repo.dirstate.normallookup(dst)
385 repo.dirstate.normallookup(dst)
386 else:
386 else:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
387 if repo.dirstate[origsrc] == 'a' and origsrc == src:
388 if not ui.quiet:
388 if not ui.quiet:
389 ui.warn(_("%s has not been committed yet, so no copy "
389 ui.warn(_("%s has not been committed yet, so no copy "
390 "data will be stored for %s.\n")
390 "data will be stored for %s.\n")
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
391 % (repo.pathto(origsrc, cwd), repo.pathto(dst, cwd)))
392 if repo.dirstate[dst] in '?r' and not dryrun:
392 if repo.dirstate[dst] in '?r' and not dryrun:
393 wctx.add([dst])
393 wctx.add([dst])
394 elif not dryrun:
394 elif not dryrun:
395 wctx.copy(origsrc, dst)
395 wctx.copy(origsrc, dst)
396
396
397 def copy(ui, repo, pats, opts, rename=False):
397 def copy(ui, repo, pats, opts, rename=False):
398 # called with the repo lock held
398 # called with the repo lock held
399 #
399 #
400 # hgsep => pathname that uses "/" to separate directories
400 # hgsep => pathname that uses "/" to separate directories
401 # ossep => pathname that uses os.sep to separate directories
401 # ossep => pathname that uses os.sep to separate directories
402 cwd = repo.getcwd()
402 cwd = repo.getcwd()
403 targets = {}
403 targets = {}
404 after = opts.get("after")
404 after = opts.get("after")
405 dryrun = opts.get("dry_run")
405 dryrun = opts.get("dry_run")
406 wctx = repo[None]
406 wctx = repo[None]
407
407
408 def walkpat(pat):
408 def walkpat(pat):
409 srcs = []
409 srcs = []
410 badstates = after and '?' or '?r'
410 badstates = after and '?' or '?r'
411 m = match(repo, [pat], opts, globbed=True)
411 m = match(repo, [pat], opts, globbed=True)
412 for abs in repo.walk(m):
412 for abs in repo.walk(m):
413 state = repo.dirstate[abs]
413 state = repo.dirstate[abs]
414 rel = m.rel(abs)
414 rel = m.rel(abs)
415 exact = m.exact(abs)
415 exact = m.exact(abs)
416 if state in badstates:
416 if state in badstates:
417 if exact and state == '?':
417 if exact and state == '?':
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
418 ui.warn(_('%s: not copying - file is not managed\n') % rel)
419 if exact and state == 'r':
419 if exact and state == 'r':
420 ui.warn(_('%s: not copying - file has been marked for'
420 ui.warn(_('%s: not copying - file has been marked for'
421 ' remove\n') % rel)
421 ' remove\n') % rel)
422 continue
422 continue
423 # abs: hgsep
423 # abs: hgsep
424 # rel: ossep
424 # rel: ossep
425 srcs.append((abs, rel, exact))
425 srcs.append((abs, rel, exact))
426 return srcs
426 return srcs
427
427
428 # abssrc: hgsep
428 # abssrc: hgsep
429 # relsrc: ossep
429 # relsrc: ossep
430 # otarget: ossep
430 # otarget: ossep
431 def copyfile(abssrc, relsrc, otarget, exact):
431 def copyfile(abssrc, relsrc, otarget, exact):
432 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
432 abstarget = scmutil.canonpath(repo.root, cwd, otarget)
433 reltarget = repo.pathto(abstarget, cwd)
433 reltarget = repo.pathto(abstarget, cwd)
434 target = repo.wjoin(abstarget)
434 target = repo.wjoin(abstarget)
435 src = repo.wjoin(abssrc)
435 src = repo.wjoin(abssrc)
436 state = repo.dirstate[abstarget]
436 state = repo.dirstate[abstarget]
437
437
438 scmutil.checkportable(ui, abstarget)
438 scmutil.checkportable(ui, abstarget)
439
439
440 # check for collisions
440 # check for collisions
441 prevsrc = targets.get(abstarget)
441 prevsrc = targets.get(abstarget)
442 if prevsrc is not None:
442 if prevsrc is not None:
443 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
443 ui.warn(_('%s: not overwriting - %s collides with %s\n') %
444 (reltarget, repo.pathto(abssrc, cwd),
444 (reltarget, repo.pathto(abssrc, cwd),
445 repo.pathto(prevsrc, cwd)))
445 repo.pathto(prevsrc, cwd)))
446 return
446 return
447
447
448 # check for overwrites
448 # check for overwrites
449 exists = os.path.lexists(target)
449 exists = os.path.lexists(target)
450 if not after and exists or after and state in 'mn':
450 if not after and exists or after and state in 'mn':
451 if not opts['force']:
451 if not opts['force']:
452 ui.warn(_('%s: not overwriting - file exists\n') %
452 ui.warn(_('%s: not overwriting - file exists\n') %
453 reltarget)
453 reltarget)
454 return
454 return
455
455
456 if after:
456 if after:
457 if not exists:
457 if not exists:
458 if rename:
458 if rename:
459 ui.warn(_('%s: not recording move - %s does not exist\n') %
459 ui.warn(_('%s: not recording move - %s does not exist\n') %
460 (relsrc, reltarget))
460 (relsrc, reltarget))
461 else:
461 else:
462 ui.warn(_('%s: not recording copy - %s does not exist\n') %
462 ui.warn(_('%s: not recording copy - %s does not exist\n') %
463 (relsrc, reltarget))
463 (relsrc, reltarget))
464 return
464 return
465 elif not dryrun:
465 elif not dryrun:
466 try:
466 try:
467 if exists:
467 if exists:
468 os.unlink(target)
468 os.unlink(target)
469 targetdir = os.path.dirname(target) or '.'
469 targetdir = os.path.dirname(target) or '.'
470 if not os.path.isdir(targetdir):
470 if not os.path.isdir(targetdir):
471 os.makedirs(targetdir)
471 os.makedirs(targetdir)
472 util.copyfile(src, target)
472 util.copyfile(src, target)
473 except IOError, inst:
473 except IOError, inst:
474 if inst.errno == errno.ENOENT:
474 if inst.errno == errno.ENOENT:
475 ui.warn(_('%s: deleted in working copy\n') % relsrc)
475 ui.warn(_('%s: deleted in working copy\n') % relsrc)
476 else:
476 else:
477 ui.warn(_('%s: cannot copy - %s\n') %
477 ui.warn(_('%s: cannot copy - %s\n') %
478 (relsrc, inst.strerror))
478 (relsrc, inst.strerror))
479 return True # report a failure
479 return True # report a failure
480
480
481 if ui.verbose or not exact:
481 if ui.verbose or not exact:
482 if rename:
482 if rename:
483 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
483 ui.status(_('moving %s to %s\n') % (relsrc, reltarget))
484 else:
484 else:
485 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
485 ui.status(_('copying %s to %s\n') % (relsrc, reltarget))
486
486
487 targets[abstarget] = abssrc
487 targets[abstarget] = abssrc
488
488
489 # fix up dirstate
489 # fix up dirstate
490 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
490 dirstatecopy(ui, repo, wctx, abssrc, abstarget, dryrun=dryrun, cwd=cwd)
491 if rename and not dryrun:
491 if rename and not dryrun:
492 wctx.remove([abssrc], not after)
492 wctx.remove([abssrc], not after)
493
493
494 # pat: ossep
494 # pat: ossep
495 # dest ossep
495 # dest ossep
496 # srcs: list of (hgsep, hgsep, ossep, bool)
496 # srcs: list of (hgsep, hgsep, ossep, bool)
497 # return: function that takes hgsep and returns ossep
497 # return: function that takes hgsep and returns ossep
498 def targetpathfn(pat, dest, srcs):
498 def targetpathfn(pat, dest, srcs):
499 if os.path.isdir(pat):
499 if os.path.isdir(pat):
500 abspfx = scmutil.canonpath(repo.root, cwd, pat)
500 abspfx = scmutil.canonpath(repo.root, cwd, pat)
501 abspfx = util.localpath(abspfx)
501 abspfx = util.localpath(abspfx)
502 if destdirexists:
502 if destdirexists:
503 striplen = len(os.path.split(abspfx)[0])
503 striplen = len(os.path.split(abspfx)[0])
504 else:
504 else:
505 striplen = len(abspfx)
505 striplen = len(abspfx)
506 if striplen:
506 if striplen:
507 striplen += len(os.sep)
507 striplen += len(os.sep)
508 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
508 res = lambda p: os.path.join(dest, util.localpath(p)[striplen:])
509 elif destdirexists:
509 elif destdirexists:
510 res = lambda p: os.path.join(dest,
510 res = lambda p: os.path.join(dest,
511 os.path.basename(util.localpath(p)))
511 os.path.basename(util.localpath(p)))
512 else:
512 else:
513 res = lambda p: dest
513 res = lambda p: dest
514 return res
514 return res
515
515
516 # pat: ossep
516 # pat: ossep
517 # dest ossep
517 # dest ossep
518 # srcs: list of (hgsep, hgsep, ossep, bool)
518 # srcs: list of (hgsep, hgsep, ossep, bool)
519 # return: function that takes hgsep and returns ossep
519 # return: function that takes hgsep and returns ossep
520 def targetpathafterfn(pat, dest, srcs):
520 def targetpathafterfn(pat, dest, srcs):
521 if matchmod.patkind(pat):
521 if matchmod.patkind(pat):
522 # a mercurial pattern
522 # a mercurial pattern
523 res = lambda p: os.path.join(dest,
523 res = lambda p: os.path.join(dest,
524 os.path.basename(util.localpath(p)))
524 os.path.basename(util.localpath(p)))
525 else:
525 else:
526 abspfx = scmutil.canonpath(repo.root, cwd, pat)
526 abspfx = scmutil.canonpath(repo.root, cwd, pat)
527 if len(abspfx) < len(srcs[0][0]):
527 if len(abspfx) < len(srcs[0][0]):
528 # A directory. Either the target path contains the last
528 # A directory. Either the target path contains the last
529 # component of the source path or it does not.
529 # component of the source path or it does not.
530 def evalpath(striplen):
530 def evalpath(striplen):
531 score = 0
531 score = 0
532 for s in srcs:
532 for s in srcs:
533 t = os.path.join(dest, util.localpath(s[0])[striplen:])
533 t = os.path.join(dest, util.localpath(s[0])[striplen:])
534 if os.path.lexists(t):
534 if os.path.lexists(t):
535 score += 1
535 score += 1
536 return score
536 return score
537
537
538 abspfx = util.localpath(abspfx)
538 abspfx = util.localpath(abspfx)
539 striplen = len(abspfx)
539 striplen = len(abspfx)
540 if striplen:
540 if striplen:
541 striplen += len(os.sep)
541 striplen += len(os.sep)
542 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
542 if os.path.isdir(os.path.join(dest, os.path.split(abspfx)[1])):
543 score = evalpath(striplen)
543 score = evalpath(striplen)
544 striplen1 = len(os.path.split(abspfx)[0])
544 striplen1 = len(os.path.split(abspfx)[0])
545 if striplen1:
545 if striplen1:
546 striplen1 += len(os.sep)
546 striplen1 += len(os.sep)
547 if evalpath(striplen1) > score:
547 if evalpath(striplen1) > score:
548 striplen = striplen1
548 striplen = striplen1
549 res = lambda p: os.path.join(dest,
549 res = lambda p: os.path.join(dest,
550 util.localpath(p)[striplen:])
550 util.localpath(p)[striplen:])
551 else:
551 else:
552 # a file
552 # a file
553 if destdirexists:
553 if destdirexists:
554 res = lambda p: os.path.join(dest,
554 res = lambda p: os.path.join(dest,
555 os.path.basename(util.localpath(p)))
555 os.path.basename(util.localpath(p)))
556 else:
556 else:
557 res = lambda p: dest
557 res = lambda p: dest
558 return res
558 return res
559
559
560
560
561 pats = expandpats(pats)
561 pats = expandpats(pats)
562 if not pats:
562 if not pats:
563 raise util.Abort(_('no source or destination specified'))
563 raise util.Abort(_('no source or destination specified'))
564 if len(pats) == 1:
564 if len(pats) == 1:
565 raise util.Abort(_('no destination specified'))
565 raise util.Abort(_('no destination specified'))
566 dest = pats.pop()
566 dest = pats.pop()
567 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
567 destdirexists = os.path.isdir(dest) and not os.path.islink(dest)
568 if not destdirexists:
568 if not destdirexists:
569 if len(pats) > 1 or matchmod.patkind(pats[0]):
569 if len(pats) > 1 or matchmod.patkind(pats[0]):
570 raise util.Abort(_('with multiple sources, destination must be an '
570 raise util.Abort(_('with multiple sources, destination must be an '
571 'existing directory'))
571 'existing directory'))
572 if util.endswithsep(dest):
572 if util.endswithsep(dest):
573 raise util.Abort(_('destination %s is not a directory') % dest)
573 raise util.Abort(_('destination %s is not a directory') % dest)
574
574
575 tfn = targetpathfn
575 tfn = targetpathfn
576 if after:
576 if after:
577 tfn = targetpathafterfn
577 tfn = targetpathafterfn
578 copylist = []
578 copylist = []
579 for pat in pats:
579 for pat in pats:
580 srcs = walkpat(pat)
580 srcs = walkpat(pat)
581 if not srcs:
581 if not srcs:
582 continue
582 continue
583 copylist.append((tfn(pat, dest, srcs), srcs))
583 copylist.append((tfn(pat, dest, srcs), srcs))
584 if not copylist:
584 if not copylist:
585 raise util.Abort(_('no files to copy'))
585 raise util.Abort(_('no files to copy'))
586
586
587 errors = 0
587 errors = 0
588 for targetpath, srcs in copylist:
588 for targetpath, srcs in copylist:
589 for abssrc, relsrc, exact in srcs:
589 for abssrc, relsrc, exact in srcs:
590 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
590 if copyfile(abssrc, relsrc, targetpath(abssrc), exact):
591 errors += 1
591 errors += 1
592
592
593 if errors:
593 if errors:
594 ui.warn(_('(consider using --after)\n'))
594 ui.warn(_('(consider using --after)\n'))
595
595
596 return errors != 0
596 return errors != 0
597
597
598 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
598 def service(opts, parentfn=None, initfn=None, runfn=None, logfile=None,
599 runargs=None, appendpid=False):
599 runargs=None, appendpid=False):
600 '''Run a command as a service.'''
600 '''Run a command as a service.'''
601
601
602 if opts['daemon'] and not opts['daemon_pipefds']:
602 if opts['daemon'] and not opts['daemon_pipefds']:
603 # Signal child process startup with file removal
603 # Signal child process startup with file removal
604 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
604 lockfd, lockpath = tempfile.mkstemp(prefix='hg-service-')
605 os.close(lockfd)
605 os.close(lockfd)
606 try:
606 try:
607 if not runargs:
607 if not runargs:
608 runargs = util.hgcmd() + sys.argv[1:]
608 runargs = util.hgcmd() + sys.argv[1:]
609 runargs.append('--daemon-pipefds=%s' % lockpath)
609 runargs.append('--daemon-pipefds=%s' % lockpath)
610 # Don't pass --cwd to the child process, because we've already
610 # Don't pass --cwd to the child process, because we've already
611 # changed directory.
611 # changed directory.
612 for i in xrange(1, len(runargs)):
612 for i in xrange(1, len(runargs)):
613 if runargs[i].startswith('--cwd='):
613 if runargs[i].startswith('--cwd='):
614 del runargs[i]
614 del runargs[i]
615 break
615 break
616 elif runargs[i].startswith('--cwd'):
616 elif runargs[i].startswith('--cwd'):
617 del runargs[i:i + 2]
617 del runargs[i:i + 2]
618 break
618 break
619 def condfn():
619 def condfn():
620 return not os.path.exists(lockpath)
620 return not os.path.exists(lockpath)
621 pid = util.rundetached(runargs, condfn)
621 pid = util.rundetached(runargs, condfn)
622 if pid < 0:
622 if pid < 0:
623 raise util.Abort(_('child process failed to start'))
623 raise util.Abort(_('child process failed to start'))
624 finally:
624 finally:
625 try:
625 try:
626 os.unlink(lockpath)
626 os.unlink(lockpath)
627 except OSError, e:
627 except OSError, e:
628 if e.errno != errno.ENOENT:
628 if e.errno != errno.ENOENT:
629 raise
629 raise
630 if parentfn:
630 if parentfn:
631 return parentfn(pid)
631 return parentfn(pid)
632 else:
632 else:
633 return
633 return
634
634
635 if initfn:
635 if initfn:
636 initfn()
636 initfn()
637
637
638 if opts['pid_file']:
638 if opts['pid_file']:
639 mode = appendpid and 'a' or 'w'
639 mode = appendpid and 'a' or 'w'
640 fp = open(opts['pid_file'], mode)
640 fp = open(opts['pid_file'], mode)
641 fp.write(str(os.getpid()) + '\n')
641 fp.write(str(os.getpid()) + '\n')
642 fp.close()
642 fp.close()
643
643
644 if opts['daemon_pipefds']:
644 if opts['daemon_pipefds']:
645 lockpath = opts['daemon_pipefds']
645 lockpath = opts['daemon_pipefds']
646 try:
646 try:
647 os.setsid()
647 os.setsid()
648 except AttributeError:
648 except AttributeError:
649 pass
649 pass
650 os.unlink(lockpath)
650 os.unlink(lockpath)
651 util.hidewindow()
651 util.hidewindow()
652 sys.stdout.flush()
652 sys.stdout.flush()
653 sys.stderr.flush()
653 sys.stderr.flush()
654
654
655 nullfd = os.open(util.nulldev, os.O_RDWR)
655 nullfd = os.open(util.nulldev, os.O_RDWR)
656 logfilefd = nullfd
656 logfilefd = nullfd
657 if logfile:
657 if logfile:
658 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
658 logfilefd = os.open(logfile, os.O_RDWR | os.O_CREAT | os.O_APPEND)
659 os.dup2(nullfd, 0)
659 os.dup2(nullfd, 0)
660 os.dup2(logfilefd, 1)
660 os.dup2(logfilefd, 1)
661 os.dup2(logfilefd, 2)
661 os.dup2(logfilefd, 2)
662 if nullfd not in (0, 1, 2):
662 if nullfd not in (0, 1, 2):
663 os.close(nullfd)
663 os.close(nullfd)
664 if logfile and logfilefd not in (0, 1, 2):
664 if logfile and logfilefd not in (0, 1, 2):
665 os.close(logfilefd)
665 os.close(logfilefd)
666
666
667 if runfn:
667 if runfn:
668 return runfn()
668 return runfn()
669
669
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
670 def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False,
671 opts=None):
671 opts=None):
672 '''export changesets as hg patches.'''
672 '''export changesets as hg patches.'''
673
673
674 total = len(revs)
674 total = len(revs)
675 revwidth = max([len(str(rev)) for rev in revs])
675 revwidth = max([len(str(rev)) for rev in revs])
676
676
677 def single(rev, seqno, fp):
677 def single(rev, seqno, fp):
678 ctx = repo[rev]
678 ctx = repo[rev]
679 node = ctx.node()
679 node = ctx.node()
680 parents = [p.node() for p in ctx.parents() if p]
680 parents = [p.node() for p in ctx.parents() if p]
681 branch = ctx.branch()
681 branch = ctx.branch()
682 if switch_parent:
682 if switch_parent:
683 parents.reverse()
683 parents.reverse()
684 prev = (parents and parents[0]) or nullid
684 prev = (parents and parents[0]) or nullid
685
685
686 shouldclose = False
686 shouldclose = False
687 if not fp:
687 if not fp:
688 fp = make_file(repo, template, node, total=total, seqno=seqno,
688 fp = make_file(repo, template, node, total=total, seqno=seqno,
689 revwidth=revwidth, mode='ab')
689 revwidth=revwidth, mode='ab')
690 if fp != template:
690 if fp != template:
691 shouldclose = True
691 shouldclose = True
692 if fp != sys.stdout and hasattr(fp, 'name'):
692 if fp != sys.stdout and hasattr(fp, 'name'):
693 repo.ui.note("%s\n" % fp.name)
693 repo.ui.note("%s\n" % fp.name)
694
694
695 fp.write("# HG changeset patch\n")
695 fp.write("# HG changeset patch\n")
696 fp.write("# User %s\n" % ctx.user())
696 fp.write("# User %s\n" % ctx.user())
697 fp.write("# Date %d %d\n" % ctx.date())
697 fp.write("# Date %d %d\n" % ctx.date())
698 if branch and branch != 'default':
698 if branch and branch != 'default':
699 fp.write("# Branch %s\n" % branch)
699 fp.write("# Branch %s\n" % branch)
700 fp.write("# Node ID %s\n" % hex(node))
700 fp.write("# Node ID %s\n" % hex(node))
701 fp.write("# Parent %s\n" % hex(prev))
701 fp.write("# Parent %s\n" % hex(prev))
702 if len(parents) > 1:
702 if len(parents) > 1:
703 fp.write("# Parent %s\n" % hex(parents[1]))
703 fp.write("# Parent %s\n" % hex(parents[1]))
704 fp.write(ctx.description().rstrip())
704 fp.write(ctx.description().rstrip())
705 fp.write("\n\n")
705 fp.write("\n\n")
706
706
707 for chunk in patch.diff(repo, prev, node, opts=opts):
707 for chunk in patch.diff(repo, prev, node, opts=opts):
708 fp.write(chunk)
708 fp.write(chunk)
709
709
710 if shouldclose:
710 if shouldclose:
711 fp.close()
711 fp.close()
712
712
713 for seqno, rev in enumerate(revs):
713 for seqno, rev in enumerate(revs):
714 single(rev, seqno + 1, fp)
714 single(rev, seqno + 1, fp)
715
715
716 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
716 def diffordiffstat(ui, repo, diffopts, node1, node2, match,
717 changes=None, stat=False, fp=None, prefix='',
717 changes=None, stat=False, fp=None, prefix='',
718 listsubrepos=False):
718 listsubrepos=False):
719 '''show diff or diffstat.'''
719 '''show diff or diffstat.'''
720 if fp is None:
720 if fp is None:
721 write = ui.write
721 write = ui.write
722 else:
722 else:
723 def write(s, **kw):
723 def write(s, **kw):
724 fp.write(s)
724 fp.write(s)
725
725
726 if stat:
726 if stat:
727 diffopts = diffopts.copy(context=0)
727 diffopts = diffopts.copy(context=0)
728 width = 80
728 width = 80
729 if not ui.plain():
729 if not ui.plain():
730 width = ui.termwidth()
730 width = ui.termwidth()
731 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
731 chunks = patch.diff(repo, node1, node2, match, changes, diffopts,
732 prefix=prefix)
732 prefix=prefix)
733 for chunk, label in patch.diffstatui(util.iterlines(chunks),
733 for chunk, label in patch.diffstatui(util.iterlines(chunks),
734 width=width,
734 width=width,
735 git=diffopts.git):
735 git=diffopts.git):
736 write(chunk, label=label)
736 write(chunk, label=label)
737 else:
737 else:
738 for chunk, label in patch.diffui(repo, node1, node2, match,
738 for chunk, label in patch.diffui(repo, node1, node2, match,
739 changes, diffopts, prefix=prefix):
739 changes, diffopts, prefix=prefix):
740 write(chunk, label=label)
740 write(chunk, label=label)
741
741
742 if listsubrepos:
742 if listsubrepos:
743 ctx1 = repo[node1]
743 ctx1 = repo[node1]
744 ctx2 = repo[node2]
744 ctx2 = repo[node2]
745 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
745 for subpath, sub in subrepo.itersubrepos(ctx1, ctx2):
746 if node2 is not None:
746 if node2 is not None:
747 node2 = ctx2.substate[subpath][1]
747 node2 = ctx2.substate[subpath][1]
748 submatch = matchmod.narrowmatcher(subpath, match)
748 submatch = matchmod.narrowmatcher(subpath, match)
749 sub.diff(diffopts, node2, submatch, changes=changes,
749 sub.diff(diffopts, node2, submatch, changes=changes,
750 stat=stat, fp=fp, prefix=prefix)
750 stat=stat, fp=fp, prefix=prefix)
751
751
752 class changeset_printer(object):
752 class changeset_printer(object):
753 '''show changeset information when templating not requested.'''
753 '''show changeset information when templating not requested.'''
754
754
755 def __init__(self, ui, repo, patch, diffopts, buffered):
755 def __init__(self, ui, repo, patch, diffopts, buffered):
756 self.ui = ui
756 self.ui = ui
757 self.repo = repo
757 self.repo = repo
758 self.buffered = buffered
758 self.buffered = buffered
759 self.patch = patch
759 self.patch = patch
760 self.diffopts = diffopts
760 self.diffopts = diffopts
761 self.header = {}
761 self.header = {}
762 self.hunk = {}
762 self.hunk = {}
763 self.lastheader = None
763 self.lastheader = None
764 self.footer = None
764 self.footer = None
765
765
766 def flush(self, rev):
766 def flush(self, rev):
767 if rev in self.header:
767 if rev in self.header:
768 h = self.header[rev]
768 h = self.header[rev]
769 if h != self.lastheader:
769 if h != self.lastheader:
770 self.lastheader = h
770 self.lastheader = h
771 self.ui.write(h)
771 self.ui.write(h)
772 del self.header[rev]
772 del self.header[rev]
773 if rev in self.hunk:
773 if rev in self.hunk:
774 self.ui.write(self.hunk[rev])
774 self.ui.write(self.hunk[rev])
775 del self.hunk[rev]
775 del self.hunk[rev]
776 return 1
776 return 1
777 return 0
777 return 0
778
778
779 def close(self):
779 def close(self):
780 if self.footer:
780 if self.footer:
781 self.ui.write(self.footer)
781 self.ui.write(self.footer)
782
782
783 def show(self, ctx, copies=None, matchfn=None, **props):
783 def show(self, ctx, copies=None, matchfn=None, **props):
784 if self.buffered:
784 if self.buffered:
785 self.ui.pushbuffer()
785 self.ui.pushbuffer()
786 self._show(ctx, copies, matchfn, props)
786 self._show(ctx, copies, matchfn, props)
787 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
787 self.hunk[ctx.rev()] = self.ui.popbuffer(labeled=True)
788 else:
788 else:
789 self._show(ctx, copies, matchfn, props)
789 self._show(ctx, copies, matchfn, props)
790
790
791 def _show(self, ctx, copies, matchfn, props):
791 def _show(self, ctx, copies, matchfn, props):
792 '''show a single changeset or file revision'''
792 '''show a single changeset or file revision'''
793 changenode = ctx.node()
793 changenode = ctx.node()
794 rev = ctx.rev()
794 rev = ctx.rev()
795
795
796 if self.ui.quiet:
796 if self.ui.quiet:
797 self.ui.write("%d:%s\n" % (rev, short(changenode)),
797 self.ui.write("%d:%s\n" % (rev, short(changenode)),
798 label='log.node')
798 label='log.node')
799 return
799 return
800
800
801 log = self.repo.changelog
801 log = self.repo.changelog
802 date = util.datestr(ctx.date())
802 date = util.datestr(ctx.date())
803
803
804 hexfunc = self.ui.debugflag and hex or short
804 hexfunc = self.ui.debugflag and hex or short
805
805
806 parents = [(p, hexfunc(log.node(p)))
806 parents = [(p, hexfunc(log.node(p)))
807 for p in self._meaningful_parentrevs(log, rev)]
807 for p in self._meaningful_parentrevs(log, rev)]
808
808
809 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
809 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)),
810 label='log.changeset')
810 label='log.changeset')
811
811
812 branch = ctx.branch()
812 branch = ctx.branch()
813 # don't show the default branch name
813 # don't show the default branch name
814 if branch != 'default':
814 if branch != 'default':
815 self.ui.write(_("branch: %s\n") % branch,
815 self.ui.write(_("branch: %s\n") % branch,
816 label='log.branch')
816 label='log.branch')
817 for bookmark in self.repo.nodebookmarks(changenode):
817 for bookmark in self.repo.nodebookmarks(changenode):
818 self.ui.write(_("bookmark: %s\n") % bookmark,
818 self.ui.write(_("bookmark: %s\n") % bookmark,
819 label='log.bookmark')
819 label='log.bookmark')
820 for tag in self.repo.nodetags(changenode):
820 for tag in self.repo.nodetags(changenode):
821 self.ui.write(_("tag: %s\n") % tag,
821 self.ui.write(_("tag: %s\n") % tag,
822 label='log.tag')
822 label='log.tag')
823 for parent in parents:
823 for parent in parents:
824 self.ui.write(_("parent: %d:%s\n") % parent,
824 self.ui.write(_("parent: %d:%s\n") % parent,
825 label='log.parent')
825 label='log.parent')
826
826
827 if self.ui.debugflag:
827 if self.ui.debugflag:
828 mnode = ctx.manifestnode()
828 mnode = ctx.manifestnode()
829 self.ui.write(_("manifest: %d:%s\n") %
829 self.ui.write(_("manifest: %d:%s\n") %
830 (self.repo.manifest.rev(mnode), hex(mnode)),
830 (self.repo.manifest.rev(mnode), hex(mnode)),
831 label='ui.debug log.manifest')
831 label='ui.debug log.manifest')
832 self.ui.write(_("user: %s\n") % ctx.user(),
832 self.ui.write(_("user: %s\n") % ctx.user(),
833 label='log.user')
833 label='log.user')
834 self.ui.write(_("date: %s\n") % date,
834 self.ui.write(_("date: %s\n") % date,
835 label='log.date')
835 label='log.date')
836
836
837 if self.ui.debugflag:
837 if self.ui.debugflag:
838 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
838 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
839 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
839 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
840 files):
840 files):
841 if value:
841 if value:
842 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
842 self.ui.write("%-12s %s\n" % (key, " ".join(value)),
843 label='ui.debug log.files')
843 label='ui.debug log.files')
844 elif ctx.files() and self.ui.verbose:
844 elif ctx.files() and self.ui.verbose:
845 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
845 self.ui.write(_("files: %s\n") % " ".join(ctx.files()),
846 label='ui.note log.files')
846 label='ui.note log.files')
847 if copies and self.ui.verbose:
847 if copies and self.ui.verbose:
848 copies = ['%s (%s)' % c for c in copies]
848 copies = ['%s (%s)' % c for c in copies]
849 self.ui.write(_("copies: %s\n") % ' '.join(copies),
849 self.ui.write(_("copies: %s\n") % ' '.join(copies),
850 label='ui.note log.copies')
850 label='ui.note log.copies')
851
851
852 extra = ctx.extra()
852 extra = ctx.extra()
853 if extra and self.ui.debugflag:
853 if extra and self.ui.debugflag:
854 for key, value in sorted(extra.items()):
854 for key, value in sorted(extra.items()):
855 self.ui.write(_("extra: %s=%s\n")
855 self.ui.write(_("extra: %s=%s\n")
856 % (key, value.encode('string_escape')),
856 % (key, value.encode('string_escape')),
857 label='ui.debug log.extra')
857 label='ui.debug log.extra')
858
858
859 description = ctx.description().strip()
859 description = ctx.description().strip()
860 if description:
860 if description:
861 if self.ui.verbose:
861 if self.ui.verbose:
862 self.ui.write(_("description:\n"),
862 self.ui.write(_("description:\n"),
863 label='ui.note log.description')
863 label='ui.note log.description')
864 self.ui.write(description,
864 self.ui.write(description,
865 label='ui.note log.description')
865 label='ui.note log.description')
866 self.ui.write("\n\n")
866 self.ui.write("\n\n")
867 else:
867 else:
868 self.ui.write(_("summary: %s\n") %
868 self.ui.write(_("summary: %s\n") %
869 description.splitlines()[0],
869 description.splitlines()[0],
870 label='log.summary')
870 label='log.summary')
871 self.ui.write("\n")
871 self.ui.write("\n")
872
872
873 self.showpatch(changenode, matchfn)
873 self.showpatch(changenode, matchfn)
874
874
875 def showpatch(self, node, matchfn):
875 def showpatch(self, node, matchfn):
876 if not matchfn:
876 if not matchfn:
877 matchfn = self.patch
877 matchfn = self.patch
878 if matchfn:
878 if matchfn:
879 stat = self.diffopts.get('stat')
879 stat = self.diffopts.get('stat')
880 diff = self.diffopts.get('patch')
880 diff = self.diffopts.get('patch')
881 diffopts = patch.diffopts(self.ui, self.diffopts)
881 diffopts = patch.diffopts(self.ui, self.diffopts)
882 prev = self.repo.changelog.parents(node)[0]
882 prev = self.repo.changelog.parents(node)[0]
883 if stat:
883 if stat:
884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
884 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
885 match=matchfn, stat=True)
885 match=matchfn, stat=True)
886 if diff:
886 if diff:
887 if stat:
887 if stat:
888 self.ui.write("\n")
888 self.ui.write("\n")
889 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
889 diffordiffstat(self.ui, self.repo, diffopts, prev, node,
890 match=matchfn, stat=False)
890 match=matchfn, stat=False)
891 self.ui.write("\n")
891 self.ui.write("\n")
892
892
893 def _meaningful_parentrevs(self, log, rev):
893 def _meaningful_parentrevs(self, log, rev):
894 """Return list of meaningful (or all if debug) parentrevs for rev.
894 """Return list of meaningful (or all if debug) parentrevs for rev.
895
895
896 For merges (two non-nullrev revisions) both parents are meaningful.
896 For merges (two non-nullrev revisions) both parents are meaningful.
897 Otherwise the first parent revision is considered meaningful if it
897 Otherwise the first parent revision is considered meaningful if it
898 is not the preceding revision.
898 is not the preceding revision.
899 """
899 """
900 parents = log.parentrevs(rev)
900 parents = log.parentrevs(rev)
901 if not self.ui.debugflag and parents[1] == nullrev:
901 if not self.ui.debugflag and parents[1] == nullrev:
902 if parents[0] >= rev - 1:
902 if parents[0] >= rev - 1:
903 parents = []
903 parents = []
904 else:
904 else:
905 parents = [parents[0]]
905 parents = [parents[0]]
906 return parents
906 return parents
907
907
908
908
909 class changeset_templater(changeset_printer):
909 class changeset_templater(changeset_printer):
910 '''format changeset information.'''
910 '''format changeset information.'''
911
911
912 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
912 def __init__(self, ui, repo, patch, diffopts, mapfile, buffered):
913 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
913 changeset_printer.__init__(self, ui, repo, patch, diffopts, buffered)
914 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
914 formatnode = ui.debugflag and (lambda x: x) or (lambda x: x[:12])
915 defaulttempl = {
915 defaulttempl = {
916 'parent': '{rev}:{node|formatnode} ',
916 'parent': '{rev}:{node|formatnode} ',
917 'manifest': '{rev}:{node|formatnode}',
917 'manifest': '{rev}:{node|formatnode}',
918 'file_copy': '{name} ({source})',
918 'file_copy': '{name} ({source})',
919 'extra': '{key}={value|stringescape}'
919 'extra': '{key}={value|stringescape}'
920 }
920 }
921 # filecopy is preserved for compatibility reasons
921 # filecopy is preserved for compatibility reasons
922 defaulttempl['filecopy'] = defaulttempl['file_copy']
922 defaulttempl['filecopy'] = defaulttempl['file_copy']
923 self.t = templater.templater(mapfile, {'formatnode': formatnode},
923 self.t = templater.templater(mapfile, {'formatnode': formatnode},
924 cache=defaulttempl)
924 cache=defaulttempl)
925 self.cache = {}
925 self.cache = {}
926
926
927 def use_template(self, t):
927 def use_template(self, t):
928 '''set template string to use'''
928 '''set template string to use'''
929 self.t.cache['changeset'] = t
929 self.t.cache['changeset'] = t
930
930
931 def _meaningful_parentrevs(self, ctx):
931 def _meaningful_parentrevs(self, ctx):
932 """Return list of meaningful (or all if debug) parentrevs for rev.
932 """Return list of meaningful (or all if debug) parentrevs for rev.
933 """
933 """
934 parents = ctx.parents()
934 parents = ctx.parents()
935 if len(parents) > 1:
935 if len(parents) > 1:
936 return parents
936 return parents
937 if self.ui.debugflag:
937 if self.ui.debugflag:
938 return [parents[0], self.repo['null']]
938 return [parents[0], self.repo['null']]
939 if parents[0].rev() >= ctx.rev() - 1:
939 if parents[0].rev() >= ctx.rev() - 1:
940 return []
940 return []
941 return parents
941 return parents
942
942
943 def _show(self, ctx, copies, matchfn, props):
943 def _show(self, ctx, copies, matchfn, props):
944 '''show a single changeset or file revision'''
944 '''show a single changeset or file revision'''
945
945
946 showlist = templatekw.showlist
946 showlist = templatekw.showlist
947
947
948 # showparents() behaviour depends on ui trace level which
948 # showparents() behaviour depends on ui trace level which
949 # causes unexpected behaviours at templating level and makes
949 # causes unexpected behaviours at templating level and makes
950 # it harder to extract it in a standalone function. Its
950 # it harder to extract it in a standalone function. Its
951 # behaviour cannot be changed so leave it here for now.
951 # behaviour cannot be changed so leave it here for now.
952 def showparents(**args):
952 def showparents(**args):
953 ctx = args['ctx']
953 ctx = args['ctx']
954 parents = [[('rev', p.rev()), ('node', p.hex())]
954 parents = [[('rev', p.rev()), ('node', p.hex())]
955 for p in self._meaningful_parentrevs(ctx)]
955 for p in self._meaningful_parentrevs(ctx)]
956 return showlist('parent', parents, **args)
956 return showlist('parent', parents, **args)
957
957
958 props = props.copy()
958 props = props.copy()
959 props.update(templatekw.keywords)
959 props.update(templatekw.keywords)
960 props['parents'] = showparents
960 props['parents'] = showparents
961 props['templ'] = self.t
961 props['templ'] = self.t
962 props['ctx'] = ctx
962 props['ctx'] = ctx
963 props['repo'] = self.repo
963 props['repo'] = self.repo
964 props['revcache'] = {'copies': copies}
964 props['revcache'] = {'copies': copies}
965 props['cache'] = self.cache
965 props['cache'] = self.cache
966
966
967 # find correct templates for current mode
967 # find correct templates for current mode
968
968
969 tmplmodes = [
969 tmplmodes = [
970 (True, None),
970 (True, None),
971 (self.ui.verbose, 'verbose'),
971 (self.ui.verbose, 'verbose'),
972 (self.ui.quiet, 'quiet'),
972 (self.ui.quiet, 'quiet'),
973 (self.ui.debugflag, 'debug'),
973 (self.ui.debugflag, 'debug'),
974 ]
974 ]
975
975
976 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
976 types = {'header': '', 'footer':'', 'changeset': 'changeset'}
977 for mode, postfix in tmplmodes:
977 for mode, postfix in tmplmodes:
978 for type in types:
978 for type in types:
979 cur = postfix and ('%s_%s' % (type, postfix)) or type
979 cur = postfix and ('%s_%s' % (type, postfix)) or type
980 if mode and cur in self.t:
980 if mode and cur in self.t:
981 types[type] = cur
981 types[type] = cur
982
982
983 try:
983 try:
984
984
985 # write header
985 # write header
986 if types['header']:
986 if types['header']:
987 h = templater.stringify(self.t(types['header'], **props))
987 h = templater.stringify(self.t(types['header'], **props))
988 if self.buffered:
988 if self.buffered:
989 self.header[ctx.rev()] = h
989 self.header[ctx.rev()] = h
990 else:
990 else:
991 if self.lastheader != h:
991 if self.lastheader != h:
992 self.lastheader = h
992 self.lastheader = h
993 self.ui.write(h)
993 self.ui.write(h)
994
994
995 # write changeset metadata, then patch if requested
995 # write changeset metadata, then patch if requested
996 key = types['changeset']
996 key = types['changeset']
997 self.ui.write(templater.stringify(self.t(key, **props)))
997 self.ui.write(templater.stringify(self.t(key, **props)))
998 self.showpatch(ctx.node(), matchfn)
998 self.showpatch(ctx.node(), matchfn)
999
999
1000 if types['footer']:
1000 if types['footer']:
1001 if not self.footer:
1001 if not self.footer:
1002 self.footer = templater.stringify(self.t(types['footer'],
1002 self.footer = templater.stringify(self.t(types['footer'],
1003 **props))
1003 **props))
1004
1004
1005 except KeyError, inst:
1005 except KeyError, inst:
1006 msg = _("%s: no key named '%s'")
1006 msg = _("%s: no key named '%s'")
1007 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1007 raise util.Abort(msg % (self.t.mapfile, inst.args[0]))
1008 except SyntaxError, inst:
1008 except SyntaxError, inst:
1009 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1009 raise util.Abort('%s: %s' % (self.t.mapfile, inst.args[0]))
1010
1010
1011 def show_changeset(ui, repo, opts, buffered=False):
1011 def show_changeset(ui, repo, opts, buffered=False):
1012 """show one changeset using template or regular display.
1012 """show one changeset using template or regular display.
1013
1013
1014 Display format will be the first non-empty hit of:
1014 Display format will be the first non-empty hit of:
1015 1. option 'template'
1015 1. option 'template'
1016 2. option 'style'
1016 2. option 'style'
1017 3. [ui] setting 'logtemplate'
1017 3. [ui] setting 'logtemplate'
1018 4. [ui] setting 'style'
1018 4. [ui] setting 'style'
1019 If all of these values are either the unset or the empty string,
1019 If all of these values are either the unset or the empty string,
1020 regular display via changeset_printer() is done.
1020 regular display via changeset_printer() is done.
1021 """
1021 """
1022 # options
1022 # options
1023 patch = False
1023 patch = False
1024 if opts.get('patch') or opts.get('stat'):
1024 if opts.get('patch') or opts.get('stat'):
1025 patch = matchall(repo)
1025 patch = matchall(repo)
1026
1026
1027 tmpl = opts.get('template')
1027 tmpl = opts.get('template')
1028 style = None
1028 style = None
1029 if tmpl:
1029 if tmpl:
1030 tmpl = templater.parsestring(tmpl, quoted=False)
1030 tmpl = templater.parsestring(tmpl, quoted=False)
1031 else:
1031 else:
1032 style = opts.get('style')
1032 style = opts.get('style')
1033
1033
1034 # ui settings
1034 # ui settings
1035 if not (tmpl or style):
1035 if not (tmpl or style):
1036 tmpl = ui.config('ui', 'logtemplate')
1036 tmpl = ui.config('ui', 'logtemplate')
1037 if tmpl:
1037 if tmpl:
1038 tmpl = templater.parsestring(tmpl)
1038 tmpl = templater.parsestring(tmpl)
1039 else:
1039 else:
1040 style = util.expandpath(ui.config('ui', 'style', ''))
1040 style = util.expandpath(ui.config('ui', 'style', ''))
1041
1041
1042 if not (tmpl or style):
1042 if not (tmpl or style):
1043 return changeset_printer(ui, repo, patch, opts, buffered)
1043 return changeset_printer(ui, repo, patch, opts, buffered)
1044
1044
1045 mapfile = None
1045 mapfile = None
1046 if style and not tmpl:
1046 if style and not tmpl:
1047 mapfile = style
1047 mapfile = style
1048 if not os.path.split(mapfile)[0]:
1048 if not os.path.split(mapfile)[0]:
1049 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1049 mapname = (templater.templatepath('map-cmdline.' + mapfile)
1050 or templater.templatepath(mapfile))
1050 or templater.templatepath(mapfile))
1051 if mapname:
1051 if mapname:
1052 mapfile = mapname
1052 mapfile = mapname
1053
1053
1054 try:
1054 try:
1055 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1055 t = changeset_templater(ui, repo, patch, opts, mapfile, buffered)
1056 except SyntaxError, inst:
1056 except SyntaxError, inst:
1057 raise util.Abort(inst.args[0])
1057 raise util.Abort(inst.args[0])
1058 if tmpl:
1058 if tmpl:
1059 t.use_template(tmpl)
1059 t.use_template(tmpl)
1060 return t
1060 return t
1061
1061
1062 def finddate(ui, repo, date):
1062 def finddate(ui, repo, date):
1063 """Find the tipmost changeset that matches the given date spec"""
1063 """Find the tipmost changeset that matches the given date spec"""
1064
1064
1065 df = util.matchdate(date)
1065 df = util.matchdate(date)
1066 m = matchall(repo)
1066 m = matchall(repo)
1067 results = {}
1067 results = {}
1068
1068
1069 def prep(ctx, fns):
1069 def prep(ctx, fns):
1070 d = ctx.date()
1070 d = ctx.date()
1071 if df(d[0]):
1071 if df(d[0]):
1072 results[ctx.rev()] = d
1072 results[ctx.rev()] = d
1073
1073
1074 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1074 for ctx in walkchangerevs(repo, m, {'rev': None}, prep):
1075 rev = ctx.rev()
1075 rev = ctx.rev()
1076 if rev in results:
1076 if rev in results:
1077 ui.status(_("Found revision %s from %s\n") %
1077 ui.status(_("Found revision %s from %s\n") %
1078 (rev, util.datestr(results[rev])))
1078 (rev, util.datestr(results[rev])))
1079 return str(rev)
1079 return str(rev)
1080
1080
1081 raise util.Abort(_("revision matching date not found"))
1081 raise util.Abort(_("revision matching date not found"))
1082
1082
1083 def walkchangerevs(repo, match, opts, prepare):
1083 def walkchangerevs(repo, match, opts, prepare):
1084 '''Iterate over files and the revs in which they changed.
1084 '''Iterate over files and the revs in which they changed.
1085
1085
1086 Callers most commonly need to iterate backwards over the history
1086 Callers most commonly need to iterate backwards over the history
1087 in which they are interested. Doing so has awful (quadratic-looking)
1087 in which they are interested. Doing so has awful (quadratic-looking)
1088 performance, so we use iterators in a "windowed" way.
1088 performance, so we use iterators in a "windowed" way.
1089
1089
1090 We walk a window of revisions in the desired order. Within the
1090 We walk a window of revisions in the desired order. Within the
1091 window, we first walk forwards to gather data, then in the desired
1091 window, we first walk forwards to gather data, then in the desired
1092 order (usually backwards) to display it.
1092 order (usually backwards) to display it.
1093
1093
1094 This function returns an iterator yielding contexts. Before
1094 This function returns an iterator yielding contexts. Before
1095 yielding each context, the iterator will first call the prepare
1095 yielding each context, the iterator will first call the prepare
1096 function on each context in the window in forward order.'''
1096 function on each context in the window in forward order.'''
1097
1097
1098 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1098 def increasing_windows(start, end, windowsize=8, sizelimit=512):
1099 if start < end:
1099 if start < end:
1100 while start < end:
1100 while start < end:
1101 yield start, min(windowsize, end - start)
1101 yield start, min(windowsize, end - start)
1102 start += windowsize
1102 start += windowsize
1103 if windowsize < sizelimit:
1103 if windowsize < sizelimit:
1104 windowsize *= 2
1104 windowsize *= 2
1105 else:
1105 else:
1106 while start > end:
1106 while start > end:
1107 yield start, min(windowsize, start - end - 1)
1107 yield start, min(windowsize, start - end - 1)
1108 start -= windowsize
1108 start -= windowsize
1109 if windowsize < sizelimit:
1109 if windowsize < sizelimit:
1110 windowsize *= 2
1110 windowsize *= 2
1111
1111
1112 follow = opts.get('follow') or opts.get('follow_first')
1112 follow = opts.get('follow') or opts.get('follow_first')
1113
1113
1114 if not len(repo):
1114 if not len(repo):
1115 return []
1115 return []
1116
1116
1117 if follow:
1117 if follow:
1118 defrange = '%s:0' % repo['.'].rev()
1118 defrange = '%s:0' % repo['.'].rev()
1119 else:
1119 else:
1120 defrange = '-1:0'
1120 defrange = '-1:0'
1121 revs = revrange(repo, opts['rev'] or [defrange])
1121 revs = revrange(repo, opts['rev'] or [defrange])
1122 if not revs:
1122 if not revs:
1123 return []
1123 return []
1124 wanted = set()
1124 wanted = set()
1125 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1125 slowpath = match.anypats() or (match.files() and opts.get('removed'))
1126 fncache = {}
1126 fncache = {}
1127 change = util.cachefunc(repo.changectx)
1127 change = util.cachefunc(repo.changectx)
1128
1128
1129 # First step is to fill wanted, the set of revisions that we want to yield.
1129 # First step is to fill wanted, the set of revisions that we want to yield.
1130 # When it does not induce extra cost, we also fill fncache for revisions in
1130 # When it does not induce extra cost, we also fill fncache for revisions in
1131 # wanted: a cache of filenames that were changed (ctx.files()) and that
1131 # wanted: a cache of filenames that were changed (ctx.files()) and that
1132 # match the file filtering conditions.
1132 # match the file filtering conditions.
1133
1133
1134 if not slowpath and not match.files():
1134 if not slowpath and not match.files():
1135 # No files, no patterns. Display all revs.
1135 # No files, no patterns. Display all revs.
1136 wanted = set(revs)
1136 wanted = set(revs)
1137 copies = []
1137 copies = []
1138
1138
1139 if not slowpath:
1139 if not slowpath:
1140 # We only have to read through the filelog to find wanted revisions
1140 # We only have to read through the filelog to find wanted revisions
1141
1141
1142 minrev, maxrev = min(revs), max(revs)
1142 minrev, maxrev = min(revs), max(revs)
1143 def filerevgen(filelog, last):
1143 def filerevgen(filelog, last):
1144 """
1144 """
1145 Only files, no patterns. Check the history of each file.
1145 Only files, no patterns. Check the history of each file.
1146
1146
1147 Examines filelog entries within minrev, maxrev linkrev range
1147 Examines filelog entries within minrev, maxrev linkrev range
1148 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1148 Returns an iterator yielding (linkrev, parentlinkrevs, copied)
1149 tuples in backwards order
1149 tuples in backwards order
1150 """
1150 """
1151 cl_count = len(repo)
1151 cl_count = len(repo)
1152 revs = []
1152 revs = []
1153 for j in xrange(0, last + 1):
1153 for j in xrange(0, last + 1):
1154 linkrev = filelog.linkrev(j)
1154 linkrev = filelog.linkrev(j)
1155 if linkrev < minrev:
1155 if linkrev < minrev:
1156 continue
1156 continue
1157 # only yield rev for which we have the changelog, it can
1157 # only yield rev for which we have the changelog, it can
1158 # happen while doing "hg log" during a pull or commit
1158 # happen while doing "hg log" during a pull or commit
1159 if linkrev >= cl_count:
1159 if linkrev >= cl_count:
1160 break
1160 break
1161
1161
1162 parentlinkrevs = []
1162 parentlinkrevs = []
1163 for p in filelog.parentrevs(j):
1163 for p in filelog.parentrevs(j):
1164 if p != nullrev:
1164 if p != nullrev:
1165 parentlinkrevs.append(filelog.linkrev(p))
1165 parentlinkrevs.append(filelog.linkrev(p))
1166 n = filelog.node(j)
1166 n = filelog.node(j)
1167 revs.append((linkrev, parentlinkrevs,
1167 revs.append((linkrev, parentlinkrevs,
1168 follow and filelog.renamed(n)))
1168 follow and filelog.renamed(n)))
1169
1169
1170 return reversed(revs)
1170 return reversed(revs)
1171 def iterfiles():
1171 def iterfiles():
1172 for filename in match.files():
1172 for filename in match.files():
1173 yield filename, None
1173 yield filename, None
1174 for filename_node in copies:
1174 for filename_node in copies:
1175 yield filename_node
1175 yield filename_node
1176 for file_, node in iterfiles():
1176 for file_, node in iterfiles():
1177 filelog = repo.file(file_)
1177 filelog = repo.file(file_)
1178 if not len(filelog):
1178 if not len(filelog):
1179 if node is None:
1179 if node is None:
1180 # A zero count may be a directory or deleted file, so
1180 # A zero count may be a directory or deleted file, so
1181 # try to find matching entries on the slow path.
1181 # try to find matching entries on the slow path.
1182 if follow:
1182 if follow:
1183 raise util.Abort(
1183 raise util.Abort(
1184 _('cannot follow nonexistent file: "%s"') % file_)
1184 _('cannot follow nonexistent file: "%s"') % file_)
1185 slowpath = True
1185 slowpath = True
1186 break
1186 break
1187 else:
1187 else:
1188 continue
1188 continue
1189
1189
1190 if node is None:
1190 if node is None:
1191 last = len(filelog) - 1
1191 last = len(filelog) - 1
1192 else:
1192 else:
1193 last = filelog.rev(node)
1193 last = filelog.rev(node)
1194
1194
1195
1195
1196 # keep track of all ancestors of the file
1196 # keep track of all ancestors of the file
1197 ancestors = set([filelog.linkrev(last)])
1197 ancestors = set([filelog.linkrev(last)])
1198
1198
1199 # iterate from latest to oldest revision
1199 # iterate from latest to oldest revision
1200 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1200 for rev, flparentlinkrevs, copied in filerevgen(filelog, last):
1201 if not follow:
1201 if not follow:
1202 if rev > maxrev:
1202 if rev > maxrev:
1203 continue
1203 continue
1204 else:
1204 else:
1205 # Note that last might not be the first interesting
1205 # Note that last might not be the first interesting
1206 # rev to us:
1206 # rev to us:
1207 # if the file has been changed after maxrev, we'll
1207 # if the file has been changed after maxrev, we'll
1208 # have linkrev(last) > maxrev, and we still need
1208 # have linkrev(last) > maxrev, and we still need
1209 # to explore the file graph
1209 # to explore the file graph
1210 if rev not in ancestors:
1210 if rev not in ancestors:
1211 continue
1211 continue
1212 # XXX insert 1327 fix here
1212 # XXX insert 1327 fix here
1213 if flparentlinkrevs:
1213 if flparentlinkrevs:
1214 ancestors.update(flparentlinkrevs)
1214 ancestors.update(flparentlinkrevs)
1215
1215
1216 fncache.setdefault(rev, []).append(file_)
1216 fncache.setdefault(rev, []).append(file_)
1217 wanted.add(rev)
1217 wanted.add(rev)
1218 if copied:
1218 if copied:
1219 copies.append(copied)
1219 copies.append(copied)
1220 if slowpath:
1220 if slowpath:
1221 # We have to read the changelog to match filenames against
1221 # We have to read the changelog to match filenames against
1222 # changed files
1222 # changed files
1223
1223
1224 if follow:
1224 if follow:
1225 raise util.Abort(_('can only follow copies/renames for explicit '
1225 raise util.Abort(_('can only follow copies/renames for explicit '
1226 'filenames'))
1226 'filenames'))
1227
1227
1228 # The slow path checks files modified in every changeset.
1228 # The slow path checks files modified in every changeset.
1229 for i in sorted(revs):
1229 for i in sorted(revs):
1230 ctx = change(i)
1230 ctx = change(i)
1231 matches = filter(match, ctx.files())
1231 matches = filter(match, ctx.files())
1232 if matches:
1232 if matches:
1233 fncache[i] = matches
1233 fncache[i] = matches
1234 wanted.add(i)
1234 wanted.add(i)
1235
1235
1236 class followfilter(object):
1236 class followfilter(object):
1237 def __init__(self, onlyfirst=False):
1237 def __init__(self, onlyfirst=False):
1238 self.startrev = nullrev
1238 self.startrev = nullrev
1239 self.roots = set()
1239 self.roots = set()
1240 self.onlyfirst = onlyfirst
1240 self.onlyfirst = onlyfirst
1241
1241
1242 def match(self, rev):
1242 def match(self, rev):
1243 def realparents(rev):
1243 def realparents(rev):
1244 if self.onlyfirst:
1244 if self.onlyfirst:
1245 return repo.changelog.parentrevs(rev)[0:1]
1245 return repo.changelog.parentrevs(rev)[0:1]
1246 else:
1246 else:
1247 return filter(lambda x: x != nullrev,
1247 return filter(lambda x: x != nullrev,
1248 repo.changelog.parentrevs(rev))
1248 repo.changelog.parentrevs(rev))
1249
1249
1250 if self.startrev == nullrev:
1250 if self.startrev == nullrev:
1251 self.startrev = rev
1251 self.startrev = rev
1252 return True
1252 return True
1253
1253
1254 if rev > self.startrev:
1254 if rev > self.startrev:
1255 # forward: all descendants
1255 # forward: all descendants
1256 if not self.roots:
1256 if not self.roots:
1257 self.roots.add(self.startrev)
1257 self.roots.add(self.startrev)
1258 for parent in realparents(rev):
1258 for parent in realparents(rev):
1259 if parent in self.roots:
1259 if parent in self.roots:
1260 self.roots.add(rev)
1260 self.roots.add(rev)
1261 return True
1261 return True
1262 else:
1262 else:
1263 # backwards: all parents
1263 # backwards: all parents
1264 if not self.roots:
1264 if not self.roots:
1265 self.roots.update(realparents(self.startrev))
1265 self.roots.update(realparents(self.startrev))
1266 if rev in self.roots:
1266 if rev in self.roots:
1267 self.roots.remove(rev)
1267 self.roots.remove(rev)
1268 self.roots.update(realparents(rev))
1268 self.roots.update(realparents(rev))
1269 return True
1269 return True
1270
1270
1271 return False
1271 return False
1272
1272
1273 # it might be worthwhile to do this in the iterator if the rev range
1273 # it might be worthwhile to do this in the iterator if the rev range
1274 # is descending and the prune args are all within that range
1274 # is descending and the prune args are all within that range
1275 for rev in opts.get('prune', ()):
1275 for rev in opts.get('prune', ()):
1276 rev = repo.changelog.rev(repo.lookup(rev))
1276 rev = repo.changelog.rev(repo.lookup(rev))
1277 ff = followfilter()
1277 ff = followfilter()
1278 stop = min(revs[0], revs[-1])
1278 stop = min(revs[0], revs[-1])
1279 for x in xrange(rev, stop - 1, -1):
1279 for x in xrange(rev, stop - 1, -1):
1280 if ff.match(x):
1280 if ff.match(x):
1281 wanted.discard(x)
1281 wanted.discard(x)
1282
1282
1283 # Now that wanted is correctly initialized, we can iterate over the
1283 # Now that wanted is correctly initialized, we can iterate over the
1284 # revision range, yielding only revisions in wanted.
1284 # revision range, yielding only revisions in wanted.
1285 def iterate():
1285 def iterate():
1286 if follow and not match.files():
1286 if follow and not match.files():
1287 ff = followfilter(onlyfirst=opts.get('follow_first'))
1287 ff = followfilter(onlyfirst=opts.get('follow_first'))
1288 def want(rev):
1288 def want(rev):
1289 return ff.match(rev) and rev in wanted
1289 return ff.match(rev) and rev in wanted
1290 else:
1290 else:
1291 def want(rev):
1291 def want(rev):
1292 return rev in wanted
1292 return rev in wanted
1293
1293
1294 for i, window in increasing_windows(0, len(revs)):
1294 for i, window in increasing_windows(0, len(revs)):
1295 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1295 nrevs = [rev for rev in revs[i:i + window] if want(rev)]
1296 for rev in sorted(nrevs):
1296 for rev in sorted(nrevs):
1297 fns = fncache.get(rev)
1297 fns = fncache.get(rev)
1298 ctx = change(rev)
1298 ctx = change(rev)
1299 if not fns:
1299 if not fns:
1300 def fns_generator():
1300 def fns_generator():
1301 for f in ctx.files():
1301 for f in ctx.files():
1302 if match(f):
1302 if match(f):
1303 yield f
1303 yield f
1304 fns = fns_generator()
1304 fns = fns_generator()
1305 prepare(ctx, fns)
1305 prepare(ctx, fns)
1306 for rev in nrevs:
1306 for rev in nrevs:
1307 yield change(rev)
1307 yield change(rev)
1308 return iterate()
1308 return iterate()
1309
1309
1310 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1310 def add(ui, repo, match, dryrun, listsubrepos, prefix):
1311 join = lambda f: os.path.join(prefix, f)
1311 join = lambda f: os.path.join(prefix, f)
1312 bad = []
1312 bad = []
1313 oldbad = match.bad
1313 oldbad = match.bad
1314 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1314 match.bad = lambda x, y: bad.append(x) or oldbad(x, y)
1315 names = []
1315 names = []
1316 wctx = repo[None]
1316 wctx = repo[None]
1317 wctx.status(clean=True)
1318 existing = None
1319 if scmutil.showportabilityalert(ui):
1320 existing = dict([(fn.lower(), fn) for fn in
1321 wctx.added() + wctx.clean() + wctx.modified()])
1317 for f in repo.walk(match):
1322 for f in repo.walk(match):
1318 exact = match.exact(f)
1323 exact = match.exact(f)
1319 if exact or f not in repo.dirstate:
1324 if exact or f not in repo.dirstate:
1325 if existing:
1326 scmutil.checkcasecollision(ui, f, existing)
1320 names.append(f)
1327 names.append(f)
1321 if ui.verbose or not exact:
1328 if ui.verbose or not exact:
1322 ui.status(_('adding %s\n') % match.rel(join(f)))
1329 ui.status(_('adding %s\n') % match.rel(join(f)))
1323
1330
1324 if listsubrepos:
1331 if listsubrepos:
1325 for subpath in wctx.substate:
1332 for subpath in wctx.substate:
1326 sub = wctx.sub(subpath)
1333 sub = wctx.sub(subpath)
1327 try:
1334 try:
1328 submatch = matchmod.narrowmatcher(subpath, match)
1335 submatch = matchmod.narrowmatcher(subpath, match)
1329 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1336 bad.extend(sub.add(ui, submatch, dryrun, prefix))
1330 except error.LookupError:
1337 except error.LookupError:
1331 ui.status(_("skipping missing subrepository: %s\n")
1338 ui.status(_("skipping missing subrepository: %s\n")
1332 % join(subpath))
1339 % join(subpath))
1333
1340
1334 if not dryrun:
1341 if not dryrun:
1335 rejected = wctx.add(names, prefix)
1342 rejected = wctx.add(names, prefix)
1336 bad.extend(f for f in rejected if f in match.files())
1343 bad.extend(f for f in rejected if f in match.files())
1337 return bad
1344 return bad
1338
1345
1339 def commit(ui, repo, commitfunc, pats, opts):
1346 def commit(ui, repo, commitfunc, pats, opts):
1340 '''commit the specified files or all outstanding changes'''
1347 '''commit the specified files or all outstanding changes'''
1341 date = opts.get('date')
1348 date = opts.get('date')
1342 if date:
1349 if date:
1343 opts['date'] = util.parsedate(date)
1350 opts['date'] = util.parsedate(date)
1344 message = logmessage(opts)
1351 message = logmessage(opts)
1345
1352
1346 # extract addremove carefully -- this function can be called from a command
1353 # extract addremove carefully -- this function can be called from a command
1347 # that doesn't support addremove
1354 # that doesn't support addremove
1348 if opts.get('addremove'):
1355 if opts.get('addremove'):
1349 addremove(repo, pats, opts)
1356 addremove(repo, pats, opts)
1350
1357
1351 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1358 return commitfunc(ui, repo, message, match(repo, pats, opts), opts)
1352
1359
1353 def commiteditor(repo, ctx, subs):
1360 def commiteditor(repo, ctx, subs):
1354 if ctx.description():
1361 if ctx.description():
1355 return ctx.description()
1362 return ctx.description()
1356 return commitforceeditor(repo, ctx, subs)
1363 return commitforceeditor(repo, ctx, subs)
1357
1364
1358 def commitforceeditor(repo, ctx, subs):
1365 def commitforceeditor(repo, ctx, subs):
1359 edittext = []
1366 edittext = []
1360 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1367 modified, added, removed = ctx.modified(), ctx.added(), ctx.removed()
1361 if ctx.description():
1368 if ctx.description():
1362 edittext.append(ctx.description())
1369 edittext.append(ctx.description())
1363 edittext.append("")
1370 edittext.append("")
1364 edittext.append("") # Empty line between message and comments.
1371 edittext.append("") # Empty line between message and comments.
1365 edittext.append(_("HG: Enter commit message."
1372 edittext.append(_("HG: Enter commit message."
1366 " Lines beginning with 'HG:' are removed."))
1373 " Lines beginning with 'HG:' are removed."))
1367 edittext.append(_("HG: Leave message empty to abort commit."))
1374 edittext.append(_("HG: Leave message empty to abort commit."))
1368 edittext.append("HG: --")
1375 edittext.append("HG: --")
1369 edittext.append(_("HG: user: %s") % ctx.user())
1376 edittext.append(_("HG: user: %s") % ctx.user())
1370 if ctx.p2():
1377 if ctx.p2():
1371 edittext.append(_("HG: branch merge"))
1378 edittext.append(_("HG: branch merge"))
1372 if ctx.branch():
1379 if ctx.branch():
1373 edittext.append(_("HG: branch '%s'") % ctx.branch())
1380 edittext.append(_("HG: branch '%s'") % ctx.branch())
1374 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1381 edittext.extend([_("HG: subrepo %s") % s for s in subs])
1375 edittext.extend([_("HG: added %s") % f for f in added])
1382 edittext.extend([_("HG: added %s") % f for f in added])
1376 edittext.extend([_("HG: changed %s") % f for f in modified])
1383 edittext.extend([_("HG: changed %s") % f for f in modified])
1377 edittext.extend([_("HG: removed %s") % f for f in removed])
1384 edittext.extend([_("HG: removed %s") % f for f in removed])
1378 if not added and not modified and not removed:
1385 if not added and not modified and not removed:
1379 edittext.append(_("HG: no files changed"))
1386 edittext.append(_("HG: no files changed"))
1380 edittext.append("")
1387 edittext.append("")
1381 # run editor in the repository root
1388 # run editor in the repository root
1382 olddir = os.getcwd()
1389 olddir = os.getcwd()
1383 os.chdir(repo.root)
1390 os.chdir(repo.root)
1384 text = repo.ui.edit("\n".join(edittext), ctx.user())
1391 text = repo.ui.edit("\n".join(edittext), ctx.user())
1385 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1392 text = re.sub("(?m)^HG:.*(\n|$)", "", text)
1386 os.chdir(olddir)
1393 os.chdir(olddir)
1387
1394
1388 if not text.strip():
1395 if not text.strip():
1389 raise util.Abort(_("empty commit message"))
1396 raise util.Abort(_("empty commit message"))
1390
1397
1391 return text
1398 return text
@@ -1,419 +1,424 b''
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
9 import util, error, osutil
10 import os, errno, stat, sys
10 import os, errno, stat, sys
11
11
12 def checkfilename(f):
12 def checkfilename(f):
13 '''Check that the filename f is an acceptable filename for a tracked file'''
13 '''Check that the filename f is an acceptable filename for a tracked file'''
14 if '\r' in f or '\n' in f:
14 if '\r' in f or '\n' in f:
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
15 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames: %r") % f)
16
16
17 def checkportable(ui, f):
17 def checkportable(ui, f):
18 '''Check if filename f is portable and warn or abort depending on config'''
18 '''Check if filename f is portable and warn or abort depending on config'''
19 checkfilename(f)
19 checkfilename(f)
20 if showportabilityalert(ui):
20 if showportabilityalert(ui):
21 msg = util.checkwinfilename(f)
21 msg = util.checkwinfilename(f)
22 if msg:
22 if msg:
23 portabilityalert(ui, "%s: %r" % (msg, f))
23 portabilityalert(ui, "%s: %r" % (msg, f))
24
24
25 def checkcasecollision(ui, f, files):
26 if f.lower() in files and files[f.lower()] != f:
27 portabilityalert(ui, _('possible case-folding collision for %s') % f)
28 files[f.lower()] = f
29
25 def checkportabilityalert(ui):
30 def checkportabilityalert(ui):
26 '''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
27 non-portable filenames'''
32 non-portable filenames'''
28 val = ui.config('ui', 'portablefilenames', 'warn')
33 val = ui.config('ui', 'portablefilenames', 'warn')
29 lval = val.lower()
34 lval = val.lower()
30 bval = util.parsebool(val)
35 bval = util.parsebool(val)
31 abort = os.name == 'nt' or lval == 'abort'
36 abort = os.name == 'nt' or lval == 'abort'
32 warn = bval or lval == 'warn'
37 warn = bval or lval == 'warn'
33 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'):
34 raise error.ConfigError(
39 raise error.ConfigError(
35 _("ui.portablefilenames value is invalid ('%s')") % val)
40 _("ui.portablefilenames value is invalid ('%s')") % val)
36 return abort, warn
41 return abort, warn
37
42
38 def showportabilityalert(ui):
43 def showportabilityalert(ui):
39 '''check if the user wants any notification of portability problems'''
44 '''check if the user wants any notification of portability problems'''
40 abort, warn = checkportabilityalert(ui)
45 abort, warn = checkportabilityalert(ui)
41 return abort or warn
46 return abort or warn
42
47
43 def portabilityalert(ui, msg):
48 def portabilityalert(ui, msg):
44 if not msg:
49 if not msg:
45 return
50 return
46 abort, warn = checkportabilityalert(ui)
51 abort, warn = checkportabilityalert(ui)
47 if abort:
52 if abort:
48 raise util.Abort("%s" % msg)
53 raise util.Abort("%s" % msg)
49 elif warn:
54 elif warn:
50 ui.warn(_("warning: %s\n") % msg)
55 ui.warn(_("warning: %s\n") % msg)
51
56
52 class path_auditor(object):
57 class path_auditor(object):
53 '''ensure that a filesystem path contains no banned components.
58 '''ensure that a filesystem path contains no banned components.
54 the following properties of a path are checked:
59 the following properties of a path are checked:
55
60
56 - ends with a directory separator
61 - ends with a directory separator
57 - under top-level .hg
62 - under top-level .hg
58 - starts at the root of a windows drive
63 - starts at the root of a windows drive
59 - contains ".."
64 - contains ".."
60 - traverses a symlink (e.g. a/symlink_here/b)
65 - traverses a symlink (e.g. a/symlink_here/b)
61 - inside a nested repository (a callback can be used to approve
66 - inside a nested repository (a callback can be used to approve
62 some nested repositories, e.g., subrepositories)
67 some nested repositories, e.g., subrepositories)
63 '''
68 '''
64
69
65 def __init__(self, root, callback=None):
70 def __init__(self, root, callback=None):
66 self.audited = set()
71 self.audited = set()
67 self.auditeddir = set()
72 self.auditeddir = set()
68 self.root = root
73 self.root = root
69 self.callback = callback
74 self.callback = callback
70
75
71 def __call__(self, path):
76 def __call__(self, path):
72 '''Check the relative path.
77 '''Check the relative path.
73 path may contain a pattern (e.g. foodir/**.txt)'''
78 path may contain a pattern (e.g. foodir/**.txt)'''
74
79
75 if path in self.audited:
80 if path in self.audited:
76 return
81 return
77 # AIX ignores "/" at end of path, others raise EISDIR.
82 # AIX ignores "/" at end of path, others raise EISDIR.
78 if util.endswithsep(path):
83 if util.endswithsep(path):
79 raise util.Abort(_("path ends in directory separator: %s") % path)
84 raise util.Abort(_("path ends in directory separator: %s") % path)
80 normpath = os.path.normcase(path)
85 normpath = os.path.normcase(path)
81 parts = util.splitpath(normpath)
86 parts = util.splitpath(normpath)
82 if (os.path.splitdrive(path)[0]
87 if (os.path.splitdrive(path)[0]
83 or parts[0].lower() in ('.hg', '.hg.', '')
88 or parts[0].lower() in ('.hg', '.hg.', '')
84 or os.pardir in parts):
89 or os.pardir in parts):
85 raise util.Abort(_("path contains illegal component: %s") % path)
90 raise util.Abort(_("path contains illegal component: %s") % path)
86 if '.hg' in path.lower():
91 if '.hg' in path.lower():
87 lparts = [p.lower() for p in parts]
92 lparts = [p.lower() for p in parts]
88 for p in '.hg', '.hg.':
93 for p in '.hg', '.hg.':
89 if p in lparts[1:]:
94 if p in lparts[1:]:
90 pos = lparts.index(p)
95 pos = lparts.index(p)
91 base = os.path.join(*parts[:pos])
96 base = os.path.join(*parts[:pos])
92 raise util.Abort(_('path %r is inside nested repo %r')
97 raise util.Abort(_('path %r is inside nested repo %r')
93 % (path, base))
98 % (path, base))
94
99
95 parts.pop()
100 parts.pop()
96 prefixes = []
101 prefixes = []
97 while parts:
102 while parts:
98 prefix = os.sep.join(parts)
103 prefix = os.sep.join(parts)
99 if prefix in self.auditeddir:
104 if prefix in self.auditeddir:
100 break
105 break
101 curpath = os.path.join(self.root, prefix)
106 curpath = os.path.join(self.root, prefix)
102 try:
107 try:
103 st = os.lstat(curpath)
108 st = os.lstat(curpath)
104 except OSError, err:
109 except OSError, err:
105 # EINVAL can be raised as invalid path syntax under win32.
110 # EINVAL can be raised as invalid path syntax under win32.
106 # They must be ignored for patterns can be checked too.
111 # They must be ignored for patterns can be checked too.
107 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
112 if err.errno not in (errno.ENOENT, errno.ENOTDIR, errno.EINVAL):
108 raise
113 raise
109 else:
114 else:
110 if stat.S_ISLNK(st.st_mode):
115 if stat.S_ISLNK(st.st_mode):
111 raise util.Abort(
116 raise util.Abort(
112 _('path %r traverses symbolic link %r')
117 _('path %r traverses symbolic link %r')
113 % (path, prefix))
118 % (path, prefix))
114 elif (stat.S_ISDIR(st.st_mode) and
119 elif (stat.S_ISDIR(st.st_mode) and
115 os.path.isdir(os.path.join(curpath, '.hg'))):
120 os.path.isdir(os.path.join(curpath, '.hg'))):
116 if not self.callback or not self.callback(curpath):
121 if not self.callback or not self.callback(curpath):
117 raise util.Abort(_('path %r is inside nested repo %r') %
122 raise util.Abort(_('path %r is inside nested repo %r') %
118 (path, prefix))
123 (path, prefix))
119 prefixes.append(prefix)
124 prefixes.append(prefix)
120 parts.pop()
125 parts.pop()
121
126
122 self.audited.add(path)
127 self.audited.add(path)
123 # only add prefixes to the cache after checking everything: we don't
128 # only add prefixes to the cache after checking everything: we don't
124 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
129 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
125 self.auditeddir.update(prefixes)
130 self.auditeddir.update(prefixes)
126
131
127 class opener(object):
132 class opener(object):
128 '''Open files relative to a base directory
133 '''Open files relative to a base directory
129
134
130 This class is used to hide the details of COW semantics and
135 This class is used to hide the details of COW semantics and
131 remote file access from higher level code.
136 remote file access from higher level code.
132 '''
137 '''
133 def __init__(self, base, audit=True):
138 def __init__(self, base, audit=True):
134 self.base = base
139 self.base = base
135 if audit:
140 if audit:
136 self.auditor = path_auditor(base)
141 self.auditor = path_auditor(base)
137 else:
142 else:
138 self.auditor = util.always
143 self.auditor = util.always
139 self.createmode = None
144 self.createmode = None
140 self._trustnlink = None
145 self._trustnlink = None
141
146
142 @util.propertycache
147 @util.propertycache
143 def _can_symlink(self):
148 def _can_symlink(self):
144 return util.checklink(self.base)
149 return util.checklink(self.base)
145
150
146 def _fixfilemode(self, name):
151 def _fixfilemode(self, name):
147 if self.createmode is None:
152 if self.createmode is None:
148 return
153 return
149 os.chmod(name, self.createmode & 0666)
154 os.chmod(name, self.createmode & 0666)
150
155
151 def __call__(self, path, mode="r", text=False, atomictemp=False):
156 def __call__(self, path, mode="r", text=False, atomictemp=False):
152 r = util.checkosfilename(path)
157 r = util.checkosfilename(path)
153 if r:
158 if r:
154 raise util.Abort("%s: %r" % (r, path))
159 raise util.Abort("%s: %r" % (r, path))
155 self.auditor(path)
160 self.auditor(path)
156 f = os.path.join(self.base, path)
161 f = os.path.join(self.base, path)
157
162
158 if not text and "b" not in mode:
163 if not text and "b" not in mode:
159 mode += "b" # for that other OS
164 mode += "b" # for that other OS
160
165
161 nlink = -1
166 nlink = -1
162 dirname, basename = os.path.split(f)
167 dirname, basename = os.path.split(f)
163 # If basename is empty, then the path is malformed because it points
168 # If basename is empty, then the path is malformed because it points
164 # to a directory. Let the posixfile() call below raise IOError.
169 # to a directory. Let the posixfile() call below raise IOError.
165 if basename and mode not in ('r', 'rb'):
170 if basename and mode not in ('r', 'rb'):
166 if atomictemp:
171 if atomictemp:
167 if not os.path.isdir(dirname):
172 if not os.path.isdir(dirname):
168 util.makedirs(dirname, self.createmode)
173 util.makedirs(dirname, self.createmode)
169 return util.atomictempfile(f, mode, self.createmode)
174 return util.atomictempfile(f, mode, self.createmode)
170 try:
175 try:
171 if 'w' in mode:
176 if 'w' in mode:
172 util.unlink(f)
177 util.unlink(f)
173 nlink = 0
178 nlink = 0
174 else:
179 else:
175 # nlinks() may behave differently for files on Windows
180 # nlinks() may behave differently for files on Windows
176 # shares if the file is open.
181 # shares if the file is open.
177 fd = util.posixfile(f)
182 fd = util.posixfile(f)
178 nlink = util.nlinks(f)
183 nlink = util.nlinks(f)
179 if nlink < 1:
184 if nlink < 1:
180 nlink = 2 # force mktempcopy (issue1922)
185 nlink = 2 # force mktempcopy (issue1922)
181 fd.close()
186 fd.close()
182 except (OSError, IOError), e:
187 except (OSError, IOError), e:
183 if e.errno != errno.ENOENT:
188 if e.errno != errno.ENOENT:
184 raise
189 raise
185 nlink = 0
190 nlink = 0
186 if not os.path.isdir(dirname):
191 if not os.path.isdir(dirname):
187 util.makedirs(dirname, self.createmode)
192 util.makedirs(dirname, self.createmode)
188 if nlink > 0:
193 if nlink > 0:
189 if self._trustnlink is None:
194 if self._trustnlink is None:
190 self._trustnlink = nlink > 1 or util.checknlink(f)
195 self._trustnlink = nlink > 1 or util.checknlink(f)
191 if nlink > 1 or not self._trustnlink:
196 if nlink > 1 or not self._trustnlink:
192 util.rename(util.mktempcopy(f), f)
197 util.rename(util.mktempcopy(f), f)
193 fp = util.posixfile(f, mode)
198 fp = util.posixfile(f, mode)
194 if nlink == 0:
199 if nlink == 0:
195 self._fixfilemode(f)
200 self._fixfilemode(f)
196 return fp
201 return fp
197
202
198 def symlink(self, src, dst):
203 def symlink(self, src, dst):
199 self.auditor(dst)
204 self.auditor(dst)
200 linkname = os.path.join(self.base, dst)
205 linkname = os.path.join(self.base, dst)
201 try:
206 try:
202 os.unlink(linkname)
207 os.unlink(linkname)
203 except OSError:
208 except OSError:
204 pass
209 pass
205
210
206 dirname = os.path.dirname(linkname)
211 dirname = os.path.dirname(linkname)
207 if not os.path.exists(dirname):
212 if not os.path.exists(dirname):
208 util.makedirs(dirname, self.createmode)
213 util.makedirs(dirname, self.createmode)
209
214
210 if self._can_symlink:
215 if self._can_symlink:
211 try:
216 try:
212 os.symlink(src, linkname)
217 os.symlink(src, linkname)
213 except OSError, err:
218 except OSError, err:
214 raise OSError(err.errno, _('could not symlink to %r: %s') %
219 raise OSError(err.errno, _('could not symlink to %r: %s') %
215 (src, err.strerror), linkname)
220 (src, err.strerror), linkname)
216 else:
221 else:
217 f = self(dst, "w")
222 f = self(dst, "w")
218 f.write(src)
223 f.write(src)
219 f.close()
224 f.close()
220 self._fixfilemode(dst)
225 self._fixfilemode(dst)
221
226
222 def canonpath(root, cwd, myname, auditor=None):
227 def canonpath(root, cwd, myname, auditor=None):
223 '''return the canonical path of myname, given cwd and root'''
228 '''return the canonical path of myname, given cwd and root'''
224 if util.endswithsep(root):
229 if util.endswithsep(root):
225 rootsep = root
230 rootsep = root
226 else:
231 else:
227 rootsep = root + os.sep
232 rootsep = root + os.sep
228 name = myname
233 name = myname
229 if not os.path.isabs(name):
234 if not os.path.isabs(name):
230 name = os.path.join(root, cwd, name)
235 name = os.path.join(root, cwd, name)
231 name = os.path.normpath(name)
236 name = os.path.normpath(name)
232 if auditor is None:
237 if auditor is None:
233 auditor = path_auditor(root)
238 auditor = path_auditor(root)
234 if name != rootsep and name.startswith(rootsep):
239 if name != rootsep and name.startswith(rootsep):
235 name = name[len(rootsep):]
240 name = name[len(rootsep):]
236 auditor(name)
241 auditor(name)
237 return util.pconvert(name)
242 return util.pconvert(name)
238 elif name == root:
243 elif name == root:
239 return ''
244 return ''
240 else:
245 else:
241 # Determine whether `name' is in the hierarchy at or beneath `root',
246 # Determine whether `name' is in the hierarchy at or beneath `root',
242 # by iterating name=dirname(name) until that causes no change (can't
247 # by iterating name=dirname(name) until that causes no change (can't
243 # check name == '/', because that doesn't work on windows). For each
248 # check name == '/', because that doesn't work on windows). For each
244 # `name', compare dev/inode numbers. If they match, the list `rel'
249 # `name', compare dev/inode numbers. If they match, the list `rel'
245 # holds the reversed list of components making up the relative file
250 # holds the reversed list of components making up the relative file
246 # name we want.
251 # name we want.
247 root_st = os.stat(root)
252 root_st = os.stat(root)
248 rel = []
253 rel = []
249 while True:
254 while True:
250 try:
255 try:
251 name_st = os.stat(name)
256 name_st = os.stat(name)
252 except OSError:
257 except OSError:
253 break
258 break
254 if util.samestat(name_st, root_st):
259 if util.samestat(name_st, root_st):
255 if not rel:
260 if not rel:
256 # name was actually the same as root (maybe a symlink)
261 # name was actually the same as root (maybe a symlink)
257 return ''
262 return ''
258 rel.reverse()
263 rel.reverse()
259 name = os.path.join(*rel)
264 name = os.path.join(*rel)
260 auditor(name)
265 auditor(name)
261 return util.pconvert(name)
266 return util.pconvert(name)
262 dirname, basename = os.path.split(name)
267 dirname, basename = os.path.split(name)
263 rel.append(basename)
268 rel.append(basename)
264 if dirname == name:
269 if dirname == name:
265 break
270 break
266 name = dirname
271 name = dirname
267
272
268 raise util.Abort('%s not under root' % myname)
273 raise util.Abort('%s not under root' % myname)
269
274
270 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
275 def walkrepos(path, followsym=False, seen_dirs=None, recurse=False):
271 '''yield every hg repository under path, recursively.'''
276 '''yield every hg repository under path, recursively.'''
272 def errhandler(err):
277 def errhandler(err):
273 if err.filename == path:
278 if err.filename == path:
274 raise err
279 raise err
275 if followsym and hasattr(os.path, 'samestat'):
280 if followsym and hasattr(os.path, 'samestat'):
276 def _add_dir_if_not_there(dirlst, dirname):
281 def _add_dir_if_not_there(dirlst, dirname):
277 match = False
282 match = False
278 samestat = os.path.samestat
283 samestat = os.path.samestat
279 dirstat = os.stat(dirname)
284 dirstat = os.stat(dirname)
280 for lstdirstat in dirlst:
285 for lstdirstat in dirlst:
281 if samestat(dirstat, lstdirstat):
286 if samestat(dirstat, lstdirstat):
282 match = True
287 match = True
283 break
288 break
284 if not match:
289 if not match:
285 dirlst.append(dirstat)
290 dirlst.append(dirstat)
286 return not match
291 return not match
287 else:
292 else:
288 followsym = False
293 followsym = False
289
294
290 if (seen_dirs is None) and followsym:
295 if (seen_dirs is None) and followsym:
291 seen_dirs = []
296 seen_dirs = []
292 _add_dir_if_not_there(seen_dirs, path)
297 _add_dir_if_not_there(seen_dirs, path)
293 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
298 for root, dirs, files in os.walk(path, topdown=True, onerror=errhandler):
294 dirs.sort()
299 dirs.sort()
295 if '.hg' in dirs:
300 if '.hg' in dirs:
296 yield root # found a repository
301 yield root # found a repository
297 qroot = os.path.join(root, '.hg', 'patches')
302 qroot = os.path.join(root, '.hg', 'patches')
298 if os.path.isdir(os.path.join(qroot, '.hg')):
303 if os.path.isdir(os.path.join(qroot, '.hg')):
299 yield qroot # we have a patch queue repo here
304 yield qroot # we have a patch queue repo here
300 if recurse:
305 if recurse:
301 # avoid recursing inside the .hg directory
306 # avoid recursing inside the .hg directory
302 dirs.remove('.hg')
307 dirs.remove('.hg')
303 else:
308 else:
304 dirs[:] = [] # don't descend further
309 dirs[:] = [] # don't descend further
305 elif followsym:
310 elif followsym:
306 newdirs = []
311 newdirs = []
307 for d in dirs:
312 for d in dirs:
308 fname = os.path.join(root, d)
313 fname = os.path.join(root, d)
309 if _add_dir_if_not_there(seen_dirs, fname):
314 if _add_dir_if_not_there(seen_dirs, fname):
310 if os.path.islink(fname):
315 if os.path.islink(fname):
311 for hgname in walkrepos(fname, True, seen_dirs):
316 for hgname in walkrepos(fname, True, seen_dirs):
312 yield hgname
317 yield hgname
313 else:
318 else:
314 newdirs.append(d)
319 newdirs.append(d)
315 dirs[:] = newdirs
320 dirs[:] = newdirs
316
321
317 def os_rcpath():
322 def os_rcpath():
318 '''return default os-specific hgrc search path'''
323 '''return default os-specific hgrc search path'''
319 path = system_rcpath()
324 path = system_rcpath()
320 path.extend(user_rcpath())
325 path.extend(user_rcpath())
321 path = [os.path.normpath(f) for f in path]
326 path = [os.path.normpath(f) for f in path]
322 return path
327 return path
323
328
324 _rcpath = None
329 _rcpath = None
325
330
326 def rcpath():
331 def rcpath():
327 '''return hgrc search path. if env var HGRCPATH is set, use it.
332 '''return hgrc search path. if env var HGRCPATH is set, use it.
328 for each item in path, if directory, use files ending in .rc,
333 for each item in path, if directory, use files ending in .rc,
329 else use item.
334 else use item.
330 make HGRCPATH empty to only look in .hg/hgrc of current repo.
335 make HGRCPATH empty to only look in .hg/hgrc of current repo.
331 if no HGRCPATH, use default os-specific path.'''
336 if no HGRCPATH, use default os-specific path.'''
332 global _rcpath
337 global _rcpath
333 if _rcpath is None:
338 if _rcpath is None:
334 if 'HGRCPATH' in os.environ:
339 if 'HGRCPATH' in os.environ:
335 _rcpath = []
340 _rcpath = []
336 for p in os.environ['HGRCPATH'].split(os.pathsep):
341 for p in os.environ['HGRCPATH'].split(os.pathsep):
337 if not p:
342 if not p:
338 continue
343 continue
339 p = util.expandpath(p)
344 p = util.expandpath(p)
340 if os.path.isdir(p):
345 if os.path.isdir(p):
341 for f, kind in osutil.listdir(p):
346 for f, kind in osutil.listdir(p):
342 if f.endswith('.rc'):
347 if f.endswith('.rc'):
343 _rcpath.append(os.path.join(p, f))
348 _rcpath.append(os.path.join(p, f))
344 else:
349 else:
345 _rcpath.append(p)
350 _rcpath.append(p)
346 else:
351 else:
347 _rcpath = os_rcpath()
352 _rcpath = os_rcpath()
348 return _rcpath
353 return _rcpath
349
354
350 if os.name != 'nt':
355 if os.name != 'nt':
351
356
352 def rcfiles(path):
357 def rcfiles(path):
353 rcs = [os.path.join(path, 'hgrc')]
358 rcs = [os.path.join(path, 'hgrc')]
354 rcdir = os.path.join(path, 'hgrc.d')
359 rcdir = os.path.join(path, 'hgrc.d')
355 try:
360 try:
356 rcs.extend([os.path.join(rcdir, f)
361 rcs.extend([os.path.join(rcdir, f)
357 for f, kind in osutil.listdir(rcdir)
362 for f, kind in osutil.listdir(rcdir)
358 if f.endswith(".rc")])
363 if f.endswith(".rc")])
359 except OSError:
364 except OSError:
360 pass
365 pass
361 return rcs
366 return rcs
362
367
363 def system_rcpath():
368 def system_rcpath():
364 path = []
369 path = []
365 # old mod_python does not set sys.argv
370 # old mod_python does not set sys.argv
366 if len(getattr(sys, 'argv', [])) > 0:
371 if len(getattr(sys, 'argv', [])) > 0:
367 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
372 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
368 '/../etc/mercurial'))
373 '/../etc/mercurial'))
369 path.extend(rcfiles('/etc/mercurial'))
374 path.extend(rcfiles('/etc/mercurial'))
370 return path
375 return path
371
376
372 def user_rcpath():
377 def user_rcpath():
373 return [os.path.expanduser('~/.hgrc')]
378 return [os.path.expanduser('~/.hgrc')]
374
379
375 else:
380 else:
376
381
377 _HKEY_LOCAL_MACHINE = 0x80000002L
382 _HKEY_LOCAL_MACHINE = 0x80000002L
378
383
379 def system_rcpath():
384 def system_rcpath():
380 '''return default os-specific hgrc search path'''
385 '''return default os-specific hgrc search path'''
381 rcpath = []
386 rcpath = []
382 filename = util.executable_path()
387 filename = util.executable_path()
383 # Use mercurial.ini found in directory with hg.exe
388 # Use mercurial.ini found in directory with hg.exe
384 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
389 progrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
385 if os.path.isfile(progrc):
390 if os.path.isfile(progrc):
386 rcpath.append(progrc)
391 rcpath.append(progrc)
387 return rcpath
392 return rcpath
388 # Use hgrc.d found in directory with hg.exe
393 # Use hgrc.d found in directory with hg.exe
389 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
394 progrcd = os.path.join(os.path.dirname(filename), 'hgrc.d')
390 if os.path.isdir(progrcd):
395 if os.path.isdir(progrcd):
391 for f, kind in osutil.listdir(progrcd):
396 for f, kind in osutil.listdir(progrcd):
392 if f.endswith('.rc'):
397 if f.endswith('.rc'):
393 rcpath.append(os.path.join(progrcd, f))
398 rcpath.append(os.path.join(progrcd, f))
394 return rcpath
399 return rcpath
395 # else look for a system rcpath in the registry
400 # else look for a system rcpath in the registry
396 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
401 value = util.lookup_reg('SOFTWARE\\Mercurial', None,
397 _HKEY_LOCAL_MACHINE)
402 _HKEY_LOCAL_MACHINE)
398 if not isinstance(value, str) or not value:
403 if not isinstance(value, str) or not value:
399 return rcpath
404 return rcpath
400 value = value.replace('/', os.sep)
405 value = value.replace('/', os.sep)
401 for p in value.split(os.pathsep):
406 for p in value.split(os.pathsep):
402 if p.lower().endswith('mercurial.ini'):
407 if p.lower().endswith('mercurial.ini'):
403 rcpath.append(p)
408 rcpath.append(p)
404 elif os.path.isdir(p):
409 elif os.path.isdir(p):
405 for f, kind in osutil.listdir(p):
410 for f, kind in osutil.listdir(p):
406 if f.endswith('.rc'):
411 if f.endswith('.rc'):
407 rcpath.append(os.path.join(p, f))
412 rcpath.append(os.path.join(p, f))
408 return rcpath
413 return rcpath
409
414
410 def user_rcpath():
415 def user_rcpath():
411 '''return os-specific hgrc search path to the user dir'''
416 '''return os-specific hgrc search path to the user dir'''
412 home = os.path.expanduser('~')
417 home = os.path.expanduser('~')
413 path = [os.path.join(home, 'mercurial.ini'),
418 path = [os.path.join(home, 'mercurial.ini'),
414 os.path.join(home, '.hgrc')]
419 os.path.join(home, '.hgrc')]
415 userprofile = os.environ.get('USERPROFILE')
420 userprofile = os.environ.get('USERPROFILE')
416 if userprofile:
421 if userprofile:
417 path.append(os.path.join(userprofile, 'mercurial.ini'))
422 path.append(os.path.join(userprofile, 'mercurial.ini'))
418 path.append(os.path.join(userprofile, '.hgrc'))
423 path.append(os.path.join(userprofile, '.hgrc'))
419 return path
424 return path
General Comments 0
You need to be logged in to leave comments. Login now