##// END OF EJS Templates
Fix file-changed-to-dir and dir-to-file commits (issue660)....
Maxim Dounin -
r5487:7a64931e default
parent child Browse files
Show More
@@ -0,0 +1,89 b''
1 #!/bin/sh
2 # http://www.selenic.com/mercurial/bts/issue660
3
4
5 hg init a
6 cd a
7 echo a > a
8 mkdir b
9 echo b > b/b
10 hg commit -A -m "a is file, b is dir"
11
12 echo % file replaced with directory
13
14 rm a
15 mkdir a
16 echo a > a/a
17
18 echo % should fail - would corrupt dirstate
19 hg add a/a
20
21 echo % removing shadow
22 hg rm --after a
23
24 echo % should succeed - shadow removed
25 hg add a/a
26
27 echo % directory replaced with file
28
29 rm -r b
30 echo b > b
31
32 echo % should fail - would corrupt dirstate
33 hg add b
34
35 echo % removing shadow
36 hg rm --after b/b
37
38 echo % should succeed - shadow removed
39 hg add b
40
41 echo % look what we got
42 hg st
43
44 echo % revert reintroducing shadow - should fail
45 rm -r a b
46 hg revert b/b
47
48 echo % revert all - should succeed
49 hg revert --all
50 hg st
51
52 echo % addremove
53
54 rm -r a b
55 mkdir a
56 echo a > a/a
57 echo b > b
58
59 hg addremove
60 hg st
61
62 echo % commit
63 hg ci -A -m "a is dir, b is file"
64 hg st --all
65
66 echo % long directory replaced with file
67
68 mkdir d
69 mkdir d/d
70 echo d > d/d/d
71 hg commit -A -m "d is long directory"
72 rm -r d
73 echo d > d
74
75 echo % should fail - would corrupt dirstate
76 hg add d
77
78 echo % removing shadow
79 hg rm --after d/d/d
80
81 echo % should succeed - shadow removed
82 hg add d
83
84 #echo % update should work
85 #
86 #hg up -r 0
87 #hg up -r 1
88
89 exit 0
@@ -0,0 +1,42 b''
1 adding a
2 adding b/b
3 % file replaced with directory
4 % should fail - would corrupt dirstate
5 abort: file 'a' in dirstate clashes with 'a/a'
6 % removing shadow
7 % should succeed - shadow removed
8 % directory replaced with file
9 % should fail - would corrupt dirstate
10 abort: directory 'b' already in dirstate
11 % removing shadow
12 % should succeed - shadow removed
13 % look what we got
14 A a/a
15 A b
16 R a
17 R b/b
18 % revert reintroducing shadow - should fail
19 abort: file 'b' in dirstate clashes with 'b/b'
20 % revert all - should succeed
21 undeleting a
22 forgetting a/a
23 forgetting b
24 undeleting b/b
25 % addremove
26 removing a
27 adding a/a
28 adding b
29 removing b/b
30 A a/a
31 A b
32 R a
33 R b/b
34 % commit
35 C a/a
36 C b
37 % long directory replaced with file
38 adding d/d/d
39 % should fail - would corrupt dirstate
40 abort: directory 'd' already in dirstate
41 % removing shadow
42 % should succeed - shadow removed
@@ -1,943 +1,944 b''
1 # cmdutil.py - help for command processing in mercurial
1 # cmdutil.py - help for command processing in mercurial
2 #
2 #
3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2005-2007 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 i18n import _
9 from i18n import _
10 import os, sys, bisect, stat
10 import os, sys, bisect, stat
11 import mdiff, bdiff, util, templater, patch
11 import mdiff, bdiff, util, templater, patch
12
12
13 revrangesep = ':'
13 revrangesep = ':'
14
14
15 class UnknownCommand(Exception):
15 class UnknownCommand(Exception):
16 """Exception raised if command is not in the command table."""
16 """Exception raised if command is not in the command table."""
17 class AmbiguousCommand(Exception):
17 class AmbiguousCommand(Exception):
18 """Exception raised if command shortcut matches more than one command."""
18 """Exception raised if command shortcut matches more than one command."""
19
19
20 def findpossible(ui, cmd, table):
20 def findpossible(ui, cmd, table):
21 """
21 """
22 Return cmd -> (aliases, command table entry)
22 Return cmd -> (aliases, command table entry)
23 for each matching command.
23 for each matching command.
24 Return debug commands (or their aliases) only if no normal command matches.
24 Return debug commands (or their aliases) only if no normal command matches.
25 """
25 """
26 choice = {}
26 choice = {}
27 debugchoice = {}
27 debugchoice = {}
28 for e in table.keys():
28 for e in table.keys():
29 aliases = e.lstrip("^").split("|")
29 aliases = e.lstrip("^").split("|")
30 found = None
30 found = None
31 if cmd in aliases:
31 if cmd in aliases:
32 found = cmd
32 found = cmd
33 elif not ui.config("ui", "strict"):
33 elif not ui.config("ui", "strict"):
34 for a in aliases:
34 for a in aliases:
35 if a.startswith(cmd):
35 if a.startswith(cmd):
36 found = a
36 found = a
37 break
37 break
38 if found is not None:
38 if found is not None:
39 if aliases[0].startswith("debug") or found.startswith("debug"):
39 if aliases[0].startswith("debug") or found.startswith("debug"):
40 debugchoice[found] = (aliases, table[e])
40 debugchoice[found] = (aliases, table[e])
41 else:
41 else:
42 choice[found] = (aliases, table[e])
42 choice[found] = (aliases, table[e])
43
43
44 if not choice and debugchoice:
44 if not choice and debugchoice:
45 choice = debugchoice
45 choice = debugchoice
46
46
47 return choice
47 return choice
48
48
49 def findcmd(ui, cmd, table):
49 def findcmd(ui, cmd, table):
50 """Return (aliases, command table entry) for command string."""
50 """Return (aliases, command table entry) for command string."""
51 choice = findpossible(ui, cmd, table)
51 choice = findpossible(ui, cmd, table)
52
52
53 if choice.has_key(cmd):
53 if choice.has_key(cmd):
54 return choice[cmd]
54 return choice[cmd]
55
55
56 if len(choice) > 1:
56 if len(choice) > 1:
57 clist = choice.keys()
57 clist = choice.keys()
58 clist.sort()
58 clist.sort()
59 raise AmbiguousCommand(cmd, clist)
59 raise AmbiguousCommand(cmd, clist)
60
60
61 if choice:
61 if choice:
62 return choice.values()[0]
62 return choice.values()[0]
63
63
64 raise UnknownCommand(cmd)
64 raise UnknownCommand(cmd)
65
65
66 def bail_if_changed(repo):
66 def bail_if_changed(repo):
67 modified, added, removed, deleted = repo.status()[:4]
67 modified, added, removed, deleted = repo.status()[:4]
68 if modified or added or removed or deleted:
68 if modified or added or removed or deleted:
69 raise util.Abort(_("outstanding uncommitted changes"))
69 raise util.Abort(_("outstanding uncommitted changes"))
70
70
71 def logmessage(opts):
71 def logmessage(opts):
72 """ get the log message according to -m and -l option """
72 """ get the log message according to -m and -l option """
73 message = opts['message']
73 message = opts['message']
74 logfile = opts['logfile']
74 logfile = opts['logfile']
75
75
76 if message and logfile:
76 if message and logfile:
77 raise util.Abort(_('options --message and --logfile are mutually '
77 raise util.Abort(_('options --message and --logfile are mutually '
78 'exclusive'))
78 'exclusive'))
79 if not message and logfile:
79 if not message and logfile:
80 try:
80 try:
81 if logfile == '-':
81 if logfile == '-':
82 message = sys.stdin.read()
82 message = sys.stdin.read()
83 else:
83 else:
84 message = open(logfile).read()
84 message = open(logfile).read()
85 except IOError, inst:
85 except IOError, inst:
86 raise util.Abort(_("can't read commit message '%s': %s") %
86 raise util.Abort(_("can't read commit message '%s': %s") %
87 (logfile, inst.strerror))
87 (logfile, inst.strerror))
88 return message
88 return message
89
89
90 def setremoteconfig(ui, opts):
90 def setremoteconfig(ui, opts):
91 "copy remote options to ui tree"
91 "copy remote options to ui tree"
92 if opts.get('ssh'):
92 if opts.get('ssh'):
93 ui.setconfig("ui", "ssh", opts['ssh'])
93 ui.setconfig("ui", "ssh", opts['ssh'])
94 if opts.get('remotecmd'):
94 if opts.get('remotecmd'):
95 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
95 ui.setconfig("ui", "remotecmd", opts['remotecmd'])
96
96
97 def revpair(repo, revs):
97 def revpair(repo, revs):
98 '''return pair of nodes, given list of revisions. second item can
98 '''return pair of nodes, given list of revisions. second item can
99 be None, meaning use working dir.'''
99 be None, meaning use working dir.'''
100
100
101 def revfix(repo, val, defval):
101 def revfix(repo, val, defval):
102 if not val and val != 0 and defval is not None:
102 if not val and val != 0 and defval is not None:
103 val = defval
103 val = defval
104 return repo.lookup(val)
104 return repo.lookup(val)
105
105
106 if not revs:
106 if not revs:
107 return repo.dirstate.parents()[0], None
107 return repo.dirstate.parents()[0], None
108 end = None
108 end = None
109 if len(revs) == 1:
109 if len(revs) == 1:
110 if revrangesep in revs[0]:
110 if revrangesep in revs[0]:
111 start, end = revs[0].split(revrangesep, 1)
111 start, end = revs[0].split(revrangesep, 1)
112 start = revfix(repo, start, 0)
112 start = revfix(repo, start, 0)
113 end = revfix(repo, end, repo.changelog.count() - 1)
113 end = revfix(repo, end, repo.changelog.count() - 1)
114 else:
114 else:
115 start = revfix(repo, revs[0], None)
115 start = revfix(repo, revs[0], None)
116 elif len(revs) == 2:
116 elif len(revs) == 2:
117 if revrangesep in revs[0] or revrangesep in revs[1]:
117 if revrangesep in revs[0] or revrangesep in revs[1]:
118 raise util.Abort(_('too many revisions specified'))
118 raise util.Abort(_('too many revisions specified'))
119 start = revfix(repo, revs[0], None)
119 start = revfix(repo, revs[0], None)
120 end = revfix(repo, revs[1], None)
120 end = revfix(repo, revs[1], None)
121 else:
121 else:
122 raise util.Abort(_('too many revisions specified'))
122 raise util.Abort(_('too many revisions specified'))
123 return start, end
123 return start, end
124
124
125 def revrange(repo, revs):
125 def revrange(repo, revs):
126 """Yield revision as strings from a list of revision specifications."""
126 """Yield revision as strings from a list of revision specifications."""
127
127
128 def revfix(repo, val, defval):
128 def revfix(repo, val, defval):
129 if not val and val != 0 and defval is not None:
129 if not val and val != 0 and defval is not None:
130 return defval
130 return defval
131 return repo.changelog.rev(repo.lookup(val))
131 return repo.changelog.rev(repo.lookup(val))
132
132
133 seen, l = {}, []
133 seen, l = {}, []
134 for spec in revs:
134 for spec in revs:
135 if revrangesep in spec:
135 if revrangesep in spec:
136 start, end = spec.split(revrangesep, 1)
136 start, end = spec.split(revrangesep, 1)
137 start = revfix(repo, start, 0)
137 start = revfix(repo, start, 0)
138 end = revfix(repo, end, repo.changelog.count() - 1)
138 end = revfix(repo, end, repo.changelog.count() - 1)
139 step = start > end and -1 or 1
139 step = start > end and -1 or 1
140 for rev in xrange(start, end+step, step):
140 for rev in xrange(start, end+step, step):
141 if rev in seen:
141 if rev in seen:
142 continue
142 continue
143 seen[rev] = 1
143 seen[rev] = 1
144 l.append(rev)
144 l.append(rev)
145 else:
145 else:
146 rev = revfix(repo, spec, None)
146 rev = revfix(repo, spec, None)
147 if rev in seen:
147 if rev in seen:
148 continue
148 continue
149 seen[rev] = 1
149 seen[rev] = 1
150 l.append(rev)
150 l.append(rev)
151
151
152 return l
152 return l
153
153
154 def make_filename(repo, pat, node,
154 def make_filename(repo, pat, node,
155 total=None, seqno=None, revwidth=None, pathname=None):
155 total=None, seqno=None, revwidth=None, pathname=None):
156 node_expander = {
156 node_expander = {
157 'H': lambda: hex(node),
157 'H': lambda: hex(node),
158 'R': lambda: str(repo.changelog.rev(node)),
158 'R': lambda: str(repo.changelog.rev(node)),
159 'h': lambda: short(node),
159 'h': lambda: short(node),
160 }
160 }
161 expander = {
161 expander = {
162 '%': lambda: '%',
162 '%': lambda: '%',
163 'b': lambda: os.path.basename(repo.root),
163 'b': lambda: os.path.basename(repo.root),
164 }
164 }
165
165
166 try:
166 try:
167 if node:
167 if node:
168 expander.update(node_expander)
168 expander.update(node_expander)
169 if node:
169 if node:
170 expander['r'] = (lambda:
170 expander['r'] = (lambda:
171 str(repo.changelog.rev(node)).zfill(revwidth or 0))
171 str(repo.changelog.rev(node)).zfill(revwidth or 0))
172 if total is not None:
172 if total is not None:
173 expander['N'] = lambda: str(total)
173 expander['N'] = lambda: str(total)
174 if seqno is not None:
174 if seqno is not None:
175 expander['n'] = lambda: str(seqno)
175 expander['n'] = lambda: str(seqno)
176 if total is not None and seqno is not None:
176 if total is not None and seqno is not None:
177 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
177 expander['n'] = lambda: str(seqno).zfill(len(str(total)))
178 if pathname is not None:
178 if pathname is not None:
179 expander['s'] = lambda: os.path.basename(pathname)
179 expander['s'] = lambda: os.path.basename(pathname)
180 expander['d'] = lambda: os.path.dirname(pathname) or '.'
180 expander['d'] = lambda: os.path.dirname(pathname) or '.'
181 expander['p'] = lambda: pathname
181 expander['p'] = lambda: pathname
182
182
183 newname = []
183 newname = []
184 patlen = len(pat)
184 patlen = len(pat)
185 i = 0
185 i = 0
186 while i < patlen:
186 while i < patlen:
187 c = pat[i]
187 c = pat[i]
188 if c == '%':
188 if c == '%':
189 i += 1
189 i += 1
190 c = pat[i]
190 c = pat[i]
191 c = expander[c]()
191 c = expander[c]()
192 newname.append(c)
192 newname.append(c)
193 i += 1
193 i += 1
194 return ''.join(newname)
194 return ''.join(newname)
195 except KeyError, inst:
195 except KeyError, inst:
196 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
196 raise util.Abort(_("invalid format spec '%%%s' in output file name") %
197 inst.args[0])
197 inst.args[0])
198
198
199 def make_file(repo, pat, node=None,
199 def make_file(repo, pat, node=None,
200 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
200 total=None, seqno=None, revwidth=None, mode='wb', pathname=None):
201 if not pat or pat == '-':
201 if not pat or pat == '-':
202 return 'w' in mode and sys.stdout or sys.stdin
202 return 'w' in mode and sys.stdout or sys.stdin
203 if hasattr(pat, 'write') and 'w' in mode:
203 if hasattr(pat, 'write') and 'w' in mode:
204 return pat
204 return pat
205 if hasattr(pat, 'read') and 'r' in mode:
205 if hasattr(pat, 'read') and 'r' in mode:
206 return pat
206 return pat
207 return open(make_filename(repo, pat, node, total, seqno, revwidth,
207 return open(make_filename(repo, pat, node, total, seqno, revwidth,
208 pathname),
208 pathname),
209 mode)
209 mode)
210
210
211 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
211 def matchpats(repo, pats=[], opts={}, globbed=False, default=None):
212 cwd = repo.getcwd()
212 cwd = repo.getcwd()
213 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
213 return util.cmdmatcher(repo.root, cwd, pats or [], opts.get('include'),
214 opts.get('exclude'), globbed=globbed,
214 opts.get('exclude'), globbed=globbed,
215 default=default)
215 default=default)
216
216
217 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
217 def walk(repo, pats=[], opts={}, node=None, badmatch=None, globbed=False,
218 default=None):
218 default=None):
219 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
219 files, matchfn, anypats = matchpats(repo, pats, opts, globbed=globbed,
220 default=default)
220 default=default)
221 exact = dict.fromkeys(files)
221 exact = dict.fromkeys(files)
222 cwd = repo.getcwd()
222 cwd = repo.getcwd()
223 for src, fn in repo.walk(node=node, files=files, match=matchfn,
223 for src, fn in repo.walk(node=node, files=files, match=matchfn,
224 badmatch=badmatch):
224 badmatch=badmatch):
225 yield src, fn, repo.pathto(fn, cwd), fn in exact
225 yield src, fn, repo.pathto(fn, cwd), fn in exact
226
226
227 def findrenames(repo, added=None, removed=None, threshold=0.5):
227 def findrenames(repo, added=None, removed=None, threshold=0.5):
228 '''find renamed files -- yields (before, after, score) tuples'''
228 '''find renamed files -- yields (before, after, score) tuples'''
229 if added is None or removed is None:
229 if added is None or removed is None:
230 added, removed = repo.status()[1:3]
230 added, removed = repo.status()[1:3]
231 ctx = repo.changectx()
231 ctx = repo.changectx()
232 for a in added:
232 for a in added:
233 aa = repo.wread(a)
233 aa = repo.wread(a)
234 bestname, bestscore = None, threshold
234 bestname, bestscore = None, threshold
235 for r in removed:
235 for r in removed:
236 rr = ctx.filectx(r).data()
236 rr = ctx.filectx(r).data()
237
237
238 # bdiff.blocks() returns blocks of matching lines
238 # bdiff.blocks() returns blocks of matching lines
239 # count the number of bytes in each
239 # count the number of bytes in each
240 equal = 0
240 equal = 0
241 alines = mdiff.splitnewlines(aa)
241 alines = mdiff.splitnewlines(aa)
242 matches = bdiff.blocks(aa, rr)
242 matches = bdiff.blocks(aa, rr)
243 for x1,x2,y1,y2 in matches:
243 for x1,x2,y1,y2 in matches:
244 for line in alines[x1:x2]:
244 for line in alines[x1:x2]:
245 equal += len(line)
245 equal += len(line)
246
246
247 lengths = len(aa) + len(rr)
247 lengths = len(aa) + len(rr)
248 if lengths:
248 if lengths:
249 myscore = equal*2.0 / lengths
249 myscore = equal*2.0 / lengths
250 if myscore >= bestscore:
250 if myscore >= bestscore:
251 bestname, bestscore = r, myscore
251 bestname, bestscore = r, myscore
252 if bestname:
252 if bestname:
253 yield bestname, a, bestscore
253 yield bestname, a, bestscore
254
254
255 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
255 def addremove(repo, pats=[], opts={}, dry_run=None, similarity=None):
256 if dry_run is None:
256 if dry_run is None:
257 dry_run = opts.get('dry_run')
257 dry_run = opts.get('dry_run')
258 if similarity is None:
258 if similarity is None:
259 similarity = float(opts.get('similarity') or 0)
259 similarity = float(opts.get('similarity') or 0)
260 add, remove = [], []
260 add, remove = [], []
261 mapping = {}
261 mapping = {}
262 for src, abs, rel, exact in walk(repo, pats, opts):
262 for src, abs, rel, exact in walk(repo, pats, opts):
263 target = repo.wjoin(abs)
263 target = repo.wjoin(abs)
264 if src == 'f' and abs not in repo.dirstate:
264 if src == 'f' and abs not in repo.dirstate:
265 add.append(abs)
265 add.append(abs)
266 mapping[abs] = rel, exact
266 mapping[abs] = rel, exact
267 if repo.ui.verbose or not exact:
267 if repo.ui.verbose or not exact:
268 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
268 repo.ui.status(_('adding %s\n') % ((pats and rel) or abs))
269 if repo.dirstate[abs] != 'r' and not util.lexists(target):
269 if repo.dirstate[abs] != 'r' and (not util.lexists(target)
270 or (os.path.isdir(target) and not os.path.islink(target))):
270 remove.append(abs)
271 remove.append(abs)
271 mapping[abs] = rel, exact
272 mapping[abs] = rel, exact
272 if repo.ui.verbose or not exact:
273 if repo.ui.verbose or not exact:
273 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
274 repo.ui.status(_('removing %s\n') % ((pats and rel) or abs))
274 if not dry_run:
275 if not dry_run:
276 repo.remove(remove)
275 repo.add(add)
277 repo.add(add)
276 repo.remove(remove)
277 if similarity > 0:
278 if similarity > 0:
278 for old, new, score in findrenames(repo, add, remove, similarity):
279 for old, new, score in findrenames(repo, add, remove, similarity):
279 oldrel, oldexact = mapping[old]
280 oldrel, oldexact = mapping[old]
280 newrel, newexact = mapping[new]
281 newrel, newexact = mapping[new]
281 if repo.ui.verbose or not oldexact or not newexact:
282 if repo.ui.verbose or not oldexact or not newexact:
282 repo.ui.status(_('recording removal of %s as rename to %s '
283 repo.ui.status(_('recording removal of %s as rename to %s '
283 '(%d%% similar)\n') %
284 '(%d%% similar)\n') %
284 (oldrel, newrel, score * 100))
285 (oldrel, newrel, score * 100))
285 if not dry_run:
286 if not dry_run:
286 repo.copy(old, new)
287 repo.copy(old, new)
287
288
288 def service(opts, parentfn=None, initfn=None, runfn=None):
289 def service(opts, parentfn=None, initfn=None, runfn=None):
289 '''Run a command as a service.'''
290 '''Run a command as a service.'''
290
291
291 if opts['daemon'] and not opts['daemon_pipefds']:
292 if opts['daemon'] and not opts['daemon_pipefds']:
292 rfd, wfd = os.pipe()
293 rfd, wfd = os.pipe()
293 args = sys.argv[:]
294 args = sys.argv[:]
294 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
295 args.append('--daemon-pipefds=%d,%d' % (rfd, wfd))
295 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
296 pid = os.spawnvp(os.P_NOWAIT | getattr(os, 'P_DETACH', 0),
296 args[0], args)
297 args[0], args)
297 os.close(wfd)
298 os.close(wfd)
298 os.read(rfd, 1)
299 os.read(rfd, 1)
299 if parentfn:
300 if parentfn:
300 return parentfn(pid)
301 return parentfn(pid)
301 else:
302 else:
302 os._exit(0)
303 os._exit(0)
303
304
304 if initfn:
305 if initfn:
305 initfn()
306 initfn()
306
307
307 if opts['pid_file']:
308 if opts['pid_file']:
308 fp = open(opts['pid_file'], 'w')
309 fp = open(opts['pid_file'], 'w')
309 fp.write(str(os.getpid()) + '\n')
310 fp.write(str(os.getpid()) + '\n')
310 fp.close()
311 fp.close()
311
312
312 if opts['daemon_pipefds']:
313 if opts['daemon_pipefds']:
313 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
314 rfd, wfd = [int(x) for x in opts['daemon_pipefds'].split(',')]
314 os.close(rfd)
315 os.close(rfd)
315 try:
316 try:
316 os.setsid()
317 os.setsid()
317 except AttributeError:
318 except AttributeError:
318 pass
319 pass
319 os.write(wfd, 'y')
320 os.write(wfd, 'y')
320 os.close(wfd)
321 os.close(wfd)
321 sys.stdout.flush()
322 sys.stdout.flush()
322 sys.stderr.flush()
323 sys.stderr.flush()
323 fd = os.open(util.nulldev, os.O_RDWR)
324 fd = os.open(util.nulldev, os.O_RDWR)
324 if fd != 0: os.dup2(fd, 0)
325 if fd != 0: os.dup2(fd, 0)
325 if fd != 1: os.dup2(fd, 1)
326 if fd != 1: os.dup2(fd, 1)
326 if fd != 2: os.dup2(fd, 2)
327 if fd != 2: os.dup2(fd, 2)
327 if fd not in (0, 1, 2): os.close(fd)
328 if fd not in (0, 1, 2): os.close(fd)
328
329
329 if runfn:
330 if runfn:
330 return runfn()
331 return runfn()
331
332
332 class changeset_printer(object):
333 class changeset_printer(object):
333 '''show changeset information when templating not requested.'''
334 '''show changeset information when templating not requested.'''
334
335
335 def __init__(self, ui, repo, patch, buffered):
336 def __init__(self, ui, repo, patch, buffered):
336 self.ui = ui
337 self.ui = ui
337 self.repo = repo
338 self.repo = repo
338 self.buffered = buffered
339 self.buffered = buffered
339 self.patch = patch
340 self.patch = patch
340 self.header = {}
341 self.header = {}
341 self.hunk = {}
342 self.hunk = {}
342 self.lastheader = None
343 self.lastheader = None
343
344
344 def flush(self, rev):
345 def flush(self, rev):
345 if rev in self.header:
346 if rev in self.header:
346 h = self.header[rev]
347 h = self.header[rev]
347 if h != self.lastheader:
348 if h != self.lastheader:
348 self.lastheader = h
349 self.lastheader = h
349 self.ui.write(h)
350 self.ui.write(h)
350 del self.header[rev]
351 del self.header[rev]
351 if rev in self.hunk:
352 if rev in self.hunk:
352 self.ui.write(self.hunk[rev])
353 self.ui.write(self.hunk[rev])
353 del self.hunk[rev]
354 del self.hunk[rev]
354 return 1
355 return 1
355 return 0
356 return 0
356
357
357 def show(self, rev=0, changenode=None, copies=(), **props):
358 def show(self, rev=0, changenode=None, copies=(), **props):
358 if self.buffered:
359 if self.buffered:
359 self.ui.pushbuffer()
360 self.ui.pushbuffer()
360 self._show(rev, changenode, copies, props)
361 self._show(rev, changenode, copies, props)
361 self.hunk[rev] = self.ui.popbuffer()
362 self.hunk[rev] = self.ui.popbuffer()
362 else:
363 else:
363 self._show(rev, changenode, copies, props)
364 self._show(rev, changenode, copies, props)
364
365
365 def _show(self, rev, changenode, copies, props):
366 def _show(self, rev, changenode, copies, props):
366 '''show a single changeset or file revision'''
367 '''show a single changeset or file revision'''
367 log = self.repo.changelog
368 log = self.repo.changelog
368 if changenode is None:
369 if changenode is None:
369 changenode = log.node(rev)
370 changenode = log.node(rev)
370 elif not rev:
371 elif not rev:
371 rev = log.rev(changenode)
372 rev = log.rev(changenode)
372
373
373 if self.ui.quiet:
374 if self.ui.quiet:
374 self.ui.write("%d:%s\n" % (rev, short(changenode)))
375 self.ui.write("%d:%s\n" % (rev, short(changenode)))
375 return
376 return
376
377
377 changes = log.read(changenode)
378 changes = log.read(changenode)
378 date = util.datestr(changes[2])
379 date = util.datestr(changes[2])
379 extra = changes[5]
380 extra = changes[5]
380 branch = extra.get("branch")
381 branch = extra.get("branch")
381
382
382 hexfunc = self.ui.debugflag and hex or short
383 hexfunc = self.ui.debugflag and hex or short
383
384
384 parents = [(p, hexfunc(log.node(p)))
385 parents = [(p, hexfunc(log.node(p)))
385 for p in self._meaningful_parentrevs(log, rev)]
386 for p in self._meaningful_parentrevs(log, rev)]
386
387
387 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
388 self.ui.write(_("changeset: %d:%s\n") % (rev, hexfunc(changenode)))
388
389
389 # don't show the default branch name
390 # don't show the default branch name
390 if branch != 'default':
391 if branch != 'default':
391 branch = util.tolocal(branch)
392 branch = util.tolocal(branch)
392 self.ui.write(_("branch: %s\n") % branch)
393 self.ui.write(_("branch: %s\n") % branch)
393 for tag in self.repo.nodetags(changenode):
394 for tag in self.repo.nodetags(changenode):
394 self.ui.write(_("tag: %s\n") % tag)
395 self.ui.write(_("tag: %s\n") % tag)
395 for parent in parents:
396 for parent in parents:
396 self.ui.write(_("parent: %d:%s\n") % parent)
397 self.ui.write(_("parent: %d:%s\n") % parent)
397
398
398 if self.ui.debugflag:
399 if self.ui.debugflag:
399 self.ui.write(_("manifest: %d:%s\n") %
400 self.ui.write(_("manifest: %d:%s\n") %
400 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
401 (self.repo.manifest.rev(changes[0]), hex(changes[0])))
401 self.ui.write(_("user: %s\n") % changes[1])
402 self.ui.write(_("user: %s\n") % changes[1])
402 self.ui.write(_("date: %s\n") % date)
403 self.ui.write(_("date: %s\n") % date)
403
404
404 if self.ui.debugflag:
405 if self.ui.debugflag:
405 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
406 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
406 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
407 for key, value in zip([_("files:"), _("files+:"), _("files-:")],
407 files):
408 files):
408 if value:
409 if value:
409 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
410 self.ui.write("%-12s %s\n" % (key, " ".join(value)))
410 elif changes[3] and self.ui.verbose:
411 elif changes[3] and self.ui.verbose:
411 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
412 self.ui.write(_("files: %s\n") % " ".join(changes[3]))
412 if copies and self.ui.verbose:
413 if copies and self.ui.verbose:
413 copies = ['%s (%s)' % c for c in copies]
414 copies = ['%s (%s)' % c for c in copies]
414 self.ui.write(_("copies: %s\n") % ' '.join(copies))
415 self.ui.write(_("copies: %s\n") % ' '.join(copies))
415
416
416 if extra and self.ui.debugflag:
417 if extra and self.ui.debugflag:
417 extraitems = extra.items()
418 extraitems = extra.items()
418 extraitems.sort()
419 extraitems.sort()
419 for key, value in extraitems:
420 for key, value in extraitems:
420 self.ui.write(_("extra: %s=%s\n")
421 self.ui.write(_("extra: %s=%s\n")
421 % (key, value.encode('string_escape')))
422 % (key, value.encode('string_escape')))
422
423
423 description = changes[4].strip()
424 description = changes[4].strip()
424 if description:
425 if description:
425 if self.ui.verbose:
426 if self.ui.verbose:
426 self.ui.write(_("description:\n"))
427 self.ui.write(_("description:\n"))
427 self.ui.write(description)
428 self.ui.write(description)
428 self.ui.write("\n\n")
429 self.ui.write("\n\n")
429 else:
430 else:
430 self.ui.write(_("summary: %s\n") %
431 self.ui.write(_("summary: %s\n") %
431 description.splitlines()[0])
432 description.splitlines()[0])
432 self.ui.write("\n")
433 self.ui.write("\n")
433
434
434 self.showpatch(changenode)
435 self.showpatch(changenode)
435
436
436 def showpatch(self, node):
437 def showpatch(self, node):
437 if self.patch:
438 if self.patch:
438 prev = self.repo.changelog.parents(node)[0]
439 prev = self.repo.changelog.parents(node)[0]
439 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
440 patch.diff(self.repo, prev, node, match=self.patch, fp=self.ui,
440 opts=patch.diffopts(self.ui))
441 opts=patch.diffopts(self.ui))
441 self.ui.write("\n")
442 self.ui.write("\n")
442
443
443 def _meaningful_parentrevs(self, log, rev):
444 def _meaningful_parentrevs(self, log, rev):
444 """Return list of meaningful (or all if debug) parentrevs for rev.
445 """Return list of meaningful (or all if debug) parentrevs for rev.
445
446
446 For merges (two non-nullrev revisions) both parents are meaningful.
447 For merges (two non-nullrev revisions) both parents are meaningful.
447 Otherwise the first parent revision is considered meaningful if it
448 Otherwise the first parent revision is considered meaningful if it
448 is not the preceding revision.
449 is not the preceding revision.
449 """
450 """
450 parents = log.parentrevs(rev)
451 parents = log.parentrevs(rev)
451 if not self.ui.debugflag and parents[1] == nullrev:
452 if not self.ui.debugflag and parents[1] == nullrev:
452 if parents[0] >= rev - 1:
453 if parents[0] >= rev - 1:
453 parents = []
454 parents = []
454 else:
455 else:
455 parents = [parents[0]]
456 parents = [parents[0]]
456 return parents
457 return parents
457
458
458
459
459 class changeset_templater(changeset_printer):
460 class changeset_templater(changeset_printer):
460 '''format changeset information.'''
461 '''format changeset information.'''
461
462
462 def __init__(self, ui, repo, patch, mapfile, buffered):
463 def __init__(self, ui, repo, patch, mapfile, buffered):
463 changeset_printer.__init__(self, ui, repo, patch, buffered)
464 changeset_printer.__init__(self, ui, repo, patch, buffered)
464 filters = templater.common_filters.copy()
465 filters = templater.common_filters.copy()
465 filters['formatnode'] = (ui.debugflag and (lambda x: x)
466 filters['formatnode'] = (ui.debugflag and (lambda x: x)
466 or (lambda x: x[:12]))
467 or (lambda x: x[:12]))
467 self.t = templater.templater(mapfile, filters,
468 self.t = templater.templater(mapfile, filters,
468 cache={
469 cache={
469 'parent': '{rev}:{node|formatnode} ',
470 'parent': '{rev}:{node|formatnode} ',
470 'manifest': '{rev}:{node|formatnode}',
471 'manifest': '{rev}:{node|formatnode}',
471 'filecopy': '{name} ({source})'})
472 'filecopy': '{name} ({source})'})
472
473
473 def use_template(self, t):
474 def use_template(self, t):
474 '''set template string to use'''
475 '''set template string to use'''
475 self.t.cache['changeset'] = t
476 self.t.cache['changeset'] = t
476
477
477 def _show(self, rev, changenode, copies, props):
478 def _show(self, rev, changenode, copies, props):
478 '''show a single changeset or file revision'''
479 '''show a single changeset or file revision'''
479 log = self.repo.changelog
480 log = self.repo.changelog
480 if changenode is None:
481 if changenode is None:
481 changenode = log.node(rev)
482 changenode = log.node(rev)
482 elif not rev:
483 elif not rev:
483 rev = log.rev(changenode)
484 rev = log.rev(changenode)
484
485
485 changes = log.read(changenode)
486 changes = log.read(changenode)
486
487
487 def showlist(name, values, plural=None, **args):
488 def showlist(name, values, plural=None, **args):
488 '''expand set of values.
489 '''expand set of values.
489 name is name of key in template map.
490 name is name of key in template map.
490 values is list of strings or dicts.
491 values is list of strings or dicts.
491 plural is plural of name, if not simply name + 's'.
492 plural is plural of name, if not simply name + 's'.
492
493
493 expansion works like this, given name 'foo'.
494 expansion works like this, given name 'foo'.
494
495
495 if values is empty, expand 'no_foos'.
496 if values is empty, expand 'no_foos'.
496
497
497 if 'foo' not in template map, return values as a string,
498 if 'foo' not in template map, return values as a string,
498 joined by space.
499 joined by space.
499
500
500 expand 'start_foos'.
501 expand 'start_foos'.
501
502
502 for each value, expand 'foo'. if 'last_foo' in template
503 for each value, expand 'foo'. if 'last_foo' in template
503 map, expand it instead of 'foo' for last key.
504 map, expand it instead of 'foo' for last key.
504
505
505 expand 'end_foos'.
506 expand 'end_foos'.
506 '''
507 '''
507 if plural: names = plural
508 if plural: names = plural
508 else: names = name + 's'
509 else: names = name + 's'
509 if not values:
510 if not values:
510 noname = 'no_' + names
511 noname = 'no_' + names
511 if noname in self.t:
512 if noname in self.t:
512 yield self.t(noname, **args)
513 yield self.t(noname, **args)
513 return
514 return
514 if name not in self.t:
515 if name not in self.t:
515 if isinstance(values[0], str):
516 if isinstance(values[0], str):
516 yield ' '.join(values)
517 yield ' '.join(values)
517 else:
518 else:
518 for v in values:
519 for v in values:
519 yield dict(v, **args)
520 yield dict(v, **args)
520 return
521 return
521 startname = 'start_' + names
522 startname = 'start_' + names
522 if startname in self.t:
523 if startname in self.t:
523 yield self.t(startname, **args)
524 yield self.t(startname, **args)
524 vargs = args.copy()
525 vargs = args.copy()
525 def one(v, tag=name):
526 def one(v, tag=name):
526 try:
527 try:
527 vargs.update(v)
528 vargs.update(v)
528 except (AttributeError, ValueError):
529 except (AttributeError, ValueError):
529 try:
530 try:
530 for a, b in v:
531 for a, b in v:
531 vargs[a] = b
532 vargs[a] = b
532 except ValueError:
533 except ValueError:
533 vargs[name] = v
534 vargs[name] = v
534 return self.t(tag, **vargs)
535 return self.t(tag, **vargs)
535 lastname = 'last_' + name
536 lastname = 'last_' + name
536 if lastname in self.t:
537 if lastname in self.t:
537 last = values.pop()
538 last = values.pop()
538 else:
539 else:
539 last = None
540 last = None
540 for v in values:
541 for v in values:
541 yield one(v)
542 yield one(v)
542 if last is not None:
543 if last is not None:
543 yield one(last, tag=lastname)
544 yield one(last, tag=lastname)
544 endname = 'end_' + names
545 endname = 'end_' + names
545 if endname in self.t:
546 if endname in self.t:
546 yield self.t(endname, **args)
547 yield self.t(endname, **args)
547
548
548 def showbranches(**args):
549 def showbranches(**args):
549 branch = changes[5].get("branch")
550 branch = changes[5].get("branch")
550 if branch != 'default':
551 if branch != 'default':
551 branch = util.tolocal(branch)
552 branch = util.tolocal(branch)
552 return showlist('branch', [branch], plural='branches', **args)
553 return showlist('branch', [branch], plural='branches', **args)
553
554
554 def showparents(**args):
555 def showparents(**args):
555 parents = [[('rev', p), ('node', hex(log.node(p)))]
556 parents = [[('rev', p), ('node', hex(log.node(p)))]
556 for p in self._meaningful_parentrevs(log, rev)]
557 for p in self._meaningful_parentrevs(log, rev)]
557 return showlist('parent', parents, **args)
558 return showlist('parent', parents, **args)
558
559
559 def showtags(**args):
560 def showtags(**args):
560 return showlist('tag', self.repo.nodetags(changenode), **args)
561 return showlist('tag', self.repo.nodetags(changenode), **args)
561
562
562 def showextras(**args):
563 def showextras(**args):
563 extras = changes[5].items()
564 extras = changes[5].items()
564 extras.sort()
565 extras.sort()
565 for key, value in extras:
566 for key, value in extras:
566 args = args.copy()
567 args = args.copy()
567 args.update(dict(key=key, value=value))
568 args.update(dict(key=key, value=value))
568 yield self.t('extra', **args)
569 yield self.t('extra', **args)
569
570
570 def showcopies(**args):
571 def showcopies(**args):
571 c = [{'name': x[0], 'source': x[1]} for x in copies]
572 c = [{'name': x[0], 'source': x[1]} for x in copies]
572 return showlist('file_copy', c, plural='file_copies', **args)
573 return showlist('file_copy', c, plural='file_copies', **args)
573
574
574 if self.ui.debugflag:
575 if self.ui.debugflag:
575 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
576 files = self.repo.status(log.parents(changenode)[0], changenode)[:3]
576 def showfiles(**args):
577 def showfiles(**args):
577 return showlist('file', files[0], **args)
578 return showlist('file', files[0], **args)
578 def showadds(**args):
579 def showadds(**args):
579 return showlist('file_add', files[1], **args)
580 return showlist('file_add', files[1], **args)
580 def showdels(**args):
581 def showdels(**args):
581 return showlist('file_del', files[2], **args)
582 return showlist('file_del', files[2], **args)
582 def showmanifest(**args):
583 def showmanifest(**args):
583 args = args.copy()
584 args = args.copy()
584 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
585 args.update(dict(rev=self.repo.manifest.rev(changes[0]),
585 node=hex(changes[0])))
586 node=hex(changes[0])))
586 return self.t('manifest', **args)
587 return self.t('manifest', **args)
587 else:
588 else:
588 def showfiles(**args):
589 def showfiles(**args):
589 return showlist('file', changes[3], **args)
590 return showlist('file', changes[3], **args)
590 showadds = ''
591 showadds = ''
591 showdels = ''
592 showdels = ''
592 showmanifest = ''
593 showmanifest = ''
593
594
594 defprops = {
595 defprops = {
595 'author': changes[1],
596 'author': changes[1],
596 'branches': showbranches,
597 'branches': showbranches,
597 'date': changes[2],
598 'date': changes[2],
598 'desc': changes[4].strip(),
599 'desc': changes[4].strip(),
599 'file_adds': showadds,
600 'file_adds': showadds,
600 'file_dels': showdels,
601 'file_dels': showdels,
601 'files': showfiles,
602 'files': showfiles,
602 'file_copies': showcopies,
603 'file_copies': showcopies,
603 'manifest': showmanifest,
604 'manifest': showmanifest,
604 'node': hex(changenode),
605 'node': hex(changenode),
605 'parents': showparents,
606 'parents': showparents,
606 'rev': rev,
607 'rev': rev,
607 'tags': showtags,
608 'tags': showtags,
608 'extras': showextras,
609 'extras': showextras,
609 }
610 }
610 props = props.copy()
611 props = props.copy()
611 props.update(defprops)
612 props.update(defprops)
612
613
613 try:
614 try:
614 if self.ui.debugflag and 'header_debug' in self.t:
615 if self.ui.debugflag and 'header_debug' in self.t:
615 key = 'header_debug'
616 key = 'header_debug'
616 elif self.ui.quiet and 'header_quiet' in self.t:
617 elif self.ui.quiet and 'header_quiet' in self.t:
617 key = 'header_quiet'
618 key = 'header_quiet'
618 elif self.ui.verbose and 'header_verbose' in self.t:
619 elif self.ui.verbose and 'header_verbose' in self.t:
619 key = 'header_verbose'
620 key = 'header_verbose'
620 elif 'header' in self.t:
621 elif 'header' in self.t:
621 key = 'header'
622 key = 'header'
622 else:
623 else:
623 key = ''
624 key = ''
624 if key:
625 if key:
625 h = templater.stringify(self.t(key, **props))
626 h = templater.stringify(self.t(key, **props))
626 if self.buffered:
627 if self.buffered:
627 self.header[rev] = h
628 self.header[rev] = h
628 else:
629 else:
629 self.ui.write(h)
630 self.ui.write(h)
630 if self.ui.debugflag and 'changeset_debug' in self.t:
631 if self.ui.debugflag and 'changeset_debug' in self.t:
631 key = 'changeset_debug'
632 key = 'changeset_debug'
632 elif self.ui.quiet and 'changeset_quiet' in self.t:
633 elif self.ui.quiet and 'changeset_quiet' in self.t:
633 key = 'changeset_quiet'
634 key = 'changeset_quiet'
634 elif self.ui.verbose and 'changeset_verbose' in self.t:
635 elif self.ui.verbose and 'changeset_verbose' in self.t:
635 key = 'changeset_verbose'
636 key = 'changeset_verbose'
636 else:
637 else:
637 key = 'changeset'
638 key = 'changeset'
638 self.ui.write(templater.stringify(self.t(key, **props)))
639 self.ui.write(templater.stringify(self.t(key, **props)))
639 self.showpatch(changenode)
640 self.showpatch(changenode)
640 except KeyError, inst:
641 except KeyError, inst:
641 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
642 raise util.Abort(_("%s: no key named '%s'") % (self.t.mapfile,
642 inst.args[0]))
643 inst.args[0]))
643 except SyntaxError, inst:
644 except SyntaxError, inst:
644 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
645 raise util.Abort(_('%s: %s') % (self.t.mapfile, inst.args[0]))
645
646
646 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
647 def show_changeset(ui, repo, opts, buffered=False, matchfn=False):
647 """show one changeset using template or regular display.
648 """show one changeset using template or regular display.
648
649
649 Display format will be the first non-empty hit of:
650 Display format will be the first non-empty hit of:
650 1. option 'template'
651 1. option 'template'
651 2. option 'style'
652 2. option 'style'
652 3. [ui] setting 'logtemplate'
653 3. [ui] setting 'logtemplate'
653 4. [ui] setting 'style'
654 4. [ui] setting 'style'
654 If all of these values are either the unset or the empty string,
655 If all of these values are either the unset or the empty string,
655 regular display via changeset_printer() is done.
656 regular display via changeset_printer() is done.
656 """
657 """
657 # options
658 # options
658 patch = False
659 patch = False
659 if opts.get('patch'):
660 if opts.get('patch'):
660 patch = matchfn or util.always
661 patch = matchfn or util.always
661
662
662 tmpl = opts.get('template')
663 tmpl = opts.get('template')
663 mapfile = None
664 mapfile = None
664 if tmpl:
665 if tmpl:
665 tmpl = templater.parsestring(tmpl, quoted=False)
666 tmpl = templater.parsestring(tmpl, quoted=False)
666 else:
667 else:
667 mapfile = opts.get('style')
668 mapfile = opts.get('style')
668 # ui settings
669 # ui settings
669 if not mapfile:
670 if not mapfile:
670 tmpl = ui.config('ui', 'logtemplate')
671 tmpl = ui.config('ui', 'logtemplate')
671 if tmpl:
672 if tmpl:
672 tmpl = templater.parsestring(tmpl)
673 tmpl = templater.parsestring(tmpl)
673 else:
674 else:
674 mapfile = ui.config('ui', 'style')
675 mapfile = ui.config('ui', 'style')
675
676
676 if tmpl or mapfile:
677 if tmpl or mapfile:
677 if mapfile:
678 if mapfile:
678 if not os.path.split(mapfile)[0]:
679 if not os.path.split(mapfile)[0]:
679 mapname = (templater.templatepath('map-cmdline.' + mapfile)
680 mapname = (templater.templatepath('map-cmdline.' + mapfile)
680 or templater.templatepath(mapfile))
681 or templater.templatepath(mapfile))
681 if mapname: mapfile = mapname
682 if mapname: mapfile = mapname
682 try:
683 try:
683 t = changeset_templater(ui, repo, patch, mapfile, buffered)
684 t = changeset_templater(ui, repo, patch, mapfile, buffered)
684 except SyntaxError, inst:
685 except SyntaxError, inst:
685 raise util.Abort(inst.args[0])
686 raise util.Abort(inst.args[0])
686 if tmpl: t.use_template(tmpl)
687 if tmpl: t.use_template(tmpl)
687 return t
688 return t
688 return changeset_printer(ui, repo, patch, buffered)
689 return changeset_printer(ui, repo, patch, buffered)
689
690
690 def finddate(ui, repo, date):
691 def finddate(ui, repo, date):
691 """Find the tipmost changeset that matches the given date spec"""
692 """Find the tipmost changeset that matches the given date spec"""
692 df = util.matchdate(date + " to " + date)
693 df = util.matchdate(date + " to " + date)
693 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
694 get = util.cachefunc(lambda r: repo.changectx(r).changeset())
694 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
695 changeiter, matchfn = walkchangerevs(ui, repo, [], get, {'rev':None})
695 results = {}
696 results = {}
696 for st, rev, fns in changeiter:
697 for st, rev, fns in changeiter:
697 if st == 'add':
698 if st == 'add':
698 d = get(rev)[2]
699 d = get(rev)[2]
699 if df(d[0]):
700 if df(d[0]):
700 results[rev] = d
701 results[rev] = d
701 elif st == 'iter':
702 elif st == 'iter':
702 if rev in results:
703 if rev in results:
703 ui.status("Found revision %s from %s\n" %
704 ui.status("Found revision %s from %s\n" %
704 (rev, util.datestr(results[rev])))
705 (rev, util.datestr(results[rev])))
705 return str(rev)
706 return str(rev)
706
707
707 raise util.Abort(_("revision matching date not found"))
708 raise util.Abort(_("revision matching date not found"))
708
709
709 def walkchangerevs(ui, repo, pats, change, opts):
710 def walkchangerevs(ui, repo, pats, change, opts):
710 '''Iterate over files and the revs they changed in.
711 '''Iterate over files and the revs they changed in.
711
712
712 Callers most commonly need to iterate backwards over the history
713 Callers most commonly need to iterate backwards over the history
713 it is interested in. Doing so has awful (quadratic-looking)
714 it is interested in. Doing so has awful (quadratic-looking)
714 performance, so we use iterators in a "windowed" way.
715 performance, so we use iterators in a "windowed" way.
715
716
716 We walk a window of revisions in the desired order. Within the
717 We walk a window of revisions in the desired order. Within the
717 window, we first walk forwards to gather data, then in the desired
718 window, we first walk forwards to gather data, then in the desired
718 order (usually backwards) to display it.
719 order (usually backwards) to display it.
719
720
720 This function returns an (iterator, matchfn) tuple. The iterator
721 This function returns an (iterator, matchfn) tuple. The iterator
721 yields 3-tuples. They will be of one of the following forms:
722 yields 3-tuples. They will be of one of the following forms:
722
723
723 "window", incrementing, lastrev: stepping through a window,
724 "window", incrementing, lastrev: stepping through a window,
724 positive if walking forwards through revs, last rev in the
725 positive if walking forwards through revs, last rev in the
725 sequence iterated over - use to reset state for the current window
726 sequence iterated over - use to reset state for the current window
726
727
727 "add", rev, fns: out-of-order traversal of the given file names
728 "add", rev, fns: out-of-order traversal of the given file names
728 fns, which changed during revision rev - use to gather data for
729 fns, which changed during revision rev - use to gather data for
729 possible display
730 possible display
730
731
731 "iter", rev, None: in-order traversal of the revs earlier iterated
732 "iter", rev, None: in-order traversal of the revs earlier iterated
732 over with "add" - use to display data'''
733 over with "add" - use to display data'''
733
734
734 def increasing_windows(start, end, windowsize=8, sizelimit=512):
735 def increasing_windows(start, end, windowsize=8, sizelimit=512):
735 if start < end:
736 if start < end:
736 while start < end:
737 while start < end:
737 yield start, min(windowsize, end-start)
738 yield start, min(windowsize, end-start)
738 start += windowsize
739 start += windowsize
739 if windowsize < sizelimit:
740 if windowsize < sizelimit:
740 windowsize *= 2
741 windowsize *= 2
741 else:
742 else:
742 while start > end:
743 while start > end:
743 yield start, min(windowsize, start-end-1)
744 yield start, min(windowsize, start-end-1)
744 start -= windowsize
745 start -= windowsize
745 if windowsize < sizelimit:
746 if windowsize < sizelimit:
746 windowsize *= 2
747 windowsize *= 2
747
748
748 files, matchfn, anypats = matchpats(repo, pats, opts)
749 files, matchfn, anypats = matchpats(repo, pats, opts)
749 follow = opts.get('follow') or opts.get('follow_first')
750 follow = opts.get('follow') or opts.get('follow_first')
750
751
751 if repo.changelog.count() == 0:
752 if repo.changelog.count() == 0:
752 return [], matchfn
753 return [], matchfn
753
754
754 if follow:
755 if follow:
755 defrange = '%s:0' % repo.changectx().rev()
756 defrange = '%s:0' % repo.changectx().rev()
756 else:
757 else:
757 defrange = 'tip:0'
758 defrange = 'tip:0'
758 revs = revrange(repo, opts['rev'] or [defrange])
759 revs = revrange(repo, opts['rev'] or [defrange])
759 wanted = {}
760 wanted = {}
760 slowpath = anypats or opts.get('removed')
761 slowpath = anypats or opts.get('removed')
761 fncache = {}
762 fncache = {}
762
763
763 if not slowpath and not files:
764 if not slowpath and not files:
764 # No files, no patterns. Display all revs.
765 # No files, no patterns. Display all revs.
765 wanted = dict.fromkeys(revs)
766 wanted = dict.fromkeys(revs)
766 copies = []
767 copies = []
767 if not slowpath:
768 if not slowpath:
768 # Only files, no patterns. Check the history of each file.
769 # Only files, no patterns. Check the history of each file.
769 def filerevgen(filelog, node):
770 def filerevgen(filelog, node):
770 cl_count = repo.changelog.count()
771 cl_count = repo.changelog.count()
771 if node is None:
772 if node is None:
772 last = filelog.count() - 1
773 last = filelog.count() - 1
773 else:
774 else:
774 last = filelog.rev(node)
775 last = filelog.rev(node)
775 for i, window in increasing_windows(last, nullrev):
776 for i, window in increasing_windows(last, nullrev):
776 revs = []
777 revs = []
777 for j in xrange(i - window, i + 1):
778 for j in xrange(i - window, i + 1):
778 n = filelog.node(j)
779 n = filelog.node(j)
779 revs.append((filelog.linkrev(n),
780 revs.append((filelog.linkrev(n),
780 follow and filelog.renamed(n)))
781 follow and filelog.renamed(n)))
781 revs.reverse()
782 revs.reverse()
782 for rev in revs:
783 for rev in revs:
783 # only yield rev for which we have the changelog, it can
784 # only yield rev for which we have the changelog, it can
784 # happen while doing "hg log" during a pull or commit
785 # happen while doing "hg log" during a pull or commit
785 if rev[0] < cl_count:
786 if rev[0] < cl_count:
786 yield rev
787 yield rev
787 def iterfiles():
788 def iterfiles():
788 for filename in files:
789 for filename in files:
789 yield filename, None
790 yield filename, None
790 for filename_node in copies:
791 for filename_node in copies:
791 yield filename_node
792 yield filename_node
792 minrev, maxrev = min(revs), max(revs)
793 minrev, maxrev = min(revs), max(revs)
793 for file_, node in iterfiles():
794 for file_, node in iterfiles():
794 filelog = repo.file(file_)
795 filelog = repo.file(file_)
795 # A zero count may be a directory or deleted file, so
796 # A zero count may be a directory or deleted file, so
796 # try to find matching entries on the slow path.
797 # try to find matching entries on the slow path.
797 if filelog.count() == 0:
798 if filelog.count() == 0:
798 slowpath = True
799 slowpath = True
799 break
800 break
800 for rev, copied in filerevgen(filelog, node):
801 for rev, copied in filerevgen(filelog, node):
801 if rev <= maxrev:
802 if rev <= maxrev:
802 if rev < minrev:
803 if rev < minrev:
803 break
804 break
804 fncache.setdefault(rev, [])
805 fncache.setdefault(rev, [])
805 fncache[rev].append(file_)
806 fncache[rev].append(file_)
806 wanted[rev] = 1
807 wanted[rev] = 1
807 if follow and copied:
808 if follow and copied:
808 copies.append(copied)
809 copies.append(copied)
809 if slowpath:
810 if slowpath:
810 if follow:
811 if follow:
811 raise util.Abort(_('can only follow copies/renames for explicit '
812 raise util.Abort(_('can only follow copies/renames for explicit '
812 'file names'))
813 'file names'))
813
814
814 # The slow path checks files modified in every changeset.
815 # The slow path checks files modified in every changeset.
815 def changerevgen():
816 def changerevgen():
816 for i, window in increasing_windows(repo.changelog.count()-1,
817 for i, window in increasing_windows(repo.changelog.count()-1,
817 nullrev):
818 nullrev):
818 for j in xrange(i - window, i + 1):
819 for j in xrange(i - window, i + 1):
819 yield j, change(j)[3]
820 yield j, change(j)[3]
820
821
821 for rev, changefiles in changerevgen():
822 for rev, changefiles in changerevgen():
822 matches = filter(matchfn, changefiles)
823 matches = filter(matchfn, changefiles)
823 if matches:
824 if matches:
824 fncache[rev] = matches
825 fncache[rev] = matches
825 wanted[rev] = 1
826 wanted[rev] = 1
826
827
827 class followfilter:
828 class followfilter:
828 def __init__(self, onlyfirst=False):
829 def __init__(self, onlyfirst=False):
829 self.startrev = nullrev
830 self.startrev = nullrev
830 self.roots = []
831 self.roots = []
831 self.onlyfirst = onlyfirst
832 self.onlyfirst = onlyfirst
832
833
833 def match(self, rev):
834 def match(self, rev):
834 def realparents(rev):
835 def realparents(rev):
835 if self.onlyfirst:
836 if self.onlyfirst:
836 return repo.changelog.parentrevs(rev)[0:1]
837 return repo.changelog.parentrevs(rev)[0:1]
837 else:
838 else:
838 return filter(lambda x: x != nullrev,
839 return filter(lambda x: x != nullrev,
839 repo.changelog.parentrevs(rev))
840 repo.changelog.parentrevs(rev))
840
841
841 if self.startrev == nullrev:
842 if self.startrev == nullrev:
842 self.startrev = rev
843 self.startrev = rev
843 return True
844 return True
844
845
845 if rev > self.startrev:
846 if rev > self.startrev:
846 # forward: all descendants
847 # forward: all descendants
847 if not self.roots:
848 if not self.roots:
848 self.roots.append(self.startrev)
849 self.roots.append(self.startrev)
849 for parent in realparents(rev):
850 for parent in realparents(rev):
850 if parent in self.roots:
851 if parent in self.roots:
851 self.roots.append(rev)
852 self.roots.append(rev)
852 return True
853 return True
853 else:
854 else:
854 # backwards: all parents
855 # backwards: all parents
855 if not self.roots:
856 if not self.roots:
856 self.roots.extend(realparents(self.startrev))
857 self.roots.extend(realparents(self.startrev))
857 if rev in self.roots:
858 if rev in self.roots:
858 self.roots.remove(rev)
859 self.roots.remove(rev)
859 self.roots.extend(realparents(rev))
860 self.roots.extend(realparents(rev))
860 return True
861 return True
861
862
862 return False
863 return False
863
864
864 # it might be worthwhile to do this in the iterator if the rev range
865 # it might be worthwhile to do this in the iterator if the rev range
865 # is descending and the prune args are all within that range
866 # is descending and the prune args are all within that range
866 for rev in opts.get('prune', ()):
867 for rev in opts.get('prune', ()):
867 rev = repo.changelog.rev(repo.lookup(rev))
868 rev = repo.changelog.rev(repo.lookup(rev))
868 ff = followfilter()
869 ff = followfilter()
869 stop = min(revs[0], revs[-1])
870 stop = min(revs[0], revs[-1])
870 for x in xrange(rev, stop-1, -1):
871 for x in xrange(rev, stop-1, -1):
871 if ff.match(x) and x in wanted:
872 if ff.match(x) and x in wanted:
872 del wanted[x]
873 del wanted[x]
873
874
874 def iterate():
875 def iterate():
875 if follow and not files:
876 if follow and not files:
876 ff = followfilter(onlyfirst=opts.get('follow_first'))
877 ff = followfilter(onlyfirst=opts.get('follow_first'))
877 def want(rev):
878 def want(rev):
878 if ff.match(rev) and rev in wanted:
879 if ff.match(rev) and rev in wanted:
879 return True
880 return True
880 return False
881 return False
881 else:
882 else:
882 def want(rev):
883 def want(rev):
883 return rev in wanted
884 return rev in wanted
884
885
885 for i, window in increasing_windows(0, len(revs)):
886 for i, window in increasing_windows(0, len(revs)):
886 yield 'window', revs[0] < revs[-1], revs[-1]
887 yield 'window', revs[0] < revs[-1], revs[-1]
887 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
888 nrevs = [rev for rev in revs[i:i+window] if want(rev)]
888 srevs = list(nrevs)
889 srevs = list(nrevs)
889 srevs.sort()
890 srevs.sort()
890 for rev in srevs:
891 for rev in srevs:
891 fns = fncache.get(rev)
892 fns = fncache.get(rev)
892 if not fns:
893 if not fns:
893 def fns_generator():
894 def fns_generator():
894 for f in change(rev)[3]:
895 for f in change(rev)[3]:
895 if matchfn(f):
896 if matchfn(f):
896 yield f
897 yield f
897 fns = fns_generator()
898 fns = fns_generator()
898 yield 'add', rev, fns
899 yield 'add', rev, fns
899 for rev in nrevs:
900 for rev in nrevs:
900 yield 'iter', rev, None
901 yield 'iter', rev, None
901 return iterate(), matchfn
902 return iterate(), matchfn
902
903
903 def commit(ui, repo, commitfunc, pats, opts):
904 def commit(ui, repo, commitfunc, pats, opts):
904 '''commit the specified files or all outstanding changes'''
905 '''commit the specified files or all outstanding changes'''
905 message = logmessage(opts)
906 message = logmessage(opts)
906
907
907 if opts['addremove']:
908 if opts['addremove']:
908 addremove(repo, pats, opts)
909 addremove(repo, pats, opts)
909 fns, match, anypats = matchpats(repo, pats, opts)
910 fns, match, anypats = matchpats(repo, pats, opts)
910 if pats:
911 if pats:
911 status = repo.status(files=fns, match=match)
912 status = repo.status(files=fns, match=match)
912 modified, added, removed, deleted, unknown = status[:5]
913 modified, added, removed, deleted, unknown = status[:5]
913 files = modified + added + removed
914 files = modified + added + removed
914 slist = None
915 slist = None
915 for f in fns:
916 for f in fns:
916 if f == '.':
917 if f == '.':
917 continue
918 continue
918 if f not in files:
919 if f not in files:
919 rf = repo.wjoin(f)
920 rf = repo.wjoin(f)
920 try:
921 try:
921 mode = os.lstat(rf)[stat.ST_MODE]
922 mode = os.lstat(rf)[stat.ST_MODE]
922 except OSError:
923 except OSError:
923 raise util.Abort(_("file %s not found!") % rf)
924 raise util.Abort(_("file %s not found!") % rf)
924 if stat.S_ISDIR(mode):
925 if stat.S_ISDIR(mode):
925 name = f + '/'
926 name = f + '/'
926 if slist is None:
927 if slist is None:
927 slist = list(files)
928 slist = list(files)
928 slist.sort()
929 slist.sort()
929 i = bisect.bisect(slist, name)
930 i = bisect.bisect(slist, name)
930 if i >= len(slist) or not slist[i].startswith(name):
931 if i >= len(slist) or not slist[i].startswith(name):
931 raise util.Abort(_("no match under directory %s!")
932 raise util.Abort(_("no match under directory %s!")
932 % rf)
933 % rf)
933 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
934 elif not (stat.S_ISREG(mode) or stat.S_ISLNK(mode)):
934 raise util.Abort(_("can't commit %s: "
935 raise util.Abort(_("can't commit %s: "
935 "unsupported file type!") % rf)
936 "unsupported file type!") % rf)
936 elif f not in repo.dirstate:
937 elif f not in repo.dirstate:
937 raise util.Abort(_("file %s not tracked!") % rf)
938 raise util.Abort(_("file %s not tracked!") % rf)
938 else:
939 else:
939 files = []
940 files = []
940 try:
941 try:
941 return commitfunc(ui, repo, files, message, match, opts)
942 return commitfunc(ui, repo, files, message, match, opts)
942 except ValueError, inst:
943 except ValueError, inst:
943 raise util.Abort(str(inst))
944 raise util.Abort(str(inst))
@@ -1,557 +1,574 b''
1 """
1 """
2 dirstate.py - working directory tracking for mercurial
2 dirstate.py - working directory tracking for mercurial
3
3
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
5
5
6 This software may be used and distributed according to the terms
6 This software may be used and distributed according to the terms
7 of the GNU General Public License, incorporated herein by reference.
7 of the GNU General Public License, incorporated herein by reference.
8 """
8 """
9
9
10 from node import *
10 from node import *
11 from i18n import _
11 from i18n import _
12 import struct, os, time, bisect, stat, strutil, util, re, errno, ignore
12 import struct, os, time, bisect, stat, strutil, util, re, errno, ignore
13 import cStringIO, osutil
13 import cStringIO, osutil
14
14
15 _unknown = ('?', 0, 0, 0)
15 _unknown = ('?', 0, 0, 0)
16 _format = ">cllll"
16 _format = ">cllll"
17
17
18 class dirstate(object):
18 class dirstate(object):
19
19
20 def __init__(self, opener, ui, root):
20 def __init__(self, opener, ui, root):
21 self._opener = opener
21 self._opener = opener
22 self._root = root
22 self._root = root
23 self._dirty = False
23 self._dirty = False
24 self._dirtypl = False
24 self._dirtypl = False
25 self._ui = ui
25 self._ui = ui
26
26
27 def __getattr__(self, name):
27 def __getattr__(self, name):
28 if name == '_map':
28 if name == '_map':
29 self._read()
29 self._read()
30 return self._map
30 return self._map
31 elif name == '_copymap':
31 elif name == '_copymap':
32 self._read()
32 self._read()
33 return self._copymap
33 return self._copymap
34 elif name == '_branch':
34 elif name == '_branch':
35 try:
35 try:
36 self._branch = (self._opener("branch").read().strip()
36 self._branch = (self._opener("branch").read().strip()
37 or "default")
37 or "default")
38 except IOError:
38 except IOError:
39 self._branch = "default"
39 self._branch = "default"
40 return self._branch
40 return self._branch
41 elif name == '_pl':
41 elif name == '_pl':
42 self._pl = [nullid, nullid]
42 self._pl = [nullid, nullid]
43 try:
43 try:
44 st = self._opener("dirstate").read(40)
44 st = self._opener("dirstate").read(40)
45 if len(st) == 40:
45 if len(st) == 40:
46 self._pl = st[:20], st[20:40]
46 self._pl = st[:20], st[20:40]
47 except IOError, err:
47 except IOError, err:
48 if err.errno != errno.ENOENT: raise
48 if err.errno != errno.ENOENT: raise
49 return self._pl
49 return self._pl
50 elif name == '_dirs':
50 elif name == '_dirs':
51 self._dirs = {}
51 self._dirs = {}
52 for f in self._map:
52 for f in self._map:
53 self._incpath(f)
53 if self[f] != 'r':
54 self._incpath(f)
54 return self._dirs
55 return self._dirs
55 elif name == '_ignore':
56 elif name == '_ignore':
56 files = [self._join('.hgignore')]
57 files = [self._join('.hgignore')]
57 for name, path in self._ui.configitems("ui"):
58 for name, path in self._ui.configitems("ui"):
58 if name == 'ignore' or name.startswith('ignore.'):
59 if name == 'ignore' or name.startswith('ignore.'):
59 files.append(os.path.expanduser(path))
60 files.append(os.path.expanduser(path))
60 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 self._ignore = ignore.ignore(self._root, files, self._ui.warn)
61 return self._ignore
62 return self._ignore
62 elif name == '_slash':
63 elif name == '_slash':
63 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 self._slash = self._ui.configbool('ui', 'slash') and os.sep != '/'
64 return self._slash
65 return self._slash
65 else:
66 else:
66 raise AttributeError, name
67 raise AttributeError, name
67
68
68 def _join(self, f):
69 def _join(self, f):
69 return os.path.join(self._root, f)
70 return os.path.join(self._root, f)
70
71
71 def getcwd(self):
72 def getcwd(self):
72 cwd = os.getcwd()
73 cwd = os.getcwd()
73 if cwd == self._root: return ''
74 if cwd == self._root: return ''
74 # self._root ends with a path separator if self._root is '/' or 'C:\'
75 # self._root ends with a path separator if self._root is '/' or 'C:\'
75 rootsep = self._root
76 rootsep = self._root
76 if not rootsep.endswith(os.sep):
77 if not rootsep.endswith(os.sep):
77 rootsep += os.sep
78 rootsep += os.sep
78 if cwd.startswith(rootsep):
79 if cwd.startswith(rootsep):
79 return cwd[len(rootsep):]
80 return cwd[len(rootsep):]
80 else:
81 else:
81 # we're outside the repo. return an absolute path.
82 # we're outside the repo. return an absolute path.
82 return cwd
83 return cwd
83
84
84 def pathto(self, f, cwd=None):
85 def pathto(self, f, cwd=None):
85 if cwd is None:
86 if cwd is None:
86 cwd = self.getcwd()
87 cwd = self.getcwd()
87 path = util.pathto(self._root, cwd, f)
88 path = util.pathto(self._root, cwd, f)
88 if self._slash:
89 if self._slash:
89 return path.replace(os.sep, '/')
90 return path.replace(os.sep, '/')
90 return path
91 return path
91
92
92 def __getitem__(self, key):
93 def __getitem__(self, key):
93 ''' current states:
94 ''' current states:
94 n normal
95 n normal
95 m needs merging
96 m needs merging
96 r marked for removal
97 r marked for removal
97 a marked for addition
98 a marked for addition
98 ? not tracked'''
99 ? not tracked'''
99 return self._map.get(key, ("?",))[0]
100 return self._map.get(key, ("?",))[0]
100
101
101 def __contains__(self, key):
102 def __contains__(self, key):
102 return key in self._map
103 return key in self._map
103
104
104 def __iter__(self):
105 def __iter__(self):
105 a = self._map.keys()
106 a = self._map.keys()
106 a.sort()
107 a.sort()
107 for x in a:
108 for x in a:
108 yield x
109 yield x
109
110
110 def parents(self):
111 def parents(self):
111 return self._pl
112 return self._pl
112
113
113 def branch(self):
114 def branch(self):
114 return self._branch
115 return self._branch
115
116
116 def setparents(self, p1, p2=nullid):
117 def setparents(self, p1, p2=nullid):
117 self._dirty = self._dirtypl = True
118 self._dirty = self._dirtypl = True
118 self._pl = p1, p2
119 self._pl = p1, p2
119
120
120 def setbranch(self, branch):
121 def setbranch(self, branch):
121 self._branch = branch
122 self._branch = branch
122 self._opener("branch", "w").write(branch + '\n')
123 self._opener("branch", "w").write(branch + '\n')
123
124
124 def _read(self):
125 def _read(self):
125 self._map = {}
126 self._map = {}
126 self._copymap = {}
127 self._copymap = {}
127 if not self._dirtypl:
128 if not self._dirtypl:
128 self._pl = [nullid, nullid]
129 self._pl = [nullid, nullid]
129 try:
130 try:
130 st = self._opener("dirstate").read()
131 st = self._opener("dirstate").read()
131 except IOError, err:
132 except IOError, err:
132 if err.errno != errno.ENOENT: raise
133 if err.errno != errno.ENOENT: raise
133 return
134 return
134 if not st:
135 if not st:
135 return
136 return
136
137
137 if not self._dirtypl:
138 if not self._dirtypl:
138 self._pl = [st[:20], st[20: 40]]
139 self._pl = [st[:20], st[20: 40]]
139
140
140 # deref fields so they will be local in loop
141 # deref fields so they will be local in loop
141 dmap = self._map
142 dmap = self._map
142 copymap = self._copymap
143 copymap = self._copymap
143 unpack = struct.unpack
144 unpack = struct.unpack
144 e_size = struct.calcsize(_format)
145 e_size = struct.calcsize(_format)
145 pos1 = 40
146 pos1 = 40
146 l = len(st)
147 l = len(st)
147
148
148 # the inner loop
149 # the inner loop
149 while pos1 < l:
150 while pos1 < l:
150 pos2 = pos1 + e_size
151 pos2 = pos1 + e_size
151 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
152 e = unpack(">cllll", st[pos1:pos2]) # a literal here is faster
152 pos1 = pos2 + e[4]
153 pos1 = pos2 + e[4]
153 f = st[pos2:pos1]
154 f = st[pos2:pos1]
154 if '\0' in f:
155 if '\0' in f:
155 f, c = f.split('\0')
156 f, c = f.split('\0')
156 copymap[f] = c
157 copymap[f] = c
157 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
158 dmap[f] = e # we hold onto e[4] because making a subtuple is slow
158
159
159 def invalidate(self):
160 def invalidate(self):
160 for a in "_map _copymap _branch _pl _dirs _ignore".split():
161 for a in "_map _copymap _branch _pl _dirs _ignore".split():
161 if a in self.__dict__:
162 if a in self.__dict__:
162 delattr(self, a)
163 delattr(self, a)
163 self._dirty = False
164 self._dirty = False
164
165
165 def copy(self, source, dest):
166 def copy(self, source, dest):
166 self._dirty = True
167 self._dirty = True
167 self._copymap[dest] = source
168 self._copymap[dest] = source
168
169
169 def copied(self, file):
170 def copied(self, file):
170 return self._copymap.get(file, None)
171 return self._copymap.get(file, None)
171
172
172 def copies(self):
173 def copies(self):
173 return self._copymap
174 return self._copymap
174
175
175 def _incpath(self, path):
176 def _incpath(self, path):
176 c = path.rfind('/')
177 c = path.rfind('/')
177 if c >= 0:
178 if c >= 0:
178 dirs = self._dirs
179 dirs = self._dirs
179 base = path[:c]
180 base = path[:c]
180 if base not in dirs:
181 if base not in dirs:
181 self._incpath(base)
182 self._incpath(base)
182 dirs[base] = 1
183 dirs[base] = 1
183 else:
184 else:
184 dirs[base] += 1
185 dirs[base] += 1
185
186
186 def _decpath(self, path):
187 def _decpath(self, path):
187 if "_dirs" in self.__dict__:
188 if "_dirs" in self.__dict__:
188 c = path.rfind('/')
189 c = path.rfind('/')
189 if c >= 0:
190 if c >= 0:
190 base = path[:c]
191 base = path[:c]
191 dirs = self._dirs
192 dirs = self._dirs
192 if dirs[base] == 1:
193 if dirs[base] == 1:
193 del dirs[base]
194 del dirs[base]
194 self._decpath(base)
195 self._decpath(base)
195 else:
196 else:
196 dirs[base] -= 1
197 dirs[base] -= 1
197
198
198 def _incpathcheck(self, f):
199 def _incpathcheck(self, f):
199 if '\r' in f or '\n' in f:
200 if '\r' in f or '\n' in f:
200 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
201 raise util.Abort(_("'\\n' and '\\r' disallowed in filenames"))
201 # shadows
202 # shadows
202 if f in self._dirs:
203 if f in self._dirs:
203 raise util.Abort(_('directory %r already in dirstate') % f)
204 raise util.Abort(_('directory %r already in dirstate') % f)
204 for c in strutil.rfindall(f, '/'):
205 for c in strutil.rfindall(f, '/'):
205 d = f[:c]
206 d = f[:c]
206 if d in self._dirs:
207 if d in self._dirs:
207 break
208 break
208 if d in self._map:
209 if d in self._map and self[d] != 'r':
209 raise util.Abort(_('file %r in dirstate clashes with %r') %
210 raise util.Abort(_('file %r in dirstate clashes with %r') %
210 (d, f))
211 (d, f))
211 self._incpath(f)
212 self._incpath(f)
212
213
214 def _changepath(self, f, newstate):
215 # handle upcoming path changes
216 oldstate = self[f]
217 if oldstate not in "?r" and newstate in "?r":
218 self._decpath(f)
219 return
220 if oldstate in "?r" and newstate not in "?r":
221 self._incpathcheck(f)
222 return
223
213 def normal(self, f):
224 def normal(self, f):
214 'mark a file normal and clean'
225 'mark a file normal and clean'
215 self._dirty = True
226 self._dirty = True
227 self._changepath(f, 'n')
216 s = os.lstat(self._join(f))
228 s = os.lstat(self._join(f))
217 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
229 self._map[f] = ('n', s.st_mode, s.st_size, s.st_mtime, 0)
218 if self._copymap.has_key(f):
230 if self._copymap.has_key(f):
219 del self._copymap[f]
231 del self._copymap[f]
220
232
221 def normallookup(self, f):
233 def normallookup(self, f):
222 'mark a file normal, but possibly dirty'
234 'mark a file normal, but possibly dirty'
223 self._dirty = True
235 self._dirty = True
236 self._changepath(f, 'n')
224 self._map[f] = ('n', 0, -1, -1, 0)
237 self._map[f] = ('n', 0, -1, -1, 0)
225 if f in self._copymap:
238 if f in self._copymap:
226 del self._copymap[f]
239 del self._copymap[f]
227
240
228 def normaldirty(self, f):
241 def normaldirty(self, f):
229 'mark a file normal, but dirty'
242 'mark a file normal, but dirty'
230 self._dirty = True
243 self._dirty = True
244 self._changepath(f, 'n')
231 self._map[f] = ('n', 0, -2, -1, 0)
245 self._map[f] = ('n', 0, -2, -1, 0)
232 if f in self._copymap:
246 if f in self._copymap:
233 del self._copymap[f]
247 del self._copymap[f]
234
248
235 def add(self, f):
249 def add(self, f):
236 'mark a file added'
250 'mark a file added'
237 self._dirty = True
251 self._dirty = True
238 self._incpathcheck(f)
252 self._changepath(f, 'a')
239 self._map[f] = ('a', 0, -1, -1, 0)
253 self._map[f] = ('a', 0, -1, -1, 0)
240 if f in self._copymap:
254 if f in self._copymap:
241 del self._copymap[f]
255 del self._copymap[f]
242
256
243 def remove(self, f):
257 def remove(self, f):
244 'mark a file removed'
258 'mark a file removed'
245 self._dirty = True
259 self._dirty = True
260 self._changepath(f, 'r')
246 self._map[f] = ('r', 0, 0, 0, 0)
261 self._map[f] = ('r', 0, 0, 0, 0)
247 self._decpath(f)
248 if f in self._copymap:
262 if f in self._copymap:
249 del self._copymap[f]
263 del self._copymap[f]
250
264
251 def merge(self, f):
265 def merge(self, f):
252 'mark a file merged'
266 'mark a file merged'
253 self._dirty = True
267 self._dirty = True
254 s = os.lstat(self._join(f))
268 s = os.lstat(self._join(f))
269 self._changepath(f, 'm')
255 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
270 self._map[f] = ('m', s.st_mode, s.st_size, s.st_mtime, 0)
256 if f in self._copymap:
271 if f in self._copymap:
257 del self._copymap[f]
272 del self._copymap[f]
258
273
259 def forget(self, f):
274 def forget(self, f):
260 'forget a file'
275 'forget a file'
261 self._dirty = True
276 self._dirty = True
262 try:
277 try:
278 self._changepath(f, '?')
263 del self._map[f]
279 del self._map[f]
264 self._decpath(f)
265 except KeyError:
280 except KeyError:
266 self._ui.warn(_("not in dirstate: %s!\n") % f)
281 self._ui.warn(_("not in dirstate: %s!\n") % f)
267
282
268 def clear(self):
283 def clear(self):
269 self._map = {}
284 self._map = {}
285 if "_dirs" in self.__dict__:
286 delattr(self, "_dirs");
270 self._copymap = {}
287 self._copymap = {}
271 self._pl = [nullid, nullid]
288 self._pl = [nullid, nullid]
272 self._dirty = True
289 self._dirty = True
273
290
274 def rebuild(self, parent, files):
291 def rebuild(self, parent, files):
275 self.clear()
292 self.clear()
276 for f in files:
293 for f in files:
277 if files.execf(f):
294 if files.execf(f):
278 self._map[f] = ('n', 0777, -1, 0, 0)
295 self._map[f] = ('n', 0777, -1, 0, 0)
279 else:
296 else:
280 self._map[f] = ('n', 0666, -1, 0, 0)
297 self._map[f] = ('n', 0666, -1, 0, 0)
281 self._pl = (parent, nullid)
298 self._pl = (parent, nullid)
282 self._dirty = True
299 self._dirty = True
283
300
284 def write(self):
301 def write(self):
285 if not self._dirty:
302 if not self._dirty:
286 return
303 return
287 cs = cStringIO.StringIO()
304 cs = cStringIO.StringIO()
288 copymap = self._copymap
305 copymap = self._copymap
289 pack = struct.pack
306 pack = struct.pack
290 write = cs.write
307 write = cs.write
291 write("".join(self._pl))
308 write("".join(self._pl))
292 for f, e in self._map.iteritems():
309 for f, e in self._map.iteritems():
293 if f in copymap:
310 if f in copymap:
294 f = "%s\0%s" % (f, copymap[f])
311 f = "%s\0%s" % (f, copymap[f])
295 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
312 e = pack(_format, e[0], e[1], e[2], e[3], len(f))
296 write(e)
313 write(e)
297 write(f)
314 write(f)
298 st = self._opener("dirstate", "w", atomictemp=True)
315 st = self._opener("dirstate", "w", atomictemp=True)
299 st.write(cs.getvalue())
316 st.write(cs.getvalue())
300 st.rename()
317 st.rename()
301 self._dirty = self._dirtypl = False
318 self._dirty = self._dirtypl = False
302
319
303 def _filter(self, files):
320 def _filter(self, files):
304 ret = {}
321 ret = {}
305 unknown = []
322 unknown = []
306
323
307 for x in files:
324 for x in files:
308 if x == '.':
325 if x == '.':
309 return self._map.copy()
326 return self._map.copy()
310 if x not in self._map:
327 if x not in self._map:
311 unknown.append(x)
328 unknown.append(x)
312 else:
329 else:
313 ret[x] = self._map[x]
330 ret[x] = self._map[x]
314
331
315 if not unknown:
332 if not unknown:
316 return ret
333 return ret
317
334
318 b = self._map.keys()
335 b = self._map.keys()
319 b.sort()
336 b.sort()
320 blen = len(b)
337 blen = len(b)
321
338
322 for x in unknown:
339 for x in unknown:
323 bs = bisect.bisect(b, "%s%s" % (x, '/'))
340 bs = bisect.bisect(b, "%s%s" % (x, '/'))
324 while bs < blen:
341 while bs < blen:
325 s = b[bs]
342 s = b[bs]
326 if len(s) > len(x) and s.startswith(x):
343 if len(s) > len(x) and s.startswith(x):
327 ret[s] = self._map[s]
344 ret[s] = self._map[s]
328 else:
345 else:
329 break
346 break
330 bs += 1
347 bs += 1
331 return ret
348 return ret
332
349
333 def _supported(self, f, mode, verbose=False):
350 def _supported(self, f, mode, verbose=False):
334 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
351 if stat.S_ISREG(mode) or stat.S_ISLNK(mode):
335 return True
352 return True
336 if verbose:
353 if verbose:
337 kind = 'unknown'
354 kind = 'unknown'
338 if stat.S_ISCHR(mode): kind = _('character device')
355 if stat.S_ISCHR(mode): kind = _('character device')
339 elif stat.S_ISBLK(mode): kind = _('block device')
356 elif stat.S_ISBLK(mode): kind = _('block device')
340 elif stat.S_ISFIFO(mode): kind = _('fifo')
357 elif stat.S_ISFIFO(mode): kind = _('fifo')
341 elif stat.S_ISSOCK(mode): kind = _('socket')
358 elif stat.S_ISSOCK(mode): kind = _('socket')
342 elif stat.S_ISDIR(mode): kind = _('directory')
359 elif stat.S_ISDIR(mode): kind = _('directory')
343 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
360 self._ui.warn(_('%s: unsupported file type (type is %s)\n')
344 % (self.pathto(f), kind))
361 % (self.pathto(f), kind))
345 return False
362 return False
346
363
347 def walk(self, files=None, match=util.always, badmatch=None):
364 def walk(self, files=None, match=util.always, badmatch=None):
348 # filter out the stat
365 # filter out the stat
349 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
366 for src, f, st in self.statwalk(files, match, badmatch=badmatch):
350 yield src, f
367 yield src, f
351
368
352 def statwalk(self, files=None, match=util.always, ignored=False,
369 def statwalk(self, files=None, match=util.always, ignored=False,
353 badmatch=None, directories=False):
370 badmatch=None, directories=False):
354 '''
371 '''
355 walk recursively through the directory tree, finding all files
372 walk recursively through the directory tree, finding all files
356 matched by the match function
373 matched by the match function
357
374
358 results are yielded in a tuple (src, filename, st), where src
375 results are yielded in a tuple (src, filename, st), where src
359 is one of:
376 is one of:
360 'f' the file was found in the directory tree
377 'f' the file was found in the directory tree
361 'd' the file is a directory of the tree
378 'd' the file is a directory of the tree
362 'm' the file was only in the dirstate and not in the tree
379 'm' the file was only in the dirstate and not in the tree
363 'b' file was not found and matched badmatch
380 'b' file was not found and matched badmatch
364
381
365 and st is the stat result if the file was found in the directory.
382 and st is the stat result if the file was found in the directory.
366 '''
383 '''
367
384
368 # walk all files by default
385 # walk all files by default
369 if not files:
386 if not files:
370 files = ['.']
387 files = ['.']
371 dc = self._map.copy()
388 dc = self._map.copy()
372 else:
389 else:
373 files = util.unique(files)
390 files = util.unique(files)
374 dc = self._filter(files)
391 dc = self._filter(files)
375
392
376 def imatch(file_):
393 def imatch(file_):
377 if file_ not in dc and self._ignore(file_):
394 if file_ not in dc and self._ignore(file_):
378 return False
395 return False
379 return match(file_)
396 return match(file_)
380
397
381 ignore = self._ignore
398 ignore = self._ignore
382 if ignored:
399 if ignored:
383 imatch = match
400 imatch = match
384 ignore = util.never
401 ignore = util.never
385
402
386 # self._root may end with a path separator when self._root == '/'
403 # self._root may end with a path separator when self._root == '/'
387 common_prefix_len = len(self._root)
404 common_prefix_len = len(self._root)
388 if not self._root.endswith(os.sep):
405 if not self._root.endswith(os.sep):
389 common_prefix_len += 1
406 common_prefix_len += 1
390
407
391 normpath = util.normpath
408 normpath = util.normpath
392 listdir = osutil.listdir
409 listdir = osutil.listdir
393 lstat = os.lstat
410 lstat = os.lstat
394 bisect_left = bisect.bisect_left
411 bisect_left = bisect.bisect_left
395 isdir = os.path.isdir
412 isdir = os.path.isdir
396 pconvert = util.pconvert
413 pconvert = util.pconvert
397 join = os.path.join
414 join = os.path.join
398 s_isdir = stat.S_ISDIR
415 s_isdir = stat.S_ISDIR
399 supported = self._supported
416 supported = self._supported
400 _join = self._join
417 _join = self._join
401 known = {'.hg': 1}
418 known = {'.hg': 1}
402
419
403 # recursion free walker, faster than os.walk.
420 # recursion free walker, faster than os.walk.
404 def findfiles(s):
421 def findfiles(s):
405 work = [s]
422 work = [s]
406 wadd = work.append
423 wadd = work.append
407 found = []
424 found = []
408 add = found.append
425 add = found.append
409 if directories:
426 if directories:
410 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
427 add((normpath(s[common_prefix_len:]), 'd', lstat(s)))
411 while work:
428 while work:
412 top = work.pop()
429 top = work.pop()
413 entries = listdir(top, stat=True)
430 entries = listdir(top, stat=True)
414 # nd is the top of the repository dir tree
431 # nd is the top of the repository dir tree
415 nd = normpath(top[common_prefix_len:])
432 nd = normpath(top[common_prefix_len:])
416 if nd == '.':
433 if nd == '.':
417 nd = ''
434 nd = ''
418 else:
435 else:
419 # do not recurse into a repo contained in this
436 # do not recurse into a repo contained in this
420 # one. use bisect to find .hg directory so speed
437 # one. use bisect to find .hg directory so speed
421 # is good on big directory.
438 # is good on big directory.
422 names = [e[0] for e in entries]
439 names = [e[0] for e in entries]
423 hg = bisect_left(names, '.hg')
440 hg = bisect_left(names, '.hg')
424 if hg < len(names) and names[hg] == '.hg':
441 if hg < len(names) and names[hg] == '.hg':
425 if isdir(join(top, '.hg')):
442 if isdir(join(top, '.hg')):
426 continue
443 continue
427 for f, kind, st in entries:
444 for f, kind, st in entries:
428 np = pconvert(join(nd, f))
445 np = pconvert(join(nd, f))
429 if np in known:
446 if np in known:
430 continue
447 continue
431 known[np] = 1
448 known[np] = 1
432 p = join(top, f)
449 p = join(top, f)
433 # don't trip over symlinks
450 # don't trip over symlinks
434 if kind == stat.S_IFDIR:
451 if kind == stat.S_IFDIR:
435 if not ignore(np):
452 if not ignore(np):
436 wadd(p)
453 wadd(p)
437 if directories:
454 if directories:
438 add((np, 'd', st))
455 add((np, 'd', st))
439 if np in dc and match(np):
456 if np in dc and match(np):
440 add((np, 'm', st))
457 add((np, 'm', st))
441 elif imatch(np):
458 elif imatch(np):
442 if supported(np, st.st_mode):
459 if supported(np, st.st_mode):
443 add((np, 'f', st))
460 add((np, 'f', st))
444 elif np in dc:
461 elif np in dc:
445 add((np, 'm', st))
462 add((np, 'm', st))
446 found.sort()
463 found.sort()
447 return found
464 return found
448
465
449 # step one, find all files that match our criteria
466 # step one, find all files that match our criteria
450 files.sort()
467 files.sort()
451 for ff in files:
468 for ff in files:
452 nf = normpath(ff)
469 nf = normpath(ff)
453 f = _join(ff)
470 f = _join(ff)
454 try:
471 try:
455 st = lstat(f)
472 st = lstat(f)
456 except OSError, inst:
473 except OSError, inst:
457 found = False
474 found = False
458 for fn in dc:
475 for fn in dc:
459 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
476 if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'):
460 found = True
477 found = True
461 break
478 break
462 if not found:
479 if not found:
463 if inst.errno != errno.ENOENT or not badmatch:
480 if inst.errno != errno.ENOENT or not badmatch:
464 self._ui.warn('%s: %s\n' %
481 self._ui.warn('%s: %s\n' %
465 (self.pathto(ff), inst.strerror))
482 (self.pathto(ff), inst.strerror))
466 elif badmatch and badmatch(ff) and imatch(nf):
483 elif badmatch and badmatch(ff) and imatch(nf):
467 yield 'b', ff, None
484 yield 'b', ff, None
468 continue
485 continue
469 if s_isdir(st.st_mode):
486 if s_isdir(st.st_mode):
470 for f, src, st in findfiles(f):
487 for f, src, st in findfiles(f):
471 yield src, f, st
488 yield src, f, st
472 else:
489 else:
473 if nf in known:
490 if nf in known:
474 continue
491 continue
475 known[nf] = 1
492 known[nf] = 1
476 if match(nf):
493 if match(nf):
477 if supported(ff, st.st_mode, verbose=True):
494 if supported(ff, st.st_mode, verbose=True):
478 yield 'f', nf, st
495 yield 'f', nf, st
479 elif ff in dc:
496 elif ff in dc:
480 yield 'm', nf, st
497 yield 'm', nf, st
481
498
482 # step two run through anything left in the dc hash and yield
499 # step two run through anything left in the dc hash and yield
483 # if we haven't already seen it
500 # if we haven't already seen it
484 ks = dc.keys()
501 ks = dc.keys()
485 ks.sort()
502 ks.sort()
486 for k in ks:
503 for k in ks:
487 if k in known:
504 if k in known:
488 continue
505 continue
489 known[k] = 1
506 known[k] = 1
490 if imatch(k):
507 if imatch(k):
491 yield 'm', k, None
508 yield 'm', k, None
492
509
493 def status(self, files, match, list_ignored, list_clean):
510 def status(self, files, match, list_ignored, list_clean):
494 lookup, modified, added, unknown, ignored = [], [], [], [], []
511 lookup, modified, added, unknown, ignored = [], [], [], [], []
495 removed, deleted, clean = [], [], []
512 removed, deleted, clean = [], [], []
496
513
497 _join = self._join
514 _join = self._join
498 lstat = os.lstat
515 lstat = os.lstat
499 cmap = self._copymap
516 cmap = self._copymap
500 dmap = self._map
517 dmap = self._map
501 ladd = lookup.append
518 ladd = lookup.append
502 madd = modified.append
519 madd = modified.append
503 aadd = added.append
520 aadd = added.append
504 uadd = unknown.append
521 uadd = unknown.append
505 iadd = ignored.append
522 iadd = ignored.append
506 radd = removed.append
523 radd = removed.append
507 dadd = deleted.append
524 dadd = deleted.append
508 cadd = clean.append
525 cadd = clean.append
509
526
510 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
527 for src, fn, st in self.statwalk(files, match, ignored=list_ignored):
511 if fn in dmap:
528 if fn in dmap:
512 type_, mode, size, time, foo = dmap[fn]
529 type_, mode, size, time, foo = dmap[fn]
513 else:
530 else:
514 if list_ignored and self._ignore(fn):
531 if list_ignored and self._ignore(fn):
515 iadd(fn)
532 iadd(fn)
516 else:
533 else:
517 uadd(fn)
534 uadd(fn)
518 continue
535 continue
519 if src == 'm':
536 if src == 'm':
520 nonexistent = True
537 nonexistent = True
521 if not st:
538 if not st:
522 try:
539 try:
523 st = lstat(_join(fn))
540 st = lstat(_join(fn))
524 except OSError, inst:
541 except OSError, inst:
525 if inst.errno != errno.ENOENT:
542 if inst.errno not in (errno.ENOENT, errno.ENOTDIR):
526 raise
543 raise
527 st = None
544 st = None
528 # We need to re-check that it is a valid file
545 # We need to re-check that it is a valid file
529 if st and self._supported(fn, st.st_mode):
546 if st and self._supported(fn, st.st_mode):
530 nonexistent = False
547 nonexistent = False
531 # XXX: what to do with file no longer present in the fs
548 # XXX: what to do with file no longer present in the fs
532 # who are not removed in the dirstate ?
549 # who are not removed in the dirstate ?
533 if nonexistent and type_ in "nm":
550 if nonexistent and type_ in "nm":
534 dadd(fn)
551 dadd(fn)
535 continue
552 continue
536 # check the common case first
553 # check the common case first
537 if type_ == 'n':
554 if type_ == 'n':
538 if not st:
555 if not st:
539 st = lstat(_join(fn))
556 st = lstat(_join(fn))
540 if (size >= 0 and (size != st.st_size
557 if (size >= 0 and (size != st.st_size
541 or (mode ^ st.st_mode) & 0100)
558 or (mode ^ st.st_mode) & 0100)
542 or size == -2
559 or size == -2
543 or fn in self._copymap):
560 or fn in self._copymap):
544 madd(fn)
561 madd(fn)
545 elif time != int(st.st_mtime):
562 elif time != int(st.st_mtime):
546 ladd(fn)
563 ladd(fn)
547 elif list_clean:
564 elif list_clean:
548 cadd(fn)
565 cadd(fn)
549 elif type_ == 'm':
566 elif type_ == 'm':
550 madd(fn)
567 madd(fn)
551 elif type_ == 'a':
568 elif type_ == 'a':
552 aadd(fn)
569 aadd(fn)
553 elif type_ == 'r':
570 elif type_ == 'r':
554 radd(fn)
571 radd(fn)
555
572
556 return (lookup, modified, added, removed, deleted, unknown, ignored,
573 return (lookup, modified, added, removed, deleted, unknown, ignored,
557 clean)
574 clean)
@@ -1,1700 +1,1700 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, osutil
17 import os, stat, threading, time, calendar, ConfigParser, locale, glob, osutil
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, kind in osutil.listdir(src):
679 for name, kind in osutil.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.ENOTDIR, 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 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
854 EXECFLAGS = stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
855 fh, fn = tempfile.mkstemp("", "", path)
855 fh, fn = tempfile.mkstemp("", "", path)
856 os.close(fh)
856 os.close(fh)
857 m = os.stat(fn).st_mode
857 m = os.stat(fn).st_mode
858 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
858 # VFAT on Linux can flip mode but it doesn't persist a FS remount.
859 # frequently we can detect it if files are created with exec bit on.
859 # frequently we can detect it if files are created with exec bit on.
860 new_file_has_exec = m & EXECFLAGS
860 new_file_has_exec = m & EXECFLAGS
861 os.chmod(fn, m ^ EXECFLAGS)
861 os.chmod(fn, m ^ EXECFLAGS)
862 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
862 exec_flags_cannot_flip = (os.stat(fn).st_mode == m)
863 os.unlink(fn)
863 os.unlink(fn)
864 except (IOError,OSError):
864 except (IOError,OSError):
865 # we don't care, the user probably won't be able to commit anyway
865 # we don't care, the user probably won't be able to commit anyway
866 return False
866 return False
867 return not (new_file_has_exec or exec_flags_cannot_flip)
867 return not (new_file_has_exec or exec_flags_cannot_flip)
868
868
869 def execfunc(path, fallback):
869 def execfunc(path, fallback):
870 '''return an is_exec() function with default to fallback'''
870 '''return an is_exec() function with default to fallback'''
871 if checkexec(path):
871 if checkexec(path):
872 return lambda x: is_exec(os.path.join(path, x))
872 return lambda x: is_exec(os.path.join(path, x))
873 return fallback
873 return fallback
874
874
875 def checklink(path):
875 def checklink(path):
876 """check whether the given path is on a symlink-capable filesystem"""
876 """check whether the given path is on a symlink-capable filesystem"""
877 # mktemp is not racy because symlink creation will fail if the
877 # mktemp is not racy because symlink creation will fail if the
878 # file already exists
878 # file already exists
879 name = tempfile.mktemp(dir=path)
879 name = tempfile.mktemp(dir=path)
880 try:
880 try:
881 os.symlink(".", name)
881 os.symlink(".", name)
882 os.unlink(name)
882 os.unlink(name)
883 return True
883 return True
884 except (OSError, AttributeError):
884 except (OSError, AttributeError):
885 return False
885 return False
886
886
887 def linkfunc(path, fallback):
887 def linkfunc(path, fallback):
888 '''return an is_link() function with default to fallback'''
888 '''return an is_link() function with default to fallback'''
889 if checklink(path):
889 if checklink(path):
890 return lambda x: os.path.islink(os.path.join(path, x))
890 return lambda x: os.path.islink(os.path.join(path, x))
891 return fallback
891 return fallback
892
892
893 _umask = os.umask(0)
893 _umask = os.umask(0)
894 os.umask(_umask)
894 os.umask(_umask)
895
895
896 def needbinarypatch():
896 def needbinarypatch():
897 """return True if patches should be applied in binary mode by default."""
897 """return True if patches should be applied in binary mode by default."""
898 return os.name == 'nt'
898 return os.name == 'nt'
899
899
900 # Platform specific variants
900 # Platform specific variants
901 if os.name == 'nt':
901 if os.name == 'nt':
902 import msvcrt
902 import msvcrt
903 nulldev = 'NUL:'
903 nulldev = 'NUL:'
904
904
905 class winstdout:
905 class winstdout:
906 '''stdout on windows misbehaves if sent through a pipe'''
906 '''stdout on windows misbehaves if sent through a pipe'''
907
907
908 def __init__(self, fp):
908 def __init__(self, fp):
909 self.fp = fp
909 self.fp = fp
910
910
911 def __getattr__(self, key):
911 def __getattr__(self, key):
912 return getattr(self.fp, key)
912 return getattr(self.fp, key)
913
913
914 def close(self):
914 def close(self):
915 try:
915 try:
916 self.fp.close()
916 self.fp.close()
917 except: pass
917 except: pass
918
918
919 def write(self, s):
919 def write(self, s):
920 try:
920 try:
921 return self.fp.write(s)
921 return self.fp.write(s)
922 except IOError, inst:
922 except IOError, inst:
923 if inst.errno != 0: raise
923 if inst.errno != 0: raise
924 self.close()
924 self.close()
925 raise IOError(errno.EPIPE, 'Broken pipe')
925 raise IOError(errno.EPIPE, 'Broken pipe')
926
926
927 def flush(self):
927 def flush(self):
928 try:
928 try:
929 return self.fp.flush()
929 return self.fp.flush()
930 except IOError, inst:
930 except IOError, inst:
931 if inst.errno != errno.EINVAL: raise
931 if inst.errno != errno.EINVAL: raise
932 self.close()
932 self.close()
933 raise IOError(errno.EPIPE, 'Broken pipe')
933 raise IOError(errno.EPIPE, 'Broken pipe')
934
934
935 sys.stdout = winstdout(sys.stdout)
935 sys.stdout = winstdout(sys.stdout)
936
936
937 def system_rcpath():
937 def system_rcpath():
938 try:
938 try:
939 return system_rcpath_win32()
939 return system_rcpath_win32()
940 except:
940 except:
941 return [r'c:\mercurial\mercurial.ini']
941 return [r'c:\mercurial\mercurial.ini']
942
942
943 def user_rcpath():
943 def user_rcpath():
944 '''return os-specific hgrc search path to the user dir'''
944 '''return os-specific hgrc search path to the user dir'''
945 try:
945 try:
946 userrc = user_rcpath_win32()
946 userrc = user_rcpath_win32()
947 except:
947 except:
948 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
948 userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini')
949 path = [userrc]
949 path = [userrc]
950 userprofile = os.environ.get('USERPROFILE')
950 userprofile = os.environ.get('USERPROFILE')
951 if userprofile:
951 if userprofile:
952 path.append(os.path.join(userprofile, 'mercurial.ini'))
952 path.append(os.path.join(userprofile, 'mercurial.ini'))
953 return path
953 return path
954
954
955 def parse_patch_output(output_line):
955 def parse_patch_output(output_line):
956 """parses the output produced by patch and returns the file name"""
956 """parses the output produced by patch and returns the file name"""
957 pf = output_line[14:]
957 pf = output_line[14:]
958 if pf[0] == '`':
958 if pf[0] == '`':
959 pf = pf[1:-1] # Remove the quotes
959 pf = pf[1:-1] # Remove the quotes
960 return pf
960 return pf
961
961
962 def testpid(pid):
962 def testpid(pid):
963 '''return False if pid dead, True if running or not known'''
963 '''return False if pid dead, True if running or not known'''
964 return True
964 return True
965
965
966 def set_exec(f, mode):
966 def set_exec(f, mode):
967 pass
967 pass
968
968
969 def set_link(f, mode):
969 def set_link(f, mode):
970 pass
970 pass
971
971
972 def set_binary(fd):
972 def set_binary(fd):
973 msvcrt.setmode(fd.fileno(), os.O_BINARY)
973 msvcrt.setmode(fd.fileno(), os.O_BINARY)
974
974
975 def pconvert(path):
975 def pconvert(path):
976 return path.replace("\\", "/")
976 return path.replace("\\", "/")
977
977
978 def localpath(path):
978 def localpath(path):
979 return path.replace('/', '\\')
979 return path.replace('/', '\\')
980
980
981 def normpath(path):
981 def normpath(path):
982 return pconvert(os.path.normpath(path))
982 return pconvert(os.path.normpath(path))
983
983
984 makelock = _makelock_file
984 makelock = _makelock_file
985 readlock = _readlock_file
985 readlock = _readlock_file
986
986
987 def samestat(s1, s2):
987 def samestat(s1, s2):
988 return False
988 return False
989
989
990 # A sequence of backslashes is special iff it precedes a double quote:
990 # A sequence of backslashes is special iff it precedes a double quote:
991 # - if there's an even number of backslashes, the double quote is not
991 # - if there's an even number of backslashes, the double quote is not
992 # quoted (i.e. it ends the quoted region)
992 # quoted (i.e. it ends the quoted region)
993 # - if there's an odd number of backslashes, the double quote is quoted
993 # - if there's an odd number of backslashes, the double quote is quoted
994 # - in both cases, every pair of backslashes is unquoted into a single
994 # - in both cases, every pair of backslashes is unquoted into a single
995 # backslash
995 # backslash
996 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
996 # (See http://msdn2.microsoft.com/en-us/library/a1y7w461.aspx )
997 # So, to quote a string, we must surround it in double quotes, double
997 # So, to quote a string, we must surround it in double quotes, double
998 # the number of backslashes that preceed double quotes and add another
998 # the number of backslashes that preceed double quotes and add another
999 # backslash before every double quote (being careful with the double
999 # backslash before every double quote (being careful with the double
1000 # quote we've appended to the end)
1000 # quote we've appended to the end)
1001 _quotere = None
1001 _quotere = None
1002 def shellquote(s):
1002 def shellquote(s):
1003 global _quotere
1003 global _quotere
1004 if _quotere is None:
1004 if _quotere is None:
1005 _quotere = re.compile(r'(\\*)("|\\$)')
1005 _quotere = re.compile(r'(\\*)("|\\$)')
1006 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1006 return '"%s"' % _quotere.sub(r'\1\1\\\2', s)
1007
1007
1008 def quotecommand(cmd):
1008 def quotecommand(cmd):
1009 """Build a command string suitable for os.popen* calls."""
1009 """Build a command string suitable for os.popen* calls."""
1010 # The extra quotes are needed because popen* runs the command
1010 # The extra quotes are needed because popen* runs the command
1011 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1011 # through the current COMSPEC. cmd.exe suppress enclosing quotes.
1012 return '"' + cmd + '"'
1012 return '"' + cmd + '"'
1013
1013
1014 def popen(command):
1014 def popen(command):
1015 # Work around "popen spawned process may not write to stdout
1015 # Work around "popen spawned process may not write to stdout
1016 # under windows"
1016 # under windows"
1017 # http://bugs.python.org/issue1366
1017 # http://bugs.python.org/issue1366
1018 command += " 2> %s" % nulldev
1018 command += " 2> %s" % nulldev
1019 return os.popen(quotecommand(command))
1019 return os.popen(quotecommand(command))
1020
1020
1021 def explain_exit(code):
1021 def explain_exit(code):
1022 return _("exited with status %d") % code, code
1022 return _("exited with status %d") % code, code
1023
1023
1024 # if you change this stub into a real check, please try to implement the
1024 # if you change this stub into a real check, please try to implement the
1025 # username and groupname functions above, too.
1025 # username and groupname functions above, too.
1026 def isowner(fp, st=None):
1026 def isowner(fp, st=None):
1027 return True
1027 return True
1028
1028
1029 def find_in_path(name, path, default=None):
1029 def find_in_path(name, path, default=None):
1030 '''find name in search path. path can be string (will be split
1030 '''find name in search path. path can be string (will be split
1031 with os.pathsep), or iterable thing that returns strings. if name
1031 with os.pathsep), or iterable thing that returns strings. if name
1032 found, return path to name. else return default. name is looked up
1032 found, return path to name. else return default. name is looked up
1033 using cmd.exe rules, using PATHEXT.'''
1033 using cmd.exe rules, using PATHEXT.'''
1034 if isinstance(path, str):
1034 if isinstance(path, str):
1035 path = path.split(os.pathsep)
1035 path = path.split(os.pathsep)
1036
1036
1037 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1037 pathext = os.environ.get('PATHEXT', '.COM;.EXE;.BAT;.CMD')
1038 pathext = pathext.lower().split(os.pathsep)
1038 pathext = pathext.lower().split(os.pathsep)
1039 isexec = os.path.splitext(name)[1].lower() in pathext
1039 isexec = os.path.splitext(name)[1].lower() in pathext
1040
1040
1041 for p in path:
1041 for p in path:
1042 p_name = os.path.join(p, name)
1042 p_name = os.path.join(p, name)
1043
1043
1044 if isexec and os.path.exists(p_name):
1044 if isexec and os.path.exists(p_name):
1045 return p_name
1045 return p_name
1046
1046
1047 for ext in pathext:
1047 for ext in pathext:
1048 p_name_ext = p_name + ext
1048 p_name_ext = p_name + ext
1049 if os.path.exists(p_name_ext):
1049 if os.path.exists(p_name_ext):
1050 return p_name_ext
1050 return p_name_ext
1051 return default
1051 return default
1052
1052
1053 def set_signal_handler():
1053 def set_signal_handler():
1054 try:
1054 try:
1055 set_signal_handler_win32()
1055 set_signal_handler_win32()
1056 except NameError:
1056 except NameError:
1057 pass
1057 pass
1058
1058
1059 try:
1059 try:
1060 # override functions with win32 versions if possible
1060 # override functions with win32 versions if possible
1061 from util_win32 import *
1061 from util_win32 import *
1062 if not is_win_9x():
1062 if not is_win_9x():
1063 posixfile = posixfile_nt
1063 posixfile = posixfile_nt
1064 except ImportError:
1064 except ImportError:
1065 pass
1065 pass
1066
1066
1067 else:
1067 else:
1068 nulldev = '/dev/null'
1068 nulldev = '/dev/null'
1069
1069
1070 def rcfiles(path):
1070 def rcfiles(path):
1071 rcs = [os.path.join(path, 'hgrc')]
1071 rcs = [os.path.join(path, 'hgrc')]
1072 rcdir = os.path.join(path, 'hgrc.d')
1072 rcdir = os.path.join(path, 'hgrc.d')
1073 try:
1073 try:
1074 rcs.extend([os.path.join(rcdir, f)
1074 rcs.extend([os.path.join(rcdir, f)
1075 for f, kind in osutil.listdir(rcdir)
1075 for f, kind in osutil.listdir(rcdir)
1076 if f.endswith(".rc")])
1076 if f.endswith(".rc")])
1077 except OSError:
1077 except OSError:
1078 pass
1078 pass
1079 return rcs
1079 return rcs
1080
1080
1081 def system_rcpath():
1081 def system_rcpath():
1082 path = []
1082 path = []
1083 # old mod_python does not set sys.argv
1083 # old mod_python does not set sys.argv
1084 if len(getattr(sys, 'argv', [])) > 0:
1084 if len(getattr(sys, 'argv', [])) > 0:
1085 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1085 path.extend(rcfiles(os.path.dirname(sys.argv[0]) +
1086 '/../etc/mercurial'))
1086 '/../etc/mercurial'))
1087 path.extend(rcfiles('/etc/mercurial'))
1087 path.extend(rcfiles('/etc/mercurial'))
1088 return path
1088 return path
1089
1089
1090 def user_rcpath():
1090 def user_rcpath():
1091 return [os.path.expanduser('~/.hgrc')]
1091 return [os.path.expanduser('~/.hgrc')]
1092
1092
1093 def parse_patch_output(output_line):
1093 def parse_patch_output(output_line):
1094 """parses the output produced by patch and returns the file name"""
1094 """parses the output produced by patch and returns the file name"""
1095 pf = output_line[14:]
1095 pf = output_line[14:]
1096 if os.sys.platform == 'OpenVMS':
1096 if os.sys.platform == 'OpenVMS':
1097 if pf[0] == '`':
1097 if pf[0] == '`':
1098 pf = pf[1:-1] # Remove the quotes
1098 pf = pf[1:-1] # Remove the quotes
1099 else:
1099 else:
1100 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1100 if pf.startswith("'") and pf.endswith("'") and " " in pf:
1101 pf = pf[1:-1] # Remove the quotes
1101 pf = pf[1:-1] # Remove the quotes
1102 return pf
1102 return pf
1103
1103
1104 def is_exec(f):
1104 def is_exec(f):
1105 """check whether a file is executable"""
1105 """check whether a file is executable"""
1106 return (os.lstat(f).st_mode & 0100 != 0)
1106 return (os.lstat(f).st_mode & 0100 != 0)
1107
1107
1108 def set_exec(f, mode):
1108 def set_exec(f, mode):
1109 s = os.lstat(f).st_mode
1109 s = os.lstat(f).st_mode
1110 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1110 if stat.S_ISLNK(s) or (s & 0100 != 0) == mode:
1111 return
1111 return
1112 if mode:
1112 if mode:
1113 # Turn on +x for every +r bit when making a file executable
1113 # Turn on +x for every +r bit when making a file executable
1114 # and obey umask.
1114 # and obey umask.
1115 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1115 os.chmod(f, s | (s & 0444) >> 2 & ~_umask)
1116 else:
1116 else:
1117 os.chmod(f, s & 0666)
1117 os.chmod(f, s & 0666)
1118
1118
1119 def set_link(f, mode):
1119 def set_link(f, mode):
1120 """make a file a symbolic link/regular file
1120 """make a file a symbolic link/regular file
1121
1121
1122 if a file is changed to a link, its contents become the link data
1122 if a file is changed to a link, its contents become the link data
1123 if a link is changed to a file, its link data become its contents
1123 if a link is changed to a file, its link data become its contents
1124 """
1124 """
1125
1125
1126 m = os.path.islink(f)
1126 m = os.path.islink(f)
1127 if m == bool(mode):
1127 if m == bool(mode):
1128 return
1128 return
1129
1129
1130 if mode: # switch file to link
1130 if mode: # switch file to link
1131 data = file(f).read()
1131 data = file(f).read()
1132 os.unlink(f)
1132 os.unlink(f)
1133 os.symlink(data, f)
1133 os.symlink(data, f)
1134 else:
1134 else:
1135 data = os.readlink(f)
1135 data = os.readlink(f)
1136 os.unlink(f)
1136 os.unlink(f)
1137 file(f, "w").write(data)
1137 file(f, "w").write(data)
1138
1138
1139 def set_binary(fd):
1139 def set_binary(fd):
1140 pass
1140 pass
1141
1141
1142 def pconvert(path):
1142 def pconvert(path):
1143 return path
1143 return path
1144
1144
1145 def localpath(path):
1145 def localpath(path):
1146 return path
1146 return path
1147
1147
1148 normpath = os.path.normpath
1148 normpath = os.path.normpath
1149 samestat = os.path.samestat
1149 samestat = os.path.samestat
1150
1150
1151 def makelock(info, pathname):
1151 def makelock(info, pathname):
1152 try:
1152 try:
1153 os.symlink(info, pathname)
1153 os.symlink(info, pathname)
1154 except OSError, why:
1154 except OSError, why:
1155 if why.errno == errno.EEXIST:
1155 if why.errno == errno.EEXIST:
1156 raise
1156 raise
1157 else:
1157 else:
1158 _makelock_file(info, pathname)
1158 _makelock_file(info, pathname)
1159
1159
1160 def readlock(pathname):
1160 def readlock(pathname):
1161 try:
1161 try:
1162 return os.readlink(pathname)
1162 return os.readlink(pathname)
1163 except OSError, why:
1163 except OSError, why:
1164 if why.errno in (errno.EINVAL, errno.ENOSYS):
1164 if why.errno in (errno.EINVAL, errno.ENOSYS):
1165 return _readlock_file(pathname)
1165 return _readlock_file(pathname)
1166 else:
1166 else:
1167 raise
1167 raise
1168
1168
1169 def shellquote(s):
1169 def shellquote(s):
1170 if os.sys.platform == 'OpenVMS':
1170 if os.sys.platform == 'OpenVMS':
1171 return '"%s"' % s
1171 return '"%s"' % s
1172 else:
1172 else:
1173 return "'%s'" % s.replace("'", "'\\''")
1173 return "'%s'" % s.replace("'", "'\\''")
1174
1174
1175 def quotecommand(cmd):
1175 def quotecommand(cmd):
1176 return cmd
1176 return cmd
1177
1177
1178 def popen(command):
1178 def popen(command):
1179 return os.popen(command)
1179 return os.popen(command)
1180
1180
1181 def testpid(pid):
1181 def testpid(pid):
1182 '''return False if pid dead, True if running or not sure'''
1182 '''return False if pid dead, True if running or not sure'''
1183 if os.sys.platform == 'OpenVMS':
1183 if os.sys.platform == 'OpenVMS':
1184 return True
1184 return True
1185 try:
1185 try:
1186 os.kill(pid, 0)
1186 os.kill(pid, 0)
1187 return True
1187 return True
1188 except OSError, inst:
1188 except OSError, inst:
1189 return inst.errno != errno.ESRCH
1189 return inst.errno != errno.ESRCH
1190
1190
1191 def explain_exit(code):
1191 def explain_exit(code):
1192 """return a 2-tuple (desc, code) describing a process's status"""
1192 """return a 2-tuple (desc, code) describing a process's status"""
1193 if os.WIFEXITED(code):
1193 if os.WIFEXITED(code):
1194 val = os.WEXITSTATUS(code)
1194 val = os.WEXITSTATUS(code)
1195 return _("exited with status %d") % val, val
1195 return _("exited with status %d") % val, val
1196 elif os.WIFSIGNALED(code):
1196 elif os.WIFSIGNALED(code):
1197 val = os.WTERMSIG(code)
1197 val = os.WTERMSIG(code)
1198 return _("killed by signal %d") % val, val
1198 return _("killed by signal %d") % val, val
1199 elif os.WIFSTOPPED(code):
1199 elif os.WIFSTOPPED(code):
1200 val = os.WSTOPSIG(code)
1200 val = os.WSTOPSIG(code)
1201 return _("stopped by signal %d") % val, val
1201 return _("stopped by signal %d") % val, val
1202 raise ValueError(_("invalid exit code"))
1202 raise ValueError(_("invalid exit code"))
1203
1203
1204 def isowner(fp, st=None):
1204 def isowner(fp, st=None):
1205 """Return True if the file object f belongs to the current user.
1205 """Return True if the file object f belongs to the current user.
1206
1206
1207 The return value of a util.fstat(f) may be passed as the st argument.
1207 The return value of a util.fstat(f) may be passed as the st argument.
1208 """
1208 """
1209 if st is None:
1209 if st is None:
1210 st = fstat(fp)
1210 st = fstat(fp)
1211 return st.st_uid == os.getuid()
1211 return st.st_uid == os.getuid()
1212
1212
1213 def find_in_path(name, path, default=None):
1213 def find_in_path(name, path, default=None):
1214 '''find name in search path. path can be string (will be split
1214 '''find name in search path. path can be string (will be split
1215 with os.pathsep), or iterable thing that returns strings. if name
1215 with os.pathsep), or iterable thing that returns strings. if name
1216 found, return path to name. else return default.'''
1216 found, return path to name. else return default.'''
1217 if isinstance(path, str):
1217 if isinstance(path, str):
1218 path = path.split(os.pathsep)
1218 path = path.split(os.pathsep)
1219 for p in path:
1219 for p in path:
1220 p_name = os.path.join(p, name)
1220 p_name = os.path.join(p, name)
1221 if os.path.exists(p_name):
1221 if os.path.exists(p_name):
1222 return p_name
1222 return p_name
1223 return default
1223 return default
1224
1224
1225 def set_signal_handler():
1225 def set_signal_handler():
1226 pass
1226 pass
1227
1227
1228 def find_exe(name, default=None):
1228 def find_exe(name, default=None):
1229 '''find path of an executable.
1229 '''find path of an executable.
1230 if name contains a path component, return it as is. otherwise,
1230 if name contains a path component, return it as is. otherwise,
1231 use normal executable search path.'''
1231 use normal executable search path.'''
1232
1232
1233 if os.sep in name or sys.platform == 'OpenVMS':
1233 if os.sep in name or sys.platform == 'OpenVMS':
1234 # don't check the executable bit. if the file isn't
1234 # don't check the executable bit. if the file isn't
1235 # executable, whoever tries to actually run it will give a
1235 # executable, whoever tries to actually run it will give a
1236 # much more useful error message.
1236 # much more useful error message.
1237 return name
1237 return name
1238 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1238 return find_in_path(name, os.environ.get('PATH', ''), default=default)
1239
1239
1240 def _buildencodefun():
1240 def _buildencodefun():
1241 e = '_'
1241 e = '_'
1242 win_reserved = [ord(x) for x in '\\:*?"<>|']
1242 win_reserved = [ord(x) for x in '\\:*?"<>|']
1243 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1243 cmap = dict([ (chr(x), chr(x)) for x in xrange(127) ])
1244 for x in (range(32) + range(126, 256) + win_reserved):
1244 for x in (range(32) + range(126, 256) + win_reserved):
1245 cmap[chr(x)] = "~%02x" % x
1245 cmap[chr(x)] = "~%02x" % x
1246 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1246 for x in range(ord("A"), ord("Z")+1) + [ord(e)]:
1247 cmap[chr(x)] = e + chr(x).lower()
1247 cmap[chr(x)] = e + chr(x).lower()
1248 dmap = {}
1248 dmap = {}
1249 for k, v in cmap.iteritems():
1249 for k, v in cmap.iteritems():
1250 dmap[v] = k
1250 dmap[v] = k
1251 def decode(s):
1251 def decode(s):
1252 i = 0
1252 i = 0
1253 while i < len(s):
1253 while i < len(s):
1254 for l in xrange(1, 4):
1254 for l in xrange(1, 4):
1255 try:
1255 try:
1256 yield dmap[s[i:i+l]]
1256 yield dmap[s[i:i+l]]
1257 i += l
1257 i += l
1258 break
1258 break
1259 except KeyError:
1259 except KeyError:
1260 pass
1260 pass
1261 else:
1261 else:
1262 raise KeyError
1262 raise KeyError
1263 return (lambda s: "".join([cmap[c] for c in s]),
1263 return (lambda s: "".join([cmap[c] for c in s]),
1264 lambda s: "".join(list(decode(s))))
1264 lambda s: "".join(list(decode(s))))
1265
1265
1266 encodefilename, decodefilename = _buildencodefun()
1266 encodefilename, decodefilename = _buildencodefun()
1267
1267
1268 def encodedopener(openerfn, fn):
1268 def encodedopener(openerfn, fn):
1269 def o(path, *args, **kw):
1269 def o(path, *args, **kw):
1270 return openerfn(fn(path), *args, **kw)
1270 return openerfn(fn(path), *args, **kw)
1271 return o
1271 return o
1272
1272
1273 def mktempcopy(name, emptyok=False):
1273 def mktempcopy(name, emptyok=False):
1274 """Create a temporary file with the same contents from name
1274 """Create a temporary file with the same contents from name
1275
1275
1276 The permission bits are copied from the original file.
1276 The permission bits are copied from the original file.
1277
1277
1278 If the temporary file is going to be truncated immediately, you
1278 If the temporary file is going to be truncated immediately, you
1279 can use emptyok=True as an optimization.
1279 can use emptyok=True as an optimization.
1280
1280
1281 Returns the name of the temporary file.
1281 Returns the name of the temporary file.
1282 """
1282 """
1283 d, fn = os.path.split(name)
1283 d, fn = os.path.split(name)
1284 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1284 fd, temp = tempfile.mkstemp(prefix='.%s-' % fn, dir=d)
1285 os.close(fd)
1285 os.close(fd)
1286 # Temporary files are created with mode 0600, which is usually not
1286 # Temporary files are created with mode 0600, which is usually not
1287 # what we want. If the original file already exists, just copy
1287 # what we want. If the original file already exists, just copy
1288 # its mode. Otherwise, manually obey umask.
1288 # its mode. Otherwise, manually obey umask.
1289 try:
1289 try:
1290 st_mode = os.lstat(name).st_mode
1290 st_mode = os.lstat(name).st_mode
1291 except OSError, inst:
1291 except OSError, inst:
1292 if inst.errno != errno.ENOENT:
1292 if inst.errno != errno.ENOENT:
1293 raise
1293 raise
1294 st_mode = 0666 & ~_umask
1294 st_mode = 0666 & ~_umask
1295 os.chmod(temp, st_mode)
1295 os.chmod(temp, st_mode)
1296 if emptyok:
1296 if emptyok:
1297 return temp
1297 return temp
1298 try:
1298 try:
1299 try:
1299 try:
1300 ifp = posixfile(name, "rb")
1300 ifp = posixfile(name, "rb")
1301 except IOError, inst:
1301 except IOError, inst:
1302 if inst.errno == errno.ENOENT:
1302 if inst.errno == errno.ENOENT:
1303 return temp
1303 return temp
1304 if not getattr(inst, 'filename', None):
1304 if not getattr(inst, 'filename', None):
1305 inst.filename = name
1305 inst.filename = name
1306 raise
1306 raise
1307 ofp = posixfile(temp, "wb")
1307 ofp = posixfile(temp, "wb")
1308 for chunk in filechunkiter(ifp):
1308 for chunk in filechunkiter(ifp):
1309 ofp.write(chunk)
1309 ofp.write(chunk)
1310 ifp.close()
1310 ifp.close()
1311 ofp.close()
1311 ofp.close()
1312 except:
1312 except:
1313 try: os.unlink(temp)
1313 try: os.unlink(temp)
1314 except: pass
1314 except: pass
1315 raise
1315 raise
1316 return temp
1316 return temp
1317
1317
1318 class atomictempfile(posixfile):
1318 class atomictempfile(posixfile):
1319 """file-like object that atomically updates a file
1319 """file-like object that atomically updates a file
1320
1320
1321 All writes will be redirected to a temporary copy of the original
1321 All writes will be redirected to a temporary copy of the original
1322 file. When rename is called, the copy is renamed to the original
1322 file. When rename is called, the copy is renamed to the original
1323 name, making the changes visible.
1323 name, making the changes visible.
1324 """
1324 """
1325 def __init__(self, name, mode):
1325 def __init__(self, name, mode):
1326 self.__name = name
1326 self.__name = name
1327 self.temp = mktempcopy(name, emptyok=('w' in mode))
1327 self.temp = mktempcopy(name, emptyok=('w' in mode))
1328 posixfile.__init__(self, self.temp, mode)
1328 posixfile.__init__(self, self.temp, mode)
1329
1329
1330 def rename(self):
1330 def rename(self):
1331 if not self.closed:
1331 if not self.closed:
1332 posixfile.close(self)
1332 posixfile.close(self)
1333 rename(self.temp, localpath(self.__name))
1333 rename(self.temp, localpath(self.__name))
1334
1334
1335 def __del__(self):
1335 def __del__(self):
1336 if not self.closed:
1336 if not self.closed:
1337 try:
1337 try:
1338 os.unlink(self.temp)
1338 os.unlink(self.temp)
1339 except: pass
1339 except: pass
1340 posixfile.close(self)
1340 posixfile.close(self)
1341
1341
1342 class opener(object):
1342 class opener(object):
1343 """Open files relative to a base directory
1343 """Open files relative to a base directory
1344
1344
1345 This class is used to hide the details of COW semantics and
1345 This class is used to hide the details of COW semantics and
1346 remote file access from higher level code.
1346 remote file access from higher level code.
1347 """
1347 """
1348 def __init__(self, base, audit=True):
1348 def __init__(self, base, audit=True):
1349 self.base = base
1349 self.base = base
1350 if audit:
1350 if audit:
1351 self.audit_path = path_auditor(base)
1351 self.audit_path = path_auditor(base)
1352 else:
1352 else:
1353 self.audit_path = always
1353 self.audit_path = always
1354
1354
1355 def __getattr__(self, name):
1355 def __getattr__(self, name):
1356 if name == '_can_symlink':
1356 if name == '_can_symlink':
1357 self._can_symlink = checklink(self.base)
1357 self._can_symlink = checklink(self.base)
1358 return self._can_symlink
1358 return self._can_symlink
1359 raise AttributeError(name)
1359 raise AttributeError(name)
1360
1360
1361 def __call__(self, path, mode="r", text=False, atomictemp=False):
1361 def __call__(self, path, mode="r", text=False, atomictemp=False):
1362 self.audit_path(path)
1362 self.audit_path(path)
1363 f = os.path.join(self.base, path)
1363 f = os.path.join(self.base, path)
1364
1364
1365 if not text and "b" not in mode:
1365 if not text and "b" not in mode:
1366 mode += "b" # for that other OS
1366 mode += "b" # for that other OS
1367
1367
1368 if mode[0] != "r":
1368 if mode[0] != "r":
1369 try:
1369 try:
1370 nlink = nlinks(f)
1370 nlink = nlinks(f)
1371 except OSError:
1371 except OSError:
1372 nlink = 0
1372 nlink = 0
1373 d = os.path.dirname(f)
1373 d = os.path.dirname(f)
1374 if not os.path.isdir(d):
1374 if not os.path.isdir(d):
1375 os.makedirs(d)
1375 os.makedirs(d)
1376 if atomictemp:
1376 if atomictemp:
1377 return atomictempfile(f, mode)
1377 return atomictempfile(f, mode)
1378 if nlink > 1:
1378 if nlink > 1:
1379 rename(mktempcopy(f), f)
1379 rename(mktempcopy(f), f)
1380 return posixfile(f, mode)
1380 return posixfile(f, mode)
1381
1381
1382 def symlink(self, src, dst):
1382 def symlink(self, src, dst):
1383 self.audit_path(dst)
1383 self.audit_path(dst)
1384 linkname = os.path.join(self.base, dst)
1384 linkname = os.path.join(self.base, dst)
1385 try:
1385 try:
1386 os.unlink(linkname)
1386 os.unlink(linkname)
1387 except OSError:
1387 except OSError:
1388 pass
1388 pass
1389
1389
1390 dirname = os.path.dirname(linkname)
1390 dirname = os.path.dirname(linkname)
1391 if not os.path.exists(dirname):
1391 if not os.path.exists(dirname):
1392 os.makedirs(dirname)
1392 os.makedirs(dirname)
1393
1393
1394 if self._can_symlink:
1394 if self._can_symlink:
1395 try:
1395 try:
1396 os.symlink(src, linkname)
1396 os.symlink(src, linkname)
1397 except OSError, err:
1397 except OSError, err:
1398 raise OSError(err.errno, _('could not symlink to %r: %s') %
1398 raise OSError(err.errno, _('could not symlink to %r: %s') %
1399 (src, err.strerror), linkname)
1399 (src, err.strerror), linkname)
1400 else:
1400 else:
1401 f = self(dst, "w")
1401 f = self(dst, "w")
1402 f.write(src)
1402 f.write(src)
1403 f.close()
1403 f.close()
1404
1404
1405 class chunkbuffer(object):
1405 class chunkbuffer(object):
1406 """Allow arbitrary sized chunks of data to be efficiently read from an
1406 """Allow arbitrary sized chunks of data to be efficiently read from an
1407 iterator over chunks of arbitrary size."""
1407 iterator over chunks of arbitrary size."""
1408
1408
1409 def __init__(self, in_iter):
1409 def __init__(self, in_iter):
1410 """in_iter is the iterator that's iterating over the input chunks.
1410 """in_iter is the iterator that's iterating over the input chunks.
1411 targetsize is how big a buffer to try to maintain."""
1411 targetsize is how big a buffer to try to maintain."""
1412 self.iter = iter(in_iter)
1412 self.iter = iter(in_iter)
1413 self.buf = ''
1413 self.buf = ''
1414 self.targetsize = 2**16
1414 self.targetsize = 2**16
1415
1415
1416 def read(self, l):
1416 def read(self, l):
1417 """Read L bytes of data from the iterator of chunks of data.
1417 """Read L bytes of data from the iterator of chunks of data.
1418 Returns less than L bytes if the iterator runs dry."""
1418 Returns less than L bytes if the iterator runs dry."""
1419 if l > len(self.buf) and self.iter:
1419 if l > len(self.buf) and self.iter:
1420 # Clamp to a multiple of self.targetsize
1420 # Clamp to a multiple of self.targetsize
1421 targetsize = max(l, self.targetsize)
1421 targetsize = max(l, self.targetsize)
1422 collector = cStringIO.StringIO()
1422 collector = cStringIO.StringIO()
1423 collector.write(self.buf)
1423 collector.write(self.buf)
1424 collected = len(self.buf)
1424 collected = len(self.buf)
1425 for chunk in self.iter:
1425 for chunk in self.iter:
1426 collector.write(chunk)
1426 collector.write(chunk)
1427 collected += len(chunk)
1427 collected += len(chunk)
1428 if collected >= targetsize:
1428 if collected >= targetsize:
1429 break
1429 break
1430 if collected < targetsize:
1430 if collected < targetsize:
1431 self.iter = False
1431 self.iter = False
1432 self.buf = collector.getvalue()
1432 self.buf = collector.getvalue()
1433 if len(self.buf) == l:
1433 if len(self.buf) == l:
1434 s, self.buf = str(self.buf), ''
1434 s, self.buf = str(self.buf), ''
1435 else:
1435 else:
1436 s, self.buf = self.buf[:l], buffer(self.buf, l)
1436 s, self.buf = self.buf[:l], buffer(self.buf, l)
1437 return s
1437 return s
1438
1438
1439 def filechunkiter(f, size=65536, limit=None):
1439 def filechunkiter(f, size=65536, limit=None):
1440 """Create a generator that produces the data in the file size
1440 """Create a generator that produces the data in the file size
1441 (default 65536) bytes at a time, up to optional limit (default is
1441 (default 65536) bytes at a time, up to optional limit (default is
1442 to read all data). Chunks may be less than size bytes if the
1442 to read all data). Chunks may be less than size bytes if the
1443 chunk is the last chunk in the file, or the file is a socket or
1443 chunk is the last chunk in the file, or the file is a socket or
1444 some other type of file that sometimes reads less data than is
1444 some other type of file that sometimes reads less data than is
1445 requested."""
1445 requested."""
1446 assert size >= 0
1446 assert size >= 0
1447 assert limit is None or limit >= 0
1447 assert limit is None or limit >= 0
1448 while True:
1448 while True:
1449 if limit is None: nbytes = size
1449 if limit is None: nbytes = size
1450 else: nbytes = min(limit, size)
1450 else: nbytes = min(limit, size)
1451 s = nbytes and f.read(nbytes)
1451 s = nbytes and f.read(nbytes)
1452 if not s: break
1452 if not s: break
1453 if limit: limit -= len(s)
1453 if limit: limit -= len(s)
1454 yield s
1454 yield s
1455
1455
1456 def makedate():
1456 def makedate():
1457 lt = time.localtime()
1457 lt = time.localtime()
1458 if lt[8] == 1 and time.daylight:
1458 if lt[8] == 1 and time.daylight:
1459 tz = time.altzone
1459 tz = time.altzone
1460 else:
1460 else:
1461 tz = time.timezone
1461 tz = time.timezone
1462 return time.mktime(lt), tz
1462 return time.mktime(lt), tz
1463
1463
1464 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1464 def datestr(date=None, format='%a %b %d %H:%M:%S %Y', timezone=True, timezone_format=" %+03d%02d"):
1465 """represent a (unixtime, offset) tuple as a localized time.
1465 """represent a (unixtime, offset) tuple as a localized time.
1466 unixtime is seconds since the epoch, and offset is the time zone's
1466 unixtime is seconds since the epoch, and offset is the time zone's
1467 number of seconds away from UTC. if timezone is false, do not
1467 number of seconds away from UTC. if timezone is false, do not
1468 append time zone to string."""
1468 append time zone to string."""
1469 t, tz = date or makedate()
1469 t, tz = date or makedate()
1470 s = time.strftime(format, time.gmtime(float(t) - tz))
1470 s = time.strftime(format, time.gmtime(float(t) - tz))
1471 if timezone:
1471 if timezone:
1472 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1472 s += timezone_format % (-tz / 3600, ((-tz % 3600) / 60))
1473 return s
1473 return s
1474
1474
1475 def strdate(string, format, defaults=[]):
1475 def strdate(string, format, defaults=[]):
1476 """parse a localized time string and return a (unixtime, offset) tuple.
1476 """parse a localized time string and return a (unixtime, offset) tuple.
1477 if the string cannot be parsed, ValueError is raised."""
1477 if the string cannot be parsed, ValueError is raised."""
1478 def timezone(string):
1478 def timezone(string):
1479 tz = string.split()[-1]
1479 tz = string.split()[-1]
1480 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1480 if tz[0] in "+-" and len(tz) == 5 and tz[1:].isdigit():
1481 tz = int(tz)
1481 tz = int(tz)
1482 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1482 offset = - 3600 * (tz / 100) - 60 * (tz % 100)
1483 return offset
1483 return offset
1484 if tz == "GMT" or tz == "UTC":
1484 if tz == "GMT" or tz == "UTC":
1485 return 0
1485 return 0
1486 return None
1486 return None
1487
1487
1488 # NOTE: unixtime = localunixtime + offset
1488 # NOTE: unixtime = localunixtime + offset
1489 offset, date = timezone(string), string
1489 offset, date = timezone(string), string
1490 if offset != None:
1490 if offset != None:
1491 date = " ".join(string.split()[:-1])
1491 date = " ".join(string.split()[:-1])
1492
1492
1493 # add missing elements from defaults
1493 # add missing elements from defaults
1494 for part in defaults:
1494 for part in defaults:
1495 found = [True for p in part if ("%"+p) in format]
1495 found = [True for p in part if ("%"+p) in format]
1496 if not found:
1496 if not found:
1497 date += "@" + defaults[part]
1497 date += "@" + defaults[part]
1498 format += "@%" + part[0]
1498 format += "@%" + part[0]
1499
1499
1500 timetuple = time.strptime(date, format)
1500 timetuple = time.strptime(date, format)
1501 localunixtime = int(calendar.timegm(timetuple))
1501 localunixtime = int(calendar.timegm(timetuple))
1502 if offset is None:
1502 if offset is None:
1503 # local timezone
1503 # local timezone
1504 unixtime = int(time.mktime(timetuple))
1504 unixtime = int(time.mktime(timetuple))
1505 offset = unixtime - localunixtime
1505 offset = unixtime - localunixtime
1506 else:
1506 else:
1507 unixtime = localunixtime + offset
1507 unixtime = localunixtime + offset
1508 return unixtime, offset
1508 return unixtime, offset
1509
1509
1510 def parsedate(string, formats=None, defaults=None):
1510 def parsedate(string, formats=None, defaults=None):
1511 """parse a localized time string and return a (unixtime, offset) tuple.
1511 """parse a localized time string and return a (unixtime, offset) tuple.
1512 The date may be a "unixtime offset" string or in one of the specified
1512 The date may be a "unixtime offset" string or in one of the specified
1513 formats."""
1513 formats."""
1514 if not string:
1514 if not string:
1515 return 0, 0
1515 return 0, 0
1516 if not formats:
1516 if not formats:
1517 formats = defaultdateformats
1517 formats = defaultdateformats
1518 string = string.strip()
1518 string = string.strip()
1519 try:
1519 try:
1520 when, offset = map(int, string.split(' '))
1520 when, offset = map(int, string.split(' '))
1521 except ValueError:
1521 except ValueError:
1522 # fill out defaults
1522 # fill out defaults
1523 if not defaults:
1523 if not defaults:
1524 defaults = {}
1524 defaults = {}
1525 now = makedate()
1525 now = makedate()
1526 for part in "d mb yY HI M S".split():
1526 for part in "d mb yY HI M S".split():
1527 if part not in defaults:
1527 if part not in defaults:
1528 if part[0] in "HMS":
1528 if part[0] in "HMS":
1529 defaults[part] = "00"
1529 defaults[part] = "00"
1530 elif part[0] in "dm":
1530 elif part[0] in "dm":
1531 defaults[part] = "1"
1531 defaults[part] = "1"
1532 else:
1532 else:
1533 defaults[part] = datestr(now, "%" + part[0], False)
1533 defaults[part] = datestr(now, "%" + part[0], False)
1534
1534
1535 for format in formats:
1535 for format in formats:
1536 try:
1536 try:
1537 when, offset = strdate(string, format, defaults)
1537 when, offset = strdate(string, format, defaults)
1538 except ValueError:
1538 except ValueError:
1539 pass
1539 pass
1540 else:
1540 else:
1541 break
1541 break
1542 else:
1542 else:
1543 raise Abort(_('invalid date: %r ') % string)
1543 raise Abort(_('invalid date: %r ') % string)
1544 # validate explicit (probably user-specified) date and
1544 # validate explicit (probably user-specified) date and
1545 # time zone offset. values must fit in signed 32 bits for
1545 # time zone offset. values must fit in signed 32 bits for
1546 # current 32-bit linux runtimes. timezones go from UTC-12
1546 # current 32-bit linux runtimes. timezones go from UTC-12
1547 # to UTC+14
1547 # to UTC+14
1548 if abs(when) > 0x7fffffff:
1548 if abs(when) > 0x7fffffff:
1549 raise Abort(_('date exceeds 32 bits: %d') % when)
1549 raise Abort(_('date exceeds 32 bits: %d') % when)
1550 if offset < -50400 or offset > 43200:
1550 if offset < -50400 or offset > 43200:
1551 raise Abort(_('impossible time zone offset: %d') % offset)
1551 raise Abort(_('impossible time zone offset: %d') % offset)
1552 return when, offset
1552 return when, offset
1553
1553
1554 def matchdate(date):
1554 def matchdate(date):
1555 """Return a function that matches a given date match specifier
1555 """Return a function that matches a given date match specifier
1556
1556
1557 Formats include:
1557 Formats include:
1558
1558
1559 '{date}' match a given date to the accuracy provided
1559 '{date}' match a given date to the accuracy provided
1560
1560
1561 '<{date}' on or before a given date
1561 '<{date}' on or before a given date
1562
1562
1563 '>{date}' on or after a given date
1563 '>{date}' on or after a given date
1564
1564
1565 """
1565 """
1566
1566
1567 def lower(date):
1567 def lower(date):
1568 return parsedate(date, extendeddateformats)[0]
1568 return parsedate(date, extendeddateformats)[0]
1569
1569
1570 def upper(date):
1570 def upper(date):
1571 d = dict(mb="12", HI="23", M="59", S="59")
1571 d = dict(mb="12", HI="23", M="59", S="59")
1572 for days in "31 30 29".split():
1572 for days in "31 30 29".split():
1573 try:
1573 try:
1574 d["d"] = days
1574 d["d"] = days
1575 return parsedate(date, extendeddateformats, d)[0]
1575 return parsedate(date, extendeddateformats, d)[0]
1576 except:
1576 except:
1577 pass
1577 pass
1578 d["d"] = "28"
1578 d["d"] = "28"
1579 return parsedate(date, extendeddateformats, d)[0]
1579 return parsedate(date, extendeddateformats, d)[0]
1580
1580
1581 if date[0] == "<":
1581 if date[0] == "<":
1582 when = upper(date[1:])
1582 when = upper(date[1:])
1583 return lambda x: x <= when
1583 return lambda x: x <= when
1584 elif date[0] == ">":
1584 elif date[0] == ">":
1585 when = lower(date[1:])
1585 when = lower(date[1:])
1586 return lambda x: x >= when
1586 return lambda x: x >= when
1587 elif date[0] == "-":
1587 elif date[0] == "-":
1588 try:
1588 try:
1589 days = int(date[1:])
1589 days = int(date[1:])
1590 except ValueError:
1590 except ValueError:
1591 raise Abort(_("invalid day spec: %s") % date[1:])
1591 raise Abort(_("invalid day spec: %s") % date[1:])
1592 when = makedate()[0] - days * 3600 * 24
1592 when = makedate()[0] - days * 3600 * 24
1593 return lambda x: x >= when
1593 return lambda x: x >= when
1594 elif " to " in date:
1594 elif " to " in date:
1595 a, b = date.split(" to ")
1595 a, b = date.split(" to ")
1596 start, stop = lower(a), upper(b)
1596 start, stop = lower(a), upper(b)
1597 return lambda x: x >= start and x <= stop
1597 return lambda x: x >= start and x <= stop
1598 else:
1598 else:
1599 start, stop = lower(date), upper(date)
1599 start, stop = lower(date), upper(date)
1600 return lambda x: x >= start and x <= stop
1600 return lambda x: x >= start and x <= stop
1601
1601
1602 def shortuser(user):
1602 def shortuser(user):
1603 """Return a short representation of a user name or email address."""
1603 """Return a short representation of a user name or email address."""
1604 f = user.find('@')
1604 f = user.find('@')
1605 if f >= 0:
1605 if f >= 0:
1606 user = user[:f]
1606 user = user[:f]
1607 f = user.find('<')
1607 f = user.find('<')
1608 if f >= 0:
1608 if f >= 0:
1609 user = user[f+1:]
1609 user = user[f+1:]
1610 f = user.find(' ')
1610 f = user.find(' ')
1611 if f >= 0:
1611 if f >= 0:
1612 user = user[:f]
1612 user = user[:f]
1613 f = user.find('.')
1613 f = user.find('.')
1614 if f >= 0:
1614 if f >= 0:
1615 user = user[:f]
1615 user = user[:f]
1616 return user
1616 return user
1617
1617
1618 def ellipsis(text, maxlength=400):
1618 def ellipsis(text, maxlength=400):
1619 """Trim string to at most maxlength (default: 400) characters."""
1619 """Trim string to at most maxlength (default: 400) characters."""
1620 if len(text) <= maxlength:
1620 if len(text) <= maxlength:
1621 return text
1621 return text
1622 else:
1622 else:
1623 return "%s..." % (text[:maxlength-3])
1623 return "%s..." % (text[:maxlength-3])
1624
1624
1625 def walkrepos(path):
1625 def walkrepos(path):
1626 '''yield every hg repository under path, recursively.'''
1626 '''yield every hg repository under path, recursively.'''
1627 def errhandler(err):
1627 def errhandler(err):
1628 if err.filename == path:
1628 if err.filename == path:
1629 raise err
1629 raise err
1630
1630
1631 for root, dirs, files in os.walk(path, onerror=errhandler):
1631 for root, dirs, files in os.walk(path, onerror=errhandler):
1632 for d in dirs:
1632 for d in dirs:
1633 if d == '.hg':
1633 if d == '.hg':
1634 yield root
1634 yield root
1635 dirs[:] = []
1635 dirs[:] = []
1636 break
1636 break
1637
1637
1638 _rcpath = None
1638 _rcpath = None
1639
1639
1640 def os_rcpath():
1640 def os_rcpath():
1641 '''return default os-specific hgrc search path'''
1641 '''return default os-specific hgrc search path'''
1642 path = system_rcpath()
1642 path = system_rcpath()
1643 path.extend(user_rcpath())
1643 path.extend(user_rcpath())
1644 path = [os.path.normpath(f) for f in path]
1644 path = [os.path.normpath(f) for f in path]
1645 return path
1645 return path
1646
1646
1647 def rcpath():
1647 def rcpath():
1648 '''return hgrc search path. if env var HGRCPATH is set, use it.
1648 '''return hgrc search path. if env var HGRCPATH is set, use it.
1649 for each item in path, if directory, use files ending in .rc,
1649 for each item in path, if directory, use files ending in .rc,
1650 else use item.
1650 else use item.
1651 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1651 make HGRCPATH empty to only look in .hg/hgrc of current repo.
1652 if no HGRCPATH, use default os-specific path.'''
1652 if no HGRCPATH, use default os-specific path.'''
1653 global _rcpath
1653 global _rcpath
1654 if _rcpath is None:
1654 if _rcpath is None:
1655 if 'HGRCPATH' in os.environ:
1655 if 'HGRCPATH' in os.environ:
1656 _rcpath = []
1656 _rcpath = []
1657 for p in os.environ['HGRCPATH'].split(os.pathsep):
1657 for p in os.environ['HGRCPATH'].split(os.pathsep):
1658 if not p: continue
1658 if not p: continue
1659 if os.path.isdir(p):
1659 if os.path.isdir(p):
1660 for f, kind in osutil.listdir(p):
1660 for f, kind in osutil.listdir(p):
1661 if f.endswith('.rc'):
1661 if f.endswith('.rc'):
1662 _rcpath.append(os.path.join(p, f))
1662 _rcpath.append(os.path.join(p, f))
1663 else:
1663 else:
1664 _rcpath.append(p)
1664 _rcpath.append(p)
1665 else:
1665 else:
1666 _rcpath = os_rcpath()
1666 _rcpath = os_rcpath()
1667 return _rcpath
1667 return _rcpath
1668
1668
1669 def bytecount(nbytes):
1669 def bytecount(nbytes):
1670 '''return byte count formatted as readable string, with units'''
1670 '''return byte count formatted as readable string, with units'''
1671
1671
1672 units = (
1672 units = (
1673 (100, 1<<30, _('%.0f GB')),
1673 (100, 1<<30, _('%.0f GB')),
1674 (10, 1<<30, _('%.1f GB')),
1674 (10, 1<<30, _('%.1f GB')),
1675 (1, 1<<30, _('%.2f GB')),
1675 (1, 1<<30, _('%.2f GB')),
1676 (100, 1<<20, _('%.0f MB')),
1676 (100, 1<<20, _('%.0f MB')),
1677 (10, 1<<20, _('%.1f MB')),
1677 (10, 1<<20, _('%.1f MB')),
1678 (1, 1<<20, _('%.2f MB')),
1678 (1, 1<<20, _('%.2f MB')),
1679 (100, 1<<10, _('%.0f KB')),
1679 (100, 1<<10, _('%.0f KB')),
1680 (10, 1<<10, _('%.1f KB')),
1680 (10, 1<<10, _('%.1f KB')),
1681 (1, 1<<10, _('%.2f KB')),
1681 (1, 1<<10, _('%.2f KB')),
1682 (1, 1, _('%.0f bytes')),
1682 (1, 1, _('%.0f bytes')),
1683 )
1683 )
1684
1684
1685 for multiplier, divisor, format in units:
1685 for multiplier, divisor, format in units:
1686 if nbytes >= divisor * multiplier:
1686 if nbytes >= divisor * multiplier:
1687 return format % (nbytes / float(divisor))
1687 return format % (nbytes / float(divisor))
1688 return units[-1][2] % nbytes
1688 return units[-1][2] % nbytes
1689
1689
1690 def drop_scheme(scheme, path):
1690 def drop_scheme(scheme, path):
1691 sc = scheme + ':'
1691 sc = scheme + ':'
1692 if path.startswith(sc):
1692 if path.startswith(sc):
1693 path = path[len(sc):]
1693 path = path[len(sc):]
1694 if path.startswith('//'):
1694 if path.startswith('//'):
1695 path = path[2:]
1695 path = path[2:]
1696 return path
1696 return path
1697
1697
1698 def uirepr(s):
1698 def uirepr(s):
1699 # Avoid double backslash in Windows path repr()
1699 # Avoid double backslash in Windows path repr()
1700 return repr(s).replace('\\\\', '\\')
1700 return repr(s).replace('\\\\', '\\')
General Comments 0
You need to be logged in to leave comments. Login now