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