##// END OF EJS Templates
add HGRCPATH env var, list of places to look for hgrc files....
Vadim Gelfer -
r1951:696230e5 default
parent child Browse files
Show More
@@ -1,213 +1,223 b''
1 1 HG(1)
2 2 =====
3 3 Matt Mackall <mpm@selenic.com>
4 4
5 5 NAME
6 6 ----
7 7 hg - Mercurial source code management system
8 8
9 9 SYNOPSIS
10 10 --------
11 11 'hg' [-v -d -q -y] <command> [command options] [files]
12 12
13 13 DESCRIPTION
14 14 -----------
15 15 The hg(1) command provides a command line interface to the Mercurial system.
16 16
17 17 COMMAND ELEMENTS
18 18 ----------------
19 19
20 20 files ...::
21 21 indicates one or more filename or relative path filenames; see
22 22 "FILE NAME PATTERNS" for information on pattern matching
23 23
24 24 path::
25 25 indicates a path on the local machine
26 26
27 27 revision::
28 28 indicates a changeset which can be specified as a changeset revision
29 29 number, a tag, or a unique substring of the changeset hash value
30 30
31 31 repository path::
32 32 either the pathname of a local repository or the URI of a remote
33 33 repository. There are two available URI protocols, http:// which is
34 34 fast and the old-http:// protocol which is much slower but does not
35 35 require a special server on the web host.
36 36
37 37
38 38 include::hg.1.gendoc.txt[]
39 39
40 40 FILE NAME PATTERNS
41 41 ------------------
42 42
43 43 Mercurial accepts several notations for identifying one or more
44 44 files at a time.
45 45
46 46 By default, Mercurial treats filenames as shell-style extended
47 47 glob patterns.
48 48
49 49 Alternate pattern notations must be specified explicitly.
50 50
51 51 To use a plain path name without any pattern matching, start a
52 52 name with "path:". These path names must match completely, from
53 53 the root of the current repository.
54 54
55 55 To use an extended glob, start a name with "glob:". Globs are
56 56 rooted at the current directory; a glob such as "*.c" will match
57 57 files ending in ".c" in the current directory only.
58 58
59 59 The supported glob syntax extensions are "**" to match any string
60 60 across path separators, and "{a,b}" to mean "a or b".
61 61
62 62 To use a Perl/Python regular expression, start a name with "re:".
63 63 Regexp pattern matching is anchored at the root of the repository.
64 64
65 65 Plain examples:
66 66
67 67 path:foo/bar a name bar in a directory named foo in the root of
68 68 the repository
69 69 path:path:name a file or directory named "path:name"
70 70
71 71 Glob examples:
72 72
73 73 glob:*.c any name ending in ".c" in the current directory
74 74 *.c any name ending in ".c" in the current directory
75 75 **.c any name ending in ".c" in the current directory, or
76 76 any subdirectory
77 77 foo/*.c any name ending in ".c" in the directory foo
78 78 foo/**.c any name ending in ".c" in the directory foo, or any
79 79 subdirectory
80 80
81 81 Regexp examples:
82 82
83 83 re:.*\.c$ any name ending in ".c", anywhere in the repository
84 84
85 85
86 86 SPECIFYING SINGLE REVISIONS
87 87 ---------------------------
88 88
89 89 Mercurial accepts several notations for identifying individual
90 90 revisions.
91 91
92 92 A plain integer is treated as a revision number. Negative
93 93 integers are treated as offsets from the tip, with -1 denoting the
94 94 tip.
95 95
96 96 A 40-digit hexadecimal string is treated as a unique revision
97 97 identifier.
98 98
99 99 A hexadecimal string less than 40 characters long is treated as a
100 100 unique revision identifier, and referred to as a short-form
101 101 identifier. A short-form identifier is only valid if it is the
102 102 prefix of one full-length identifier.
103 103
104 104 Any other string is treated as a tag name, which is a symbolic
105 105 name associated with a revision identifier. Tag names may not
106 106 contain the ":" character.
107 107
108 108 The reserved name "tip" is a special tag that always identifies
109 109 the most recent revision.
110 110
111 111 SPECIFYING MULTIPLE REVISIONS
112 112 -----------------------------
113 113
114 114 When Mercurial accepts more than one revision, they may be
115 115 specified individually, or provided as a continuous range,
116 116 separated by the ":" character.
117 117
118 118 The syntax of range notation is [BEGIN]:[END], where BEGIN and END
119 119 are revision identifiers. Both BEGIN and END are optional. If
120 120 BEGIN is not specified, it defaults to revision number 0. If END
121 121 is not specified, it defaults to the tip. The range ":" thus
122 122 means "all revisions".
123 123
124 124 If BEGIN is greater than END, revisions are treated in reverse
125 125 order.
126 126
127 127 A range acts as a closed interval. This means that a range of 3:5
128 128 gives 3, 4 and 5. Similarly, a range of 4:2 gives 4, 3, and 2.
129 129
130 130 ENVIRONMENT VARIABLES
131 131 ---------------------
132 132
133 133 HGEDITOR::
134 134 This is the name of the editor to use when committing. Defaults to the
135 135 value of EDITOR.
136 136
137 137 (deprecated, use .hgrc)
138 138
139 139 HGMERGE::
140 140 An executable to use for resolving merge conflicts. The program
141 141 will be executed with three arguments: local file, remote file,
142 142 ancestor file.
143 143
144 144 The default program is "hgmerge", which is a shell script provided
145 145 by Mercurial with some sensible defaults.
146 146
147 147 (deprecated, use .hgrc)
148 148
149 HGRCPATH::
150 A list of files or directories to search for hgrc files. Item
151 separator is ":" on Unix, ";" on Windows. If HGRCPATH is not set,
152 platform default search path is used. If empty, only .hg/hgrc of
153 current repository is read.
154
155 For each element in path, if a directory, all entries in directory
156 ending with ".rc" are added to path. Else, element itself is
157 added to path.
158
149 159 HGUSER::
150 160 This is the string used for the author of a commit.
151 161
152 162 (deprecated, use .hgrc)
153 163
154 164 EMAIL::
155 165 If HGUSER is not set, this will be used as the author for a commit.
156 166
157 167 LOGNAME::
158 168 If neither HGUSER nor EMAIL is set, LOGNAME will be used (with
159 169 '@hostname' appended) as the author value for a commit.
160 170
161 171 EDITOR::
162 172 This is the name of the editor used in the hgmerge script. It will be
163 173 used for commit messages if HGEDITOR isn't set. Defaults to 'vi'.
164 174
165 175 PYTHONPATH::
166 176 This is used by Python to find imported modules and may need to be set
167 177 appropriately if Mercurial is not installed system-wide.
168 178
169 179 FILES
170 180 -----
171 181 .hgignore::
172 182 This file contains regular expressions (one per line) that describe file
173 183 names that should be ignored by hg.
174 184
175 185 .hgtags::
176 186 This file contains changeset hash values and text tag names (one of each
177 187 separated by spaces) that correspond to tagged versions of the repository
178 188 contents.
179 189
180 190 /etc/mercurial/hgrc, $HOME/.hgrc, .hg/hgrc::
181 191 This file contains defaults and configuration. Values in .hg/hgrc
182 192 override those in $HOME/.hgrc, and these override settings made in the
183 193 global /etc/mercurial/hgrc configuration. See hgrc(5) for details of
184 194 the contents and format of these files.
185 195
186 196 BUGS
187 197 ----
188 198 Probably lots, please post them to the mailing list (See Resources below)
189 199 when you find them.
190 200
191 201 SEE ALSO
192 202 --------
193 203 hgrc(5)
194 204
195 205 AUTHOR
196 206 ------
197 207 Written by Matt Mackall <mpm@selenic.com>
198 208
199 209 RESOURCES
200 210 ---------
201 211 http://selenic.com/mercurial[Main Web Site]
202 212
203 213 http://www.serpentine.com/mercurial[Wiki site]
204 214
205 215 http://selenic.com/hg[Source code repository]
206 216
207 217 http://selenic.com/mailman/listinfo/mercurial[Mailing list]
208 218
209 219 COPYING
210 220 -------
211 221 Copyright \(C) 2005 Matt Mackall.
212 222 Free use of this software is granted under the terms of the GNU General
213 223 Public License (GPL).
@@ -1,218 +1,218 b''
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import ConfigParser
9 9 from i18n import gettext as _
10 10 from demandload import *
11 11 demandload(globals(), "os re socket sys util")
12 12
13 13 class ui(object):
14 14 def __init__(self, verbose=False, debug=False, quiet=False,
15 15 interactive=True, parentui=None):
16 16 self.overlay = {}
17 17 if parentui is None:
18 18 # this is the parent of all ui children
19 19 self.parentui = None
20 20 self.cdata = ConfigParser.SafeConfigParser()
21 self.readconfig(util.rcpath)
21 self.readconfig(util.rcpath())
22 22
23 23 self.quiet = self.configbool("ui", "quiet")
24 24 self.verbose = self.configbool("ui", "verbose")
25 25 self.debugflag = self.configbool("ui", "debug")
26 26 self.interactive = self.configbool("ui", "interactive", True)
27 27
28 28 self.updateopts(verbose, debug, quiet, interactive)
29 29 self.diffcache = None
30 30 else:
31 31 # parentui may point to an ui object which is already a child
32 32 self.parentui = parentui.parentui or parentui
33 33 parent_cdata = self.parentui.cdata
34 34 self.cdata = ConfigParser.SafeConfigParser(parent_cdata.defaults())
35 35 # make interpolation work
36 36 for section in parent_cdata.sections():
37 37 self.cdata.add_section(section)
38 38 for name, value in parent_cdata.items(section, raw=True):
39 39 self.cdata.set(section, name, value)
40 40
41 41 def __getattr__(self, key):
42 42 return getattr(self.parentui, key)
43 43
44 44 def updateopts(self, verbose=False, debug=False, quiet=False,
45 45 interactive=True):
46 46 self.quiet = (self.quiet or quiet) and not verbose and not debug
47 47 self.verbose = (self.verbose or verbose) or debug
48 48 self.debugflag = (self.debugflag or debug)
49 49 self.interactive = (self.interactive and interactive)
50 50
51 51 def readconfig(self, fn, root=None):
52 52 if isinstance(fn, basestring):
53 53 fn = [fn]
54 54 for f in fn:
55 55 try:
56 56 self.cdata.read(f)
57 57 except ConfigParser.ParsingError, inst:
58 58 raise util.Abort(_("Failed to parse %s\n%s") % (f, inst))
59 59 # translate paths relative to root (or home) into absolute paths
60 60 if root is None:
61 61 root = os.path.expanduser('~')
62 62 for name, path in self.configitems("paths"):
63 63 if path and path.find("://") == -1 and not os.path.isabs(path):
64 64 self.cdata.set("paths", name, os.path.join(root, path))
65 65
66 66 def setconfig(self, section, name, val):
67 67 self.overlay[(section, name)] = val
68 68
69 69 def config(self, section, name, default=None):
70 70 if self.overlay.has_key((section, name)):
71 71 return self.overlay[(section, name)]
72 72 if self.cdata.has_option(section, name):
73 73 try:
74 74 return self.cdata.get(section, name)
75 75 except ConfigParser.InterpolationError, inst:
76 76 raise util.Abort(_("Error in configuration:\n%s") % inst)
77 77 if self.parentui is None:
78 78 return default
79 79 else:
80 80 return self.parentui.config(section, name, default)
81 81
82 82 def configbool(self, section, name, default=False):
83 83 if self.overlay.has_key((section, name)):
84 84 return self.overlay[(section, name)]
85 85 if self.cdata.has_option(section, name):
86 86 try:
87 87 return self.cdata.getboolean(section, name)
88 88 except ConfigParser.InterpolationError, inst:
89 89 raise util.Abort(_("Error in configuration:\n%s") % inst)
90 90 if self.parentui is None:
91 91 return default
92 92 else:
93 93 return self.parentui.configbool(section, name, default)
94 94
95 95 def configitems(self, section):
96 96 items = {}
97 97 if self.parentui is not None:
98 98 items = dict(self.parentui.configitems(section))
99 99 if self.cdata.has_section(section):
100 100 try:
101 101 items.update(dict(self.cdata.items(section)))
102 102 except ConfigParser.InterpolationError, inst:
103 103 raise util.Abort(_("Error in configuration:\n%s") % inst)
104 104 x = items.items()
105 105 x.sort()
106 106 return x
107 107
108 108 def walkconfig(self, seen=None):
109 109 if seen is None:
110 110 seen = {}
111 111 for (section, name), value in self.overlay.iteritems():
112 112 yield section, name, value
113 113 seen[section, name] = 1
114 114 for section in self.cdata.sections():
115 115 for name, value in self.cdata.items(section):
116 116 if (section, name) in seen: continue
117 117 yield section, name, value.replace('\n', '\\n')
118 118 seen[section, name] = 1
119 119 if self.parentui is not None:
120 120 for parent in self.parentui.walkconfig(seen):
121 121 yield parent
122 122
123 123 def extensions(self):
124 124 return self.configitems("extensions")
125 125
126 126 def diffopts(self):
127 127 if self.diffcache:
128 128 return self.diffcache
129 129 ret = { 'showfunc' : True, 'ignorews' : False}
130 130 for x in self.configitems("diff"):
131 131 k = x[0].lower()
132 132 v = x[1]
133 133 if v:
134 134 v = v.lower()
135 135 if v == 'true':
136 136 value = True
137 137 else:
138 138 value = False
139 139 ret[k] = value
140 140 self.diffcache = ret
141 141 return ret
142 142
143 143 def username(self):
144 144 return (os.environ.get("HGUSER") or
145 145 self.config("ui", "username") or
146 146 os.environ.get("EMAIL") or
147 147 (os.environ.get("LOGNAME",
148 148 os.environ.get("USERNAME", "unknown"))
149 149 + '@' + socket.getfqdn()))
150 150
151 151 def shortuser(self, user):
152 152 """Return a short representation of a user name or email address."""
153 153 if not self.verbose: user = util.shortuser(user)
154 154 return user
155 155
156 156 def expandpath(self, loc):
157 157 """Return repository location relative to cwd or from [paths]"""
158 158 if loc.find("://") != -1 or os.path.exists(loc):
159 159 return loc
160 160
161 161 return self.config("paths", loc, loc)
162 162
163 163 def write(self, *args):
164 164 for a in args:
165 165 sys.stdout.write(str(a))
166 166
167 167 def write_err(self, *args):
168 168 if not sys.stdout.closed: sys.stdout.flush()
169 169 for a in args:
170 170 sys.stderr.write(str(a))
171 171
172 172 def flush(self):
173 173 try:
174 174 sys.stdout.flush()
175 175 finally:
176 176 sys.stderr.flush()
177 177
178 178 def readline(self):
179 179 return sys.stdin.readline()[:-1]
180 180 def prompt(self, msg, pat, default="y"):
181 181 if not self.interactive: return default
182 182 while 1:
183 183 self.write(msg, " ")
184 184 r = self.readline()
185 185 if re.match(pat, r):
186 186 return r
187 187 else:
188 188 self.write(_("unrecognized response\n"))
189 189 def status(self, *msg):
190 190 if not self.quiet: self.write(*msg)
191 191 def warn(self, *msg):
192 192 self.write_err(*msg)
193 193 def note(self, *msg):
194 194 if self.verbose: self.write(*msg)
195 195 def debug(self, *msg):
196 196 if self.debugflag: self.write(*msg)
197 197 def edit(self, text):
198 198 import tempfile
199 199 (fd, name) = tempfile.mkstemp("hg")
200 200 f = os.fdopen(fd, "w")
201 201 f.write(text)
202 202 f.close()
203 203
204 204 editor = (os.environ.get("HGEDITOR") or
205 205 self.config("ui", "editor") or
206 206 os.environ.get("EDITOR", "vi"))
207 207
208 208 os.environ["HGUSER"] = self.username()
209 209 util.system("%s \"%s\"" % (editor, name),
210 210 environ={'HGUSER': self.username()},
211 211 onerr=util.Abort, errprefix=_("edit failed"))
212 212
213 213 t = open(name).read()
214 214 t = re.sub("(?m)^HG:.*\n", "", t)
215 215
216 216 os.unlink(name)
217 217
218 218 return t
@@ -1,770 +1,802 b''
1 1 """
2 2 util.py - Mercurial utility functions and platform specfic implementations
3 3
4 4 Copyright 2005 K. Thananchayan <thananck@yahoo.com>
5 5
6 6 This software may be used and distributed according to the terms
7 7 of the GNU General Public License, incorporated herein by reference.
8 8
9 9 This contains helper routines that are independent of the SCM core and hide
10 10 platform-specific details from the core.
11 11 """
12 12
13 13 import os, errno
14 14 from i18n import gettext as _
15 15 from demandload import *
16 16 demandload(globals(), "cStringIO errno popen2 re shutil sys tempfile")
17 17 demandload(globals(), "threading time")
18 18
19 19 def pipefilter(s, cmd):
20 20 '''filter string S through command CMD, returning its output'''
21 21 (pout, pin) = popen2.popen2(cmd, -1, 'b')
22 22 def writer():
23 23 pin.write(s)
24 24 pin.close()
25 25
26 26 # we should use select instead on UNIX, but this will work on most
27 27 # systems, including Windows
28 28 w = threading.Thread(target=writer)
29 29 w.start()
30 30 f = pout.read()
31 31 pout.close()
32 32 w.join()
33 33 return f
34 34
35 35 def tempfilter(s, cmd):
36 36 '''filter string S through a pair of temporary files with CMD.
37 37 CMD is used as a template to create the real command to be run,
38 38 with the strings INFILE and OUTFILE replaced by the real names of
39 39 the temporary files generated.'''
40 40 inname, outname = None, None
41 41 try:
42 42 infd, inname = tempfile.mkstemp(prefix='hgfin')
43 43 fp = os.fdopen(infd, 'wb')
44 44 fp.write(s)
45 45 fp.close()
46 46 outfd, outname = tempfile.mkstemp(prefix='hgfout')
47 47 os.close(outfd)
48 48 cmd = cmd.replace('INFILE', inname)
49 49 cmd = cmd.replace('OUTFILE', outname)
50 50 code = os.system(cmd)
51 51 if code: raise Abort(_("command '%s' failed: %s") %
52 52 (cmd, explain_exit(code)))
53 53 return open(outname, 'rb').read()
54 54 finally:
55 55 try:
56 56 if inname: os.unlink(inname)
57 57 except: pass
58 58 try:
59 59 if outname: os.unlink(outname)
60 60 except: pass
61 61
62 62 filtertable = {
63 63 'tempfile:': tempfilter,
64 64 'pipe:': pipefilter,
65 65 }
66 66
67 67 def filter(s, cmd):
68 68 "filter a string through a command that transforms its input to its output"
69 69 for name, fn in filtertable.iteritems():
70 70 if cmd.startswith(name):
71 71 return fn(s, cmd[len(name):].lstrip())
72 72 return pipefilter(s, cmd)
73 73
74 74 def patch(strip, patchname, ui):
75 75 """apply the patch <patchname> to the working directory.
76 76 a list of patched files is returned"""
77 77 fp = os.popen('patch -p%d < "%s"' % (strip, patchname))
78 78 files = {}
79 79 for line in fp:
80 80 line = line.rstrip()
81 81 ui.status("%s\n" % line)
82 82 if line.startswith('patching file '):
83 83 pf = parse_patch_output(line)
84 84 files.setdefault(pf, 1)
85 85 code = fp.close()
86 86 if code:
87 87 raise Abort(_("patch command failed: %s") % explain_exit(code)[0])
88 88 return files.keys()
89 89
90 90 def binary(s):
91 91 """return true if a string is binary data using diff's heuristic"""
92 92 if s and '\0' in s[:4096]:
93 93 return True
94 94 return False
95 95
96 96 def unique(g):
97 97 """return the uniq elements of iterable g"""
98 98 seen = {}
99 99 for f in g:
100 100 if f not in seen:
101 101 seen[f] = 1
102 102 yield f
103 103
104 104 class Abort(Exception):
105 105 """Raised if a command needs to print an error and exit."""
106 106
107 107 def always(fn): return True
108 108 def never(fn): return False
109 109
110 110 def patkind(name, dflt_pat='glob'):
111 111 """Split a string into an optional pattern kind prefix and the
112 112 actual pattern."""
113 113 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
114 114 if name.startswith(prefix + ':'): return name.split(':', 1)
115 115 return dflt_pat, name
116 116
117 117 def globre(pat, head='^', tail='$'):
118 118 "convert a glob pattern into a regexp"
119 119 i, n = 0, len(pat)
120 120 res = ''
121 121 group = False
122 122 def peek(): return i < n and pat[i]
123 123 while i < n:
124 124 c = pat[i]
125 125 i = i+1
126 126 if c == '*':
127 127 if peek() == '*':
128 128 i += 1
129 129 res += '.*'
130 130 else:
131 131 res += '[^/]*'
132 132 elif c == '?':
133 133 res += '.'
134 134 elif c == '[':
135 135 j = i
136 136 if j < n and pat[j] in '!]':
137 137 j += 1
138 138 while j < n and pat[j] != ']':
139 139 j += 1
140 140 if j >= n:
141 141 res += '\\['
142 142 else:
143 143 stuff = pat[i:j].replace('\\','\\\\')
144 144 i = j + 1
145 145 if stuff[0] == '!':
146 146 stuff = '^' + stuff[1:]
147 147 elif stuff[0] == '^':
148 148 stuff = '\\' + stuff
149 149 res = '%s[%s]' % (res, stuff)
150 150 elif c == '{':
151 151 group = True
152 152 res += '(?:'
153 153 elif c == '}' and group:
154 154 res += ')'
155 155 group = False
156 156 elif c == ',' and group:
157 157 res += '|'
158 158 else:
159 159 res += re.escape(c)
160 160 return head + res + tail
161 161
162 162 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
163 163
164 164 def pathto(n1, n2):
165 165 '''return the relative path from one place to another.
166 166 this returns a path in the form used by the local filesystem, not hg.'''
167 167 if not n1: return localpath(n2)
168 168 a, b = n1.split('/'), n2.split('/')
169 169 a.reverse()
170 170 b.reverse()
171 171 while a and b and a[-1] == b[-1]:
172 172 a.pop()
173 173 b.pop()
174 174 b.reverse()
175 175 return os.sep.join((['..'] * len(a)) + b)
176 176
177 177 def canonpath(root, cwd, myname):
178 178 """return the canonical path of myname, given cwd and root"""
179 179 if root == os.sep:
180 180 rootsep = os.sep
181 181 else:
182 182 rootsep = root + os.sep
183 183 name = myname
184 184 if not name.startswith(os.sep):
185 185 name = os.path.join(root, cwd, name)
186 186 name = os.path.normpath(name)
187 187 if name.startswith(rootsep):
188 188 return pconvert(name[len(rootsep):])
189 189 elif name == root:
190 190 return ''
191 191 else:
192 192 raise Abort('%s not under root' % myname)
193 193
194 194 def matcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
195 195 return _matcher(canonroot, cwd, names, inc, exc, head, 'glob', src)
196 196
197 197 def cmdmatcher(canonroot, cwd='', names=['.'], inc=[], exc=[], head='', src=None):
198 198 if os.name == 'nt':
199 199 dflt_pat = 'glob'
200 200 else:
201 201 dflt_pat = 'relpath'
202 202 return _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src)
203 203
204 204 def _matcher(canonroot, cwd, names, inc, exc, head, dflt_pat, src):
205 205 """build a function to match a set of file patterns
206 206
207 207 arguments:
208 208 canonroot - the canonical root of the tree you're matching against
209 209 cwd - the current working directory, if relevant
210 210 names - patterns to find
211 211 inc - patterns to include
212 212 exc - patterns to exclude
213 213 head - a regex to prepend to patterns to control whether a match is rooted
214 214
215 215 a pattern is one of:
216 216 'glob:<rooted glob>'
217 217 're:<rooted regexp>'
218 218 'path:<rooted path>'
219 219 'relglob:<relative glob>'
220 220 'relpath:<relative path>'
221 221 'relre:<relative regexp>'
222 222 '<rooted path or regexp>'
223 223
224 224 returns:
225 225 a 3-tuple containing
226 226 - list of explicit non-pattern names passed in
227 227 - a bool match(filename) function
228 228 - a bool indicating if any patterns were passed in
229 229
230 230 todo:
231 231 make head regex a rooted bool
232 232 """
233 233
234 234 def contains_glob(name):
235 235 for c in name:
236 236 if c in _globchars: return True
237 237 return False
238 238
239 239 def regex(kind, name, tail):
240 240 '''convert a pattern into a regular expression'''
241 241 if kind == 're':
242 242 return name
243 243 elif kind == 'path':
244 244 return '^' + re.escape(name) + '(?:/|$)'
245 245 elif kind == 'relglob':
246 246 return head + globre(name, '(?:|.*/)', tail)
247 247 elif kind == 'relpath':
248 248 return head + re.escape(name) + tail
249 249 elif kind == 'relre':
250 250 if name.startswith('^'):
251 251 return name
252 252 return '.*' + name
253 253 return head + globre(name, '', tail)
254 254
255 255 def matchfn(pats, tail):
256 256 """build a matching function from a set of patterns"""
257 257 if not pats:
258 258 return
259 259 matches = []
260 260 for k, p in pats:
261 261 try:
262 262 pat = '(?:%s)' % regex(k, p, tail)
263 263 matches.append(re.compile(pat).match)
264 264 except re.error:
265 265 if src: raise Abort("%s: invalid pattern (%s): %s" % (src, k, p))
266 266 else: raise Abort("invalid pattern (%s): %s" % (k, p))
267 267
268 268 def buildfn(text):
269 269 for m in matches:
270 270 r = m(text)
271 271 if r:
272 272 return r
273 273
274 274 return buildfn
275 275
276 276 def globprefix(pat):
277 277 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
278 278 root = []
279 279 for p in pat.split(os.sep):
280 280 if contains_glob(p): break
281 281 root.append(p)
282 282 return '/'.join(root)
283 283
284 284 pats = []
285 285 files = []
286 286 roots = []
287 287 for kind, name in [patkind(p, dflt_pat) for p in names]:
288 288 if kind in ('glob', 'relpath'):
289 289 name = canonpath(canonroot, cwd, name)
290 290 if name == '':
291 291 kind, name = 'glob', '**'
292 292 if kind in ('glob', 'path', 're'):
293 293 pats.append((kind, name))
294 294 if kind == 'glob':
295 295 root = globprefix(name)
296 296 if root: roots.append(root)
297 297 elif kind == 'relpath':
298 298 files.append((kind, name))
299 299 roots.append(name)
300 300
301 301 patmatch = matchfn(pats, '$') or always
302 302 filematch = matchfn(files, '(?:/|$)') or always
303 303 incmatch = always
304 304 if inc:
305 305 incmatch = matchfn(map(patkind, inc), '(?:/|$)')
306 306 excmatch = lambda fn: False
307 307 if exc:
308 308 excmatch = matchfn(map(patkind, exc), '(?:/|$)')
309 309
310 310 return (roots,
311 311 lambda fn: (incmatch(fn) and not excmatch(fn) and
312 312 (fn.endswith('/') or
313 313 (not pats and not files) or
314 314 (pats and patmatch(fn)) or
315 315 (files and filematch(fn)))),
316 316 (inc or exc or (pats and pats != [('glob', '**')])) and True)
317 317
318 318 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
319 319 '''enhanced shell command execution.
320 320 run with environment maybe modified, maybe in different dir.
321 321
322 322 if command fails and onerr is None, return status. if ui object,
323 323 print error message and return status, else raise onerr object as
324 324 exception.'''
325 325 oldenv = {}
326 326 for k in environ:
327 327 oldenv[k] = os.environ.get(k)
328 328 if cwd is not None:
329 329 oldcwd = os.getcwd()
330 330 try:
331 331 for k, v in environ.iteritems():
332 332 os.environ[k] = str(v)
333 333 if cwd is not None and oldcwd != cwd:
334 334 os.chdir(cwd)
335 335 rc = os.system(cmd)
336 336 if rc and onerr:
337 337 errmsg = '%s %s' % (os.path.basename(cmd.split(None, 1)[0]),
338 338 explain_exit(rc)[0])
339 339 if errprefix:
340 340 errmsg = '%s: %s' % (errprefix, errmsg)
341 341 try:
342 342 onerr.warn(errmsg + '\n')
343 343 except AttributeError:
344 344 raise onerr(errmsg)
345 345 return rc
346 346 finally:
347 347 for k, v in oldenv.iteritems():
348 348 if v is None:
349 349 del os.environ[k]
350 350 else:
351 351 os.environ[k] = v
352 352 if cwd is not None and oldcwd != cwd:
353 353 os.chdir(oldcwd)
354 354
355 355 def rename(src, dst):
356 356 """forcibly rename a file"""
357 357 try:
358 358 os.rename(src, dst)
359 359 except:
360 360 os.unlink(dst)
361 361 os.rename(src, dst)
362 362
363 363 def unlink(f):
364 364 """unlink and remove the directory if it is empty"""
365 365 os.unlink(f)
366 366 # try removing directories that might now be empty
367 367 try: os.removedirs(os.path.dirname(f))
368 368 except: pass
369 369
370 370 def copyfiles(src, dst, hardlink=None):
371 371 """Copy a directory tree using hardlinks if possible"""
372 372
373 373 if hardlink is None:
374 374 hardlink = (os.stat(src).st_dev ==
375 375 os.stat(os.path.dirname(dst)).st_dev)
376 376
377 377 if os.path.isdir(src):
378 378 os.mkdir(dst)
379 379 for name in os.listdir(src):
380 380 srcname = os.path.join(src, name)
381 381 dstname = os.path.join(dst, name)
382 382 copyfiles(srcname, dstname, hardlink)
383 383 else:
384 384 if hardlink:
385 385 try:
386 386 os_link(src, dst)
387 387 except:
388 388 hardlink = False
389 389 shutil.copy(src, dst)
390 390 else:
391 391 shutil.copy(src, dst)
392 392
393 393 def audit_path(path):
394 394 """Abort if path contains dangerous components"""
395 395 parts = os.path.normcase(path).split(os.sep)
396 396 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
397 397 or os.pardir in parts):
398 398 raise Abort(_("path contains illegal component: %s\n") % path)
399 399
400 400 def opener(base, audit=True):
401 401 """
402 402 return a function that opens files relative to base
403 403
404 404 this function is used to hide the details of COW semantics and
405 405 remote file access from higher level code.
406 406 """
407 407 p = base
408 408 audit_p = audit
409 409
410 410 def mktempcopy(name):
411 411 d, fn = os.path.split(name)
412 412 fd, temp = tempfile.mkstemp(prefix=fn, dir=d)
413 413 fp = os.fdopen(fd, "wb")
414 414 try:
415 415 fp.write(file(name, "rb").read())
416 416 except:
417 417 try: os.unlink(temp)
418 418 except: pass
419 419 raise
420 420 fp.close()
421 421 st = os.lstat(name)
422 422 os.chmod(temp, st.st_mode)
423 423 return temp
424 424
425 425 class atomicfile(file):
426 426 """the file will only be copied on close"""
427 427 def __init__(self, name, mode, atomic=False):
428 428 self.__name = name
429 429 self.temp = mktempcopy(name)
430 430 file.__init__(self, self.temp, mode)
431 431 def close(self):
432 432 if not self.closed:
433 433 file.close(self)
434 434 rename(self.temp, self.__name)
435 435 def __del__(self):
436 436 self.close()
437 437
438 438 def o(path, mode="r", text=False, atomic=False):
439 439 if audit_p:
440 440 audit_path(path)
441 441 f = os.path.join(p, path)
442 442
443 443 if not text:
444 444 mode += "b" # for that other OS
445 445
446 446 if mode[0] != "r":
447 447 try:
448 448 nlink = nlinks(f)
449 449 except OSError:
450 450 d = os.path.dirname(f)
451 451 if not os.path.isdir(d):
452 452 os.makedirs(d)
453 453 else:
454 454 if atomic:
455 455 return atomicfile(f, mode)
456 456 if nlink > 1:
457 457 rename(mktempcopy(f), f)
458 458 return file(f, mode)
459 459
460 460 return o
461 461
462 462 def _makelock_file(info, pathname):
463 463 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
464 464 os.write(ld, info)
465 465 os.close(ld)
466 466
467 467 def _readlock_file(pathname):
468 468 return file(pathname).read()
469 469
470 470 def nlinks(pathname):
471 471 """Return number of hardlinks for the given file."""
472 472 return os.stat(pathname).st_nlink
473 473
474 474 if hasattr(os, 'link'):
475 475 os_link = os.link
476 476 else:
477 477 def os_link(src, dst):
478 478 raise OSError(0, _("Hardlinks not supported"))
479 479
480 480 # Platform specific variants
481 481 if os.name == 'nt':
482 482 demandload(globals(), "msvcrt")
483 483 nulldev = 'NUL:'
484 484
485 485 class winstdout:
486 486 '''stdout on windows misbehaves if sent through a pipe'''
487 487
488 488 def __init__(self, fp):
489 489 self.fp = fp
490 490
491 491 def __getattr__(self, key):
492 492 return getattr(self.fp, key)
493 493
494 494 def close(self):
495 495 try:
496 496 self.fp.close()
497 497 except: pass
498 498
499 499 def write(self, s):
500 500 try:
501 501 return self.fp.write(s)
502 502 except IOError, inst:
503 503 if inst.errno != 0: raise
504 504 self.close()
505 505 raise IOError(errno.EPIPE, 'Broken pipe')
506 506
507 507 sys.stdout = winstdout(sys.stdout)
508 508
509 try:
510 import win32api, win32process
511 filename = win32process.GetModuleFileNameEx(win32api.GetCurrentProcess(), 0)
512 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
509 def os_rcpath():
510 '''return default os-specific hgrc search path'''
511 try:
512 import win32api, win32process
513 proc = win32api.GetCurrentProcess()
514 filename = win32process.GetModuleFileNameEx(proc, 0)
515 systemrc = os.path.join(os.path.dirname(filename), 'mercurial.ini')
516 except ImportError:
517 systemrc = r'c:\mercurial\mercurial.ini'
513 518
514 except ImportError:
515 systemrc = r'c:\mercurial\mercurial.ini'
516 pass
517
518 rcpath = (systemrc,
519 os.path.join(os.path.expanduser('~'), 'mercurial.ini'))
519 return [systemrc,
520 os.path.join(os.path.expanduser('~'), 'mercurial.ini')]
520 521
521 522 def parse_patch_output(output_line):
522 523 """parses the output produced by patch and returns the file name"""
523 524 pf = output_line[14:]
524 525 if pf[0] == '`':
525 526 pf = pf[1:-1] # Remove the quotes
526 527 return pf
527 528
528 529 try: # ActivePython can create hard links using win32file module
529 530 import win32api, win32con, win32file
530 531
531 532 def os_link(src, dst): # NB will only succeed on NTFS
532 533 win32file.CreateHardLink(dst, src)
533 534
534 535 def nlinks(pathname):
535 536 """Return number of hardlinks for the given file."""
536 537 try:
537 538 fh = win32file.CreateFile(pathname,
538 539 win32file.GENERIC_READ, win32file.FILE_SHARE_READ,
539 540 None, win32file.OPEN_EXISTING, 0, None)
540 541 res = win32file.GetFileInformationByHandle(fh)
541 542 fh.Close()
542 543 return res[7]
543 544 except:
544 545 return os.stat(pathname).st_nlink
545 546
546 547 def testpid(pid):
547 548 '''return False if pid is dead, True if running or not known'''
548 549 try:
549 550 win32api.OpenProcess(win32con.PROCESS_QUERY_INFORMATION,
550 551 False, pid)
551 552 except:
552 553 return True
553 554
554 555 except ImportError:
555 556 def testpid(pid):
556 557 '''return False if pid dead, True if running or not known'''
557 558 return True
558 559
559 560 def is_exec(f, last):
560 561 return last
561 562
562 563 def set_exec(f, mode):
563 564 pass
564 565
565 566 def set_binary(fd):
566 567 msvcrt.setmode(fd.fileno(), os.O_BINARY)
567 568
568 569 def pconvert(path):
569 570 return path.replace("\\", "/")
570 571
571 572 def localpath(path):
572 573 return path.replace('/', '\\')
573 574
574 575 def normpath(path):
575 576 return pconvert(os.path.normpath(path))
576 577
577 578 makelock = _makelock_file
578 579 readlock = _readlock_file
579 580
580 581 def explain_exit(code):
581 582 return _("exited with status %d") % code, code
582 583
583 584 else:
584 585 nulldev = '/dev/null'
585 586
586 587 def rcfiles(path):
587 588 rcs = [os.path.join(path, 'hgrc')]
588 589 rcdir = os.path.join(path, 'hgrc.d')
589 590 try:
590 591 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
591 592 if f.endswith(".rc")])
592 593 except OSError, inst: pass
593 594 return rcs
594 rcpath = []
595 if len(sys.argv) > 0:
596 rcpath.extend(rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial'))
597 rcpath.extend(rcfiles('/etc/mercurial'))
598 rcpath.append(os.path.expanduser('~/.hgrc'))
599 rcpath = [os.path.normpath(f) for f in rcpath]
595
596 def os_rcpath():
597 '''return default os-specific hgrc search path'''
598 path = []
599 if len(sys.argv) > 0:
600 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
601 '/../etc/mercurial'))
602 path.extend(rcfiles('/etc/mercurial'))
603 path.append(os.path.expanduser('~/.hgrc'))
604 path = [os.path.normpath(f) for f in path]
605 return path
600 606
601 607 def parse_patch_output(output_line):
602 608 """parses the output produced by patch and returns the file name"""
603 609 pf = output_line[14:]
604 610 if pf.startswith("'") and pf.endswith("'") and pf.find(" ") >= 0:
605 611 pf = pf[1:-1] # Remove the quotes
606 612 return pf
607 613
608 614 def is_exec(f, last):
609 615 """check whether a file is executable"""
610 616 return (os.stat(f).st_mode & 0100 != 0)
611 617
612 618 def set_exec(f, mode):
613 619 s = os.stat(f).st_mode
614 620 if (s & 0100 != 0) == mode:
615 621 return
616 622 if mode:
617 623 # Turn on +x for every +r bit when making a file executable
618 624 # and obey umask.
619 625 umask = os.umask(0)
620 626 os.umask(umask)
621 627 os.chmod(f, s | (s & 0444) >> 2 & ~umask)
622 628 else:
623 629 os.chmod(f, s & 0666)
624 630
625 631 def set_binary(fd):
626 632 pass
627 633
628 634 def pconvert(path):
629 635 return path
630 636
631 637 def localpath(path):
632 638 return path
633 639
634 640 normpath = os.path.normpath
635 641
636 642 def makelock(info, pathname):
637 643 try:
638 644 os.symlink(info, pathname)
639 645 except OSError, why:
640 646 if why.errno == errno.EEXIST:
641 647 raise
642 648 else:
643 649 _makelock_file(info, pathname)
644 650
645 651 def readlock(pathname):
646 652 try:
647 653 return os.readlink(pathname)
648 654 except OSError, why:
649 655 if why.errno == errno.EINVAL:
650 656 return _readlock_file(pathname)
651 657 else:
652 658 raise
653 659
654 660 def testpid(pid):
655 661 '''return False if pid dead, True if running or not sure'''
656 662 try:
657 663 os.kill(pid, 0)
658 664 return True
659 665 except OSError, inst:
660 666 return inst.errno != errno.ESRCH
661 667
662 668 def explain_exit(code):
663 669 """return a 2-tuple (desc, code) describing a process's status"""
664 670 if os.WIFEXITED(code):
665 671 val = os.WEXITSTATUS(code)
666 672 return _("exited with status %d") % val, val
667 673 elif os.WIFSIGNALED(code):
668 674 val = os.WTERMSIG(code)
669 675 return _("killed by signal %d") % val, val
670 676 elif os.WIFSTOPPED(code):
671 677 val = os.WSTOPSIG(code)
672 678 return _("stopped by signal %d") % val, val
673 679 raise ValueError(_("invalid exit code"))
674 680
675 681 class chunkbuffer(object):
676 682 """Allow arbitrary sized chunks of data to be efficiently read from an
677 683 iterator over chunks of arbitrary size."""
678 684
679 685 def __init__(self, in_iter, targetsize = 2**16):
680 686 """in_iter is the iterator that's iterating over the input chunks.
681 687 targetsize is how big a buffer to try to maintain."""
682 688 self.in_iter = iter(in_iter)
683 689 self.buf = ''
684 690 self.targetsize = int(targetsize)
685 691 if self.targetsize <= 0:
686 692 raise ValueError(_("targetsize must be greater than 0, was %d") %
687 693 targetsize)
688 694 self.iterempty = False
689 695
690 696 def fillbuf(self):
691 697 """Ignore target size; read every chunk from iterator until empty."""
692 698 if not self.iterempty:
693 699 collector = cStringIO.StringIO()
694 700 collector.write(self.buf)
695 701 for ch in self.in_iter:
696 702 collector.write(ch)
697 703 self.buf = collector.getvalue()
698 704 self.iterempty = True
699 705
700 706 def read(self, l):
701 707 """Read L bytes of data from the iterator of chunks of data.
702 708 Returns less than L bytes if the iterator runs dry."""
703 709 if l > len(self.buf) and not self.iterempty:
704 710 # Clamp to a multiple of self.targetsize
705 711 targetsize = self.targetsize * ((l // self.targetsize) + 1)
706 712 collector = cStringIO.StringIO()
707 713 collector.write(self.buf)
708 714 collected = len(self.buf)
709 715 for chunk in self.in_iter:
710 716 collector.write(chunk)
711 717 collected += len(chunk)
712 718 if collected >= targetsize:
713 719 break
714 720 if collected < targetsize:
715 721 self.iterempty = True
716 722 self.buf = collector.getvalue()
717 723 s, self.buf = self.buf[:l], buffer(self.buf, l)
718 724 return s
719 725
720 726 def filechunkiter(f, size = 65536):
721 727 """Create a generator that produces all the data in the file size
722 728 (default 65536) bytes at a time. Chunks may be less than size
723 729 bytes if the chunk is the last chunk in the file, or the file is a
724 730 socket or some other type of file that sometimes reads less data
725 731 than is requested."""
726 732 s = f.read(size)
727 733 while len(s) > 0:
728 734 yield s
729 735 s = f.read(size)
730 736
731 737 def makedate():
732 738 lt = time.localtime()
733 739 if lt[8] == 1 and time.daylight:
734 740 tz = time.altzone
735 741 else:
736 742 tz = time.timezone
737 743 return time.mktime(lt), tz
738 744
739 745 def datestr(date=None, format='%c'):
740 746 """represent a (unixtime, offset) tuple as a localized time.
741 747 unixtime is seconds since the epoch, and offset is the time zone's
742 748 number of seconds away from UTC."""
743 749 t, tz = date or makedate()
744 750 return ("%s %+03d%02d" %
745 751 (time.strftime(format, time.gmtime(float(t) - tz)),
746 752 -tz / 3600,
747 753 ((-tz % 3600) / 60)))
748 754
749 755 def shortuser(user):
750 756 """Return a short representation of a user name or email address."""
751 757 f = user.find('@')
752 758 if f >= 0:
753 759 user = user[:f]
754 760 f = user.find('<')
755 761 if f >= 0:
756 762 user = user[f+1:]
757 763 return user
758 764
759 765 def walkrepos(path):
760 766 '''yield every hg repository under path, recursively.'''
761 767 def errhandler(err):
762 768 if err.filename == path:
763 769 raise err
764 770
765 771 for root, dirs, files in os.walk(path, onerror=errhandler):
766 772 for d in dirs:
767 773 if d == '.hg':
768 774 yield root
769 775 dirs[:] = []
770 776 break
777
778 _rcpath = None
779
780 def rcpath():
781 '''return hgrc search path. if env var HGRCPATH is set, use it.
782 for each item in path, if directory, use files ending in .rc,
783 else use item.
784 make HGRCPATH empty to only look in .hg/hgrc of current repo.
785 if no HGRCPATH, use default os-specific path.'''
786 global _rcpath
787 if _rcpath is None:
788 if 'HGRCPATH' in os.environ:
789 _rcpath = []
790 for p in os.environ['HGRCPATH'].split(os.pathsep):
791 if not p: continue
792 try:
793 for f in os.listdir(p):
794 if f.endswith('.rc'):
795 _rcpath.append(os.path.join(p, f))
796 continue
797 except:
798 pass
799 _rcpath.append(p)
800 else:
801 _rcpath = os_rcpath()
802 return _rcpath
@@ -1,152 +1,153 b''
1 1 #!/bin/sh -e
2 2
3 3 LANG="C"; export LANG
4 4 LC_CTYPE="C"; export LC_CTYPE
5 5 LC_NUMERIC="C"; export LC_NUMERIC
6 6 LC_TIME="C"; export LC_TIME
7 7 LC_COLLATE="C"; export LC_COLLATE
8 8 LC_MONETARY="C"; export LC_MONETARY
9 9 LC_MESSAGES="C"; export LC_MESSAGES
10 10 LC_PAPER="C"; export LC_PAPER
11 11 LC_NAME="C"; export LC_NAME
12 12 LC_ADDRESS="C"; export LC_ADDRESS
13 13 LC_TELEPHONE="C"; export LC_TELEPHONE
14 14 LC_MEASUREMENT="C"; export LC_MEASUREMENT
15 15 LC_IDENTIFICATION="C"; export LC_IDENTIFICATION
16 16 LC_ALL=""; export LC_ALL
17 17 TZ=GMT; export TZ
18 18 HGEDITOR=true; export HGEDITOR
19 19 HGMERGE=true; export HGMERGE
20 20 HGUSER="test"; export HGUSER
21 HGRCPATH=""; export HGRCPATH
21 22
22 23 ECHO_N="echo -n"
23 24 [ -x /usr/ucb/echo ] && ECHO_N="/usr/ucb/echo -n"
24 25
25 26 umask 022
26 27
27 28 tests=0
28 29 failed=0
29 30
30 31 HGTMP=""
31 32 cleanup_exit() {
32 33 rm -rf "$HGTMP"
33 34 }
34 35
35 36 # Remove temporary files even if we get interrupted
36 37 trap "cleanup_exit" 0 # normal exit
37 38 trap "exit 255" 1 2 3 6 15 # HUP INT QUIT ABRT TERM
38 39
39 40 HGTMP="${TMPDIR-/tmp}/hgtests.$RANDOM.$RANDOM.$RANDOM.$$"
40 41 (umask 077 && mkdir "$HGTMP") || {
41 42 echo "Could not create temporary directory! Exiting." 1>&2
42 43 exit 1
43 44 }
44 45
45 46 TESTDIR="$PWD"
46 47 export TESTDIR
47 48 INST="$HGTMP/install"
48 49 PYTHONDIR="$INST/lib/python"
49 50 cd ..
50 51 if ${PYTHON-python} setup.py install --home="$INST" \
51 52 --install-lib="$PYTHONDIR" > tests/install.err 2>&1
52 53 then
53 54 rm tests/install.err
54 55 else
55 56 cat tests/install.err
56 57 exit 1
57 58 fi
58 59 cd "$TESTDIR"
59 60
60 61 BINDIR="$INST/bin"
61 62 PATH="$BINDIR:$PATH"; export PATH
62 63 if [ -n "$PYTHON" ]; then
63 64 {
64 65 echo "#!/bin/sh"
65 66 echo "exec \"$PYTHON"'" "$@"'
66 67 } > "$BINDIR/python"
67 68 chmod 755 "$BINDIR/python"
68 69 fi
69 70
70 71 PYTHONPATH="$PYTHONDIR"; export PYTHONPATH
71 72
72 73 run_one() {
73 74 rm -f "$1.err"
74 75
75 76 mkdir "$HGTMP/$1"
76 77 cd "$HGTMP/$1"
77 78 fail=0
78 79 HOME="$HGTMP/$1"; export HOME
79 80 OUT="$HGTMP/$1.out"
80 81 OUTOK="$TESTDIR/$1.out"
81 82 ERR="$TESTDIR/$1.err"
82 83
83 84 if "$TESTDIR/$1" > "$OUT" 2>&1; then
84 85 : no error
85 86 else
86 87 echo "$1 failed with error code $?"
87 88 fail=1
88 89 fi
89 90
90 91 if [ -s "$OUT" -a ! -s "$OUTOK" ] ; then
91 92 cp "$OUT" "$ERR"
92 93 echo
93 94 echo "$1 generated unexpected output:"
94 95 cat "$ERR"
95 96 fail=1
96 97 elif [ -r "$OUTOK" ]; then
97 98 if diff -u "$OUTOK" "$OUT" > /dev/null; then
98 99 : no differences
99 100 else
100 101 cp "$OUT" "$ERR"
101 102 echo
102 103 echo "$1 output changed:"
103 104 diff -u "$OUTOK" "$ERR" || true
104 105 fail=1
105 106 fi
106 107 fi
107 108
108 109 cd "$TESTDIR"
109 110 rm -f "$HGTMP/$1.out"
110 111 rm -rf "$HGTMP/$1"
111 112 return $fail
112 113 }
113 114
114 115 # list of prerequisite programs
115 116 # stuff from coreutils (cat, rm, etc) are not tested
116 117 prereqs="python merge diff grep unzip gunzip bunzip2 sed"
117 118 missing=''
118 119 for pre in $prereqs ; do
119 120 if type $pre > /dev/null 2>&1 ; then
120 121 : prereq exists
121 122 else
122 123 missing="$pre $missing"
123 124 fi
124 125 done
125 126
126 127 if [ "$missing" != '' ] ; then
127 128 echo "ERROR: the test suite needs some programs to execute correctly."
128 129 echo "The following programs are missing: "
129 130 for pre in $missing; do
130 131 echo " $pre"
131 132 done
132 133 exit 1
133 134 fi
134 135
135 136 TESTS="$*"
136 137 if [ -z "$TESTS" ] ; then
137 138 TESTS=`ls test-* | grep -v "[.~]"`
138 139 fi
139 140
140 141 for f in $TESTS ; do
141 142 $ECHO_N "."
142 143 run_one $f || failed=`expr $failed + 1`
143 144 tests=`expr $tests + 1`
144 145 done
145 146
146 147 echo
147 148 echo "Ran $tests tests, $failed failed."
148 149
149 150 if [ $failed -gt 0 ] ; then
150 151 exit 1
151 152 fi
152 153 exit 0
General Comments 0
You need to be logged in to leave comments. Login now