##// END OF EJS Templates
merge with stable
Matt Mackall -
r16295:ba42eb72 merge default
parent child Browse files
Show More
@@ -1,215 +1,217
1 1 # git.py - git support for the convert extension
2 2 #
3 3 # Copyright 2005-2009 Matt Mackall <mpm@selenic.com> and others
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 import os
9 9 from mercurial import util
10 10 from mercurial.node import hex, nullid
11 11 from mercurial.i18n import _
12 12
13 13 from common import NoRepo, commit, converter_source, checktool
14 14
15 15 class convert_git(converter_source):
16 16 # Windows does not support GIT_DIR= construct while other systems
17 17 # cannot remove environment variable. Just assume none have
18 18 # both issues.
19 19 if util.safehasattr(os, 'unsetenv'):
20 20 def gitopen(self, s, noerr=False):
21 21 prevgitdir = os.environ.get('GIT_DIR')
22 22 os.environ['GIT_DIR'] = self.path
23 23 try:
24 24 if noerr:
25 25 (stdin, stdout, stderr) = util.popen3(s)
26 26 return stdout
27 27 else:
28 28 return util.popen(s, 'rb')
29 29 finally:
30 30 if prevgitdir is None:
31 31 del os.environ['GIT_DIR']
32 32 else:
33 33 os.environ['GIT_DIR'] = prevgitdir
34 34 else:
35 35 def gitopen(self, s, noerr=False):
36 36 if noerr:
37 37 (sin, so, se) = util.popen3('GIT_DIR=%s %s' % (self.path, s))
38 38 return so
39 39 else:
40 40 return util.popen('GIT_DIR=%s %s' % (self.path, s), 'rb')
41 41
42 42 def gitread(self, s):
43 43 fh = self.gitopen(s)
44 44 data = fh.read()
45 45 return data, fh.close()
46 46
47 47 def __init__(self, ui, path, rev=None):
48 48 super(convert_git, self).__init__(ui, path, rev=rev)
49 49
50 50 if os.path.isdir(path + "/.git"):
51 51 path += "/.git"
52 52 if not os.path.exists(path + "/objects"):
53 53 raise NoRepo(_("%s does not look like a Git repository") % path)
54 54
55 55 checktool('git', 'git')
56 56
57 57 self.path = path
58 58
59 59 def getheads(self):
60 60 if not self.rev:
61 61 heads, ret = self.gitread('git rev-parse --branches --remotes')
62 62 heads = heads.splitlines()
63 63 else:
64 64 heads, ret = self.gitread("git rev-parse --verify %s" % self.rev)
65 65 heads = [heads[:-1]]
66 66 if ret:
67 67 raise util.Abort(_('cannot retrieve git heads'))
68 68 return heads
69 69
70 70 def catfile(self, rev, type):
71 71 if rev == hex(nullid):
72 72 raise IOError()
73 73 data, ret = self.gitread("git cat-file %s %s" % (type, rev))
74 74 if ret:
75 75 raise util.Abort(_('cannot read %r object at %s') % (type, rev))
76 76 return data
77 77
78 78 def getfile(self, name, rev):
79 79 data = self.catfile(rev, "blob")
80 80 mode = self.modecache[(name, rev)]
81 81 return data, mode
82 82
83 83 def getchanges(self, version):
84 84 self.modecache = {}
85 85 fh = self.gitopen("git diff-tree -z --root -m -r %s" % version)
86 86 changes = []
87 87 seen = set()
88 88 entry = None
89 89 for l in fh.read().split('\x00'):
90 90 if not entry:
91 91 if not l.startswith(':'):
92 92 continue
93 93 entry = l
94 94 continue
95 95 f = l
96 96 if f not in seen:
97 97 seen.add(f)
98 98 entry = entry.split()
99 99 h = entry[3]
100 if entry[1] == '160000':
101 raise util.Abort('git submodules are not supported!')
100 102 p = (entry[1] == "100755")
101 103 s = (entry[1] == "120000")
102 104 self.modecache[(f, h)] = (p and "x") or (s and "l") or ""
103 105 changes.append((f, h))
104 106 entry = None
105 107 if fh.close():
106 108 raise util.Abort(_('cannot read changes in %s') % version)
107 109 return (changes, {})
108 110
109 111 def getcommit(self, version):
110 112 c = self.catfile(version, "commit") # read the commit hash
111 113 end = c.find("\n\n")
112 114 message = c[end + 2:]
113 115 message = self.recode(message)
114 116 l = c[:end].splitlines()
115 117 parents = []
116 118 author = committer = None
117 119 for e in l[1:]:
118 120 n, v = e.split(" ", 1)
119 121 if n == "author":
120 122 p = v.split()
121 123 tm, tz = p[-2:]
122 124 author = " ".join(p[:-2])
123 125 if author[0] == "<": author = author[1:-1]
124 126 author = self.recode(author)
125 127 if n == "committer":
126 128 p = v.split()
127 129 tm, tz = p[-2:]
128 130 committer = " ".join(p[:-2])
129 131 if committer[0] == "<": committer = committer[1:-1]
130 132 committer = self.recode(committer)
131 133 if n == "parent":
132 134 parents.append(v)
133 135
134 136 if committer and committer != author:
135 137 message += "\ncommitter: %s\n" % committer
136 138 tzs, tzh, tzm = tz[-5:-4] + "1", tz[-4:-2], tz[-2:]
137 139 tz = -int(tzs) * (int(tzh) * 3600 + int(tzm))
138 140 date = tm + " " + str(tz)
139 141
140 142 c = commit(parents=parents, date=date, author=author, desc=message,
141 143 rev=version)
142 144 return c
143 145
144 146 def gettags(self):
145 147 tags = {}
146 148 alltags = {}
147 149 fh = self.gitopen('git ls-remote --tags "%s"' % self.path)
148 150 prefix = 'refs/tags/'
149 151
150 152 # Build complete list of tags, both annotated and bare ones
151 153 for line in fh:
152 154 line = line.strip()
153 155 node, tag = line.split(None, 1)
154 156 if not tag.startswith(prefix):
155 157 continue
156 158 alltags[tag[len(prefix):]] = node
157 159 if fh.close():
158 160 raise util.Abort(_('cannot read tags from %s') % self.path)
159 161
160 162 # Filter out tag objects for annotated tag refs
161 163 for tag in alltags:
162 164 if tag.endswith('^{}'):
163 165 tags[tag[:-3]] = alltags[tag]
164 166 else:
165 167 if tag + '^{}' in alltags:
166 168 continue
167 169 else:
168 170 tags[tag] = alltags[tag]
169 171
170 172 return tags
171 173
172 174 def getchangedfiles(self, version, i):
173 175 changes = []
174 176 if i is None:
175 177 fh = self.gitopen("git diff-tree --root -m -r %s" % version)
176 178 for l in fh:
177 179 if "\t" not in l:
178 180 continue
179 181 m, f = l[:-1].split("\t")
180 182 changes.append(f)
181 183 else:
182 184 fh = self.gitopen('git diff-tree --name-only --root -r %s "%s^%s" --'
183 185 % (version, version, i + 1))
184 186 changes = [f.rstrip('\n') for f in fh]
185 187 if fh.close():
186 188 raise util.Abort(_('cannot read changes in %s') % version)
187 189
188 190 return changes
189 191
190 192 def getbookmarks(self):
191 193 bookmarks = {}
192 194
193 195 # Interesting references in git are prefixed
194 196 prefix = 'refs/heads/'
195 197 prefixlen = len(prefix)
196 198
197 199 # factor two commands
198 200 gitcmd = { 'remote/': 'git ls-remote --heads origin',
199 201 '': 'git show-ref'}
200 202
201 203 # Origin heads
202 204 for reftype in gitcmd:
203 205 try:
204 206 fh = self.gitopen(gitcmd[reftype], noerr=True)
205 207 for line in fh:
206 208 line = line.strip()
207 209 rev, name = line.split(None, 1)
208 210 if not name.startswith(prefix):
209 211 continue
210 212 name = '%s%s' % (reftype, name[prefixlen:])
211 213 bookmarks[name] = rev
212 214 except:
213 215 pass
214 216
215 217 return bookmarks
@@ -1,741 +1,741
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms of the
6 6 # GNU General Public License version 2 or any later version.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time, traceback, re
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as uimod
13 13
14 14 class request(object):
15 15 def __init__(self, args, ui=None, repo=None, fin=None, fout=None, ferr=None):
16 16 self.args = args
17 17 self.ui = ui
18 18 self.repo = repo
19 19
20 20 # input/output/error streams
21 21 self.fin = fin
22 22 self.fout = fout
23 23 self.ferr = ferr
24 24
25 25 def run():
26 26 "run the command in sys.argv"
27 27 sys.exit((dispatch(request(sys.argv[1:])) or 0) & 255)
28 28
29 29 def dispatch(req):
30 30 "run the command specified in req.args"
31 31 if req.ferr:
32 32 ferr = req.ferr
33 33 elif req.ui:
34 34 ferr = req.ui.ferr
35 35 else:
36 36 ferr = sys.stderr
37 37
38 38 try:
39 39 if not req.ui:
40 40 req.ui = uimod.ui()
41 41 if '--traceback' in req.args:
42 42 req.ui.setconfig('ui', 'traceback', 'on')
43 43
44 44 # set ui streams from the request
45 45 if req.fin:
46 46 req.ui.fin = req.fin
47 47 if req.fout:
48 48 req.ui.fout = req.fout
49 49 if req.ferr:
50 50 req.ui.ferr = req.ferr
51 51 except util.Abort, inst:
52 52 ferr.write(_("abort: %s\n") % inst)
53 53 if inst.hint:
54 54 ferr.write(_("(%s)\n") % inst.hint)
55 55 return -1
56 56 except error.ParseError, inst:
57 57 if len(inst.args) > 1:
58 58 ferr.write(_("hg: parse error at %s: %s\n") %
59 59 (inst.args[1], inst.args[0]))
60 60 else:
61 61 ferr.write(_("hg: parse error: %s\n") % inst.args[0])
62 62 return -1
63 63
64 64 return _runcatch(req)
65 65
66 66 def _runcatch(req):
67 67 def catchterm(*args):
68 68 raise error.SignalInterrupt
69 69
70 70 ui = req.ui
71 71 try:
72 72 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
73 73 num = getattr(signal, name, None)
74 74 if num:
75 75 signal.signal(num, catchterm)
76 76 except ValueError:
77 77 pass # happens if called in a thread
78 78
79 79 try:
80 80 try:
81 81 # enter the debugger before command execution
82 82 if '--debugger' in req.args:
83 83 ui.warn(_("entering debugger - "
84 84 "type c to continue starting hg or h for help\n"))
85 85 pdb.set_trace()
86 86 try:
87 87 return _dispatch(req)
88 88 finally:
89 89 ui.flush()
90 90 except:
91 91 # enter the debugger when we hit an exception
92 92 if '--debugger' in req.args:
93 93 traceback.print_exc()
94 94 pdb.post_mortem(sys.exc_info()[2])
95 95 ui.traceback()
96 96 raise
97 97
98 98 # Global exception handling, alphabetically
99 99 # Mercurial-specific first, followed by built-in and library exceptions
100 100 except error.AmbiguousCommand, inst:
101 101 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
102 102 (inst.args[0], " ".join(inst.args[1])))
103 103 except error.ParseError, inst:
104 104 if len(inst.args) > 1:
105 105 ui.warn(_("hg: parse error at %s: %s\n") %
106 106 (inst.args[1], inst.args[0]))
107 107 else:
108 108 ui.warn(_("hg: parse error: %s\n") % inst.args[0])
109 109 return -1
110 110 except error.LockHeld, inst:
111 111 if inst.errno == errno.ETIMEDOUT:
112 112 reason = _('timed out waiting for lock held by %s') % inst.locker
113 113 else:
114 114 reason = _('lock held by %s') % inst.locker
115 115 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
116 116 except error.LockUnavailable, inst:
117 117 ui.warn(_("abort: could not lock %s: %s\n") %
118 118 (inst.desc or inst.filename, inst.strerror))
119 119 except error.CommandError, inst:
120 120 if inst.args[0]:
121 121 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
122 122 commands.help_(ui, inst.args[0], full=False, command=True)
123 123 else:
124 124 ui.warn(_("hg: %s\n") % inst.args[1])
125 125 commands.help_(ui, 'shortlist')
126 126 except error.OutOfBandError, inst:
127 127 ui.warn(_("abort: remote error:\n"))
128 128 ui.warn(''.join(inst.args))
129 129 except error.RepoError, inst:
130 130 ui.warn(_("abort: %s!\n") % inst)
131 131 if inst.hint:
132 132 ui.warn(_("(%s)\n") % inst.hint)
133 133 except error.ResponseError, inst:
134 134 ui.warn(_("abort: %s") % inst.args[0])
135 135 if not isinstance(inst.args[1], basestring):
136 136 ui.warn(" %r\n" % (inst.args[1],))
137 137 elif not inst.args[1]:
138 138 ui.warn(_(" empty string\n"))
139 139 else:
140 140 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
141 141 except error.RevlogError, inst:
142 142 ui.warn(_("abort: %s!\n") % inst)
143 143 except error.SignalInterrupt:
144 144 ui.warn(_("killed!\n"))
145 145 except error.UnknownCommand, inst:
146 146 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
147 147 try:
148 148 # check if the command is in a disabled extension
149 149 # (but don't check for extensions themselves)
150 150 commands.help_(ui, inst.args[0], unknowncmd=True)
151 151 except error.UnknownCommand:
152 152 commands.help_(ui, 'shortlist')
153 153 except util.Abort, inst:
154 154 ui.warn(_("abort: %s\n") % inst)
155 155 if inst.hint:
156 156 ui.warn(_("(%s)\n") % inst.hint)
157 157 except ImportError, inst:
158 158 ui.warn(_("abort: %s!\n") % inst)
159 159 m = str(inst).split()[-1]
160 160 if m in "mpatch bdiff".split():
161 161 ui.warn(_("(did you forget to compile extensions?)\n"))
162 162 elif m in "zlib".split():
163 163 ui.warn(_("(is your Python install correct?)\n"))
164 164 except IOError, inst:
165 165 if util.safehasattr(inst, "code"):
166 166 ui.warn(_("abort: %s\n") % inst)
167 167 elif util.safehasattr(inst, "reason"):
168 168 try: # usually it is in the form (errno, strerror)
169 169 reason = inst.reason.args[1]
170 170 except (AttributeError, IndexError):
171 171 # it might be anything, for example a string
172 172 reason = inst.reason
173 173 ui.warn(_("abort: error: %s\n") % reason)
174 174 elif util.safehasattr(inst, "args") and inst.args[0] == errno.EPIPE:
175 175 if ui.debugflag:
176 176 ui.warn(_("broken pipe\n"))
177 177 elif getattr(inst, "strerror", None):
178 178 if getattr(inst, "filename", None):
179 179 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
180 180 else:
181 181 ui.warn(_("abort: %s\n") % inst.strerror)
182 182 else:
183 183 raise
184 184 except OSError, inst:
185 185 if getattr(inst, "filename", None):
186 186 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
187 187 else:
188 188 ui.warn(_("abort: %s\n") % inst.strerror)
189 189 except KeyboardInterrupt:
190 190 try:
191 191 ui.warn(_("interrupted!\n"))
192 192 except IOError, inst:
193 193 if inst.errno == errno.EPIPE:
194 194 if ui.debugflag:
195 195 ui.warn(_("\nbroken pipe\n"))
196 196 else:
197 197 raise
198 198 except MemoryError:
199 199 ui.warn(_("abort: out of memory\n"))
200 200 except SystemExit, inst:
201 201 # Commands shouldn't sys.exit directly, but give a return code.
202 202 # Just in case catch this and and pass exit code to caller.
203 203 return inst.code
204 204 except socket.error, inst:
205 205 ui.warn(_("abort: %s\n") % inst.args[-1])
206 206 except:
207 207 ui.warn(_("** unknown exception encountered,"
208 208 " please report by visiting\n"))
209 209 ui.warn(_("** http://mercurial.selenic.com/wiki/BugTracker\n"))
210 210 ui.warn(_("** Python %s\n") % sys.version.replace('\n', ''))
211 211 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
212 212 % util.version())
213 213 ui.warn(_("** Extensions loaded: %s\n")
214 214 % ", ".join([x[0] for x in extensions.extensions()]))
215 215 raise
216 216
217 217 return -1
218 218
219 219 def aliasargs(fn, givenargs):
220 220 args = getattr(fn, 'args', [])
221 if args and givenargs:
221 if args:
222 222 cmd = ' '.join(map(util.shellquote, args))
223 223
224 224 nums = []
225 225 def replacer(m):
226 226 num = int(m.group(1)) - 1
227 227 nums.append(num)
228 228 if num < len(givenargs):
229 229 return givenargs[num]
230 return ''
230 raise util.Abort(_('too few arguments for command alias'))
231 231 cmd = re.sub(r'\$(\d+|\$)', replacer, cmd)
232 232 givenargs = [x for i, x in enumerate(givenargs)
233 233 if i not in nums]
234 234 args = shlex.split(cmd)
235 235 return args + givenargs
236 236
237 237 class cmdalias(object):
238 238 def __init__(self, name, definition, cmdtable):
239 239 self.name = self.cmd = name
240 240 self.cmdname = ''
241 241 self.definition = definition
242 242 self.args = []
243 243 self.opts = []
244 244 self.help = ''
245 245 self.norepo = True
246 246 self.badalias = False
247 247
248 248 try:
249 249 aliases, entry = cmdutil.findcmd(self.name, cmdtable)
250 250 for alias, e in cmdtable.iteritems():
251 251 if e is entry:
252 252 self.cmd = alias
253 253 break
254 254 self.shadows = True
255 255 except error.UnknownCommand:
256 256 self.shadows = False
257 257
258 258 if not self.definition:
259 259 def fn(ui, *args):
260 260 ui.warn(_("no definition for alias '%s'\n") % self.name)
261 261 return 1
262 262 self.fn = fn
263 263 self.badalias = True
264 264 return
265 265
266 266 if self.definition.startswith('!'):
267 267 self.shell = True
268 268 def fn(ui, *args):
269 269 env = {'HG_ARGS': ' '.join((self.name,) + args)}
270 270 def _checkvar(m):
271 271 if m.groups()[0] == '$':
272 272 return m.group()
273 273 elif int(m.groups()[0]) <= len(args):
274 274 return m.group()
275 275 else:
276 276 ui.debug("No argument found for substitution "
277 277 "of %i variable in alias '%s' definition."
278 278 % (int(m.groups()[0]), self.name))
279 279 return ''
280 280 cmd = re.sub(r'\$(\d+|\$)', _checkvar, self.definition[1:])
281 281 replace = dict((str(i + 1), arg) for i, arg in enumerate(args))
282 282 replace['0'] = self.name
283 283 replace['@'] = ' '.join(args)
284 284 cmd = util.interpolate(r'\$', replace, cmd, escape_prefix=True)
285 285 return util.system(cmd, environ=env, out=ui.fout)
286 286 self.fn = fn
287 287 return
288 288
289 289 args = shlex.split(self.definition)
290 290 self.cmdname = cmd = args.pop(0)
291 291 args = map(util.expandpath, args)
292 292
293 293 for invalidarg in ("--cwd", "-R", "--repository", "--repo"):
294 294 if _earlygetopt([invalidarg], args):
295 295 def fn(ui, *args):
296 296 ui.warn(_("error in definition for alias '%s': %s may only "
297 297 "be given on the command line\n")
298 298 % (self.name, invalidarg))
299 299 return 1
300 300
301 301 self.fn = fn
302 302 self.badalias = True
303 303 return
304 304
305 305 try:
306 306 tableentry = cmdutil.findcmd(cmd, cmdtable, False)[1]
307 307 if len(tableentry) > 2:
308 308 self.fn, self.opts, self.help = tableentry
309 309 else:
310 310 self.fn, self.opts = tableentry
311 311
312 312 self.args = aliasargs(self.fn, args)
313 313 if cmd not in commands.norepo.split(' '):
314 314 self.norepo = False
315 315 if self.help.startswith("hg " + cmd):
316 316 # drop prefix in old-style help lines so hg shows the alias
317 317 self.help = self.help[4 + len(cmd):]
318 318 self.__doc__ = self.fn.__doc__
319 319
320 320 except error.UnknownCommand:
321 321 def fn(ui, *args):
322 322 ui.warn(_("alias '%s' resolves to unknown command '%s'\n") \
323 323 % (self.name, cmd))
324 324 try:
325 325 # check if the command is in a disabled extension
326 326 commands.help_(ui, cmd, unknowncmd=True)
327 327 except error.UnknownCommand:
328 328 pass
329 329 return 1
330 330 self.fn = fn
331 331 self.badalias = True
332 332 except error.AmbiguousCommand:
333 333 def fn(ui, *args):
334 334 ui.warn(_("alias '%s' resolves to ambiguous command '%s'\n") \
335 335 % (self.name, cmd))
336 336 return 1
337 337 self.fn = fn
338 338 self.badalias = True
339 339
340 340 def __call__(self, ui, *args, **opts):
341 341 if self.shadows:
342 342 ui.debug("alias '%s' shadows command '%s'\n" %
343 343 (self.name, self.cmdname))
344 344
345 345 if util.safehasattr(self, 'shell'):
346 346 return self.fn(ui, *args, **opts)
347 347 else:
348 348 try:
349 349 util.checksignature(self.fn)(ui, *args, **opts)
350 350 except error.SignatureError:
351 351 args = ' '.join([self.cmdname] + self.args)
352 352 ui.debug("alias '%s' expands to '%s'\n" % (self.name, args))
353 353 raise
354 354
355 355 def addaliases(ui, cmdtable):
356 356 # aliases are processed after extensions have been loaded, so they
357 357 # may use extension commands. Aliases can also use other alias definitions,
358 358 # but only if they have been defined prior to the current definition.
359 359 for alias, definition in ui.configitems('alias'):
360 360 aliasdef = cmdalias(alias, definition, cmdtable)
361 361
362 362 try:
363 363 olddef = cmdtable[aliasdef.cmd][0]
364 364 if olddef.definition == aliasdef.definition:
365 365 continue
366 366 except (KeyError, AttributeError):
367 367 # definition might not exist or it might not be a cmdalias
368 368 pass
369 369
370 370 cmdtable[aliasdef.name] = (aliasdef, aliasdef.opts, aliasdef.help)
371 371 if aliasdef.norepo:
372 372 commands.norepo += ' %s' % alias
373 373
374 374 def _parse(ui, args):
375 375 options = {}
376 376 cmdoptions = {}
377 377
378 378 try:
379 379 args = fancyopts.fancyopts(args, commands.globalopts, options)
380 380 except fancyopts.getopt.GetoptError, inst:
381 381 raise error.CommandError(None, inst)
382 382
383 383 if args:
384 384 cmd, args = args[0], args[1:]
385 385 aliases, entry = cmdutil.findcmd(cmd, commands.table,
386 386 ui.config("ui", "strict"))
387 387 cmd = aliases[0]
388 388 args = aliasargs(entry[0], args)
389 389 defaults = ui.config("defaults", cmd)
390 390 if defaults:
391 391 args = map(util.expandpath, shlex.split(defaults)) + args
392 392 c = list(entry[1])
393 393 else:
394 394 cmd = None
395 395 c = []
396 396
397 397 # combine global options into local
398 398 for o in commands.globalopts:
399 399 c.append((o[0], o[1], options[o[1]], o[3]))
400 400
401 401 try:
402 402 args = fancyopts.fancyopts(args, c, cmdoptions, True)
403 403 except fancyopts.getopt.GetoptError, inst:
404 404 raise error.CommandError(cmd, inst)
405 405
406 406 # separate global options back out
407 407 for o in commands.globalopts:
408 408 n = o[1]
409 409 options[n] = cmdoptions[n]
410 410 del cmdoptions[n]
411 411
412 412 return (cmd, cmd and entry[0] or None, args, options, cmdoptions)
413 413
414 414 def _parseconfig(ui, config):
415 415 """parse the --config options from the command line"""
416 416 configs = []
417 417
418 418 for cfg in config:
419 419 try:
420 420 name, value = cfg.split('=', 1)
421 421 section, name = name.split('.', 1)
422 422 if not section or not name:
423 423 raise IndexError
424 424 ui.setconfig(section, name, value)
425 425 configs.append((section, name, value))
426 426 except (IndexError, ValueError):
427 427 raise util.Abort(_('malformed --config option: %r '
428 428 '(use --config section.name=value)') % cfg)
429 429
430 430 return configs
431 431
432 432 def _earlygetopt(aliases, args):
433 433 """Return list of values for an option (or aliases).
434 434
435 435 The values are listed in the order they appear in args.
436 436 The options and values are removed from args.
437 437 """
438 438 try:
439 439 argcount = args.index("--")
440 440 except ValueError:
441 441 argcount = len(args)
442 442 shortopts = [opt for opt in aliases if len(opt) == 2]
443 443 values = []
444 444 pos = 0
445 445 while pos < argcount:
446 446 if args[pos] in aliases:
447 447 if pos + 1 >= argcount:
448 448 # ignore and let getopt report an error if there is no value
449 449 break
450 450 del args[pos]
451 451 values.append(args.pop(pos))
452 452 argcount -= 2
453 453 elif args[pos][:2] in shortopts:
454 454 # short option can have no following space, e.g. hg log -Rfoo
455 455 values.append(args.pop(pos)[2:])
456 456 argcount -= 1
457 457 else:
458 458 pos += 1
459 459 return values
460 460
461 461 def runcommand(lui, repo, cmd, fullargs, ui, options, d, cmdpats, cmdoptions):
462 462 # run pre-hook, and abort if it fails
463 463 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs),
464 464 pats=cmdpats, opts=cmdoptions)
465 465 if ret:
466 466 return ret
467 467 ret = _runcommand(ui, options, cmd, d)
468 468 # run post-hook, passing command result
469 469 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
470 470 result=ret, pats=cmdpats, opts=cmdoptions)
471 471 return ret
472 472
473 473 def _getlocal(ui, rpath):
474 474 """Return (path, local ui object) for the given target path.
475 475
476 476 Takes paths in [cwd]/.hg/hgrc into account."
477 477 """
478 478 try:
479 479 wd = os.getcwd()
480 480 except OSError, e:
481 481 raise util.Abort(_("error getting current working directory: %s") %
482 482 e.strerror)
483 483 path = cmdutil.findrepo(wd) or ""
484 484 if not path:
485 485 lui = ui
486 486 else:
487 487 lui = ui.copy()
488 488 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
489 489
490 490 if rpath and rpath[-1]:
491 491 path = lui.expandpath(rpath[-1])
492 492 lui = ui.copy()
493 493 lui.readconfig(os.path.join(path, ".hg", "hgrc"), path)
494 494
495 495 return path, lui
496 496
497 497 def _checkshellalias(lui, ui, args):
498 498 norepo = commands.norepo
499 499 options = {}
500 500
501 501 try:
502 502 args = fancyopts.fancyopts(args, commands.globalopts, options)
503 503 except fancyopts.getopt.GetoptError:
504 504 return
505 505
506 506 if not args:
507 507 return
508 508
509 509 cmdtable = commands.table.copy()
510 510 addaliases(lui, cmdtable)
511 511
512 512 cmd = args[0]
513 513 try:
514 514 aliases, entry = cmdutil.findcmd(cmd, cmdtable, lui.config("ui", "strict"))
515 515 except (error.AmbiguousCommand, error.UnknownCommand):
516 516 commands.norepo = norepo
517 517 return
518 518
519 519 cmd = aliases[0]
520 520 fn = entry[0]
521 521
522 522 if cmd and util.safehasattr(fn, 'shell'):
523 523 d = lambda: fn(ui, *args[1:])
524 524 return lambda: runcommand(lui, None, cmd, args[:1], ui, options, d, [], {})
525 525
526 526 commands.norepo = norepo
527 527
528 528 _loaded = set()
529 529 def _dispatch(req):
530 530 args = req.args
531 531 ui = req.ui
532 532
533 533 # read --config before doing anything else
534 534 # (e.g. to change trust settings for reading .hg/hgrc)
535 535 cfgs = _parseconfig(ui, _earlygetopt(['--config'], args))
536 536
537 537 # check for cwd
538 538 cwd = _earlygetopt(['--cwd'], args)
539 539 if cwd:
540 540 os.chdir(cwd[-1])
541 541
542 542 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
543 543 path, lui = _getlocal(ui, rpath)
544 544
545 545 # Now that we're operating in the right directory/repository with
546 546 # the right config settings, check for shell aliases
547 547 shellaliasfn = _checkshellalias(lui, ui, args)
548 548 if shellaliasfn:
549 549 return shellaliasfn()
550 550
551 551 # Configure extensions in phases: uisetup, extsetup, cmdtable, and
552 552 # reposetup. Programs like TortoiseHg will call _dispatch several
553 553 # times so we keep track of configured extensions in _loaded.
554 554 extensions.loadall(lui)
555 555 exts = [ext for ext in extensions.extensions() if ext[0] not in _loaded]
556 556 # Propagate any changes to lui.__class__ by extensions
557 557 ui.__class__ = lui.__class__
558 558
559 559 # (uisetup and extsetup are handled in extensions.loadall)
560 560
561 561 for name, module in exts:
562 562 cmdtable = getattr(module, 'cmdtable', {})
563 563 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
564 564 if overrides:
565 565 ui.warn(_("extension '%s' overrides commands: %s\n")
566 566 % (name, " ".join(overrides)))
567 567 commands.table.update(cmdtable)
568 568 _loaded.add(name)
569 569
570 570 # (reposetup is handled in hg.repository)
571 571
572 572 addaliases(lui, commands.table)
573 573
574 574 # check for fallback encoding
575 575 fallback = lui.config('ui', 'fallbackencoding')
576 576 if fallback:
577 577 encoding.fallbackencoding = fallback
578 578
579 579 fullargs = args
580 580 cmd, func, args, options, cmdoptions = _parse(lui, args)
581 581
582 582 if options["config"]:
583 583 raise util.Abort(_("option --config may not be abbreviated!"))
584 584 if options["cwd"]:
585 585 raise util.Abort(_("option --cwd may not be abbreviated!"))
586 586 if options["repository"]:
587 587 raise util.Abort(_(
588 588 "option -R has to be separated from other options (e.g. not -qR) "
589 589 "and --repository may only be abbreviated as --repo!"))
590 590
591 591 if options["encoding"]:
592 592 encoding.encoding = options["encoding"]
593 593 if options["encodingmode"]:
594 594 encoding.encodingmode = options["encodingmode"]
595 595 if options["time"]:
596 596 def get_times():
597 597 t = os.times()
598 598 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
599 599 t = (t[0], t[1], t[2], t[3], time.clock())
600 600 return t
601 601 s = get_times()
602 602 def print_time():
603 603 t = get_times()
604 604 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
605 605 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
606 606 atexit.register(print_time)
607 607
608 608 uis = set([ui, lui])
609 609
610 610 if req.repo:
611 611 uis.add(req.repo.ui)
612 612
613 613 # copy configs that were passed on the cmdline (--config) to the repo ui
614 614 for cfg in cfgs:
615 615 req.repo.ui.setconfig(*cfg)
616 616
617 617 if options['verbose'] or options['debug'] or options['quiet']:
618 618 for opt in ('verbose', 'debug', 'quiet'):
619 619 val = str(bool(options[opt]))
620 620 for ui_ in uis:
621 621 ui_.setconfig('ui', opt, val)
622 622
623 623 if options['traceback']:
624 624 for ui_ in uis:
625 625 ui_.setconfig('ui', 'traceback', 'on')
626 626
627 627 if options['noninteractive']:
628 628 for ui_ in uis:
629 629 ui_.setconfig('ui', 'interactive', 'off')
630 630
631 631 if cmdoptions.get('insecure', False):
632 632 for ui_ in uis:
633 633 ui_.setconfig('web', 'cacerts', '')
634 634
635 635 if options['version']:
636 636 return commands.version_(ui)
637 637 if options['help']:
638 638 return commands.help_(ui, cmd)
639 639 elif not cmd:
640 640 return commands.help_(ui, 'shortlist')
641 641
642 642 repo = None
643 643 cmdpats = args[:]
644 644 if cmd not in commands.norepo.split():
645 645 # use the repo from the request only if we don't have -R
646 646 if not rpath and not cwd:
647 647 repo = req.repo
648 648
649 649 if repo:
650 650 # set the descriptors of the repo ui to those of ui
651 651 repo.ui.fin = ui.fin
652 652 repo.ui.fout = ui.fout
653 653 repo.ui.ferr = ui.ferr
654 654 else:
655 655 try:
656 656 repo = hg.repository(ui, path=path)
657 657 if not repo.local():
658 658 raise util.Abort(_("repository '%s' is not local") % path)
659 659 repo.ui.setconfig("bundle", "mainreporoot", repo.root)
660 660 except error.RequirementError:
661 661 raise
662 662 except error.RepoError:
663 663 if cmd not in commands.optionalrepo.split():
664 664 if args and not path: # try to infer -R from command args
665 665 repos = map(cmdutil.findrepo, args)
666 666 guess = repos[0]
667 667 if guess and repos.count(guess) == len(repos):
668 668 req.args = ['--repository', guess] + fullargs
669 669 return _dispatch(req)
670 670 if not path:
671 671 raise error.RepoError(_("no repository found in '%s'"
672 672 " (.hg not found)") % os.getcwd())
673 673 raise
674 674 if repo:
675 675 ui = repo.ui
676 676 args.insert(0, repo)
677 677 elif rpath:
678 678 ui.warn(_("warning: --repository ignored\n"))
679 679
680 680 msg = ' '.join(' ' in a and repr(a) or a for a in fullargs)
681 681 ui.log("command", msg + "\n")
682 682 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
683 683 try:
684 684 return runcommand(lui, repo, cmd, fullargs, ui, options, d,
685 685 cmdpats, cmdoptions)
686 686 finally:
687 687 if repo and repo != req.repo:
688 688 repo.close()
689 689
690 690 def _runcommand(ui, options, cmd, cmdfunc):
691 691 def checkargs():
692 692 try:
693 693 return cmdfunc()
694 694 except error.SignatureError:
695 695 raise error.CommandError(cmd, _("invalid arguments"))
696 696
697 697 if options['profile']:
698 698 format = ui.config('profiling', 'format', default='text')
699 699 field = ui.config('profiling', 'sort', default='inlinetime')
700 700 climit = ui.configint('profiling', 'nested', default=5)
701 701
702 702 if not format in ['text', 'kcachegrind']:
703 703 ui.warn(_("unrecognized profiling format '%s'"
704 704 " - Ignored\n") % format)
705 705 format = 'text'
706 706
707 707 output = ui.config('profiling', 'output')
708 708
709 709 if output:
710 710 path = ui.expandpath(output)
711 711 ostream = open(path, 'wb')
712 712 else:
713 713 ostream = sys.stderr
714 714
715 715 try:
716 716 from mercurial import lsprof
717 717 except ImportError:
718 718 raise util.Abort(_(
719 719 'lsprof not available - install from '
720 720 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
721 721 p = lsprof.Profiler()
722 722 p.enable(subcalls=True)
723 723 try:
724 724 return checkargs()
725 725 finally:
726 726 p.disable()
727 727
728 728 if format == 'kcachegrind':
729 729 import lsprofcalltree
730 730 calltree = lsprofcalltree.KCacheGrind(p)
731 731 calltree.output(ostream)
732 732 else:
733 733 # format == 'text'
734 734 stats = lsprof.Stats(p.getstats())
735 735 stats.sort(field)
736 736 stats.pprint(limit=30, file=ostream, climit=climit)
737 737
738 738 if output:
739 739 ostream.close()
740 740 else:
741 741 return checkargs()
@@ -1,320 +1,321
1 1 """ Mercurial phases support code
2 2
3 3 ---
4 4
5 5 Copyright 2011 Pierre-Yves David <pierre-yves.david@ens-lyon.org>
6 6 Logilab SA <contact@logilab.fr>
7 7 Augie Fackler <durin42@gmail.com>
8 8
9 9 This software may be used and distributed according to the terms of the
10 10 GNU General Public License version 2 or any later version.
11 11
12 12 ---
13 13
14 14 This module implements most phase logic in mercurial.
15 15
16 16
17 17 Basic Concept
18 18 =============
19 19
20 20 A 'changeset phases' is an indicator that tells us how a changeset is
21 21 manipulated and communicated. The details of each phase is described below,
22 22 here we describe the properties they have in common.
23 23
24 24 Like bookmarks, phases are not stored in history and thus are not permanent and
25 25 leave no audit trail.
26 26
27 27 First, no changeset can be in two phases at once. Phases are ordered, so they
28 28 can be considered from lowest to highest. The default, lowest phase is 'public'
29 29 - this is the normal phase of existing changesets. A child changeset can not be
30 30 in a lower phase than its parents.
31 31
32 32 These phases share a hierarchy of traits:
33 33
34 34 immutable shared
35 35 public: X X
36 36 draft: X
37 37 secret:
38 38
39 39 local commits are draft by default
40 40
41 41 Phase movement and exchange
42 42 ============================
43 43
44 44 Phase data are exchanged by pushkey on pull and push. Some server have a
45 45 publish option set, we call them publishing server. Pushing to such server make
46 46 draft changeset publish.
47 47
48 48 A small list of fact/rules define the exchange of phase:
49 49
50 50 * old client never changes server states
51 51 * pull never changes server states
52 52 * publish and old server csets are seen as public by client
53 53
54 54 * Any secret changeset seens in another repository is lowered to at least draft
55 55
56 56
57 57 Here is the final table summing up the 49 possible usecase of phase exchange:
58 58
59 59 server
60 60 old publish non-publish
61 61 N X N D P N D P
62 62 old client
63 63 pull
64 64 N - X/X - X/D X/P - X/D X/P
65 65 X - X/X - X/D X/P - X/D X/P
66 66 push
67 67 X X/X X/X X/P X/P X/P X/D X/D X/P
68 68 new client
69 69 pull
70 70 N - P/X - P/D P/P - D/D P/P
71 71 D - P/X - P/D P/P - D/D P/P
72 72 P - P/X - P/D P/P - P/D P/P
73 73 push
74 74 D P/X P/X P/P P/P P/P D/D D/D P/P
75 75 P P/X P/X P/P P/P P/P P/P P/P P/P
76 76
77 77 Legend:
78 78
79 79 A/B = final state on client / state on server
80 80
81 81 * N = new/not present,
82 82 * P = public,
83 83 * D = draft,
84 84 * X = not tracked (ie: the old client or server has no internal way of
85 85 recording the phase.)
86 86
87 87 passive = only pushes
88 88
89 89
90 90 A cell here can be read like this:
91 91
92 92 "When a new client pushes a draft changeset (D) to a publishing server
93 93 where it's not present (N), it's marked public on both sides (P/P)."
94 94
95 95 Note: old client behave as publish server with Draft only content
96 96 - other people see it as public
97 97 - content is pushed as draft
98 98
99 99 """
100 100
101 101 import errno
102 102 from node import nullid, bin, hex, short
103 103 from i18n import _
104 104
105 105 allphases = public, draft, secret = range(3)
106 106 trackedphases = allphases[1:]
107 107 phasenames = ['public', 'draft', 'secret']
108 108
109 109 def readroots(repo):
110 110 """Read phase roots from disk"""
111 111 roots = [set() for i in allphases]
112 112 try:
113 113 f = repo.sopener('phaseroots')
114 114 try:
115 115 for line in f:
116 116 phase, nh = line.strip().split()
117 117 roots[int(phase)].add(bin(nh))
118 118 finally:
119 119 f.close()
120 120 except IOError, inst:
121 121 if inst.errno != errno.ENOENT:
122 122 raise
123 123 for f in repo._phasedefaults:
124 124 roots = f(repo, roots)
125 125 repo._dirtyphases = True
126 126 return roots
127 127
128 128 def writeroots(repo):
129 129 """Write phase roots from disk"""
130 130 f = repo.sopener('phaseroots', 'w', atomictemp=True)
131 131 try:
132 132 for phase, roots in enumerate(repo._phaseroots):
133 133 for h in roots:
134 134 f.write('%i %s\n' % (phase, hex(h)))
135 135 repo._dirtyphases = False
136 136 finally:
137 137 f.close()
138 138
139 139 def filterunknown(repo, phaseroots=None):
140 140 """remove unknown nodes from the phase boundary
141 141
142 142 no data is lost as unknown node only old data for their descentants
143 143 """
144 144 if phaseroots is None:
145 145 phaseroots = repo._phaseroots
146 146 nodemap = repo.changelog.nodemap # to filter unknown nodes
147 147 for phase, nodes in enumerate(phaseroots):
148 148 missing = [node for node in nodes if node not in nodemap]
149 149 if missing:
150 150 for mnode in missing:
151 msg = 'Removing unknown node %(n)s from %(p)i-phase boundary'
152 repo.ui.debug(msg, {'n': short(mnode), 'p': phase})
151 repo.ui.debug(
152 'removing unknown node %s from %i-phase boundary\n'
153 % (short(mnode), phase))
153 154 nodes.symmetric_difference_update(missing)
154 155 repo._dirtyphases = True
155 156
156 157 def advanceboundary(repo, targetphase, nodes):
157 158 """Add nodes to a phase changing other nodes phases if necessary.
158 159
159 160 This function move boundary *forward* this means that all nodes are set
160 161 in the target phase or kept in a *lower* phase.
161 162
162 163 Simplify boundary to contains phase roots only."""
163 164 delroots = [] # set of root deleted by this path
164 165 for phase in xrange(targetphase + 1, len(allphases)):
165 166 # filter nodes that are not in a compatible phase already
166 167 # XXX rev phase cache might have been invalidated by a previous loop
167 168 # XXX we need to be smarter here
168 169 nodes = [n for n in nodes if repo[n].phase() >= phase]
169 170 if not nodes:
170 171 break # no roots to move anymore
171 172 roots = repo._phaseroots[phase]
172 173 olds = roots.copy()
173 174 ctxs = list(repo.set('roots((%ln::) - (%ln::%ln))', olds, olds, nodes))
174 175 roots.clear()
175 176 roots.update(ctx.node() for ctx in ctxs)
176 177 if olds != roots:
177 178 # invalidate cache (we probably could be smarter here
178 179 if '_phaserev' in vars(repo):
179 180 del repo._phaserev
180 181 repo._dirtyphases = True
181 182 # some roots may need to be declared for lower phases
182 183 delroots.extend(olds - roots)
183 184 # declare deleted root in the target phase
184 185 if targetphase != 0:
185 186 retractboundary(repo, targetphase, delroots)
186 187
187 188
188 189 def retractboundary(repo, targetphase, nodes):
189 190 """Set nodes back to a phase changing other nodes phases if necessary.
190 191
191 192 This function move boundary *backward* this means that all nodes are set
192 193 in the target phase or kept in a *higher* phase.
193 194
194 195 Simplify boundary to contains phase roots only."""
195 196 currentroots = repo._phaseroots[targetphase]
196 197 newroots = [n for n in nodes if repo[n].phase() < targetphase]
197 198 if newroots:
198 199 currentroots.update(newroots)
199 200 ctxs = repo.set('roots(%ln::)', currentroots)
200 201 currentroots.intersection_update(ctx.node() for ctx in ctxs)
201 202 if '_phaserev' in vars(repo):
202 203 del repo._phaserev
203 204 repo._dirtyphases = True
204 205
205 206
206 207 def listphases(repo):
207 208 """List phases root for serialisation over pushkey"""
208 209 keys = {}
209 210 value = '%i' % draft
210 211 for root in repo._phaseroots[draft]:
211 212 keys[hex(root)] = value
212 213
213 214 if repo.ui.configbool('phases', 'publish', True):
214 215 # Add an extra data to let remote know we are a publishing repo.
215 216 # Publishing repo can't just pretend they are old repo. When pushing to
216 217 # a publishing repo, the client still need to push phase boundary
217 218 #
218 219 # Push do not only push changeset. It also push phase data. New
219 220 # phase data may apply to common changeset which won't be push (as they
220 221 # are common). Here is a very simple example:
221 222 #
222 223 # 1) repo A push changeset X as draft to repo B
223 224 # 2) repo B make changeset X public
224 225 # 3) repo B push to repo A. X is not pushed but the data that X as now
225 226 # public should
226 227 #
227 228 # The server can't handle it on it's own as it has no idea of client
228 229 # phase data.
229 230 keys['publishing'] = 'True'
230 231 return keys
231 232
232 233 def pushphase(repo, nhex, oldphasestr, newphasestr):
233 234 """List phases root for serialisation over pushkey"""
234 235 lock = repo.lock()
235 236 try:
236 237 currentphase = repo[nhex].phase()
237 238 newphase = abs(int(newphasestr)) # let's avoid negative index surprise
238 239 oldphase = abs(int(oldphasestr)) # let's avoid negative index surprise
239 240 if currentphase == oldphase and newphase < oldphase:
240 241 advanceboundary(repo, newphase, [bin(nhex)])
241 242 return 1
242 243 elif currentphase == newphase:
243 244 # raced, but got correct result
244 245 return 1
245 246 else:
246 247 return 0
247 248 finally:
248 249 lock.release()
249 250
250 251 def visibleheads(repo):
251 252 """return the set of visible head of this repo"""
252 253 # XXX we want a cache on this
253 254 sroots = repo._phaseroots[secret]
254 255 if sroots:
255 256 # XXX very slow revset. storing heads or secret "boundary" would help.
256 257 revset = repo.set('heads(not (%ln::))', sroots)
257 258
258 259 vheads = [ctx.node() for ctx in revset]
259 260 if not vheads:
260 261 vheads.append(nullid)
261 262 else:
262 263 vheads = repo.heads()
263 264 return vheads
264 265
265 266 def analyzeremotephases(repo, subset, roots):
266 267 """Compute phases heads and root in a subset of node from root dict
267 268
268 269 * subset is heads of the subset
269 270 * roots is {<nodeid> => phase} mapping. key and value are string.
270 271
271 272 Accept unknown element input
272 273 """
273 274 # build list from dictionary
274 275 draftroots = []
275 276 nodemap = repo.changelog.nodemap # to filter unknown nodes
276 277 for nhex, phase in roots.iteritems():
277 278 if nhex == 'publishing': # ignore data related to publish option
278 279 continue
279 280 node = bin(nhex)
280 281 phase = int(phase)
281 282 if phase == 0:
282 283 if node != nullid:
283 284 repo.ui.warn(_('ignoring inconsistent public root'
284 285 ' from remote: %s\n') % nhex)
285 286 elif phase == 1:
286 287 if node in nodemap:
287 288 draftroots.append(node)
288 289 else:
289 290 repo.ui.warn(_('ignoring unexpected root from remote: %i %s\n')
290 291 % (phase, nhex))
291 292 # compute heads
292 293 publicheads = newheads(repo, subset, draftroots)
293 294 return publicheads, draftroots
294 295
295 296 def newheads(repo, heads, roots):
296 297 """compute new head of a subset minus another
297 298
298 299 * `heads`: define the first subset
299 300 * `rroots`: define the second we substract to the first"""
300 301 revset = repo.set('heads((%ln + parents(%ln)) - (%ln::%ln))',
301 302 heads, roots, roots, heads)
302 303 return [c.node() for c in revset]
303 304
304 305
305 306 def newcommitphase(ui):
306 307 """helper to get the target phase of new commit
307 308
308 309 Handle all possible values for the phases.new-commit options.
309 310
310 311 """
311 312 v = ui.config('phases', 'new-commit', draft)
312 313 try:
313 314 return phasenames.index(v)
314 315 except ValueError:
315 316 try:
316 317 return int(v)
317 318 except ValueError:
318 319 msg = _("phases.new-commit: not a valid phase name ('%s')")
319 320 raise error.ConfigError(msg % v)
320 321
@@ -1,410 +1,416
1 1 $ "$TESTDIR/hghave" system-sh || exit 80
2 2
3 3 $ HGFOO=BAR; export HGFOO
4 4 $ cat >> $HGRCPATH <<EOF
5 5 > [extensions]
6 6 > graphlog=
7 7 >
8 8 > [alias]
9 9 > # should clobber ci but not commit (issue2993)
10 10 > ci = version
11 11 > myinit = init
12 12 > cleanstatus = status -c
13 13 > unknown = bargle
14 14 > ambiguous = s
15 15 > recursive = recursive
16 16 > nodefinition =
17 17 > no--cwd = status --cwd elsewhere
18 18 > no-R = status -R elsewhere
19 19 > no--repo = status --repo elsewhere
20 20 > no--repository = status --repository elsewhere
21 21 > mylog = log
22 22 > lognull = log -r null
23 23 > shortlog = log --template '{rev} {node|short} | {date|isodate}\n'
24 24 > positional = log --template '{\$2} {\$1} | {date|isodate}\n'
25 25 > dln = lognull --debug
26 26 > nousage = rollback
27 27 > put = export -r 0 -o "\$FOO/%R.diff"
28 28 > blank = !echo
29 29 > self = !echo '\$0'
30 30 > echo = !echo '\$@'
31 31 > echo1 = !echo '\$1'
32 32 > echo2 = !echo '\$2'
33 33 > echo13 = !echo '\$1' '\$3'
34 34 > count = !hg log -r '\$@' --template='.' | wc -c | sed -e 's/ //g'
35 35 > mcount = !hg log \$@ --template='.' | wc -c | sed -e 's/ //g'
36 36 > rt = root
37 37 > tglog = glog --template "{rev}:{node|short}: '{desc}' {branches}\n"
38 38 > idalias = id
39 39 > idaliaslong = id
40 40 > idaliasshell = !echo test
41 41 > parentsshell1 = !echo one
42 42 > parentsshell2 = !echo two
43 43 > escaped1 = !echo 'test\$\$test'
44 44 > escaped2 = !echo "HGFOO is \$\$HGFOO"
45 45 > escaped3 = !echo "\$1 is \$\$\$1"
46 46 > escaped4 = !echo '\$\$0' '\$\$@'
47 47 >
48 48 > [defaults]
49 49 > mylog = -q
50 50 > lognull = -q
51 51 > log = -v
52 52 > EOF
53 53
54 54
55 55 basic
56 56
57 57 $ hg myinit alias
58 58
59 59
60 60 unknown
61 61
62 62 $ hg unknown
63 63 alias 'unknown' resolves to unknown command 'bargle'
64 64 $ hg help unknown
65 65 alias 'unknown' resolves to unknown command 'bargle'
66 66
67 67
68 68 ambiguous
69 69
70 70 $ hg ambiguous
71 71 alias 'ambiguous' resolves to ambiguous command 's'
72 72 $ hg help ambiguous
73 73 alias 'ambiguous' resolves to ambiguous command 's'
74 74
75 75
76 76 recursive
77 77
78 78 $ hg recursive
79 79 alias 'recursive' resolves to unknown command 'recursive'
80 80 $ hg help recursive
81 81 alias 'recursive' resolves to unknown command 'recursive'
82 82
83 83
84 84 no definition
85 85
86 86 $ hg nodef
87 87 no definition for alias 'nodefinition'
88 88 $ hg help nodef
89 89 no definition for alias 'nodefinition'
90 90
91 91
92 92 invalid options
93 93
94 94 $ hg no--cwd
95 95 error in definition for alias 'no--cwd': --cwd may only be given on the command line
96 96 $ hg help no--cwd
97 97 error in definition for alias 'no--cwd': --cwd may only be given on the command line
98 98 $ hg no-R
99 99 error in definition for alias 'no-R': -R may only be given on the command line
100 100 $ hg help no-R
101 101 error in definition for alias 'no-R': -R may only be given on the command line
102 102 $ hg no--repo
103 103 error in definition for alias 'no--repo': --repo may only be given on the command line
104 104 $ hg help no--repo
105 105 error in definition for alias 'no--repo': --repo may only be given on the command line
106 106 $ hg no--repository
107 107 error in definition for alias 'no--repository': --repository may only be given on the command line
108 108 $ hg help no--repository
109 109 error in definition for alias 'no--repository': --repository may only be given on the command line
110 110
111 111 $ cd alias
112 112
113 113
114 114 no usage
115 115
116 116 $ hg nousage
117 117 no rollback information available
118 118
119 119 $ echo foo > foo
120 120 $ hg commit -Amfoo
121 121 adding foo
122 122
123 123
124 124 with opts
125 125
126 126 $ hg cleanst
127 127 C foo
128 128
129 129
130 130 with opts and whitespace
131 131
132 132 $ hg shortlog
133 133 0 e63c23eaa88a | 1970-01-01 00:00 +0000
134 134
135 135 positional arguments
136 136
137 $ hg positional
138 abort: too few arguments for command alias
139 [255]
140 $ hg positional a
141 abort: too few arguments for command alias
142 [255]
137 143 $ hg positional 'node|short' rev
138 144 0 e63c23eaa88a | 1970-01-01 00:00 +0000
139 145
140 146 interaction with defaults
141 147
142 148 $ hg mylog
143 149 0:e63c23eaa88a
144 150 $ hg lognull
145 151 -1:000000000000
146 152
147 153
148 154 properly recursive
149 155
150 156 $ hg dln
151 157 changeset: -1:0000000000000000000000000000000000000000
152 158 parent: -1:0000000000000000000000000000000000000000
153 159 parent: -1:0000000000000000000000000000000000000000
154 160 manifest: -1:0000000000000000000000000000000000000000
155 161 user:
156 162 date: Thu Jan 01 00:00:00 1970 +0000
157 163 extra: branch=default
158 164
159 165
160 166
161 167 path expanding
162 168
163 169 $ FOO=`pwd` hg put
164 170 $ cat 0.diff
165 171 # HG changeset patch
166 172 # User test
167 173 # Date 0 0
168 174 # Node ID e63c23eaa88ae77967edcf4ea194d31167c478b0
169 175 # Parent 0000000000000000000000000000000000000000
170 176 foo
171 177
172 178 diff -r 000000000000 -r e63c23eaa88a foo
173 179 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
174 180 +++ b/foo Thu Jan 01 00:00:00 1970 +0000
175 181 @@ -0,0 +1,1 @@
176 182 +foo
177 183
178 184
179 185 simple shell aliases
180 186
181 187 $ hg blank
182 188
183 189 $ hg blank foo
184 190
185 191 $ hg self
186 192 self
187 193 $ hg echo
188 194
189 195 $ hg echo foo
190 196 foo
191 197 $ hg echo 'test $2' foo
192 198 test $2 foo
193 199 $ hg echo1 foo bar baz
194 200 foo
195 201 $ hg echo2 foo bar baz
196 202 bar
197 203 $ hg echo13 foo bar baz test
198 204 foo baz
199 205 $ hg echo2 foo
200 206
201 207 $ echo bar > bar
202 208 $ hg commit -qA -m bar
203 209 $ hg count .
204 210 1
205 211 $ hg count 'branch(default)'
206 212 2
207 213 $ hg mcount -r '"branch(default)"'
208 214 2
209 215
210 216 $ hg tglog
211 217 @ 1:7e7f92de180e: 'bar'
212 218 |
213 219 o 0:e63c23eaa88a: 'foo'
214 220
215 221
216 222
217 223 shadowing
218 224
219 225 $ hg i
220 226 hg: command 'i' is ambiguous:
221 227 idalias idaliaslong idaliasshell identify import incoming init
222 228 [255]
223 229 $ hg id
224 230 7e7f92de180e tip
225 231 $ hg ida
226 232 hg: command 'ida' is ambiguous:
227 233 idalias idaliaslong idaliasshell
228 234 [255]
229 235 $ hg idalias
230 236 7e7f92de180e tip
231 237 $ hg idaliasl
232 238 7e7f92de180e tip
233 239 $ hg idaliass
234 240 test
235 241 $ hg parentsshell
236 242 hg: command 'parentsshell' is ambiguous:
237 243 parentsshell1 parentsshell2
238 244 [255]
239 245 $ hg parentsshell1
240 246 one
241 247 $ hg parentsshell2
242 248 two
243 249
244 250
245 251 shell aliases with global options
246 252
247 253 $ hg init sub
248 254 $ cd sub
249 255 $ hg count 'branch(default)'
250 256 0
251 257 $ hg -v count 'branch(default)'
252 258 0
253 259 $ hg -R .. count 'branch(default)'
254 260 0
255 261 $ hg --cwd .. count 'branch(default)'
256 262 2
257 263 $ hg echo --cwd ..
258 264
259 265
260 266
261 267 repo specific shell aliases
262 268
263 269 $ cat >> .hg/hgrc <<EOF
264 270 > [alias]
265 271 > subalias = !echo sub \$@
266 272 > EOF
267 273 $ cat >> ../.hg/hgrc <<EOF
268 274 > [alias]
269 275 > mainalias = !echo main \$@
270 276 > EOF
271 277
272 278
273 279 shell alias defined in current repo
274 280
275 281 $ hg subalias
276 282 sub
277 283 $ hg --cwd .. subalias > /dev/null
278 284 hg: unknown command 'subalias'
279 285 [255]
280 286 $ hg -R .. subalias > /dev/null
281 287 hg: unknown command 'subalias'
282 288 [255]
283 289
284 290
285 291 shell alias defined in other repo
286 292
287 293 $ hg mainalias > /dev/null
288 294 hg: unknown command 'mainalias'
289 295 [255]
290 296 $ hg -R .. mainalias
291 297 main
292 298 $ hg --cwd .. mainalias
293 299 main
294 300
295 301
296 302 shell aliases with escaped $ chars
297 303
298 304 $ hg escaped1
299 305 test$test
300 306 $ hg escaped2
301 307 HGFOO is BAR
302 308 $ hg escaped3 HGFOO
303 309 HGFOO is BAR
304 310 $ hg escaped4 test
305 311 $0 $@
306 312
307 313
308 314 invalid arguments
309 315
310 316 $ hg rt foo
311 317 hg rt: invalid arguments
312 318 hg rt
313 319
314 320 alias for: hg root
315 321
316 322 use "hg help rt" to show the full help text
317 323 [255]
318 324
319 325 invalid global arguments for normal commands, aliases, and shell aliases
320 326
321 327 $ hg --invalid root
322 328 hg: option --invalid not recognized
323 329 Mercurial Distributed SCM
324 330
325 331 basic commands:
326 332
327 333 add add the specified files on the next commit
328 334 annotate show changeset information by line for each file
329 335 clone make a copy of an existing repository
330 336 commit commit the specified files or all outstanding changes
331 337 diff diff repository (or selected files)
332 338 export dump the header and diffs for one or more changesets
333 339 forget forget the specified files on the next commit
334 340 init create a new repository in the given directory
335 341 log show revision history of entire repository or files
336 342 merge merge working directory with another revision
337 343 phase set or show the current phase name
338 344 pull pull changes from the specified source
339 345 push push changes to the specified destination
340 346 remove remove the specified files on the next commit
341 347 serve start stand-alone webserver
342 348 status show changed files in the working directory
343 349 summary summarize working directory state
344 350 update update working directory (or switch revisions)
345 351
346 352 use "hg help" for the full list of commands or "hg -v" for details
347 353 [255]
348 354 $ hg --invalid mylog
349 355 hg: option --invalid not recognized
350 356 Mercurial Distributed SCM
351 357
352 358 basic commands:
353 359
354 360 add add the specified files on the next commit
355 361 annotate show changeset information by line for each file
356 362 clone make a copy of an existing repository
357 363 commit commit the specified files or all outstanding changes
358 364 diff diff repository (or selected files)
359 365 export dump the header and diffs for one or more changesets
360 366 forget forget the specified files on the next commit
361 367 init create a new repository in the given directory
362 368 log show revision history of entire repository or files
363 369 merge merge working directory with another revision
364 370 phase set or show the current phase name
365 371 pull pull changes from the specified source
366 372 push push changes to the specified destination
367 373 remove remove the specified files on the next commit
368 374 serve start stand-alone webserver
369 375 status show changed files in the working directory
370 376 summary summarize working directory state
371 377 update update working directory (or switch revisions)
372 378
373 379 use "hg help" for the full list of commands or "hg -v" for details
374 380 [255]
375 381 $ hg --invalid blank
376 382 hg: option --invalid not recognized
377 383 Mercurial Distributed SCM
378 384
379 385 basic commands:
380 386
381 387 add add the specified files on the next commit
382 388 annotate show changeset information by line for each file
383 389 clone make a copy of an existing repository
384 390 commit commit the specified files or all outstanding changes
385 391 diff diff repository (or selected files)
386 392 export dump the header and diffs for one or more changesets
387 393 forget forget the specified files on the next commit
388 394 init create a new repository in the given directory
389 395 log show revision history of entire repository or files
390 396 merge merge working directory with another revision
391 397 phase set or show the current phase name
392 398 pull pull changes from the specified source
393 399 push push changes to the specified destination
394 400 remove remove the specified files on the next commit
395 401 serve start stand-alone webserver
396 402 status show changed files in the working directory
397 403 summary summarize working directory state
398 404 update update working directory (or switch revisions)
399 405
400 406 use "hg help" for the full list of commands or "hg -v" for details
401 407 [255]
402 408
403 409 This should show id:
404 410
405 411 $ hg --config alias.log='id' log
406 412 000000000000 tip
407 413
408 414 This shouldn't:
409 415
410 416 $ hg --config alias.log='id' history
General Comments 0
You need to be logged in to leave comments. Login now