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