##// END OF EJS Templates
Merge with crew-stable
Patrick Mezard -
r5293:32ec518e merge default
parent child Browse files
Show More
@@ -1,219 +1,219 b''
1 1 # extdiff.py - external diff program support for mercurial
2 2 #
3 3 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.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 '''
9 9 The `extdiff' Mercurial extension allows you to use external programs
10 10 to compare revisions, or revision with working dir. The external diff
11 11 programs are called with a configurable set of options and two
12 12 non-option arguments: paths to directories containing snapshots of
13 13 files to compare.
14 14
15 15 To enable this extension:
16 16
17 17 [extensions]
18 18 hgext.extdiff =
19 19
20 20 The `extdiff' extension also allows to configure new diff commands, so
21 21 you do not need to type "hg extdiff -p kdiff3" always.
22 22
23 23 [extdiff]
24 24 # add new command that runs GNU diff(1) in 'context diff' mode
25 25 cdiff = gdiff -Nprc5
26 26 ## or the old way:
27 27 #cmd.cdiff = gdiff
28 28 #opts.cdiff = -Nprc5
29 29
30 30 # add new command called vdiff, runs kdiff3
31 31 vdiff = kdiff3
32 32
33 33 # add new command called meld, runs meld (no need to name twice)
34 34 meld =
35 35
36 36 # add new command called vimdiff, runs gvimdiff with DirDiff plugin
37 37 #(see http://www.vim.org/scripts/script.php?script_id=102)
38 38 # Non english user, be sure to put "let g:DirDiffDynamicDiffText = 1" in
39 39 # your .vimrc
40 40 vimdiff = gvim -f '+next' '+execute "DirDiff" argv(0) argv(1)'
41 41
42 42 You can use -I/-X and list of file or directory names like normal
43 43 "hg diff" command. The `extdiff' extension makes snapshots of only
44 44 needed files, so running the external diff program will actually be
45 45 pretty fast (at least faster than having to compare the entire tree).
46 46 '''
47 47
48 48 from mercurial.i18n import _
49 49 from mercurial.node import *
50 50 from mercurial import cmdutil, util, commands
51 51 import os, shlex, shutil, tempfile
52 52
53 53 def snapshot_node(ui, repo, files, node, tmproot):
54 54 '''snapshot files as of some revision'''
55 55 mf = repo.changectx(node).manifest()
56 56 dirname = os.path.basename(repo.root)
57 57 if dirname == "":
58 58 dirname = "root"
59 59 dirname = '%s.%s' % (dirname, short(node))
60 60 base = os.path.join(tmproot, dirname)
61 61 os.mkdir(base)
62 62 ui.note(_('making snapshot of %d files from rev %s\n') %
63 63 (len(files), short(node)))
64 64 for fn in files:
65 65 if not fn in mf:
66 66 # skipping new file after a merge ?
67 67 continue
68 68 wfn = util.pconvert(fn)
69 69 ui.note(' %s\n' % wfn)
70 70 dest = os.path.join(base, wfn)
71 71 destdir = os.path.dirname(dest)
72 72 if not os.path.isdir(destdir):
73 73 os.makedirs(destdir)
74 74 data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn]))
75 75 open(dest, 'wb').write(data)
76 76 return dirname
77 77
78 78
79 79 def snapshot_wdir(ui, repo, files, tmproot):
80 80 '''snapshot files from working directory.
81 81 if not using snapshot, -I/-X does not work and recursive diff
82 82 in tools like kdiff3 and meld displays too many files.'''
83 83 dirname = os.path.basename(repo.root)
84 84 if dirname == "":
85 85 dirname = "root"
86 86 base = os.path.join(tmproot, dirname)
87 87 os.mkdir(base)
88 88 ui.note(_('making snapshot of %d files from working dir\n') %
89 89 (len(files)))
90 90 for fn in files:
91 91 wfn = util.pconvert(fn)
92 92 ui.note(' %s\n' % wfn)
93 93 dest = os.path.join(base, wfn)
94 94 destdir = os.path.dirname(dest)
95 95 if not os.path.isdir(destdir):
96 96 os.makedirs(destdir)
97 97 fp = open(dest, 'wb')
98 98 for chunk in util.filechunkiter(repo.wopener(wfn)):
99 99 fp.write(chunk)
100 100 return dirname
101 101
102 102
103 103 def dodiff(ui, repo, diffcmd, diffopts, pats, opts):
104 104 node1, node2 = cmdutil.revpair(repo, opts['rev'])
105 105 files, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
106 106 modified, added, removed, deleted, unknown = repo.status(
107 107 node1, node2, files, match=matchfn)[:5]
108 108 if not (modified or added or removed):
109 109 return 0
110 110
111 111 tmproot = tempfile.mkdtemp(prefix='extdiff.')
112 112 dir2root = ''
113 113 try:
114 114 # Always make a copy of node1
115 115 dir1 = snapshot_node(ui, repo, modified + removed, node1, tmproot)
116 116 changes = len(modified) + len(removed) + len(added)
117 117
118 118 # If node2 in not the wc or there is >1 change, copy it
119 119 if node2:
120 120 dir2 = snapshot_node(ui, repo, modified + added, node2, tmproot)
121 121 elif changes > 1:
122 122 dir2 = snapshot_wdir(ui, repo, modified + added, tmproot)
123 123 else:
124 124 # This lets the diff tool open the changed file directly
125 125 dir2 = ''
126 126 dir2root = repo.root
127 127
128 128 # If only one change, diff the files instead of the directories
129 129 if changes == 1 :
130 130 if len(modified):
131 131 dir1 = os.path.join(dir1, util.localpath(modified[0]))
132 132 dir2 = os.path.join(dir2root, dir2, util.localpath(modified[0]))
133 133 elif len(removed) :
134 134 dir1 = os.path.join(dir1, util.localpath(removed[0]))
135 135 dir2 = os.devnull
136 136 else:
137 137 dir1 = os.devnull
138 138 dir2 = os.path.join(dir2root, dir2, util.localpath(added[0]))
139 139
140 140 cmdline = ('%s %s %s %s' %
141 141 (util.shellquote(diffcmd), ' '.join(diffopts),
142 142 util.shellquote(dir1), util.shellquote(dir2)))
143 143 ui.debug('running %r in %s\n' % (cmdline, tmproot))
144 144 util.system(cmdline, cwd=tmproot)
145 145 return 1
146 146 finally:
147 147 ui.note(_('cleaning up temp directory\n'))
148 148 shutil.rmtree(tmproot)
149 149
150 150 def extdiff(ui, repo, *pats, **opts):
151 151 '''use external program to diff repository (or selected files)
152 152
153 153 Show differences between revisions for the specified files, using
154 154 an external program. The default program used is diff, with
155 155 default options "-Npru".
156 156
157 157 To select a different program, use the -p option. The program
158 158 will be passed the names of two directories to compare. To pass
159 159 additional options to the program, use the -o option. These will
160 160 be passed before the names of the directories to compare.
161 161
162 162 When two revision arguments are given, then changes are
163 163 shown between those revisions. If only one revision is
164 164 specified then that revision is compared to the working
165 165 directory, and, when no revisions are specified, the
166 166 working directory files are compared to its parent.'''
167 167 program = opts['program'] or 'diff'
168 168 if opts['program']:
169 169 option = opts['option']
170 170 else:
171 171 option = opts['option'] or ['-Npru']
172 172 return dodiff(ui, repo, program, option, pats, opts)
173 173
174 174 cmdtable = {
175 175 "extdiff":
176 176 (extdiff,
177 177 [('p', 'program', '', _('comparison program to run')),
178 178 ('o', 'option', [], _('pass option to comparison program')),
179 179 ('r', 'rev', [], _('revision')),
180 180 ] + commands.walkopts,
181 181 _('hg extdiff [OPT]... [FILE]...')),
182 182 }
183 183
184 184 def uisetup(ui):
185 185 for cmd, path in ui.configitems('extdiff'):
186 186 if cmd.startswith('cmd.'):
187 187 cmd = cmd[4:]
188 188 if not path: path = cmd
189 189 diffopts = ui.config('extdiff', 'opts.' + cmd, '')
190 190 diffopts = diffopts and [diffopts] or []
191 191 elif cmd.startswith('opts.'):
192 192 continue
193 193 else:
194 194 # command = path opts
195 195 if path:
196 196 diffopts = shlex.split(path)
197 197 path = diffopts.pop(0)
198 198 else:
199 199 path, diffopts = cmd, []
200 200 def save(cmd, path, diffopts):
201 201 '''use closure to save diff command to use'''
202 202 def mydiff(ui, repo, *pats, **opts):
203 203 return dodiff(ui, repo, path, diffopts, pats, opts)
204 mydiff.__doc__ = '''use %(path)r to diff repository (or selected files)
204 mydiff.__doc__ = '''use %(path)s to diff repository (or selected files)
205 205
206 206 Show differences between revisions for the specified
207 files, using the %(path)r program.
207 files, using the %(path)s program.
208 208
209 209 When two revision arguments are given, then changes are
210 210 shown between those revisions. If only one revision is
211 211 specified then that revision is compared to the working
212 212 directory, and, when no revisions are specified, the
213 213 working directory files are compared to its parent.''' % {
214 'path': path,
214 'path': util.uirepr(path),
215 215 }
216 216 return mydiff
217 217 cmdtable[cmd] = (save(cmd, path, diffopts),
218 218 cmdtable['extdiff'][1][1:],
219 219 _('hg %s [OPTION]... [FILE]...') % cmd)
@@ -1,227 +1,228 b''
1 1 # sshrepo.py - ssh repository proxy class for mercurial
2 2 #
3 3 # Copyright 2005, 2006 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 from node import *
9 9 from remoterepo import *
10 10 from i18n import _
11 11 import repo, os, re, stat, util
12 12
13 13 class sshrepository(remoterepository):
14 14 def __init__(self, ui, path, create=0):
15 15 self._url = path
16 16 self.ui = ui
17 17
18 18 m = re.match(r'^ssh://(([^@]+)@)?([^:/]+)(:(\d+))?(/(.*))?$', path)
19 19 if not m:
20 20 self.raise_(repo.RepoError(_("couldn't parse location %s") % path))
21 21
22 22 self.user = m.group(2)
23 23 self.host = m.group(3)
24 24 self.port = m.group(5)
25 25 self.path = m.group(7) or "."
26 26
27 27 args = self.user and ("%s@%s" % (self.user, self.host)) or self.host
28 28 args = self.port and ("%s -p %s") % (args, self.port) or args
29 29
30 30 sshcmd = self.ui.config("ui", "ssh", "ssh")
31 31 remotecmd = self.ui.config("ui", "remotecmd", "hg")
32 32
33 33 if create:
34 34 cmd = '%s %s "%s init %s"'
35 35 cmd = cmd % (sshcmd, args, remotecmd, self.path)
36 36
37 37 ui.note('running %s\n' % cmd)
38 res = os.system(cmd)
38 res = util.system(cmd)
39 39 if res != 0:
40 40 self.raise_(repo.RepoError(_("could not create remote repo")))
41 41
42 42 self.validate_repo(ui, sshcmd, args, remotecmd)
43 43
44 44 def url(self):
45 45 return self._url
46 46
47 47 def validate_repo(self, ui, sshcmd, args, remotecmd):
48 48 # cleanup up previous run
49 49 self.cleanup()
50 50
51 51 cmd = '%s %s "%s -R %s serve --stdio"'
52 52 cmd = cmd % (sshcmd, args, remotecmd, self.path)
53 53
54 cmd = util.quotecommand(cmd)
54 55 ui.note('running %s\n' % cmd)
55 56 self.pipeo, self.pipei, self.pipee = os.popen3(cmd, 'b')
56 57
57 58 # skip any noise generated by remote shell
58 59 self.do_cmd("hello")
59 60 r = self.do_cmd("between", pairs=("%s-%s" % ("0"*40, "0"*40)))
60 61 lines = ["", "dummy"]
61 62 max_noise = 500
62 63 while lines[-1] and max_noise:
63 64 l = r.readline()
64 65 self.readerr()
65 66 if lines[-1] == "1\n" and l == "\n":
66 67 break
67 68 if l:
68 69 ui.debug(_("remote: "), l)
69 70 lines.append(l)
70 71 max_noise -= 1
71 72 else:
72 73 self.raise_(repo.RepoError(_("no suitable response from remote hg")))
73 74
74 75 self.capabilities = util.set()
75 76 lines.reverse()
76 77 for l in lines:
77 78 if l.startswith("capabilities:"):
78 79 self.capabilities.update(l[:-1].split(":")[1].split())
79 80 break
80 81
81 82 def readerr(self):
82 83 while 1:
83 84 size = util.fstat(self.pipee).st_size
84 85 if size == 0: break
85 86 l = self.pipee.readline()
86 87 if not l: break
87 88 self.ui.status(_("remote: "), l)
88 89
89 90 def raise_(self, exception):
90 91 self.cleanup()
91 92 raise exception
92 93
93 94 def cleanup(self):
94 95 try:
95 96 self.pipeo.close()
96 97 self.pipei.close()
97 98 # read the error descriptor until EOF
98 99 for l in self.pipee:
99 100 self.ui.status(_("remote: "), l)
100 101 self.pipee.close()
101 102 except:
102 103 pass
103 104
104 105 __del__ = cleanup
105 106
106 107 def do_cmd(self, cmd, **args):
107 108 self.ui.debug(_("sending %s command\n") % cmd)
108 109 self.pipeo.write("%s\n" % cmd)
109 110 for k, v in args.items():
110 111 self.pipeo.write("%s %d\n" % (k, len(v)))
111 112 self.pipeo.write(v)
112 113 self.pipeo.flush()
113 114
114 115 return self.pipei
115 116
116 117 def call(self, cmd, **args):
117 118 r = self.do_cmd(cmd, **args)
118 119 l = r.readline()
119 120 self.readerr()
120 121 try:
121 122 l = int(l)
122 123 except:
123 124 self.raise_(util.UnexpectedOutput(_("unexpected response:"), l))
124 125 return r.read(l)
125 126
126 127 def lock(self):
127 128 self.call("lock")
128 129 return remotelock(self)
129 130
130 131 def unlock(self):
131 132 self.call("unlock")
132 133
133 134 def lookup(self, key):
134 135 self.requirecap('lookup', _('look up remote revision'))
135 136 d = self.call("lookup", key=key)
136 137 success, data = d[:-1].split(" ", 1)
137 138 if int(success):
138 139 return bin(data)
139 140 else:
140 141 self.raise_(repo.RepoError(data))
141 142
142 143 def heads(self):
143 144 d = self.call("heads")
144 145 try:
145 146 return map(bin, d[:-1].split(" "))
146 147 except:
147 148 self.raise_(util.UnexpectedOutput(_("unexpected response:"), d))
148 149
149 150 def branches(self, nodes):
150 151 n = " ".join(map(hex, nodes))
151 152 d = self.call("branches", nodes=n)
152 153 try:
153 154 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
154 155 return br
155 156 except:
156 157 self.raise_(util.UnexpectedOutput(_("unexpected response:"), d))
157 158
158 159 def between(self, pairs):
159 160 n = "\n".join(["-".join(map(hex, p)) for p in pairs])
160 161 d = self.call("between", pairs=n)
161 162 try:
162 163 p = [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
163 164 return p
164 165 except:
165 166 self.raise_(util.UnexpectedOutput(_("unexpected response:"), d))
166 167
167 168 def changegroup(self, nodes, kind):
168 169 n = " ".join(map(hex, nodes))
169 170 return self.do_cmd("changegroup", roots=n)
170 171
171 172 def changegroupsubset(self, bases, heads, kind):
172 173 self.requirecap('changegroupsubset', _('look up remote changes'))
173 174 bases = " ".join(map(hex, bases))
174 175 heads = " ".join(map(hex, heads))
175 176 return self.do_cmd("changegroupsubset", bases=bases, heads=heads)
176 177
177 178 def unbundle(self, cg, heads, source):
178 179 d = self.call("unbundle", heads=' '.join(map(hex, heads)))
179 180 if d:
180 181 # remote may send "unsynced changes"
181 182 self.raise_(repo.RepoError(_("push refused: %s") % d))
182 183
183 184 while 1:
184 185 d = cg.read(4096)
185 186 if not d: break
186 187 self.pipeo.write(str(len(d)) + '\n')
187 188 self.pipeo.write(d)
188 189 self.readerr()
189 190
190 191 self.pipeo.write('0\n')
191 192 self.pipeo.flush()
192 193
193 194 self.readerr()
194 195 l = int(self.pipei.readline())
195 196 r = self.pipei.read(l)
196 197 if r:
197 198 # remote may send "unsynced changes"
198 199 self.raise_(hg.RepoError(_("push failed: %s") % r))
199 200
200 201 self.readerr()
201 202 l = int(self.pipei.readline())
202 203 r = self.pipei.read(l)
203 204 return int(r)
204 205
205 206 def addchangegroup(self, cg, source, url):
206 207 d = self.call("addchangegroup")
207 208 if d:
208 209 self.raise_(repo.RepoError(_("push refused: %s") % d))
209 210 while 1:
210 211 d = cg.read(4096)
211 212 if not d: break
212 213 self.pipeo.write(d)
213 214 self.readerr()
214 215
215 216 self.pipeo.flush()
216 217
217 218 self.readerr()
218 219 l = int(self.pipei.readline())
219 220 r = self.pipei.read(l)
220 221 if not r:
221 222 return 1
222 223 return int(r)
223 224
224 225 def stream_out(self):
225 226 return self.do_cmd('stream_out')
226 227
227 228 instance = sshrepository
@@ -1,1683 +1,1696 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 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
6 6 Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
7 7
8 8 This software may be used and distributed according to the terms
9 9 of the GNU General Public License, incorporated herein by reference.
10 10
11 11 This contains helper routines that are independent of the SCM core and hide
12 12 platform-specific details from the core.
13 13 """
14 14
15 15 from i18n import _
16 16 import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile, strutil
17 17 import os, stat, threading, time, calendar, ConfigParser, locale, glob
18 18
19 19 try:
20 20 set = set
21 21 frozenset = frozenset
22 22 except NameError:
23 23 from sets import Set as set, ImmutableSet as frozenset
24 24
25 25 try:
26 26 _encoding = os.environ.get("HGENCODING")
27 27 if sys.platform == 'darwin' and not _encoding:
28 28 # On darwin, getpreferredencoding ignores the locale environment and
29 29 # always returns mac-roman. We override this if the environment is
30 30 # not C (has been customized by the user).
31 31 locale.setlocale(locale.LC_CTYPE, '')
32 32 _encoding = locale.getlocale()[1]
33 33 if not _encoding:
34 34 _encoding = locale.getpreferredencoding() or 'ascii'
35 35 except locale.Error:
36 36 _encoding = 'ascii'
37 37 _encodingmode = os.environ.get("HGENCODINGMODE", "strict")
38 38 _fallbackencoding = 'ISO-8859-1'
39 39
40 40 def tolocal(s):
41 41 """
42 42 Convert a string from internal UTF-8 to local encoding
43 43
44 44 All internal strings should be UTF-8 but some repos before the
45 45 implementation of locale support may contain latin1 or possibly
46 46 other character sets. We attempt to decode everything strictly
47 47 using UTF-8, then Latin-1, and failing that, we use UTF-8 and
48 48 replace unknown characters.
49 49 """
50 50 for e in ('UTF-8', _fallbackencoding):
51 51 try:
52 52 u = s.decode(e) # attempt strict decoding
53 53 return u.encode(_encoding, "replace")
54 54 except LookupError, k:
55 55 raise Abort(_("%s, please check your locale settings") % k)
56 56 except UnicodeDecodeError:
57 57 pass
58 58 u = s.decode("utf-8", "replace") # last ditch
59 59 return u.encode(_encoding, "replace")
60 60
61 61 def fromlocal(s):
62 62 """
63 63 Convert a string from the local character encoding to UTF-8
64 64
65 65 We attempt to decode strings using the encoding mode set by
66 66 HGENCODINGMODE, which defaults to 'strict'. In this mode, unknown
67 67 characters will cause an error message. Other modes include
68 68 'replace', which replaces unknown characters with a special
69 69 Unicode character, and 'ignore', which drops the character.
70 70 """
71 71 try:
72 72 return s.decode(_encoding, _encodingmode).encode("utf-8")
73 73 except UnicodeDecodeError, inst:
74 74 sub = s[max(0, inst.start-10):inst.start+10]
75 75 raise Abort("decoding near '%s': %s!" % (sub, inst))
76 76 except LookupError, k:
77 77 raise Abort(_("%s, please check your locale settings") % k)
78 78
79 79 def locallen(s):
80 80 """Find the length in characters of a local string"""
81 81 return len(s.decode(_encoding, "replace"))
82 82
83 83 def localsub(s, a, b=None):
84 84 try:
85 85 u = s.decode(_encoding, _encodingmode)
86 86 if b is not None:
87 87 u = u[a:b]
88 88 else:
89 89 u = u[:a]
90 90 return u.encode(_encoding, _encodingmode)
91 91 except UnicodeDecodeError, inst:
92 92 sub = s[max(0, inst.start-10), inst.start+10]
93 93 raise Abort(_("decoding near '%s': %s!") % (sub, inst))
94 94
95 95 # used by parsedate
96 96 defaultdateformats = (
97 97 '%Y-%m-%d %H:%M:%S',
98 98 '%Y-%m-%d %I:%M:%S%p',
99 99 '%Y-%m-%d %H:%M',
100 100 '%Y-%m-%d %I:%M%p',
101 101 '%Y-%m-%d',
102 102 '%m-%d',
103 103 '%m/%d',
104 104 '%m/%d/%y',
105 105 '%m/%d/%Y',
106 106 '%a %b %d %H:%M:%S %Y',
107 107 '%a %b %d %I:%M:%S%p %Y',
108 108 '%a, %d %b %Y %H:%M:%S', # GNU coreutils "/bin/date --rfc-2822"
109 109 '%b %d %H:%M:%S %Y',
110 110 '%b %d %I:%M:%S%p %Y',
111 111 '%b %d %H:%M:%S',
112 112 '%b %d %I:%M:%S%p',
113 113 '%b %d %H:%M',
114 114 '%b %d %I:%M%p',
115 115 '%b %d %Y',
116 116 '%b %d',
117 117 '%H:%M:%S',
118 118 '%I:%M:%SP',
119 119 '%H:%M',
120 120 '%I:%M%p',
121 121 )
122 122
123 123 extendeddateformats = defaultdateformats + (
124 124 "%Y",
125 125 "%Y-%m",
126 126 "%b",
127 127 "%b %Y",
128 128 )
129 129
130 130 class SignalInterrupt(Exception):
131 131 """Exception raised on SIGTERM and SIGHUP."""
132 132
133 133 # differences from SafeConfigParser:
134 134 # - case-sensitive keys
135 135 # - allows values that are not strings (this means that you may not
136 136 # be able to save the configuration to a file)
137 137 class configparser(ConfigParser.SafeConfigParser):
138 138 def optionxform(self, optionstr):
139 139 return optionstr
140 140
141 141 def set(self, section, option, value):
142 142 return ConfigParser.ConfigParser.set(self, section, option, value)
143 143
144 144 def _interpolate(self, section, option, rawval, vars):
145 145 if not isinstance(rawval, basestring):
146 146 return rawval
147 147 return ConfigParser.SafeConfigParser._interpolate(self, section,
148 148 option, rawval, vars)
149 149
150 150 def cachefunc(func):
151 151 '''cache the result of function calls'''
152 152 # XXX doesn't handle keywords args
153 153 cache = {}
154 154 if func.func_code.co_argcount == 1:
155 155 # we gain a small amount of time because
156 156 # we don't need to pack/unpack the list
157 157 def f(arg):
158 158 if arg not in cache:
159 159 cache[arg] = func(arg)
160 160 return cache[arg]
161 161 else:
162 162 def f(*args):
163 163 if args not in cache:
164 164 cache[args] = func(*args)
165 165 return cache[args]
166 166
167 167 return f
168 168
169 169 def pipefilter(s, cmd):
170 170 '''filter string S through command CMD, returning its output'''
171 171 (pin, pout) = os.popen2(cmd, 'b')
172 172 def writer():
173 173 try:
174 174 pin.write(s)
175 175 pin.close()
176 176 except IOError, inst:
177 177 if inst.errno != errno.EPIPE:
178 178 raise
179 179
180 180 # we should use select instead on UNIX, but this will work on most
181 181 # systems, including Windows
182 182 w = threading.Thread(target=writer)
183 183 w.start()
184 184 f = pout.read()
185 185 pout.close()
186 186 w.join()
187 187 return f
188 188
189 189 def tempfilter(s, cmd):
190 190 '''filter string S through a pair of temporary files with CMD.
191 191 CMD is used as a template to create the real command to be run,
192 192 with the strings INFILE and OUTFILE replaced by the real names of
193 193 the temporary files generated.'''
194 194 inname, outname = None, None
195 195 try:
196 196 infd, inname = tempfile.mkstemp(prefix='hg-filter-in-')
197 197 fp = os.fdopen(infd, 'wb')
198 198 fp.write(s)
199 199 fp.close()
200 200 outfd, outname = tempfile.mkstemp(prefix='hg-filter-out-')
201 201 os.close(outfd)
202 202 cmd = cmd.replace('INFILE', inname)
203 203 cmd = cmd.replace('OUTFILE', outname)
204 204 code = os.system(cmd)
205 205 if sys.platform == 'OpenVMS' and code & 1:
206 206 code = 0
207 207 if code: raise Abort(_("command '%s' failed: %s") %
208 208 (cmd, explain_exit(code)))
209 209 return open(outname, 'rb').read()
210 210 finally:
211 211 try:
212 212 if inname: os.unlink(inname)
213 213 except: pass
214 214 try:
215 215 if outname: os.unlink(outname)
216 216 except: pass
217 217
218 218 filtertable = {
219 219 'tempfile:': tempfilter,
220 220 'pipe:': pipefilter,
221 221 }
222 222
223 223 def filter(s, cmd):
224 224 "filter a string through a command that transforms its input to its output"
225 225 for name, fn in filtertable.iteritems():
226 226 if cmd.startswith(name):
227 227 return fn(s, cmd[len(name):].lstrip())
228 228 return pipefilter(s, cmd)
229 229
230 230 def binary(s):
231 231 """return true if a string is binary data using diff's heuristic"""
232 232 if s and '\0' in s[:4096]:
233 233 return True
234 234 return False
235 235
236 236 def unique(g):
237 237 """return the uniq elements of iterable g"""
238 238 seen = {}
239 239 l = []
240 240 for f in g:
241 241 if f not in seen:
242 242 seen[f] = 1
243 243 l.append(f)
244 244 return l
245 245
246 246 class Abort(Exception):
247 247 """Raised if a command needs to print an error and exit."""
248 248
249 249 class UnexpectedOutput(Abort):
250 250 """Raised to print an error with part of output and exit."""
251 251
252 252 def always(fn): return True
253 253 def never(fn): return False
254 254
255 255 def expand_glob(pats):
256 256 '''On Windows, expand the implicit globs in a list of patterns'''
257 257 if os.name != 'nt':
258 258 return list(pats)
259 259 ret = []
260 260 for p in pats:
261 261 kind, name = patkind(p, None)
262 262 if kind is None:
263 263 globbed = glob.glob(name)
264 264 if globbed:
265 265 ret.extend(globbed)
266 266 continue
267 267 # if we couldn't expand the glob, just keep it around
268 268 ret.append(p)
269 269 return ret
270 270
271 271 def patkind(name, dflt_pat='glob'):
272 272 """Split a string into an optional pattern kind prefix and the
273 273 actual pattern."""
274 274 for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre':
275 275 if name.startswith(prefix + ':'): return name.split(':', 1)
276 276 return dflt_pat, name
277 277
278 278 def globre(pat, head='^', tail='$'):
279 279 "convert a glob pattern into a regexp"
280 280 i, n = 0, len(pat)
281 281 res = ''
282 282 group = False
283 283 def peek(): return i < n and pat[i]
284 284 while i < n:
285 285 c = pat[i]
286 286 i = i+1
287 287 if c == '*':
288 288 if peek() == '*':
289 289 i += 1
290 290 res += '.*'
291 291 else:
292 292 res += '[^/]*'
293 293 elif c == '?':
294 294 res += '.'
295 295 elif c == '[':
296 296 j = i
297 297 if j < n and pat[j] in '!]':
298 298 j += 1
299 299 while j < n and pat[j] != ']':
300 300 j += 1
301 301 if j >= n:
302 302 res += '\\['
303 303 else:
304 304 stuff = pat[i:j].replace('\\','\\\\')
305 305 i = j + 1
306 306 if stuff[0] == '!':
307 307 stuff = '^' + stuff[1:]
308 308 elif stuff[0] == '^':
309 309 stuff = '\\' + stuff
310 310 res = '%s[%s]' % (res, stuff)
311 311 elif c == '{':
312 312 group = True
313 313 res += '(?:'
314 314 elif c == '}' and group:
315 315 res += ')'
316 316 group = False
317 317 elif c == ',' and group:
318 318 res += '|'
319 319 elif c == '\\':
320 320 p = peek()
321 321 if p:
322 322 i += 1
323 323 res += re.escape(p)
324 324 else:
325 325 res += re.escape(c)
326 326 else:
327 327 res += re.escape(c)
328 328 return head + res + tail
329 329
330 330 _globchars = {'[': 1, '{': 1, '*': 1, '?': 1}
331 331
332 332 def pathto(root, n1, n2):
333 333 '''return the relative path from one place to another.
334 334 root should use os.sep to separate directories
335 335 n1 should use os.sep to separate directories
336 336 n2 should use "/" to separate directories
337 337 returns an os.sep-separated path.
338 338
339 339 If n1 is a relative path, it's assumed it's
340 340 relative to root.
341 341 n2 should always be relative to root.
342 342 '''
343 343 if not n1: return localpath(n2)
344 344 if os.path.isabs(n1):
345 345 if os.path.splitdrive(root)[0] != os.path.splitdrive(n1)[0]:
346 346 return os.path.join(root, localpath(n2))
347 347 n2 = '/'.join((pconvert(root), n2))
348 348 a, b = n1.split(os.sep), n2.split('/')
349 349 a.reverse()
350 350 b.reverse()
351 351 while a and b and a[-1] == b[-1]:
352 352 a.pop()
353 353 b.pop()
354 354 b.reverse()
355 355 return os.sep.join((['..'] * len(a)) + b)
356 356
357 357 def canonpath(root, cwd, myname):
358 358 """return the canonical path of myname, given cwd and root"""
359 359 if root == os.sep:
360 360 rootsep = os.sep
361 361 elif root.endswith(os.sep):
362 362 rootsep = root
363 363 else:
364 364 rootsep = root + os.sep
365 365 name = myname
366 366 if not os.path.isabs(name):
367 367 name = os.path.join(root, cwd, name)
368 368 name = os.path.normpath(name)
369 369 audit_path = path_auditor(root)
370 370 if name != rootsep and name.startswith(rootsep):
371 371 name = name[len(rootsep):]
372 372 audit_path(name)
373 373 return pconvert(name)
374 374 elif name == root:
375 375 return ''
376 376 else:
377 377 # Determine whether `name' is in the hierarchy at or beneath `root',
378 378 # by iterating name=dirname(name) until that causes no change (can't
379 379 # check name == '/', because that doesn't work on windows). For each
380 380 # `name', compare dev/inode numbers. If they match, the list `rel'
381 381 # holds the reversed list of components making up the relative file
382 382 # name we want.
383 383 root_st = os.stat(root)
384 384 rel = []
385 385 while True:
386 386 try:
387 387 name_st = os.stat(name)
388 388 except OSError:
389 389 break
390 390 if samestat(name_st, root_st):
391 391 if not rel:
392 392 # name was actually the same as root (maybe a symlink)
393 393 return ''
394 394 rel.reverse()
395 395 name = os.path.join(*rel)
396 396 audit_path(name)
397 397 return pconvert(name)
398 398 dirname, basename = os.path.split(name)
399 399 rel.append(basename)
400 400 if dirname == name:
401 401 break
402 402 name = dirname
403 403
404 404 raise Abort('%s not under root' % myname)
405 405
406 406 def matcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None):
407 407 return _matcher(canonroot, cwd, names, inc, exc, 'glob', src)
408 408
409 409 def cmdmatcher(canonroot, cwd='', names=[], inc=[], exc=[], src=None,
410 410 globbed=False, default=None):
411 411 default = default or 'relpath'
412 412 if default == 'relpath' and not globbed:
413 413 names = expand_glob(names)
414 414 return _matcher(canonroot, cwd, names, inc, exc, default, src)
415 415
416 416 def _matcher(canonroot, cwd, names, inc, exc, dflt_pat, src):
417 417 """build a function to match a set of file patterns
418 418
419 419 arguments:
420 420 canonroot - the canonical root of the tree you're matching against
421 421 cwd - the current working directory, if relevant
422 422 names - patterns to find
423 423 inc - patterns to include
424 424 exc - patterns to exclude
425 425 dflt_pat - if a pattern in names has no explicit type, assume this one
426 426 src - where these patterns came from (e.g. .hgignore)
427 427
428 428 a pattern is one of:
429 429 'glob:<glob>' - a glob relative to cwd
430 430 're:<regexp>' - a regular expression
431 431 'path:<path>' - a path relative to canonroot
432 432 'relglob:<glob>' - an unrooted glob (*.c matches C files in all dirs)
433 433 'relpath:<path>' - a path relative to cwd
434 434 'relre:<regexp>' - a regexp that doesn't have to match the start of a name
435 435 '<something>' - one of the cases above, selected by the dflt_pat argument
436 436
437 437 returns:
438 438 a 3-tuple containing
439 439 - list of roots (places where one should start a recursive walk of the fs);
440 440 this often matches the explicit non-pattern names passed in, but also
441 441 includes the initial part of glob: patterns that has no glob characters
442 442 - a bool match(filename) function
443 443 - a bool indicating if any patterns were passed in
444 444 """
445 445
446 446 # a common case: no patterns at all
447 447 if not names and not inc and not exc:
448 448 return [], always, False
449 449
450 450 def contains_glob(name):
451 451 for c in name:
452 452 if c in _globchars: return True
453 453 return False
454 454
455 455 def regex(kind, name, tail):
456 456 '''convert a pattern into a regular expression'''
457 457 if not name:
458 458 return ''
459 459 if kind == 're':
460 460 return name
461 461 elif kind == 'path':
462 462 return '^' + re.escape(name) + '(?:/|$)'
463 463 elif kind == 'relglob':
464 464 return globre(name, '(?:|.*/)', tail)
465 465 elif kind == 'relpath':
466 466 return re.escape(name) + '(?:/|$)'
467 467 elif kind == 'relre':
468 468 if name.startswith('^'):
469 469 return name
470 470 return '.*' + name
471 471 return globre(name, '', tail)
472 472
473 473 def matchfn(pats, tail):
474 474 """build a matching function from a set of patterns"""
475 475 if not pats:
476 476 return
477 477 try:
478 478 pat = '(?:%s)' % '|'.join([regex(k, p, tail) for (k, p) in pats])
479 479 return re.compile(pat).match
480 480 except OverflowError:
481 481 # We're using a Python with a tiny regex engine and we
482 482 # made it explode, so we'll divide the pattern list in two
483 483 # until it works
484 484 l = len(pats)
485 485 if l < 2:
486 486 raise
487 487 a, b = matchfn(pats[:l/2], tail), matchfn(pats[l/2:], tail)
488 488 return lambda s: a(s) or b(s)
489 489 except re.error:
490 490 for k, p in pats:
491 491 try:
492 492 re.compile('(?:%s)' % regex(k, p, tail))
493 493 except re.error:
494 494 if src:
495 495 raise Abort("%s: invalid pattern (%s): %s" %
496 496 (src, k, p))
497 497 else:
498 498 raise Abort("invalid pattern (%s): %s" % (k, p))
499 499 raise Abort("invalid pattern")
500 500
501 501 def globprefix(pat):
502 502 '''return the non-glob prefix of a path, e.g. foo/* -> foo'''
503 503 root = []
504 504 for p in pat.split('/'):
505 505 if contains_glob(p): break
506 506 root.append(p)
507 507 return '/'.join(root) or '.'
508 508
509 509 def normalizepats(names, default):
510 510 pats = []
511 511 roots = []
512 512 anypats = False
513 513 for kind, name in [patkind(p, default) for p in names]:
514 514 if kind in ('glob', 'relpath'):
515 515 name = canonpath(canonroot, cwd, name)
516 516 elif kind in ('relglob', 'path'):
517 517 name = normpath(name)
518 518
519 519 pats.append((kind, name))
520 520
521 521 if kind in ('glob', 're', 'relglob', 'relre'):
522 522 anypats = True
523 523
524 524 if kind == 'glob':
525 525 root = globprefix(name)
526 526 roots.append(root)
527 527 elif kind in ('relpath', 'path'):
528 528 roots.append(name or '.')
529 529 elif kind == 'relglob':
530 530 roots.append('.')
531 531 return roots, pats, anypats
532 532
533 533 roots, pats, anypats = normalizepats(names, dflt_pat)
534 534
535 535 patmatch = matchfn(pats, '$') or always
536 536 incmatch = always
537 537 if inc:
538 538 dummy, inckinds, dummy = normalizepats(inc, 'glob')
539 539 incmatch = matchfn(inckinds, '(?:/|$)')
540 540 excmatch = lambda fn: False
541 541 if exc:
542 542 dummy, exckinds, dummy = normalizepats(exc, 'glob')
543 543 excmatch = matchfn(exckinds, '(?:/|$)')
544 544
545 545 if not names and inc and not exc:
546 546 # common case: hgignore patterns
547 547 match = incmatch
548 548 else:
549 549 match = lambda fn: incmatch(fn) and not excmatch(fn) and patmatch(fn)
550 550
551 551 return (roots, match, (inc or exc or anypats) and True)
552 552
553 553 _hgexecutable = None
554 554
555 555 def hgexecutable():
556 556 """return location of the 'hg' executable.
557 557
558 558 Defaults to $HG or 'hg' in the search path.
559 559 """
560 560 if _hgexecutable is None:
561 561 set_hgexecutable(os.environ.get('HG') or find_exe('hg', 'hg'))
562 562 return _hgexecutable
563 563
564 564 def set_hgexecutable(path):
565 565 """set location of the 'hg' executable"""
566 566 global _hgexecutable
567 567 _hgexecutable = path
568 568
569 569 def system(cmd, environ={}, cwd=None, onerr=None, errprefix=None):
570 570 '''enhanced shell command execution.
571 571 run with environment maybe modified, maybe in different dir.
572 572
573 573 if command fails and onerr is None, return status. if ui object,
574 574 print error message and return status, else raise onerr object as
575 575 exception.'''
576 576 def py2shell(val):
577 577 'convert python object into string that is useful to shell'
578 578 if val in (None, False):
579 579 return '0'
580 580 if val == True:
581 581 return '1'
582 582 return str(val)
583 583 oldenv = {}
584 584 for k in environ:
585 585 oldenv[k] = os.environ.get(k)
586 586 if cwd is not None:
587 587 oldcwd = os.getcwd()
588 588 origcmd = cmd
589 589 if os.name == 'nt':
590 590 cmd = '"%s"' % cmd
591 591 try:
592 592 for k, v in environ.iteritems():
593 593 os.environ[k] = py2shell(v)
594 594 os.environ['HG'] = hgexecutable()
595 595 if cwd is not None and oldcwd != cwd:
596 596 os.chdir(cwd)
597 597 rc = os.system(cmd)
598 598 if sys.platform == 'OpenVMS' and rc & 1:
599 599 rc = 0
600 600 if rc and onerr:
601 601 errmsg = '%s %s' % (os.path.basename(origcmd.split(None, 1)[0]),
602 602 explain_exit(rc)[0])
603 603 if errprefix:
604 604 errmsg = '%s: %s' % (errprefix, errmsg)
605 605 try:
606 606 onerr.warn(errmsg + '\n')
607 607 except AttributeError:
608 608 raise onerr(errmsg)
609 609 return rc
610 610 finally:
611 611 for k, v in oldenv.iteritems():
612 612 if v is None:
613 613 del os.environ[k]
614 614 else:
615 615 os.environ[k] = v
616 616 if cwd is not None and oldcwd != cwd:
617 617 os.chdir(oldcwd)
618 618
619 619 # os.path.lexists is not available on python2.3
620 620 def lexists(filename):
621 621 "test whether a file with this name exists. does not follow symlinks"
622 622 try:
623 623 os.lstat(filename)
624 624 except:
625 625 return False
626 626 return True
627 627
628 628 def rename(src, dst):
629 629 """forcibly rename a file"""
630 630 try:
631 631 os.rename(src, dst)
632 632 except OSError, err: # FIXME: check err (EEXIST ?)
633 633 # on windows, rename to existing file is not allowed, so we
634 634 # must delete destination first. but if file is open, unlink
635 635 # schedules it for delete but does not delete it. rename
636 636 # happens immediately even for open files, so we create
637 637 # temporary file, delete it, rename destination to that name,
638 638 # then delete that. then rename is safe to do.
639 639 fd, temp = tempfile.mkstemp(dir=os.path.dirname(dst) or '.')
640 640 os.close(fd)
641 641 os.unlink(temp)
642 642 os.rename(dst, temp)
643 643 os.unlink(temp)
644 644 os.rename(src, dst)
645 645
646 646 def unlink(f):
647 647 """unlink and remove the directory if it is empty"""
648 648 os.unlink(f)
649 649 # try removing directories that might now be empty
650 650 try:
651 651 os.removedirs(os.path.dirname(f))
652 652 except OSError:
653 653 pass
654 654
655 655 def copyfile(src, dest):
656 656 "copy a file, preserving mode"
657 657 if os.path.islink(src):
658 658 try:
659 659 os.unlink(dest)
660 660 except:
661 661 pass
662 662 os.symlink(os.readlink(src), dest)
663 663 else:
664 664 try:
665 665 shutil.copyfile(src, dest)
666 666 shutil.copymode(src, dest)
667 667 except shutil.Error, inst:
668 668 raise Abort(str(inst))
669 669
670 670 def copyfiles(src, dst, hardlink=None):
671 671 """Copy a directory tree using hardlinks if possible"""
672 672
673 673 if hardlink is None:
674 674 hardlink = (os.stat(src).st_dev ==
675 675 os.stat(os.path.dirname(dst)).st_dev)
676 676
677 677 if os.path.isdir(src):
678 678 os.mkdir(dst)
679 679 for name in os.listdir(src):
680 680 srcname = os.path.join(src, name)
681 681 dstname = os.path.join(dst, name)
682 682 copyfiles(srcname, dstname, hardlink)
683 683 else:
684 684 if hardlink:
685 685 try:
686 686 os_link(src, dst)
687 687 except (IOError, OSError):
688 688 hardlink = False
689 689 shutil.copy(src, dst)
690 690 else:
691 691 shutil.copy(src, dst)
692 692
693 693 class path_auditor(object):
694 694 '''ensure that a filesystem path contains no banned components.
695 695 the following properties of a path are checked:
696 696
697 697 - under top-level .hg
698 698 - starts at the root of a windows drive
699 699 - contains ".."
700 700 - traverses a symlink (e.g. a/symlink_here/b)
701 701 - inside a nested repository'''
702 702
703 703 def __init__(self, root):
704 704 self.audited = set()
705 705 self.auditeddir = set()
706 706 self.root = root
707 707
708 708 def __call__(self, path):
709 709 if path in self.audited:
710 710 return
711 711 normpath = os.path.normcase(path)
712 712 parts = normpath.split(os.sep)
713 713 if (os.path.splitdrive(path)[0] or parts[0] in ('.hg', '')
714 714 or os.pardir in parts):
715 715 raise Abort(_("path contains illegal component: %s") % path)
716 716 def check(prefix):
717 717 curpath = os.path.join(self.root, prefix)
718 718 try:
719 719 st = os.lstat(curpath)
720 720 except OSError, err:
721 721 # EINVAL can be raised as invalid path syntax under win32.
722 722 # They must be ignored for patterns can be checked too.
723 723 if err.errno not in (errno.ENOENT, errno.EINVAL):
724 724 raise
725 725 else:
726 726 if stat.S_ISLNK(st.st_mode):
727 727 raise Abort(_('path %r traverses symbolic link %r') %
728 728 (path, prefix))
729 729 elif (stat.S_ISDIR(st.st_mode) and
730 730 os.path.isdir(os.path.join(curpath, '.hg'))):
731 731 raise Abort(_('path %r is inside repo %r') %
732 732 (path, prefix))
733 733
734 734 prefixes = []
735 735 for c in strutil.rfindall(normpath, os.sep):
736 736 prefix = normpath[:c]
737 737 if prefix in self.auditeddir:
738 738 break
739 739 check(prefix)
740 740 prefixes.append(prefix)
741 741
742 742 self.audited.add(path)
743 743 # only add prefixes to the cache after checking everything: we don't
744 744 # want to add "foo/bar/baz" before checking if there's a "foo/.hg"
745 745 self.auditeddir.update(prefixes)
746 746
747 747 def _makelock_file(info, pathname):
748 748 ld = os.open(pathname, os.O_CREAT | os.O_WRONLY | os.O_EXCL)
749 749 os.write(ld, info)
750 750 os.close(ld)
751 751
752 752 def _readlock_file(pathname):
753 753 return posixfile(pathname).read()
754 754
755 755 def nlinks(pathname):
756 756 """Return number of hardlinks for the given file."""
757 757 return os.lstat(pathname).st_nlink
758 758
759 759 if hasattr(os, 'link'):
760 760 os_link = os.link
761 761 else:
762 762 def os_link(src, dst):
763 763 raise OSError(0, _("Hardlinks not supported"))
764 764
765 765 def fstat(fp):
766 766 '''stat file object that may not have fileno method.'''
767 767 try:
768 768 return os.fstat(fp.fileno())
769 769 except AttributeError:
770 770 return os.stat(fp.name)
771 771
772 772 posixfile = file
773 773
774 774 def is_win_9x():
775 775 '''return true if run on windows 95, 98 or me.'''
776 776 try:
777 777 return sys.getwindowsversion()[3] == 1
778 778 except AttributeError:
779 779 return os.name == 'nt' and 'command' in os.environ.get('comspec', '')
780 780
781 781 getuser_fallback = None
782 782
783 783 def getuser():
784 784 '''return name of current user'''
785 785 try:
786 786 return getpass.getuser()
787 787 except ImportError:
788 788 # import of pwd will fail on windows - try fallback
789 789 if getuser_fallback:
790 790 return getuser_fallback()
791 791 # raised if win32api not available
792 792 raise Abort(_('user name not available - set USERNAME '
793 793 'environment variable'))
794 794
795 795 def username(uid=None):
796 796 """Return the name of the user with the given uid.
797 797
798 798 If uid is None, return the name of the current user."""
799 799 try:
800 800 import pwd
801 801 if uid is None:
802 802 uid = os.getuid()
803 803 try:
804 804 return pwd.getpwuid(uid)[0]
805 805 except KeyError:
806 806 return str(uid)
807 807 except ImportError:
808 808 return None
809 809
810 810 def groupname(gid=None):
811 811 """Return the name of the group with the given gid.
812 812
813 813 If gid is None, return the name of the current group."""
814 814 try:
815 815 import grp
816 816 if gid is None:
817 817 gid = os.getgid()
818 818 try:
819 819 return grp.getgrgid(gid)[0]
820 820 except KeyError:
821 821 return str(gid)
822 822 except ImportError:
823 823 return None
824 824
825 825 # File system features
826 826
827 827 def checkfolding(path):
828 828 """
829 829 Check whether the given path is on a case-sensitive filesystem
830 830
831 831 Requires a path (like /foo/.hg) ending with a foldable final
832 832 directory component.
833 833 """
834 834 s1 = os.stat(path)
835 835 d, b = os.path.split(path)
836 836 p2 = os.path.join(d, b.upper())
837 837 if path == p2:
838 838 p2 = os.path.join(d, b.lower())
839 839 try:
840 840 s2 = os.stat(p2)
841 841 if s2 == s1:
842 842 return False
843 843 return True
844 844 except:
845 845 return True
846 846
847 847 def checkexec(path):
848 848 """
849 849 Check whether the given path is on a filesystem with UNIX-like exec flags
850 850
851 851 Requires a directory (like /foo/.hg)
852 852 """
853 853 try:
854 854 fh, fn = tempfile.mkstemp("", "", path)
855 855 os.close(fh)
856 856 m = os.stat(fn).st_mode
857 857 os.chmod(fn, m ^ 0111)
858 858 r = (os.stat(fn).st_mode != m)
859 859 os.unlink(fn)
860 860 except (IOError,OSError):
861 861 # we don't care, the user probably won't be able to commit anyway
862 862 return False
863 863 return r
864 864
865 865 def execfunc(path, fallback):
866 866 '''return an is_exec() function with default to fallback'''
867 867 if checkexec(path):
868 868 return lambda x: is_exec(os.path.join(path, x))
869 869 return fallback
870 870
871 871 def checklink(path):
872 872 """check whether the given path is on a symlink-capable filesystem"""
873 873 # mktemp is not racy because symlink creation will fail if the
874 874 # file already exists
875 875 name = tempfile.mktemp(dir=path)
876 876 try:
877 877 os.symlink(".", name)
878 878 os.unlink(name)
879 879 return True
880 880 except (OSError, AttributeError):
881 881 return False
882 882
883 883 def linkfunc(path, fallback):
884 884 '''return an is_link() function with default to fallback'''
885 885 if checklink(path):
886 886 return lambda x: os.path.islink(os.path.join(path, x))
887 887 return fallback
888 888
889 889 _umask = os.umask(0)
890 890 os.umask(_umask)
891 891
892 892 def needbinarypatch():
893 893 """return True if patches should be applied in binary mode by default."""
894 894 return os.name == 'nt'
895 895
896 896 # Platform specific variants
897 897 if os.name == 'nt':
898 898 import msvcrt
899 899 nulldev = 'NUL:'
900 900
901 901 class winstdout:
902 902 '''stdout on windows misbehaves if sent through a pipe'''
903 903
904 904 def __init__(self, fp):
905 905 self.fp = fp
906 906
907 907 def __getattr__(self, key):
908 908 return getattr(self.fp, key)
909 909
910 910 def close(self):
911 911 try:
912 912 self.fp.close()
913 913 except: pass
914 914
915 915 def write(self, s):
916 916 try:
917 917 return self.fp.write(s)
918 918 except IOError, inst:
919 919 if inst.errno != 0: raise
920 920 self.close()
921 921 raise IOError(errno.EPIPE, 'Broken pipe')
922 922
923 923 def flush(self):
924 924 try:
925 925 return self.fp.flush()
926 926 except IOError, inst:
927 927 if inst.errno != errno.EINVAL: raise
928 928 self.close()
929 929 raise IOError(errno.EPIPE, 'Broken pipe')
930 930
931 931 sys.stdout = winstdout(sys.stdout)
932 932
933 933 def system_rcpath():
934 934 try:
935 935 return system_rcpath_win32()
936 936 except:
937 937 return [r'c:\mercurial\mercurial.ini']
938 938
939 939 def user_rcpath():
940 940 '''return os-specific hgrc search path to the user dir'''
941 941 try:
942 942 userrc = user_rcpath_win32()
943 943 except:
944 944 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
945 945 path = [userrc]
946 946 userprofile = os.environ.get('USERPROFILE')
947 947 if userprofile:
948 948 path.append(os.path.join(userprofile, 'mercurial.ini'))
949 949 return path
950 950
951 951 def parse_patch_output(output_line):
952 952 """parses the output produced by patch and returns the file name"""
953 953 pf = output_line[14:]
954 954 if pf[0] == '`':
955 955 pf = pf[1:-1] # Remove the quotes
956 956 return pf
957 957
958 958 def testpid(pid):
959 959 '''return False if pid dead, True if running or not known'''
960 960 return True
961 961
962 962 def set_exec(f, mode):
963 963 pass
964 964
965 965 def set_link(f, mode):
966 966 pass
967 967
968 968 def set_binary(fd):
969 969 msvcrt.setmode(fd.fileno(), os.O_BINARY)
970 970
971 971 def pconvert(path):
972 972 return path.replace("\\", "/")
973 973
974 974 def localpath(path):
975 975 return path.replace('/', '\\')
976 976
977 977 def normpath(path):
978 978 return pconvert(os.path.normpath(path))
979 979
980 980 makelock = _makelock_file
981 981 readlock = _readlock_file
982 982
983 983 def samestat(s1, s2):
984 984 return False
985 985
986 986 # A sequence of backslashes is special iff it precedes a double quote:
987 987 # - if there's an even number of backslashes, the double quote is not
988 988 # quoted (i.e. it ends the quoted region)
989 989 # - if there's an odd number of backslashes, the double quote is quoted
990 990 # - in both cases, every pair of backslashes is unquoted into a single
991 991 # backslash
992 992 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
993 993 # So, to quote a string, we must surround it in double quotes, double
994 994 # the number of backslashes that preceed double quotes and add another
995 995 # backslash before every double quote (being careful with the double
996 996 # quote we've appended to the end)
997 997 _quotere = None
998 998 def shellquote(s):
999 999 global _quotere
1000 1000 if _quotere is None:
1001 1001 _quotere = re.compile(r'(\\*)("|\\$)')
1002 1002 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1003 1003
1004 def quotecommand(cmd):
1005 """Build a command string suitable for os.popen* calls."""
1006 # The extra quotes are needed because popen* runs the command
1007 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1008 return '"' + cmd + '"'
1009
1004 1010 def explain_exit(code):
1005 1011 return _("exited with status %d") % code, code
1006 1012
1007 1013 # if you change this stub into a real check, please try to implement the
1008 1014 # username and groupname functions above, too.
1009 1015 def isowner(fp, st=None):
1010 1016 return True
1011 1017
1012 1018 def find_in_path(name, path, default=None):
1013 1019 '''find name in search path. path can be string (will be split
1014 1020 with os.pathsep), or iterable thing that returns strings. if name
1015 1021 found, return path to name. else return default. name is looked up
1016 1022 using cmd.exe rules, using PATHEXT.'''
1017 1023 if isinstance(path, str):
1018 1024 path = path.split(os.pathsep)
1019 1025
1020 1026 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1021 1027 pathext = pathext.lower().split(os.pathsep)
1022 1028 isexec = os.path.splitext(name)[1].lower() in pathext
1023 1029
1024 1030 for p in path:
1025 1031 p_name = os.path.join(p, name)
1026 1032
1027 1033 if isexec and os.path.exists(p_name):
1028 1034 return p_name
1029 1035
1030 1036 for ext in pathext:
1031 1037 p_name_ext = p_name + ext
1032 1038 if os.path.exists(p_name_ext):
1033 1039 return p_name_ext
1034 1040 return default
1035 1041
1036 1042 def set_signal_handler():
1037 1043 try:
1038 1044 set_signal_handler_win32()
1039 1045 except NameError:
1040 1046 pass
1041 1047
1042 1048 try:
1043 1049 # override functions with win32 versions if possible
1044 1050 from util_win32 import *
1045 1051 if not is_win_9x():
1046 1052 posixfile = posixfile_nt
1047 1053 except ImportError:
1048 1054 pass
1049 1055
1050 1056 else:
1051 1057 nulldev = '/dev/null'
1052 1058
1053 1059 def rcfiles(path):
1054 1060 rcs = [os.path.join(path, 'hgrc')]
1055 1061 rcdir = os.path.join(path, 'hgrc.d')
1056 1062 try:
1057 1063 rcs.extend([os.path.join(rcdir, f) for f in os.listdir(rcdir)
1058 1064 if f.endswith(".rc")])
1059 1065 except OSError:
1060 1066 pass
1061 1067 return rcs
1062 1068
1063 1069 def system_rcpath():
1064 1070 path = []
1065 1071 # old mod_python does not set sys.argv
1066 1072 if len(getattr(sys, 'argv', [])) > 0:
1067 1073 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1068 1074 '/../etc/mercurial'))
1069 1075 path.extend(rcfiles('/etc/mercurial'))
1070 1076 return path
1071 1077
1072 1078 def user_rcpath():
1073 1079 return [os.path.expanduser('~/.hgrc')]
1074 1080
1075 1081 def parse_patch_output(output_line):
1076 1082 """parses the output produced by patch and returns the file name"""
1077 1083 pf = output_line[14:]
1078 1084 if os.sys.platform == 'OpenVMS':
1079 1085 if pf[0] == '`':
1080 1086 pf = pf[1:-1] # Remove the quotes
1081 1087 else:
1082 1088 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1083 1089 pf = pf[1:-1] # Remove the quotes
1084 1090 return pf
1085 1091
1086 1092 def is_exec(f):
1087 1093 """check whether a file is executable"""
1088 1094 return (os.lstat(f).st_mode & 0100 != 0)
1089 1095
1090 1096 def set_exec(f, mode):
1091 1097 s = os.lstat(f).st_mode
1092 1098 if (s & 0100 != 0) == mode:
1093 1099 return
1094 1100 if mode:
1095 1101 # Turn on +x for every +r bit when making a file executable
1096 1102 # and obey umask.
1097 1103 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1098 1104 else:
1099 1105 os.chmod(f, s & 0666)
1100 1106
1101 1107 def set_link(f, mode):
1102 1108 """make a file a symbolic link/regular file
1103 1109
1104 1110 if a file is changed to a link, its contents become the link data
1105 1111 if a link is changed to a file, its link data become its contents
1106 1112 """
1107 1113
1108 1114 m = os.path.islink(f)
1109 1115 if m == bool(mode):
1110 1116 return
1111 1117
1112 1118 if mode: # switch file to link
1113 1119 data = file(f).read()
1114 1120 os.unlink(f)
1115 1121 os.symlink(data, f)
1116 1122 else:
1117 1123 data = os.readlink(f)
1118 1124 os.unlink(f)
1119 1125 file(f, "w").write(data)
1120 1126
1121 1127 def set_binary(fd):
1122 1128 pass
1123 1129
1124 1130 def pconvert(path):
1125 1131 return path
1126 1132
1127 1133 def localpath(path):
1128 1134 return path
1129 1135
1130 1136 normpath = os.path.normpath
1131 1137 samestat = os.path.samestat
1132 1138
1133 1139 def makelock(info, pathname):
1134 1140 try:
1135 1141 os.symlink(info, pathname)
1136 1142 except OSError, why:
1137 1143 if why.errno == errno.EEXIST:
1138 1144 raise
1139 1145 else:
1140 1146 _makelock_file(info, pathname)
1141 1147
1142 1148 def readlock(pathname):
1143 1149 try:
1144 1150 return os.readlink(pathname)
1145 1151 except OSError, why:
1146 1152 if why.errno in (errno.EINVAL, errno.ENOSYS):
1147 1153 return _readlock_file(pathname)
1148 1154 else:
1149 1155 raise
1150 1156
1151 1157 def shellquote(s):
1152 1158 if os.sys.platform == 'OpenVMS':
1153 1159 return '"%s"' % s
1154 1160 else:
1155 1161 return "'%s'" % s.replace("'", "'\\''")
1156 1162
1163 def quotecommand(cmd):
1164 return cmd
1165
1157 1166 def testpid(pid):
1158 1167 '''return False if pid dead, True if running or not sure'''
1159 1168 if os.sys.platform == 'OpenVMS':
1160 1169 return True
1161 1170 try:
1162 1171 os.kill(pid, 0)
1163 1172 return True
1164 1173 except OSError, inst:
1165 1174 return inst.errno != errno.ESRCH
1166 1175
1167 1176 def explain_exit(code):
1168 1177 """return a 2-tuple (desc, code) describing a process's status"""
1169 1178 if os.WIFEXITED(code):
1170 1179 val = os.WEXITSTATUS(code)
1171 1180 return _("exited with status %d") % val, val
1172 1181 elif os.WIFSIGNALED(code):
1173 1182 val = os.WTERMSIG(code)
1174 1183 return _("killed by signal %d") % val, val
1175 1184 elif os.WIFSTOPPED(code):
1176 1185 val = os.WSTOPSIG(code)
1177 1186 return _("stopped by signal %d") % val, val
1178 1187 raise ValueError(_("invalid exit code"))
1179 1188
1180 1189 def isowner(fp, st=None):
1181 1190 """Return True if the file object f belongs to the current user.
1182 1191
1183 1192 The return value of a util.fstat(f) may be passed as the st argument.
1184 1193 """
1185 1194 if st is None:
1186 1195 st = fstat(fp)
1187 1196 return st.st_uid == os.getuid()
1188 1197
1189 1198 def find_in_path(name, path, default=None):
1190 1199 '''find name in search path. path can be string (will be split
1191 1200 with os.pathsep), or iterable thing that returns strings. if name
1192 1201 found, return path to name. else return default.'''
1193 1202 if isinstance(path, str):
1194 1203 path = path.split(os.pathsep)
1195 1204 for p in path:
1196 1205 p_name = os.path.join(p, name)
1197 1206 if os.path.exists(p_name):
1198 1207 return p_name
1199 1208 return default
1200 1209
1201 1210 def set_signal_handler():
1202 1211 pass
1203 1212
1204 1213 def find_exe(name, default=None):
1205 1214 '''find path of an executable.
1206 1215 if name contains a path component, return it as is. otherwise,
1207 1216 use normal executable search path.'''
1208 1217
1209 1218 if os.sep in name or sys.platform == 'OpenVMS':
1210 1219 # don't check the executable bit. if the file isn't
1211 1220 # executable, whoever tries to actually run it will give a
1212 1221 # much more useful error message.
1213 1222 return name
1214 1223 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1215 1224
1216 1225 def _buildencodefun():
1217 1226 e = '_'
1218 1227 win_reserved = [ord(x) for x in '\\:*?"<>|']
1219 1228 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1220 1229 for x in (range(32) + range(126, 256) + win_reserved):
1221 1230 cmap[chr(x)] = "~%02x" % x
1222 1231 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1223 1232 cmap[chr(x)] = e + chr(x).lower()
1224 1233 dmap = {}
1225 1234 for k, v in cmap.iteritems():
1226 1235 dmap[v] = k
1227 1236 def decode(s):
1228 1237 i = 0
1229 1238 while i < len(s):
1230 1239 for l in xrange(1, 4):
1231 1240 try:
1232 1241 yield dmap[s[i:i+l]]
1233 1242 i += l
1234 1243 break
1235 1244 except KeyError:
1236 1245 pass
1237 1246 else:
1238 1247 raise KeyError
1239 1248 return (lambda s: "".join([cmap[c] for c in s]),
1240 1249 lambda s: "".join(list(decode(s))))
1241 1250
1242 1251 encodefilename, decodefilename = _buildencodefun()
1243 1252
1244 1253 def encodedopener(openerfn, fn):
1245 1254 def o(path, *args, **kw):
1246 1255 return openerfn(fn(path), *args, **kw)
1247 1256 return o
1248 1257
1249 1258 def mktempcopy(name, emptyok=False):
1250 1259 """Create a temporary file with the same contents from name
1251 1260
1252 1261 The permission bits are copied from the original file.
1253 1262
1254 1263 If the temporary file is going to be truncated immediately, you
1255 1264 can use emptyok=True as an optimization.
1256 1265
1257 1266 Returns the name of the temporary file.
1258 1267 """
1259 1268 d, fn = os.path.split(name)
1260 1269 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1261 1270 os.close(fd)
1262 1271 # Temporary files are created with mode 0600, which is usually not
1263 1272 # what we want. If the original file already exists, just copy
1264 1273 # its mode. Otherwise, manually obey umask.
1265 1274 try:
1266 1275 st_mode = os.lstat(name).st_mode
1267 1276 except OSError, inst:
1268 1277 if inst.errno != errno.ENOENT:
1269 1278 raise
1270 1279 st_mode = 0666 & ~_umask
1271 1280 os.chmod(temp, st_mode)
1272 1281 if emptyok:
1273 1282 return temp
1274 1283 try:
1275 1284 try:
1276 1285 ifp = posixfile(name, "rb")
1277 1286 except IOError, inst:
1278 1287 if inst.errno == errno.ENOENT:
1279 1288 return temp
1280 1289 if not getattr(inst, 'filename', None):
1281 1290 inst.filename = name
1282 1291 raise
1283 1292 ofp = posixfile(temp, "wb")
1284 1293 for chunk in filechunkiter(ifp):
1285 1294 ofp.write(chunk)
1286 1295 ifp.close()
1287 1296 ofp.close()
1288 1297 except:
1289 1298 try: os.unlink(temp)
1290 1299 except: pass
1291 1300 raise
1292 1301 return temp
1293 1302
1294 1303 class atomictempfile(posixfile):
1295 1304 """file-like object that atomically updates a file
1296 1305
1297 1306 All writes will be redirected to a temporary copy of the original
1298 1307 file. When rename is called, the copy is renamed to the original
1299 1308 name, making the changes visible.
1300 1309 """
1301 1310 def __init__(self, name, mode):
1302 1311 self.__name = name
1303 1312 self.temp = mktempcopy(name, emptyok=('w' in mode))
1304 1313 posixfile.__init__(self, self.temp, mode)
1305 1314
1306 1315 def rename(self):
1307 1316 if not self.closed:
1308 1317 posixfile.close(self)
1309 1318 rename(self.temp, localpath(self.__name))
1310 1319
1311 1320 def __del__(self):
1312 1321 if not self.closed:
1313 1322 try:
1314 1323 os.unlink(self.temp)
1315 1324 except: pass
1316 1325 posixfile.close(self)
1317 1326
1318 1327 class opener(object):
1319 1328 """Open files relative to a base directory
1320 1329
1321 1330 This class is used to hide the details of COW semantics and
1322 1331 remote file access from higher level code.
1323 1332 """
1324 1333 def __init__(self, base, audit=True):
1325 1334 self.base = base
1326 1335 if audit:
1327 1336 self.audit_path = path_auditor(base)
1328 1337 else:
1329 1338 self.audit_path = always
1330 1339
1331 1340 def __getattr__(self, name):
1332 1341 if name == '_can_symlink':
1333 1342 self._can_symlink = checklink(self.base)
1334 1343 return self._can_symlink
1335 1344 raise AttributeError(name)
1336 1345
1337 1346 def __call__(self, path, mode="r", text=False, atomictemp=False):
1338 1347 self.audit_path(path)
1339 1348 f = os.path.join(self.base, path)
1340 1349
1341 1350 if not text and "b" not in mode:
1342 1351 mode += "b" # for that other OS
1343 1352
1344 1353 if mode[0] != "r":
1345 1354 try:
1346 1355 nlink = nlinks(f)
1347 1356 except OSError:
1348 1357 nlink = 0
1349 1358 d = os.path.dirname(f)
1350 1359 if not os.path.isdir(d):
1351 1360 os.makedirs(d)
1352 1361 if atomictemp:
1353 1362 return atomictempfile(f, mode)
1354 1363 if nlink > 1:
1355 1364 rename(mktempcopy(f), f)
1356 1365 return posixfile(f, mode)
1357 1366
1358 1367 def symlink(self, src, dst):
1359 1368 self.audit_path(dst)
1360 1369 linkname = os.path.join(self.base, dst)
1361 1370 try:
1362 1371 os.unlink(linkname)
1363 1372 except OSError:
1364 1373 pass
1365 1374
1366 1375 dirname = os.path.dirname(linkname)
1367 1376 if not os.path.exists(dirname):
1368 1377 os.makedirs(dirname)
1369 1378
1370 1379 if self._can_symlink:
1371 1380 try:
1372 1381 os.symlink(src, linkname)
1373 1382 except OSError, err:
1374 1383 raise OSError(err.errno, _('could not symlink to %r: %s') %
1375 1384 (src, err.strerror), linkname)
1376 1385 else:
1377 1386 f = self(dst, "w")
1378 1387 f.write(src)
1379 1388 f.close()
1380 1389
1381 1390 class chunkbuffer(object):
1382 1391 """Allow arbitrary sized chunks of data to be efficiently read from an
1383 1392 iterator over chunks of arbitrary size."""
1384 1393
1385 1394 def __init__(self, in_iter, targetsize = 2**16):
1386 1395 """in_iter is the iterator that's iterating over the input chunks.
1387 1396 targetsize is how big a buffer to try to maintain."""
1388 1397 self.in_iter = iter(in_iter)
1389 1398 self.buf = ''
1390 1399 self.targetsize = int(targetsize)
1391 1400 if self.targetsize <= 0:
1392 1401 raise ValueError(_("targetsize must be greater than 0, was %d") %
1393 1402 targetsize)
1394 1403 self.iterempty = False
1395 1404
1396 1405 def fillbuf(self):
1397 1406 """Ignore target size; read every chunk from iterator until empty."""
1398 1407 if not self.iterempty:
1399 1408 collector = cStringIO.StringIO()
1400 1409 collector.write(self.buf)
1401 1410 for ch in self.in_iter:
1402 1411 collector.write(ch)
1403 1412 self.buf = collector.getvalue()
1404 1413 self.iterempty = True
1405 1414
1406 1415 def read(self, l):
1407 1416 """Read L bytes of data from the iterator of chunks of data.
1408 1417 Returns less than L bytes if the iterator runs dry."""
1409 1418 if l > len(self.buf) and not self.iterempty:
1410 1419 # Clamp to a multiple of self.targetsize
1411 1420 targetsize = self.targetsize * ((l // self.targetsize) + 1)
1412 1421 collector = cStringIO.StringIO()
1413 1422 collector.write(self.buf)
1414 1423 collected = len(self.buf)
1415 1424 for chunk in self.in_iter:
1416 1425 collector.write(chunk)
1417 1426 collected += len(chunk)
1418 1427 if collected >= targetsize:
1419 1428 break
1420 1429 if collected < targetsize:
1421 1430 self.iterempty = True
1422 1431 self.buf = collector.getvalue()
1423 1432 s, self.buf = self.buf[:l], buffer(self.buf, l)
1424 1433 return s
1425 1434
1426 1435 def filechunkiter(f, size=65536, limit=None):
1427 1436 """Create a generator that produces the data in the file size
1428 1437 (default 65536) bytes at a time, up to optional limit (default is
1429 1438 to read all data). Chunks may be less than size bytes if the
1430 1439 chunk is the last chunk in the file, or the file is a socket or
1431 1440 some other type of file that sometimes reads less data than is
1432 1441 requested."""
1433 1442 assert size >= 0
1434 1443 assert limit is None or limit >= 0
1435 1444 while True:
1436 1445 if limit is None: nbytes = size
1437 1446 else: nbytes = min(limit, size)
1438 1447 s = nbytes and f.read(nbytes)
1439 1448 if not s: break
1440 1449 if limit: limit -= len(s)
1441 1450 yield s
1442 1451
1443 1452 def makedate():
1444 1453 lt = time.localtime()
1445 1454 if lt[8] == 1 and time.daylight:
1446 1455 tz = time.altzone
1447 1456 else:
1448 1457 tz = time.timezone
1449 1458 return time.mktime(lt), tz
1450 1459
1451 1460 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1452 1461 """represent a (unixtime, offset) tuple as a localized time.
1453 1462 unixtime is seconds since the epoch, and offset is the time zone's
1454 1463 number of seconds away from UTC. if timezone is false, do not
1455 1464 append time zone to string."""
1456 1465 t, tz = date or makedate()
1457 1466 s = time.strftime(format, time.gmtime(float(t) - tz))
1458 1467 if timezone:
1459 1468 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1460 1469 return s
1461 1470
1462 1471 def strdate(string, format, defaults):
1463 1472 """parse a localized time string and return a (unixtime, offset) tuple.
1464 1473 if the string cannot be parsed, ValueError is raised."""
1465 1474 def timezone(string):
1466 1475 tz = string.split()[-1]
1467 1476 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1468 1477 tz = int(tz)
1469 1478 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1470 1479 return offset
1471 1480 if tz == "GMT" or tz == "UTC":
1472 1481 return 0
1473 1482 return None
1474 1483
1475 1484 # NOTE: unixtime = localunixtime + offset
1476 1485 offset, date = timezone(string), string
1477 1486 if offset != None:
1478 1487 date = " ".join(string.split()[:-1])
1479 1488
1480 1489 # add missing elements from defaults
1481 1490 for part in defaults:
1482 1491 found = [True for p in part if ("%"+p) in format]
1483 1492 if not found:
1484 1493 date += "@" + defaults[part]
1485 1494 format += "@%" + part[0]
1486 1495
1487 1496 timetuple = time.strptime(date, format)
1488 1497 localunixtime = int(calendar.timegm(timetuple))
1489 1498 if offset is None:
1490 1499 # local timezone
1491 1500 unixtime = int(time.mktime(timetuple))
1492 1501 offset = unixtime - localunixtime
1493 1502 else:
1494 1503 unixtime = localunixtime + offset
1495 1504 return unixtime, offset
1496 1505
1497 1506 def parsedate(string, formats=None, defaults=None):
1498 1507 """parse a localized time string and return a (unixtime, offset) tuple.
1499 1508 The date may be a "unixtime offset" string or in one of the specified
1500 1509 formats."""
1501 1510 if not string:
1502 1511 return 0, 0
1503 1512 if not formats:
1504 1513 formats = defaultdateformats
1505 1514 string = string.strip()
1506 1515 try:
1507 1516 when, offset = map(int, string.split(' '))
1508 1517 except ValueError:
1509 1518 # fill out defaults
1510 1519 if not defaults:
1511 1520 defaults = {}
1512 1521 now = makedate()
1513 1522 for part in "d mb yY HI M S".split():
1514 1523 if part not in defaults:
1515 1524 if part[0] in "HMS":
1516 1525 defaults[part] = "00"
1517 1526 elif part[0] in "dm":
1518 1527 defaults[part] = "1"
1519 1528 else:
1520 1529 defaults[part] = datestr(now, "%" + part[0], False)
1521 1530
1522 1531 for format in formats:
1523 1532 try:
1524 1533 when, offset = strdate(string, format, defaults)
1525 1534 except ValueError:
1526 1535 pass
1527 1536 else:
1528 1537 break
1529 1538 else:
1530 1539 raise Abort(_('invalid date: %r ') % string)
1531 1540 # validate explicit (probably user-specified) date and
1532 1541 # time zone offset. values must fit in signed 32 bits for
1533 1542 # current 32-bit linux runtimes. timezones go from UTC-12
1534 1543 # to UTC+14
1535 1544 if abs(when) > 0x7fffffff:
1536 1545 raise Abort(_('date exceeds 32 bits: %d') % when)
1537 1546 if offset < -50400 or offset > 43200:
1538 1547 raise Abort(_('impossible time zone offset: %d') % offset)
1539 1548 return when, offset
1540 1549
1541 1550 def matchdate(date):
1542 1551 """Return a function that matches a given date match specifier
1543 1552
1544 1553 Formats include:
1545 1554
1546 1555 '{date}' match a given date to the accuracy provided
1547 1556
1548 1557 '<{date}' on or before a given date
1549 1558
1550 1559 '>{date}' on or after a given date
1551 1560
1552 1561 """
1553 1562
1554 1563 def lower(date):
1555 1564 return parsedate(date, extendeddateformats)[0]
1556 1565
1557 1566 def upper(date):
1558 1567 d = dict(mb="12", HI="23", M="59", S="59")
1559 1568 for days in "31 30 29".split():
1560 1569 try:
1561 1570 d["d"] = days
1562 1571 return parsedate(date, extendeddateformats, d)[0]
1563 1572 except:
1564 1573 pass
1565 1574 d["d"] = "28"
1566 1575 return parsedate(date, extendeddateformats, d)[0]
1567 1576
1568 1577 if date[0] == "<":
1569 1578 when = upper(date[1:])
1570 1579 return lambda x: x <= when
1571 1580 elif date[0] == ">":
1572 1581 when = lower(date[1:])
1573 1582 return lambda x: x >= when
1574 1583 elif date[0] == "-":
1575 1584 try:
1576 1585 days = int(date[1:])
1577 1586 except ValueError:
1578 1587 raise Abort(_("invalid day spec: %s") % date[1:])
1579 1588 when = makedate()[0] - days * 3600 * 24
1580 1589 return lambda x: x >= when
1581 1590 elif " to " in date:
1582 1591 a, b = date.split(" to ")
1583 1592 start, stop = lower(a), upper(b)
1584 1593 return lambda x: x >= start and x <= stop
1585 1594 else:
1586 1595 start, stop = lower(date), upper(date)
1587 1596 return lambda x: x >= start and x <= stop
1588 1597
1589 1598 def shortuser(user):
1590 1599 """Return a short representation of a user name or email address."""
1591 1600 f = user.find('@')
1592 1601 if f >= 0:
1593 1602 user = user[:f]
1594 1603 f = user.find('<')
1595 1604 if f >= 0:
1596 1605 user = user[f+1:]
1597 1606 f = user.find(' ')
1598 1607 if f >= 0:
1599 1608 user = user[:f]
1600 1609 f = user.find('.')
1601 1610 if f >= 0:
1602 1611 user = user[:f]
1603 1612 return user
1604 1613
1605 1614 def ellipsis(text, maxlength=400):
1606 1615 """Trim string to at most maxlength (default: 400) characters."""
1607 1616 if len(text) <= maxlength:
1608 1617 return text
1609 1618 else:
1610 1619 return "%s..." % (text[:maxlength-3])
1611 1620
1612 1621 def walkrepos(path):
1613 1622 '''yield every hg repository under path, recursively.'''
1614 1623 def errhandler(err):
1615 1624 if err.filename == path:
1616 1625 raise err
1617 1626
1618 1627 for root, dirs, files in os.walk(path, onerror=errhandler):
1619 1628 for d in dirs:
1620 1629 if d == '.hg':
1621 1630 yield root
1622 1631 dirs[:] = []
1623 1632 break
1624 1633
1625 1634 _rcpath = None
1626 1635
1627 1636 def os_rcpath():
1628 1637 '''return default os-specific hgrc search path'''
1629 1638 path = system_rcpath()
1630 1639 path.extend(user_rcpath())
1631 1640 path = [os.path.normpath(f) for f in path]
1632 1641 return path
1633 1642
1634 1643 def rcpath():
1635 1644 '''return hgrc search path. if env var HGRCPATH is set, use it.
1636 1645 for each item in path, if directory, use files ending in .rc,
1637 1646 else use item.
1638 1647 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1639 1648 if no HGRCPATH, use default os-specific path.'''
1640 1649 global _rcpath
1641 1650 if _rcpath is None:
1642 1651 if 'HGRCPATH' in os.environ:
1643 1652 _rcpath = []
1644 1653 for p in os.environ['HGRCPATH'].split(os.pathsep):
1645 1654 if not p: continue
1646 1655 if os.path.isdir(p):
1647 1656 for f in os.listdir(p):
1648 1657 if f.endswith('.rc'):
1649 1658 _rcpath.append(os.path.join(p, f))
1650 1659 else:
1651 1660 _rcpath.append(p)
1652 1661 else:
1653 1662 _rcpath = os_rcpath()
1654 1663 return _rcpath
1655 1664
1656 1665 def bytecount(nbytes):
1657 1666 '''return byte count formatted as readable string, with units'''
1658 1667
1659 1668 units = (
1660 1669 (100, 1<<30, _('%.0f GB')),
1661 1670 (10, 1<<30, _('%.1f GB')),
1662 1671 (1, 1<<30, _('%.2f GB')),
1663 1672 (100, 1<<20, _('%.0f MB')),
1664 1673 (10, 1<<20, _('%.1f MB')),
1665 1674 (1, 1<<20, _('%.2f MB')),
1666 1675 (100, 1<<10, _('%.0f KB')),
1667 1676 (10, 1<<10, _('%.1f KB')),
1668 1677 (1, 1<<10, _('%.2f KB')),
1669 1678 (1, 1, _('%.0f bytes')),
1670 1679 )
1671 1680
1672 1681 for multiplier, divisor, format in units:
1673 1682 if nbytes >= divisor * multiplier:
1674 1683 return format % (nbytes / float(divisor))
1675 1684 return units[-1][2] % nbytes
1676 1685
1677 1686 def drop_scheme(scheme, path):
1678 1687 sc = scheme + ':'
1679 1688 if path.startswith(sc):
1680 1689 path = path[len(sc):]
1681 1690 if path.startswith('//'):
1682 1691 path = path[2:]
1683 1692 return path
1693
1694 def uirepr(s):
1695 # Avoid double backslash in Windows path repr()
1696 return repr(s).replace('\\\\', '\\')
General Comments 0
You need to be logged in to leave comments. Login now