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