##// END OF EJS Templates
ui: print_exc() -> traceback()
Matt Mackall -
r8206:cce63ef1 default
parent child Browse files
Show More
@@ -1,333 +1,333
1 1 # hg backend for convert extension
2 2
3 3 # Notes for hg->hg conversion:
4 4 #
5 5 # * Old versions of Mercurial didn't trim the whitespace from the ends
6 6 # of commit messages, but new versions do. Changesets created by
7 7 # those older versions, then converted, may thus have different
8 8 # hashes for changesets that are otherwise identical.
9 9 #
10 10 # * By default, the source revision is stored in the converted
11 11 # revision. This will cause the converted revision to have a
12 12 # different identity than the source. To avoid this, use the
13 13 # following option: "--config convert.hg.saverev=false"
14 14
15 15
16 16 import os, time
17 17 from mercurial.i18n import _
18 18 from mercurial.node import bin, hex, nullid
19 19 from mercurial import hg, util, context, error
20 20
21 21 from common import NoRepo, commit, converter_source, converter_sink
22 22
23 23 class mercurial_sink(converter_sink):
24 24 def __init__(self, ui, path):
25 25 converter_sink.__init__(self, ui, path)
26 26 self.branchnames = ui.configbool('convert', 'hg.usebranchnames', True)
27 27 self.clonebranches = ui.configbool('convert', 'hg.clonebranches', False)
28 28 self.tagsbranch = ui.config('convert', 'hg.tagsbranch', 'default')
29 29 self.lastbranch = None
30 30 if os.path.isdir(path) and len(os.listdir(path)) > 0:
31 31 try:
32 32 self.repo = hg.repository(self.ui, path)
33 33 if not self.repo.local():
34 34 raise NoRepo(_('%s is not a local Mercurial repo') % path)
35 35 except error.RepoError, err:
36 ui.print_exc()
36 ui.traceback()
37 37 raise NoRepo(err.args[0])
38 38 else:
39 39 try:
40 40 ui.status(_('initializing destination %s repository\n') % path)
41 41 self.repo = hg.repository(self.ui, path, create=True)
42 42 if not self.repo.local():
43 43 raise NoRepo(_('%s is not a local Mercurial repo') % path)
44 44 self.created.append(path)
45 45 except error.RepoError:
46 ui.print_exc()
46 ui.traceback()
47 47 raise NoRepo("could not create hg repo %s as sink" % path)
48 48 self.lock = None
49 49 self.wlock = None
50 50 self.filemapmode = False
51 51
52 52 def before(self):
53 53 self.ui.debug(_('run hg sink pre-conversion action\n'))
54 54 self.wlock = self.repo.wlock()
55 55 self.lock = self.repo.lock()
56 56
57 57 def after(self):
58 58 self.ui.debug(_('run hg sink post-conversion action\n'))
59 59 self.lock.release()
60 60 self.wlock.release()
61 61
62 62 def revmapfile(self):
63 63 return os.path.join(self.path, ".hg", "shamap")
64 64
65 65 def authorfile(self):
66 66 return os.path.join(self.path, ".hg", "authormap")
67 67
68 68 def getheads(self):
69 69 h = self.repo.changelog.heads()
70 70 return [ hex(x) for x in h ]
71 71
72 72 def setbranch(self, branch, pbranches):
73 73 if not self.clonebranches:
74 74 return
75 75
76 76 setbranch = (branch != self.lastbranch)
77 77 self.lastbranch = branch
78 78 if not branch:
79 79 branch = 'default'
80 80 pbranches = [(b[0], b[1] and b[1] or 'default') for b in pbranches]
81 81 pbranch = pbranches and pbranches[0][1] or 'default'
82 82
83 83 branchpath = os.path.join(self.path, branch)
84 84 if setbranch:
85 85 self.after()
86 86 try:
87 87 self.repo = hg.repository(self.ui, branchpath)
88 88 except:
89 89 self.repo = hg.repository(self.ui, branchpath, create=True)
90 90 self.before()
91 91
92 92 # pbranches may bring revisions from other branches (merge parents)
93 93 # Make sure we have them, or pull them.
94 94 missings = {}
95 95 for b in pbranches:
96 96 try:
97 97 self.repo.lookup(b[0])
98 98 except:
99 99 missings.setdefault(b[1], []).append(b[0])
100 100
101 101 if missings:
102 102 self.after()
103 103 for pbranch, heads in missings.iteritems():
104 104 pbranchpath = os.path.join(self.path, pbranch)
105 105 prepo = hg.repository(self.ui, pbranchpath)
106 106 self.ui.note(_('pulling from %s into %s\n') % (pbranch, branch))
107 107 self.repo.pull(prepo, [prepo.lookup(h) for h in heads])
108 108 self.before()
109 109
110 110 def putcommit(self, files, copies, parents, commit, source):
111 111
112 112 files = dict(files)
113 113 def getfilectx(repo, memctx, f):
114 114 v = files[f]
115 115 data = source.getfile(f, v)
116 116 e = source.getmode(f, v)
117 117 return context.memfilectx(f, data, 'l' in e, 'x' in e, copies.get(f))
118 118
119 119 pl = []
120 120 for p in parents:
121 121 if p not in pl:
122 122 pl.append(p)
123 123 parents = pl
124 124 nparents = len(parents)
125 125 if self.filemapmode and nparents == 1:
126 126 m1node = self.repo.changelog.read(bin(parents[0]))[0]
127 127 parent = parents[0]
128 128
129 129 if len(parents) < 2: parents.append("0" * 40)
130 130 if len(parents) < 2: parents.append("0" * 40)
131 131 p2 = parents.pop(0)
132 132
133 133 text = commit.desc
134 134 extra = commit.extra.copy()
135 135 if self.branchnames and commit.branch:
136 136 extra['branch'] = commit.branch
137 137 if commit.rev:
138 138 extra['convert_revision'] = commit.rev
139 139
140 140 while parents:
141 141 p1 = p2
142 142 p2 = parents.pop(0)
143 143 ctx = context.memctx(self.repo, (p1, p2), text, files.keys(), getfilectx,
144 144 commit.author, commit.date, extra)
145 145 self.repo.commitctx(ctx)
146 146 text = "(octopus merge fixup)\n"
147 147 p2 = hex(self.repo.changelog.tip())
148 148
149 149 if self.filemapmode and nparents == 1:
150 150 man = self.repo.manifest
151 151 mnode = self.repo.changelog.read(bin(p2))[0]
152 152 if not man.cmp(m1node, man.revision(mnode)):
153 153 self.repo.rollback()
154 154 return parent
155 155 return p2
156 156
157 157 def puttags(self, tags):
158 158 try:
159 159 parentctx = self.repo[self.tagsbranch]
160 160 tagparent = parentctx.node()
161 161 except error.RepoError:
162 162 parentctx = None
163 163 tagparent = nullid
164 164
165 165 try:
166 166 oldlines = util.sort(parentctx['.hgtags'].data().splitlines(1))
167 167 except:
168 168 oldlines = []
169 169
170 170 newlines = util.sort([("%s %s\n" % (tags[tag], tag)) for tag in tags])
171 171 if newlines == oldlines:
172 172 return None
173 173 data = "".join(newlines)
174 174 def getfilectx(repo, memctx, f):
175 175 return context.memfilectx(f, data, False, False, None)
176 176
177 177 self.ui.status(_("updating tags\n"))
178 178 date = "%s 0" % int(time.mktime(time.gmtime()))
179 179 extra = {'branch': self.tagsbranch}
180 180 ctx = context.memctx(self.repo, (tagparent, None), "update tags",
181 181 [".hgtags"], getfilectx, "convert-repo", date,
182 182 extra)
183 183 self.repo.commitctx(ctx)
184 184 return hex(self.repo.changelog.tip())
185 185
186 186 def setfilemapmode(self, active):
187 187 self.filemapmode = active
188 188
189 189 class mercurial_source(converter_source):
190 190 def __init__(self, ui, path, rev=None):
191 191 converter_source.__init__(self, ui, path, rev)
192 192 self.ignoreerrors = ui.configbool('convert', 'hg.ignoreerrors', False)
193 193 self.ignored = {}
194 194 self.saverev = ui.configbool('convert', 'hg.saverev', False)
195 195 try:
196 196 self.repo = hg.repository(self.ui, path)
197 197 # try to provoke an exception if this isn't really a hg
198 198 # repo, but some other bogus compatible-looking url
199 199 if not self.repo.local():
200 200 raise error.RepoError()
201 201 except error.RepoError:
202 ui.print_exc()
202 ui.traceback()
203 203 raise NoRepo("%s is not a local Mercurial repo" % path)
204 204 self.lastrev = None
205 205 self.lastctx = None
206 206 self._changescache = None
207 207 self.convertfp = None
208 208 # Restrict converted revisions to startrev descendants
209 209 startnode = ui.config('convert', 'hg.startrev')
210 210 if startnode is not None:
211 211 try:
212 212 startnode = self.repo.lookup(startnode)
213 213 except error.RepoError:
214 214 raise util.Abort(_('%s is not a valid start revision')
215 215 % startnode)
216 216 startrev = self.repo.changelog.rev(startnode)
217 217 children = {startnode: 1}
218 218 for rev in self.repo.changelog.descendants(startrev):
219 219 children[self.repo.changelog.node(rev)] = 1
220 220 self.keep = children.__contains__
221 221 else:
222 222 self.keep = util.always
223 223
224 224 def changectx(self, rev):
225 225 if self.lastrev != rev:
226 226 self.lastctx = self.repo[rev]
227 227 self.lastrev = rev
228 228 return self.lastctx
229 229
230 230 def parents(self, ctx):
231 231 return [p.node() for p in ctx.parents()
232 232 if p and self.keep(p.node())]
233 233
234 234 def getheads(self):
235 235 if self.rev:
236 236 heads = [self.repo[self.rev].node()]
237 237 else:
238 238 heads = self.repo.heads()
239 239 return [hex(h) for h in heads if self.keep(h)]
240 240
241 241 def getfile(self, name, rev):
242 242 try:
243 243 return self.changectx(rev)[name].data()
244 244 except error.LookupError, err:
245 245 raise IOError(err)
246 246
247 247 def getmode(self, name, rev):
248 248 return self.changectx(rev).manifest().flags(name)
249 249
250 250 def getchanges(self, rev):
251 251 ctx = self.changectx(rev)
252 252 parents = self.parents(ctx)
253 253 if not parents:
254 254 files = util.sort(ctx.manifest().keys())
255 255 if self.ignoreerrors:
256 256 # calling getcopies() is a simple way to detect missing
257 257 # revlogs and populate self.ignored
258 258 self.getcopies(ctx, files)
259 259 return [(f, rev) for f in files if f not in self.ignored], {}
260 260 if self._changescache and self._changescache[0] == rev:
261 261 m, a, r = self._changescache[1]
262 262 else:
263 263 m, a, r = self.repo.status(parents[0], ctx.node())[:3]
264 264 # getcopies() detects missing revlogs early, run it before
265 265 # filtering the changes.
266 266 copies = self.getcopies(ctx, m + a)
267 267 changes = [(name, rev) for name in m + a + r
268 268 if name not in self.ignored]
269 269 return util.sort(changes), copies
270 270
271 271 def getcopies(self, ctx, files):
272 272 copies = {}
273 273 for name in files:
274 274 if name in self.ignored:
275 275 continue
276 276 try:
277 277 copysource, copynode = ctx.filectx(name).renamed()
278 278 if copysource in self.ignored or not self.keep(copynode):
279 279 continue
280 280 copies[name] = copysource
281 281 except TypeError:
282 282 pass
283 283 except error.LookupError, e:
284 284 if not self.ignoreerrors:
285 285 raise
286 286 self.ignored[name] = 1
287 287 self.ui.warn(_('ignoring: %s\n') % e)
288 288 return copies
289 289
290 290 def getcommit(self, rev):
291 291 ctx = self.changectx(rev)
292 292 parents = [hex(p) for p in self.parents(ctx)]
293 293 if self.saverev:
294 294 crev = rev
295 295 else:
296 296 crev = None
297 297 return commit(author=ctx.user(), date=util.datestr(ctx.date()),
298 298 desc=ctx.description(), rev=crev, parents=parents,
299 299 branch=ctx.branch(), extra=ctx.extra())
300 300
301 301 def gettags(self):
302 302 tags = [t for t in self.repo.tagslist() if t[0] != 'tip']
303 303 return dict([(name, hex(node)) for name, node in tags
304 304 if self.keep(node)])
305 305
306 306 def getchangedfiles(self, rev, i):
307 307 ctx = self.changectx(rev)
308 308 parents = self.parents(ctx)
309 309 if not parents and i is None:
310 310 i = 0
311 311 changes = [], ctx.manifest().keys(), []
312 312 else:
313 313 i = i or 0
314 314 changes = self.repo.status(parents[i], ctx.node())[:3]
315 315 changes = [[f for f in l if f not in self.ignored] for l in changes]
316 316
317 317 if i == 0:
318 318 self._changescache = (rev, changes)
319 319
320 320 return changes[0] + changes[1] + changes[2]
321 321
322 322 def converted(self, rev, destrev):
323 323 if self.convertfp is None:
324 324 self.convertfp = open(os.path.join(self.path, '.hg', 'shamap'),
325 325 'a')
326 326 self.convertfp.write('%s %s\n' % (destrev, rev))
327 327 self.convertfp.flush()
328 328
329 329 def before(self):
330 330 self.ui.debug(_('run hg source pre-conversion action\n'))
331 331
332 332 def after(self):
333 333 self.ui.debug(_('run hg source post-conversion action\n'))
@@ -1,1205 +1,1205
1 1 # Subversion 1.4/1.5 Python API backend
2 2 #
3 3 # Copyright(C) 2007 Daniel Holth et al
4 4 #
5 5 # Configuration options:
6 6 #
7 7 # convert.svn.trunk
8 8 # Relative path to the trunk (default: "trunk")
9 9 # convert.svn.branches
10 10 # Relative path to tree of branches (default: "branches")
11 11 # convert.svn.tags
12 12 # Relative path to tree of tags (default: "tags")
13 13 #
14 14 # Set these in a hgrc, or on the command line as follows:
15 15 #
16 16 # hg convert --config convert.svn.trunk=wackoname [...]
17 17
18 18 import locale
19 19 import os
20 20 import re
21 21 import sys
22 22 import cPickle as pickle
23 23 import tempfile
24 24 import urllib
25 25
26 26 from mercurial import strutil, util
27 27 from mercurial.i18n import _
28 28
29 29 # Subversion stuff. Works best with very recent Python SVN bindings
30 30 # e.g. SVN 1.5 or backports. Thanks to the bzr folks for enhancing
31 31 # these bindings.
32 32
33 33 from cStringIO import StringIO
34 34
35 35 from common import NoRepo, MissingTool, commit, encodeargs, decodeargs
36 36 from common import commandline, converter_source, converter_sink, mapfile
37 37
38 38 try:
39 39 from svn.core import SubversionException, Pool
40 40 import svn
41 41 import svn.client
42 42 import svn.core
43 43 import svn.ra
44 44 import svn.delta
45 45 import transport
46 46 except ImportError:
47 47 pass
48 48
49 49 class SvnPathNotFound(Exception):
50 50 pass
51 51
52 52 def geturl(path):
53 53 try:
54 54 return svn.client.url_from_path(svn.core.svn_path_canonicalize(path))
55 55 except SubversionException:
56 56 pass
57 57 if os.path.isdir(path):
58 58 path = os.path.normpath(os.path.abspath(path))
59 59 if os.name == 'nt':
60 60 path = '/' + util.normpath(path)
61 61 return 'file://%s' % urllib.quote(path)
62 62 return path
63 63
64 64 def optrev(number):
65 65 optrev = svn.core.svn_opt_revision_t()
66 66 optrev.kind = svn.core.svn_opt_revision_number
67 67 optrev.value.number = number
68 68 return optrev
69 69
70 70 class changedpath(object):
71 71 def __init__(self, p):
72 72 self.copyfrom_path = p.copyfrom_path
73 73 self.copyfrom_rev = p.copyfrom_rev
74 74 self.action = p.action
75 75
76 76 def get_log_child(fp, url, paths, start, end, limit=0, discover_changed_paths=True,
77 77 strict_node_history=False):
78 78 protocol = -1
79 79 def receiver(orig_paths, revnum, author, date, message, pool):
80 80 if orig_paths is not None:
81 81 for k, v in orig_paths.iteritems():
82 82 orig_paths[k] = changedpath(v)
83 83 pickle.dump((orig_paths, revnum, author, date, message),
84 84 fp, protocol)
85 85
86 86 try:
87 87 # Use an ra of our own so that our parent can consume
88 88 # our results without confusing the server.
89 89 t = transport.SvnRaTransport(url=url)
90 90 svn.ra.get_log(t.ra, paths, start, end, limit,
91 91 discover_changed_paths,
92 92 strict_node_history,
93 93 receiver)
94 94 except SubversionException, (inst, num):
95 95 pickle.dump(num, fp, protocol)
96 96 except IOError:
97 97 # Caller may interrupt the iteration
98 98 pickle.dump(None, fp, protocol)
99 99 else:
100 100 pickle.dump(None, fp, protocol)
101 101 fp.close()
102 102 # With large history, cleanup process goes crazy and suddenly
103 103 # consumes *huge* amount of memory. The output file being closed,
104 104 # there is no need for clean termination.
105 105 os._exit(0)
106 106
107 107 def debugsvnlog(ui, **opts):
108 108 """Fetch SVN log in a subprocess and channel them back to parent to
109 109 avoid memory collection issues.
110 110 """
111 111 util.set_binary(sys.stdin)
112 112 util.set_binary(sys.stdout)
113 113 args = decodeargs(sys.stdin.read())
114 114 get_log_child(sys.stdout, *args)
115 115
116 116 class logstream:
117 117 """Interruptible revision log iterator."""
118 118 def __init__(self, stdout):
119 119 self._stdout = stdout
120 120
121 121 def __iter__(self):
122 122 while True:
123 123 entry = pickle.load(self._stdout)
124 124 try:
125 125 orig_paths, revnum, author, date, message = entry
126 126 except:
127 127 if entry is None:
128 128 break
129 129 raise SubversionException("child raised exception", entry)
130 130 yield entry
131 131
132 132 def close(self):
133 133 if self._stdout:
134 134 self._stdout.close()
135 135 self._stdout = None
136 136
137 137
138 138 # Check to see if the given path is a local Subversion repo. Verify this by
139 139 # looking for several svn-specific files and directories in the given
140 140 # directory.
141 141 def filecheck(path, proto):
142 142 for x in ('locks', 'hooks', 'format', 'db', ):
143 143 if not os.path.exists(os.path.join(path, x)):
144 144 return False
145 145 return True
146 146
147 147 # Check to see if a given path is the root of an svn repo over http. We verify
148 148 # this by requesting a version-controlled URL we know can't exist and looking
149 149 # for the svn-specific "not found" XML.
150 150 def httpcheck(path, proto):
151 151 return ('<m:human-readable errcode="160013">' in
152 152 urllib.urlopen('%s://%s/!svn/ver/0/.svn' % (proto, path)).read())
153 153
154 154 protomap = {'http': httpcheck,
155 155 'https': httpcheck,
156 156 'file': filecheck,
157 157 }
158 158 def issvnurl(url):
159 159 if not '://' in url:
160 160 return False
161 161 proto, path = url.split('://', 1)
162 162 check = protomap.get(proto, lambda p, p2: False)
163 163 while '/' in path:
164 164 if check(path, proto):
165 165 return True
166 166 path = path.rsplit('/', 1)[0]
167 167 return False
168 168
169 169 # SVN conversion code stolen from bzr-svn and tailor
170 170 #
171 171 # Subversion looks like a versioned filesystem, branches structures
172 172 # are defined by conventions and not enforced by the tool. First,
173 173 # we define the potential branches (modules) as "trunk" and "branches"
174 174 # children directories. Revisions are then identified by their
175 175 # module and revision number (and a repository identifier).
176 176 #
177 177 # The revision graph is really a tree (or a forest). By default, a
178 178 # revision parent is the previous revision in the same module. If the
179 179 # module directory is copied/moved from another module then the
180 180 # revision is the module root and its parent the source revision in
181 181 # the parent module. A revision has at most one parent.
182 182 #
183 183 class svn_source(converter_source):
184 184 def __init__(self, ui, url, rev=None):
185 185 super(svn_source, self).__init__(ui, url, rev=rev)
186 186
187 187 if not (url.startswith('svn://') or url.startswith('svn+ssh://') or
188 188 (os.path.exists(url) and
189 189 os.path.exists(os.path.join(url, '.svn'))) or
190 190 issvnurl(url)):
191 191 raise NoRepo("%s does not look like a Subversion repo" % url)
192 192
193 193 try:
194 194 SubversionException
195 195 except NameError:
196 196 raise MissingTool(_('Subversion python bindings could not be loaded'))
197 197
198 198 try:
199 199 version = svn.core.SVN_VER_MAJOR, svn.core.SVN_VER_MINOR
200 200 if version < (1, 4):
201 201 raise MissingTool(_('Subversion python bindings %d.%d found, '
202 202 '1.4 or later required') % version)
203 203 except AttributeError:
204 204 raise MissingTool(_('Subversion python bindings are too old, 1.4 '
205 205 'or later required'))
206 206
207 207 self.encoding = locale.getpreferredencoding()
208 208 self.lastrevs = {}
209 209
210 210 latest = None
211 211 try:
212 212 # Support file://path@rev syntax. Useful e.g. to convert
213 213 # deleted branches.
214 214 at = url.rfind('@')
215 215 if at >= 0:
216 216 latest = int(url[at+1:])
217 217 url = url[:at]
218 218 except ValueError:
219 219 pass
220 220 self.url = geturl(url)
221 221 self.encoding = 'UTF-8' # Subversion is always nominal UTF-8
222 222 try:
223 223 self.transport = transport.SvnRaTransport(url=self.url)
224 224 self.ra = self.transport.ra
225 225 self.ctx = self.transport.client
226 226 self.baseurl = svn.ra.get_repos_root(self.ra)
227 227 # Module is either empty or a repository path starting with
228 228 # a slash and not ending with a slash.
229 229 self.module = urllib.unquote(self.url[len(self.baseurl):])
230 230 self.prevmodule = None
231 231 self.rootmodule = self.module
232 232 self.commits = {}
233 233 self.paths = {}
234 234 self.uuid = svn.ra.get_uuid(self.ra).decode(self.encoding)
235 235 except SubversionException:
236 ui.print_exc()
236 ui.traceback()
237 237 raise NoRepo("%s does not look like a Subversion repo" % self.url)
238 238
239 239 if rev:
240 240 try:
241 241 latest = int(rev)
242 242 except ValueError:
243 243 raise util.Abort(_('svn: revision %s is not an integer') % rev)
244 244
245 245 self.startrev = self.ui.config('convert', 'svn.startrev', default=0)
246 246 try:
247 247 self.startrev = int(self.startrev)
248 248 if self.startrev < 0:
249 249 self.startrev = 0
250 250 except ValueError:
251 251 raise util.Abort(_('svn: start revision %s is not an integer')
252 252 % self.startrev)
253 253
254 254 try:
255 255 self.get_blacklist()
256 256 except IOError:
257 257 pass
258 258
259 259 self.head = self.latest(self.module, latest)
260 260 if not self.head:
261 261 raise util.Abort(_('no revision found in module %s') %
262 262 self.module.encode(self.encoding))
263 263 self.last_changed = self.revnum(self.head)
264 264
265 265 self._changescache = None
266 266
267 267 if os.path.exists(os.path.join(url, '.svn/entries')):
268 268 self.wc = url
269 269 else:
270 270 self.wc = None
271 271 self.convertfp = None
272 272
273 273 def setrevmap(self, revmap):
274 274 lastrevs = {}
275 275 for revid in revmap.iterkeys():
276 276 uuid, module, revnum = self.revsplit(revid)
277 277 lastrevnum = lastrevs.setdefault(module, revnum)
278 278 if revnum > lastrevnum:
279 279 lastrevs[module] = revnum
280 280 self.lastrevs = lastrevs
281 281
282 282 def exists(self, path, optrev):
283 283 try:
284 284 svn.client.ls(self.url.rstrip('/') + '/' + urllib.quote(path),
285 285 optrev, False, self.ctx)
286 286 return True
287 287 except SubversionException:
288 288 return False
289 289
290 290 def getheads(self):
291 291
292 292 def isdir(path, revnum):
293 293 kind = self._checkpath(path, revnum)
294 294 return kind == svn.core.svn_node_dir
295 295
296 296 def getcfgpath(name, rev):
297 297 cfgpath = self.ui.config('convert', 'svn.' + name)
298 298 if cfgpath is not None and cfgpath.strip() == '':
299 299 return None
300 300 path = (cfgpath or name).strip('/')
301 301 if not self.exists(path, rev):
302 302 if cfgpath:
303 303 raise util.Abort(_('expected %s to be at %r, but not found')
304 304 % (name, path))
305 305 return None
306 306 self.ui.note(_('found %s at %r\n') % (name, path))
307 307 return path
308 308
309 309 rev = optrev(self.last_changed)
310 310 oldmodule = ''
311 311 trunk = getcfgpath('trunk', rev)
312 312 self.tags = getcfgpath('tags', rev)
313 313 branches = getcfgpath('branches', rev)
314 314
315 315 # If the project has a trunk or branches, we will extract heads
316 316 # from them. We keep the project root otherwise.
317 317 if trunk:
318 318 oldmodule = self.module or ''
319 319 self.module += '/' + trunk
320 320 self.head = self.latest(self.module, self.last_changed)
321 321 if not self.head:
322 322 raise util.Abort(_('no revision found in module %s') %
323 323 self.module.encode(self.encoding))
324 324
325 325 # First head in the list is the module's head
326 326 self.heads = [self.head]
327 327 if self.tags is not None:
328 328 self.tags = '%s/%s' % (oldmodule , (self.tags or 'tags'))
329 329
330 330 # Check if branches bring a few more heads to the list
331 331 if branches:
332 332 rpath = self.url.strip('/')
333 333 branchnames = svn.client.ls(rpath + '/' + urllib.quote(branches),
334 334 rev, False, self.ctx)
335 335 for branch in branchnames.keys():
336 336 module = '%s/%s/%s' % (oldmodule, branches, branch)
337 337 if not isdir(module, self.last_changed):
338 338 continue
339 339 brevid = self.latest(module, self.last_changed)
340 340 if not brevid:
341 341 self.ui.note(_('ignoring empty branch %s\n') %
342 342 branch.encode(self.encoding))
343 343 continue
344 344 self.ui.note(_('found branch %s at %d\n') %
345 345 (branch, self.revnum(brevid)))
346 346 self.heads.append(brevid)
347 347
348 348 if self.startrev and self.heads:
349 349 if len(self.heads) > 1:
350 350 raise util.Abort(_('svn: start revision is not supported '
351 351 'with more than one branch'))
352 352 revnum = self.revnum(self.heads[0])
353 353 if revnum < self.startrev:
354 354 raise util.Abort(_('svn: no revision found after start revision %d')
355 355 % self.startrev)
356 356
357 357 return self.heads
358 358
359 359 def getfile(self, file, rev):
360 360 data, mode = self._getfile(file, rev)
361 361 self.modecache[(file, rev)] = mode
362 362 return data
363 363
364 364 def getmode(self, file, rev):
365 365 return self.modecache[(file, rev)]
366 366
367 367 def getchanges(self, rev):
368 368 if self._changescache and self._changescache[0] == rev:
369 369 return self._changescache[1]
370 370 self._changescache = None
371 371 self.modecache = {}
372 372 (paths, parents) = self.paths[rev]
373 373 if parents:
374 374 files, copies = self.expandpaths(rev, paths, parents)
375 375 else:
376 376 # Perform a full checkout on roots
377 377 uuid, module, revnum = self.revsplit(rev)
378 378 entries = svn.client.ls(self.baseurl + urllib.quote(module),
379 379 optrev(revnum), True, self.ctx)
380 380 files = [n for n,e in entries.iteritems()
381 381 if e.kind == svn.core.svn_node_file]
382 382 copies = {}
383 383
384 384 files.sort()
385 385 files = zip(files, [rev] * len(files))
386 386
387 387 # caller caches the result, so free it here to release memory
388 388 del self.paths[rev]
389 389 return (files, copies)
390 390
391 391 def getchangedfiles(self, rev, i):
392 392 changes = self.getchanges(rev)
393 393 self._changescache = (rev, changes)
394 394 return [f[0] for f in changes[0]]
395 395
396 396 def getcommit(self, rev):
397 397 if rev not in self.commits:
398 398 uuid, module, revnum = self.revsplit(rev)
399 399 self.module = module
400 400 self.reparent(module)
401 401 # We assume that:
402 402 # - requests for revisions after "stop" come from the
403 403 # revision graph backward traversal. Cache all of them
404 404 # down to stop, they will be used eventually.
405 405 # - requests for revisions before "stop" come to get
406 406 # isolated branches parents. Just fetch what is needed.
407 407 stop = self.lastrevs.get(module, 0)
408 408 if revnum < stop:
409 409 stop = revnum + 1
410 410 self._fetch_revisions(revnum, stop)
411 411 commit = self.commits[rev]
412 412 # caller caches the result, so free it here to release memory
413 413 del self.commits[rev]
414 414 return commit
415 415
416 416 def gettags(self):
417 417 tags = {}
418 418 if self.tags is None:
419 419 return tags
420 420
421 421 # svn tags are just a convention, project branches left in a
422 422 # 'tags' directory. There is no other relationship than
423 423 # ancestry, which is expensive to discover and makes them hard
424 424 # to update incrementally. Worse, past revisions may be
425 425 # referenced by tags far away in the future, requiring a deep
426 426 # history traversal on every calculation. Current code
427 427 # performs a single backward traversal, tracking moves within
428 428 # the tags directory (tag renaming) and recording a new tag
429 429 # everytime a project is copied from outside the tags
430 430 # directory. It also lists deleted tags, this behaviour may
431 431 # change in the future.
432 432 pendings = []
433 433 tagspath = self.tags
434 434 start = svn.ra.get_latest_revnum(self.ra)
435 435 try:
436 436 for entry in self._getlog([self.tags], start, self.startrev):
437 437 origpaths, revnum, author, date, message = entry
438 438 copies = [(e.copyfrom_path, e.copyfrom_rev, p) for p, e
439 439 in origpaths.iteritems() if e.copyfrom_path]
440 440 copies.sort()
441 441 # Apply moves/copies from more specific to general
442 442 copies.reverse()
443 443
444 444 srctagspath = tagspath
445 445 if copies and copies[-1][2] == tagspath:
446 446 # Track tags directory moves
447 447 srctagspath = copies.pop()[0]
448 448
449 449 for source, sourcerev, dest in copies:
450 450 if not dest.startswith(tagspath + '/'):
451 451 continue
452 452 for tag in pendings:
453 453 if tag[0].startswith(dest):
454 454 tagpath = source + tag[0][len(dest):]
455 455 tag[:2] = [tagpath, sourcerev]
456 456 break
457 457 else:
458 458 pendings.append([source, sourcerev, dest.split('/')[-1]])
459 459
460 460 # Tell tag renamings from tag creations
461 461 remainings = []
462 462 for source, sourcerev, tagname in pendings:
463 463 if source.startswith(srctagspath):
464 464 remainings.append([source, sourcerev, tagname])
465 465 continue
466 466 # From revision may be fake, get one with changes
467 467 try:
468 468 tagid = self.latest(source, sourcerev)
469 469 if tagid:
470 470 tags[tagname] = tagid
471 471 except SvnPathNotFound:
472 472 # It happens when we are following directories we assumed
473 473 # were copied with their parents but were really created
474 474 # in the tag directory.
475 475 pass
476 476 pendings = remainings
477 477 tagspath = srctagspath
478 478
479 479 except SubversionException:
480 480 self.ui.note(_('no tags found at revision %d\n') % start)
481 481 return tags
482 482
483 483 def converted(self, rev, destrev):
484 484 if not self.wc:
485 485 return
486 486 if self.convertfp is None:
487 487 self.convertfp = open(os.path.join(self.wc, '.svn', 'hg-shamap'),
488 488 'a')
489 489 self.convertfp.write('%s %d\n' % (destrev, self.revnum(rev)))
490 490 self.convertfp.flush()
491 491
492 492 # -- helper functions --
493 493
494 494 def revid(self, revnum, module=None):
495 495 if not module:
496 496 module = self.module
497 497 return u"svn:%s%s@%s" % (self.uuid, module.decode(self.encoding),
498 498 revnum)
499 499
500 500 def revnum(self, rev):
501 501 return int(rev.split('@')[-1])
502 502
503 503 def revsplit(self, rev):
504 504 url, revnum = rev.encode(self.encoding).rsplit('@', 1)
505 505 revnum = int(revnum)
506 506 parts = url.split('/', 1)
507 507 uuid = parts.pop(0)[4:]
508 508 mod = ''
509 509 if parts:
510 510 mod = '/' + parts[0]
511 511 return uuid, mod, revnum
512 512
513 513 def latest(self, path, stop=0):
514 514 """Find the latest revid affecting path, up to stop. It may return
515 515 a revision in a different module, since a branch may be moved without
516 516 a change being reported. Return None if computed module does not
517 517 belong to rootmodule subtree.
518 518 """
519 519 if not path.startswith(self.rootmodule):
520 520 # Requests on foreign branches may be forbidden at server level
521 521 self.ui.debug(_('ignoring foreign branch %r\n') % path)
522 522 return None
523 523
524 524 if not stop:
525 525 stop = svn.ra.get_latest_revnum(self.ra)
526 526 try:
527 527 prevmodule = self.reparent('')
528 528 dirent = svn.ra.stat(self.ra, path.strip('/'), stop)
529 529 self.reparent(prevmodule)
530 530 except SubversionException:
531 531 dirent = None
532 532 if not dirent:
533 533 raise SvnPathNotFound(_('%s not found up to revision %d') % (path, stop))
534 534
535 535 # stat() gives us the previous revision on this line of development, but
536 536 # it might be in *another module*. Fetch the log and detect renames down
537 537 # to the latest revision.
538 538 stream = self._getlog([path], stop, dirent.created_rev)
539 539 try:
540 540 for entry in stream:
541 541 paths, revnum, author, date, message = entry
542 542 if revnum <= dirent.created_rev:
543 543 break
544 544
545 545 for p in paths:
546 546 if not path.startswith(p) or not paths[p].copyfrom_path:
547 547 continue
548 548 newpath = paths[p].copyfrom_path + path[len(p):]
549 549 self.ui.debug(_("branch renamed from %s to %s at %d\n") %
550 550 (path, newpath, revnum))
551 551 path = newpath
552 552 break
553 553 finally:
554 554 stream.close()
555 555
556 556 if not path.startswith(self.rootmodule):
557 557 self.ui.debug(_('ignoring foreign branch %r\n') % path)
558 558 return None
559 559 return self.revid(dirent.created_rev, path)
560 560
561 561 def get_blacklist(self):
562 562 """Avoid certain revision numbers.
563 563 It is not uncommon for two nearby revisions to cancel each other
564 564 out, e.g. 'I copied trunk into a subdirectory of itself instead
565 565 of making a branch'. The converted repository is significantly
566 566 smaller if we ignore such revisions."""
567 567 self.blacklist = set()
568 568 blacklist = self.blacklist
569 569 for line in file("blacklist.txt", "r"):
570 570 if not line.startswith("#"):
571 571 try:
572 572 svn_rev = int(line.strip())
573 573 blacklist.add(svn_rev)
574 574 except ValueError:
575 575 pass # not an integer or a comment
576 576
577 577 def is_blacklisted(self, svn_rev):
578 578 return svn_rev in self.blacklist
579 579
580 580 def reparent(self, module):
581 581 """Reparent the svn transport and return the previous parent."""
582 582 if self.prevmodule == module:
583 583 return module
584 584 svnurl = self.baseurl + urllib.quote(module)
585 585 prevmodule = self.prevmodule
586 586 if prevmodule is None:
587 587 prevmodule = ''
588 588 self.ui.debug(_("reparent to %s\n") % svnurl)
589 589 svn.ra.reparent(self.ra, svnurl)
590 590 self.prevmodule = module
591 591 return prevmodule
592 592
593 593 def expandpaths(self, rev, paths, parents):
594 594 entries = []
595 595 copyfrom = {} # Map of entrypath, revision for finding source of deleted revisions.
596 596 copies = {}
597 597
598 598 new_module, revnum = self.revsplit(rev)[1:]
599 599 if new_module != self.module:
600 600 self.module = new_module
601 601 self.reparent(self.module)
602 602
603 603 for path, ent in paths:
604 604 entrypath = self.getrelpath(path)
605 605 entry = entrypath.decode(self.encoding)
606 606
607 607 kind = self._checkpath(entrypath, revnum)
608 608 if kind == svn.core.svn_node_file:
609 609 entries.append(self.recode(entry))
610 610 if not ent.copyfrom_path or not parents:
611 611 continue
612 612 # Copy sources not in parent revisions cannot be represented,
613 613 # ignore their origin for now
614 614 pmodule, prevnum = self.revsplit(parents[0])[1:]
615 615 if ent.copyfrom_rev < prevnum:
616 616 continue
617 617 copyfrom_path = self.getrelpath(ent.copyfrom_path, pmodule)
618 618 if not copyfrom_path:
619 619 continue
620 620 self.ui.debug(_("copied to %s from %s@%s\n") %
621 621 (entrypath, copyfrom_path, ent.copyfrom_rev))
622 622 copies[self.recode(entry)] = self.recode(copyfrom_path)
623 623 elif kind == 0: # gone, but had better be a deleted *file*
624 624 self.ui.debug(_("gone from %s\n") % ent.copyfrom_rev)
625 625
626 626 # if a branch is created but entries are removed in the same
627 627 # changeset, get the right fromrev
628 628 # parents cannot be empty here, you cannot remove things from
629 629 # a root revision.
630 630 uuid, old_module, fromrev = self.revsplit(parents[0])
631 631
632 632 basepath = old_module + "/" + self.getrelpath(path)
633 633 entrypath = basepath
634 634
635 635 def lookup_parts(p):
636 636 rc = None
637 637 parts = p.split("/")
638 638 for i in range(len(parts)):
639 639 part = "/".join(parts[:i])
640 640 info = part, copyfrom.get(part, None)
641 641 if info[1] is not None:
642 642 self.ui.debug(_("found parent directory %s\n") % info[1])
643 643 rc = info
644 644 return rc
645 645
646 646 self.ui.debug(_("base, entry %s %s\n") % (basepath, entrypath))
647 647
648 648 frompath, froment = lookup_parts(entrypath) or (None, revnum - 1)
649 649
650 650 # need to remove fragment from lookup_parts and replace with copyfrom_path
651 651 if frompath is not None:
652 652 self.ui.debug(_("munge-o-matic\n"))
653 653 self.ui.debug(entrypath + '\n')
654 654 self.ui.debug(entrypath[len(frompath):] + '\n')
655 655 entrypath = froment.copyfrom_path + entrypath[len(frompath):]
656 656 fromrev = froment.copyfrom_rev
657 657 self.ui.debug(_("info: %s %s %s %s\n") % (frompath, froment, ent, entrypath))
658 658
659 659 # We can avoid the reparent calls if the module has not changed
660 660 # but it probably does not worth the pain.
661 661 prevmodule = self.reparent('')
662 662 fromkind = svn.ra.check_path(self.ra, entrypath.strip('/'), fromrev)
663 663 self.reparent(prevmodule)
664 664
665 665 if fromkind == svn.core.svn_node_file: # a deleted file
666 666 entries.append(self.recode(entry))
667 667 elif fromkind == svn.core.svn_node_dir:
668 668 # print "Deleted/moved non-file:", revnum, path, ent
669 669 # children = self._find_children(path, revnum - 1)
670 670 # print "find children %s@%d from %d action %s" % (path, revnum, ent.copyfrom_rev, ent.action)
671 671 # Sometimes this is tricky. For example: in
672 672 # The Subversion Repository revision 6940 a dir
673 673 # was copied and one of its files was deleted
674 674 # from the new location in the same commit. This
675 675 # code can't deal with that yet.
676 676 if ent.action == 'C':
677 677 children = self._find_children(path, fromrev)
678 678 else:
679 679 oroot = entrypath.strip('/')
680 680 nroot = path.strip('/')
681 681 children = self._find_children(oroot, fromrev)
682 682 children = [s.replace(oroot,nroot) for s in children]
683 683 # Mark all [files, not directories] as deleted.
684 684 for child in children:
685 685 # Can we move a child directory and its
686 686 # parent in the same commit? (probably can). Could
687 687 # cause problems if instead of revnum -1,
688 688 # we have to look in (copyfrom_path, revnum - 1)
689 689 entrypath = self.getrelpath("/" + child, module=old_module)
690 690 if entrypath:
691 691 entry = self.recode(entrypath.decode(self.encoding))
692 692 if entry in copies:
693 693 # deleted file within a copy
694 694 del copies[entry]
695 695 else:
696 696 entries.append(entry)
697 697 else:
698 698 self.ui.debug(_('unknown path in revision %d: %s\n') % \
699 699 (revnum, path))
700 700 elif kind == svn.core.svn_node_dir:
701 701 # Should probably synthesize normal file entries
702 702 # and handle as above to clean up copy/rename handling.
703 703
704 704 # If the directory just had a prop change,
705 705 # then we shouldn't need to look for its children.
706 706 if ent.action == 'M':
707 707 continue
708 708
709 709 # Also this could create duplicate entries. Not sure
710 710 # whether this will matter. Maybe should make entries a set.
711 711 # print "Changed directory", revnum, path, ent.action, ent.copyfrom_path, ent.copyfrom_rev
712 712 # This will fail if a directory was copied
713 713 # from another branch and then some of its files
714 714 # were deleted in the same transaction.
715 715 children = util.sort(self._find_children(path, revnum))
716 716 for child in children:
717 717 # Can we move a child directory and its
718 718 # parent in the same commit? (probably can). Could
719 719 # cause problems if instead of revnum -1,
720 720 # we have to look in (copyfrom_path, revnum - 1)
721 721 entrypath = self.getrelpath("/" + child)
722 722 # print child, self.module, entrypath
723 723 if entrypath:
724 724 # Need to filter out directories here...
725 725 kind = self._checkpath(entrypath, revnum)
726 726 if kind != svn.core.svn_node_dir:
727 727 entries.append(self.recode(entrypath))
728 728
729 729 # Copies here (must copy all from source)
730 730 # Probably not a real problem for us if
731 731 # source does not exist
732 732 if not ent.copyfrom_path or not parents:
733 733 continue
734 734 # Copy sources not in parent revisions cannot be represented,
735 735 # ignore their origin for now
736 736 pmodule, prevnum = self.revsplit(parents[0])[1:]
737 737 if ent.copyfrom_rev < prevnum:
738 738 continue
739 739 copyfrompath = ent.copyfrom_path.decode(self.encoding)
740 740 copyfrompath = self.getrelpath(copyfrompath, pmodule)
741 741 if not copyfrompath:
742 742 continue
743 743 copyfrom[path] = ent
744 744 self.ui.debug(_("mark %s came from %s:%d\n")
745 745 % (path, copyfrompath, ent.copyfrom_rev))
746 746 children = self._find_children(ent.copyfrom_path, ent.copyfrom_rev)
747 747 children.sort()
748 748 for child in children:
749 749 entrypath = self.getrelpath("/" + child, pmodule)
750 750 if not entrypath:
751 751 continue
752 752 entry = entrypath.decode(self.encoding)
753 753 copytopath = path + entry[len(copyfrompath):]
754 754 copytopath = self.getrelpath(copytopath)
755 755 copies[self.recode(copytopath)] = self.recode(entry, pmodule)
756 756
757 757 return (list(set(entries)), copies)
758 758
759 759 def _fetch_revisions(self, from_revnum, to_revnum):
760 760 if from_revnum < to_revnum:
761 761 from_revnum, to_revnum = to_revnum, from_revnum
762 762
763 763 self.child_cset = None
764 764
765 765 def parselogentry(orig_paths, revnum, author, date, message):
766 766 """Return the parsed commit object or None, and True if
767 767 the revision is a branch root.
768 768 """
769 769 self.ui.debug(_("parsing revision %d (%d changes)\n") %
770 770 (revnum, len(orig_paths)))
771 771
772 772 branched = False
773 773 rev = self.revid(revnum)
774 774 # branch log might return entries for a parent we already have
775 775
776 776 if rev in self.commits or revnum < to_revnum:
777 777 return None, branched
778 778
779 779 parents = []
780 780 # check whether this revision is the start of a branch or part
781 781 # of a branch renaming
782 782 orig_paths = util.sort(orig_paths.items())
783 783 root_paths = [(p,e) for p,e in orig_paths if self.module.startswith(p)]
784 784 if root_paths:
785 785 path, ent = root_paths[-1]
786 786 if ent.copyfrom_path:
787 787 branched = True
788 788 newpath = ent.copyfrom_path + self.module[len(path):]
789 789 # ent.copyfrom_rev may not be the actual last revision
790 790 previd = self.latest(newpath, ent.copyfrom_rev)
791 791 if previd is not None:
792 792 prevmodule, prevnum = self.revsplit(previd)[1:]
793 793 if prevnum >= self.startrev:
794 794 parents = [previd]
795 795 self.ui.note(_('found parent of branch %s at %d: %s\n') %
796 796 (self.module, prevnum, prevmodule))
797 797 else:
798 798 self.ui.debug(_("no copyfrom path, don't know what to do.\n"))
799 799
800 800 paths = []
801 801 # filter out unrelated paths
802 802 for path, ent in orig_paths:
803 803 if self.getrelpath(path) is None:
804 804 continue
805 805 paths.append((path, ent))
806 806
807 807 # Example SVN datetime. Includes microseconds.
808 808 # ISO-8601 conformant
809 809 # '2007-01-04T17:35:00.902377Z'
810 810 date = util.parsedate(date[:19] + " UTC", ["%Y-%m-%dT%H:%M:%S"])
811 811
812 812 log = message and self.recode(message) or ''
813 813 author = author and self.recode(author) or ''
814 814 try:
815 815 branch = self.module.split("/")[-1]
816 816 if branch == 'trunk':
817 817 branch = ''
818 818 except IndexError:
819 819 branch = None
820 820
821 821 cset = commit(author=author,
822 822 date=util.datestr(date),
823 823 desc=log,
824 824 parents=parents,
825 825 branch=branch,
826 826 rev=rev.encode('utf-8'))
827 827
828 828 self.commits[rev] = cset
829 829 # The parents list is *shared* among self.paths and the
830 830 # commit object. Both will be updated below.
831 831 self.paths[rev] = (paths, cset.parents)
832 832 if self.child_cset and not self.child_cset.parents:
833 833 self.child_cset.parents[:] = [rev]
834 834 self.child_cset = cset
835 835 return cset, branched
836 836
837 837 self.ui.note(_('fetching revision log for "%s" from %d to %d\n') %
838 838 (self.module, from_revnum, to_revnum))
839 839
840 840 try:
841 841 firstcset = None
842 842 lastonbranch = False
843 843 stream = self._getlog([self.module], from_revnum, to_revnum)
844 844 try:
845 845 for entry in stream:
846 846 paths, revnum, author, date, message = entry
847 847 if revnum < self.startrev:
848 848 lastonbranch = True
849 849 break
850 850 if self.is_blacklisted(revnum):
851 851 self.ui.note(_('skipping blacklisted revision %d\n')
852 852 % revnum)
853 853 continue
854 854 if not paths:
855 855 self.ui.debug(_('revision %d has no entries\n') % revnum)
856 856 continue
857 857 cset, lastonbranch = parselogentry(paths, revnum, author,
858 858 date, message)
859 859 if cset:
860 860 firstcset = cset
861 861 if lastonbranch:
862 862 break
863 863 finally:
864 864 stream.close()
865 865
866 866 if not lastonbranch and firstcset and not firstcset.parents:
867 867 # The first revision of the sequence (the last fetched one)
868 868 # has invalid parents if not a branch root. Find the parent
869 869 # revision now, if any.
870 870 try:
871 871 firstrevnum = self.revnum(firstcset.rev)
872 872 if firstrevnum > 1:
873 873 latest = self.latest(self.module, firstrevnum - 1)
874 874 if latest:
875 875 firstcset.parents.append(latest)
876 876 except SvnPathNotFound:
877 877 pass
878 878 except SubversionException, (inst, num):
879 879 if num == svn.core.SVN_ERR_FS_NO_SUCH_REVISION:
880 880 raise util.Abort(_('svn: branch has no revision %s') % to_revnum)
881 881 raise
882 882
883 883 def _getfile(self, file, rev):
884 884 # TODO: ra.get_file transmits the whole file instead of diffs.
885 885 mode = ''
886 886 try:
887 887 new_module, revnum = self.revsplit(rev)[1:]
888 888 if self.module != new_module:
889 889 self.module = new_module
890 890 self.reparent(self.module)
891 891 io = StringIO()
892 892 info = svn.ra.get_file(self.ra, file, revnum, io)
893 893 data = io.getvalue()
894 894 # ra.get_files() seems to keep a reference on the input buffer
895 895 # preventing collection. Release it explicitely.
896 896 io.close()
897 897 if isinstance(info, list):
898 898 info = info[-1]
899 899 mode = ("svn:executable" in info) and 'x' or ''
900 900 mode = ("svn:special" in info) and 'l' or mode
901 901 except SubversionException, e:
902 902 notfound = (svn.core.SVN_ERR_FS_NOT_FOUND,
903 903 svn.core.SVN_ERR_RA_DAV_PATH_NOT_FOUND)
904 904 if e.apr_err in notfound: # File not found
905 905 raise IOError()
906 906 raise
907 907 if mode == 'l':
908 908 link_prefix = "link "
909 909 if data.startswith(link_prefix):
910 910 data = data[len(link_prefix):]
911 911 return data, mode
912 912
913 913 def _find_children(self, path, revnum):
914 914 path = path.strip('/')
915 915 pool = Pool()
916 916 rpath = '/'.join([self.baseurl, urllib.quote(path)]).strip('/')
917 917 return ['%s/%s' % (path, x) for x in
918 918 svn.client.ls(rpath, optrev(revnum), True, self.ctx, pool).keys()]
919 919
920 920 def getrelpath(self, path, module=None):
921 921 if module is None:
922 922 module = self.module
923 923 # Given the repository url of this wc, say
924 924 # "http://server/plone/CMFPlone/branches/Plone-2_0-branch"
925 925 # extract the "entry" portion (a relative path) from what
926 926 # svn log --xml says, ie
927 927 # "/CMFPlone/branches/Plone-2_0-branch/tests/PloneTestCase.py"
928 928 # that is to say "tests/PloneTestCase.py"
929 929 if path.startswith(module):
930 930 relative = path.rstrip('/')[len(module):]
931 931 if relative.startswith('/'):
932 932 return relative[1:]
933 933 elif relative == '':
934 934 return relative
935 935
936 936 # The path is outside our tracked tree...
937 937 self.ui.debug(_('%r is not under %r, ignoring\n') % (path, module))
938 938 return None
939 939
940 940 def _checkpath(self, path, revnum):
941 941 # ra.check_path does not like leading slashes very much, it leads
942 942 # to PROPFIND subversion errors
943 943 return svn.ra.check_path(self.ra, path.strip('/'), revnum)
944 944
945 945 def _getlog(self, paths, start, end, limit=0, discover_changed_paths=True,
946 946 strict_node_history=False):
947 947 # Normalize path names, svn >= 1.5 only wants paths relative to
948 948 # supplied URL
949 949 relpaths = []
950 950 for p in paths:
951 951 if not p.startswith('/'):
952 952 p = self.module + '/' + p
953 953 relpaths.append(p.strip('/'))
954 954 args = [self.baseurl, relpaths, start, end, limit, discover_changed_paths,
955 955 strict_node_history]
956 956 arg = encodeargs(args)
957 957 hgexe = util.hgexecutable()
958 958 cmd = '%s debugsvnlog' % util.shellquote(hgexe)
959 959 stdin, stdout = util.popen2(cmd, 'b')
960 960 stdin.write(arg)
961 961 stdin.close()
962 962 return logstream(stdout)
963 963
964 964 pre_revprop_change = '''#!/bin/sh
965 965
966 966 REPOS="$1"
967 967 REV="$2"
968 968 USER="$3"
969 969 PROPNAME="$4"
970 970 ACTION="$5"
971 971
972 972 if [ "$ACTION" = "M" -a "$PROPNAME" = "svn:log" ]; then exit 0; fi
973 973 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-branch" ]; then exit 0; fi
974 974 if [ "$ACTION" = "A" -a "$PROPNAME" = "hg:convert-rev" ]; then exit 0; fi
975 975
976 976 echo "Changing prohibited revision property" >&2
977 977 exit 1
978 978 '''
979 979
980 980 class svn_sink(converter_sink, commandline):
981 981 commit_re = re.compile(r'Committed revision (\d+).', re.M)
982 982
983 983 def prerun(self):
984 984 if self.wc:
985 985 os.chdir(self.wc)
986 986
987 987 def postrun(self):
988 988 if self.wc:
989 989 os.chdir(self.cwd)
990 990
991 991 def join(self, name):
992 992 return os.path.join(self.wc, '.svn', name)
993 993
994 994 def revmapfile(self):
995 995 return self.join('hg-shamap')
996 996
997 997 def authorfile(self):
998 998 return self.join('hg-authormap')
999 999
1000 1000 def __init__(self, ui, path):
1001 1001 converter_sink.__init__(self, ui, path)
1002 1002 commandline.__init__(self, ui, 'svn')
1003 1003 self.delete = []
1004 1004 self.setexec = []
1005 1005 self.delexec = []
1006 1006 self.copies = []
1007 1007 self.wc = None
1008 1008 self.cwd = os.getcwd()
1009 1009
1010 1010 path = os.path.realpath(path)
1011 1011
1012 1012 created = False
1013 1013 if os.path.isfile(os.path.join(path, '.svn', 'entries')):
1014 1014 self.wc = path
1015 1015 self.run0('update')
1016 1016 else:
1017 1017 wcpath = os.path.join(os.getcwd(), os.path.basename(path) + '-wc')
1018 1018
1019 1019 if os.path.isdir(os.path.dirname(path)):
1020 1020 if not os.path.exists(os.path.join(path, 'db', 'fs-type')):
1021 1021 ui.status(_('initializing svn repo %r\n') %
1022 1022 os.path.basename(path))
1023 1023 commandline(ui, 'svnadmin').run0('create', path)
1024 1024 created = path
1025 1025 path = util.normpath(path)
1026 1026 if not path.startswith('/'):
1027 1027 path = '/' + path
1028 1028 path = 'file://' + path
1029 1029
1030 1030 ui.status(_('initializing svn wc %r\n') % os.path.basename(wcpath))
1031 1031 self.run0('checkout', path, wcpath)
1032 1032
1033 1033 self.wc = wcpath
1034 1034 self.opener = util.opener(self.wc)
1035 1035 self.wopener = util.opener(self.wc)
1036 1036 self.childmap = mapfile(ui, self.join('hg-childmap'))
1037 1037 self.is_exec = util.checkexec(self.wc) and util.is_exec or None
1038 1038
1039 1039 if created:
1040 1040 hook = os.path.join(created, 'hooks', 'pre-revprop-change')
1041 1041 fp = open(hook, 'w')
1042 1042 fp.write(pre_revprop_change)
1043 1043 fp.close()
1044 1044 util.set_flags(hook, False, True)
1045 1045
1046 1046 xport = transport.SvnRaTransport(url=geturl(path))
1047 1047 self.uuid = svn.ra.get_uuid(xport.ra)
1048 1048
1049 1049 def wjoin(self, *names):
1050 1050 return os.path.join(self.wc, *names)
1051 1051
1052 1052 def putfile(self, filename, flags, data):
1053 1053 if 'l' in flags:
1054 1054 self.wopener.symlink(data, filename)
1055 1055 else:
1056 1056 try:
1057 1057 if os.path.islink(self.wjoin(filename)):
1058 1058 os.unlink(filename)
1059 1059 except OSError:
1060 1060 pass
1061 1061 self.wopener(filename, 'w').write(data)
1062 1062
1063 1063 if self.is_exec:
1064 1064 was_exec = self.is_exec(self.wjoin(filename))
1065 1065 else:
1066 1066 # On filesystems not supporting execute-bit, there is no way
1067 1067 # to know if it is set but asking subversion. Setting it
1068 1068 # systematically is just as expensive and much simpler.
1069 1069 was_exec = 'x' not in flags
1070 1070
1071 1071 util.set_flags(self.wjoin(filename), False, 'x' in flags)
1072 1072 if was_exec:
1073 1073 if 'x' not in flags:
1074 1074 self.delexec.append(filename)
1075 1075 else:
1076 1076 if 'x' in flags:
1077 1077 self.setexec.append(filename)
1078 1078
1079 1079 def _copyfile(self, source, dest):
1080 1080 # SVN's copy command pukes if the destination file exists, but
1081 1081 # our copyfile method expects to record a copy that has
1082 1082 # already occurred. Cross the semantic gap.
1083 1083 wdest = self.wjoin(dest)
1084 1084 exists = os.path.exists(wdest)
1085 1085 if exists:
1086 1086 fd, tempname = tempfile.mkstemp(
1087 1087 prefix='hg-copy-', dir=os.path.dirname(wdest))
1088 1088 os.close(fd)
1089 1089 os.unlink(tempname)
1090 1090 os.rename(wdest, tempname)
1091 1091 try:
1092 1092 self.run0('copy', source, dest)
1093 1093 finally:
1094 1094 if exists:
1095 1095 try:
1096 1096 os.unlink(wdest)
1097 1097 except OSError:
1098 1098 pass
1099 1099 os.rename(tempname, wdest)
1100 1100
1101 1101 def dirs_of(self, files):
1102 1102 dirs = set()
1103 1103 for f in files:
1104 1104 if os.path.isdir(self.wjoin(f)):
1105 1105 dirs.add(f)
1106 1106 for i in strutil.rfindall(f, '/'):
1107 1107 dirs.add(f[:i])
1108 1108 return dirs
1109 1109
1110 1110 def add_dirs(self, files):
1111 1111 add_dirs = [d for d in util.sort(self.dirs_of(files))
1112 1112 if not os.path.exists(self.wjoin(d, '.svn', 'entries'))]
1113 1113 if add_dirs:
1114 1114 self.xargs(add_dirs, 'add', non_recursive=True, quiet=True)
1115 1115 return add_dirs
1116 1116
1117 1117 def add_files(self, files):
1118 1118 if files:
1119 1119 self.xargs(files, 'add', quiet=True)
1120 1120 return files
1121 1121
1122 1122 def tidy_dirs(self, names):
1123 1123 dirs = util.sort(self.dirs_of(names))
1124 1124 dirs.reverse()
1125 1125 deleted = []
1126 1126 for d in dirs:
1127 1127 wd = self.wjoin(d)
1128 1128 if os.listdir(wd) == '.svn':
1129 1129 self.run0('delete', d)
1130 1130 deleted.append(d)
1131 1131 return deleted
1132 1132
1133 1133 def addchild(self, parent, child):
1134 1134 self.childmap[parent] = child
1135 1135
1136 1136 def revid(self, rev):
1137 1137 return u"svn:%s@%s" % (self.uuid, rev)
1138 1138
1139 1139 def putcommit(self, files, copies, parents, commit, source):
1140 1140 # Apply changes to working copy
1141 1141 for f, v in files:
1142 1142 try:
1143 1143 data = source.getfile(f, v)
1144 1144 except IOError:
1145 1145 self.delete.append(f)
1146 1146 else:
1147 1147 e = source.getmode(f, v)
1148 1148 self.putfile(f, e, data)
1149 1149 if f in copies:
1150 1150 self.copies.append([copies[f], f])
1151 1151 files = [f[0] for f in files]
1152 1152
1153 1153 for parent in parents:
1154 1154 try:
1155 1155 return self.revid(self.childmap[parent])
1156 1156 except KeyError:
1157 1157 pass
1158 1158 entries = set(self.delete)
1159 1159 files = frozenset(files)
1160 1160 entries.update(self.add_dirs(files.difference(entries)))
1161 1161 if self.copies:
1162 1162 for s, d in self.copies:
1163 1163 self._copyfile(s, d)
1164 1164 self.copies = []
1165 1165 if self.delete:
1166 1166 self.xargs(self.delete, 'delete')
1167 1167 self.delete = []
1168 1168 entries.update(self.add_files(files.difference(entries)))
1169 1169 entries.update(self.tidy_dirs(entries))
1170 1170 if self.delexec:
1171 1171 self.xargs(self.delexec, 'propdel', 'svn:executable')
1172 1172 self.delexec = []
1173 1173 if self.setexec:
1174 1174 self.xargs(self.setexec, 'propset', 'svn:executable', '*')
1175 1175 self.setexec = []
1176 1176
1177 1177 fd, messagefile = tempfile.mkstemp(prefix='hg-convert-')
1178 1178 fp = os.fdopen(fd, 'w')
1179 1179 fp.write(commit.desc)
1180 1180 fp.close()
1181 1181 try:
1182 1182 output = self.run0('commit',
1183 1183 username=util.shortuser(commit.author),
1184 1184 file=messagefile,
1185 1185 encoding='utf-8')
1186 1186 try:
1187 1187 rev = self.commit_re.search(output).group(1)
1188 1188 except AttributeError:
1189 1189 self.ui.warn(_('unexpected svn output:\n'))
1190 1190 self.ui.warn(output)
1191 1191 raise util.Abort(_('unable to cope with svn output'))
1192 1192 if commit.rev:
1193 1193 self.run('propset', 'hg:convert-rev', commit.rev,
1194 1194 revprop=True, revision=rev)
1195 1195 if commit.branch and commit.branch != 'default':
1196 1196 self.run('propset', 'hg:convert-branch', commit.branch,
1197 1197 revprop=True, revision=rev)
1198 1198 for parent in parents:
1199 1199 self.addchild(parent, rev)
1200 1200 return self.revid(rev)
1201 1201 finally:
1202 1202 os.unlink(messagefile)
1203 1203
1204 1204 def puttags(self, tags):
1205 1205 self.ui.warn(_('XXX TAGS NOT IMPLEMENTED YET\n'))
@@ -1,125 +1,125
1 1 # __init__.py - inotify-based status acceleration for Linux
2 2 #
3 3 # Copyright 2006, 2007, 2008 Bryan O'Sullivan <bos@serpentine.com>
4 4 # Copyright 2007, 2008 Brendan Cully <brendan@kublai.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 '''inotify-based status acceleration for Linux systems
10 10 '''
11 11
12 12 # todo: socket permissions
13 13
14 14 from mercurial.i18n import _
15 15 from mercurial import cmdutil, util
16 16 import client, errno, os, server, socket
17 17 from weakref import proxy
18 18
19 19 def serve(ui, repo, **opts):
20 20 '''start an inotify server for this repository'''
21 21 timeout = opts.get('timeout')
22 22 if timeout:
23 23 timeout = float(timeout) * 1e3
24 24
25 25 class service:
26 26 def init(self):
27 27 try:
28 28 self.master = server.Master(ui, repo, timeout)
29 29 except server.AlreadyStartedException, inst:
30 30 raise util.Abort(str(inst))
31 31
32 32 def run(self):
33 33 try:
34 34 self.master.run()
35 35 finally:
36 36 self.master.shutdown()
37 37
38 38 service = service()
39 39 cmdutil.service(opts, initfn=service.init, runfn=service.run)
40 40
41 41 def reposetup(ui, repo):
42 42 if not hasattr(repo, 'dirstate'):
43 43 return
44 44
45 45 # XXX: weakref until hg stops relying on __del__
46 46 repo = proxy(repo)
47 47
48 48 class inotifydirstate(repo.dirstate.__class__):
49 49 # Set to True if we're the inotify server, so we don't attempt
50 50 # to recurse.
51 51 inotifyserver = False
52 52
53 53 def status(self, match, ignored, clean, unknown=True):
54 54 files = match.files()
55 55 if '.' in files:
56 56 files = []
57 57 try:
58 58 if not ignored and not self.inotifyserver:
59 59 result = client.query(ui, repo, files, match, False,
60 60 clean, unknown)
61 61 if result and ui.config('inotify', 'debug'):
62 62 r2 = super(inotifydirstate, self).status(
63 63 match, False, clean, unknown)
64 64 for c,a,b in zip('LMARDUIC', result, r2):
65 65 for f in a:
66 66 if f not in b:
67 67 ui.warn('*** inotify: %s +%s\n' % (c, f))
68 68 for f in b:
69 69 if f not in a:
70 70 ui.warn('*** inotify: %s -%s\n' % (c, f))
71 71 result = r2
72 72
73 73 if result is not None:
74 74 return result
75 75 except (OSError, socket.error), err:
76 76 autostart = ui.configbool('inotify', 'autostart', True)
77 77
78 78 if err[0] == errno.ECONNREFUSED:
79 79 ui.warn(_('(found dead inotify server socket; '
80 80 'removing it)\n'))
81 81 os.unlink(repo.join('inotify.sock'))
82 82 if err[0] in (errno.ECONNREFUSED, errno.ENOENT) and autostart:
83 83 ui.debug(_('(starting inotify server)\n'))
84 84 try:
85 85 try:
86 86 server.start(ui, repo)
87 87 except server.AlreadyStartedException, inst:
88 88 # another process may have started its own
89 89 # inotify server while this one was starting.
90 90 ui.debug(str(inst))
91 91 except Exception, inst:
92 92 ui.warn(_('could not start inotify server: '
93 93 '%s\n') % inst)
94 94 else:
95 95 # server is started, send query again
96 96 try:
97 97 return client.query(ui, repo, files, match,
98 98 ignored, clean, unknown)
99 99 except socket.error, err:
100 100 ui.warn(_('could not talk to new inotify '
101 101 'server: %s\n') % err[-1])
102 102 elif err[0] in (errno.ECONNREFUSED, errno.ENOENT):
103 103 # silently ignore normal errors if autostart is False
104 104 ui.debug(_('(inotify server not running)\n'))
105 105 else:
106 106 ui.warn(_('failed to contact inotify server: %s\n')
107 107 % err[-1])
108 ui.print_exc()
108 ui.traceback()
109 109 # replace by old status function
110 110 self.status = super(inotifydirstate, self).status
111 111
112 112 return super(inotifydirstate, self).status(
113 113 match, ignored, clean, unknown)
114 114
115 115 repo.dirstate.__class__ = inotifydirstate
116 116
117 117 cmdtable = {
118 118 '^inserve':
119 119 (serve,
120 120 [('d', 'daemon', None, _('run server in background')),
121 121 ('', 'daemon-pipefds', '', _('used internally by daemon mode')),
122 122 ('t', 'idle-timeout', '', _('minutes to sit idle before exiting')),
123 123 ('', 'pid-file', '', _('name of file to write process ID to'))],
124 124 _('hg inserve [OPT]...')),
125 125 }
@@ -1,430 +1,430
1 1 # dispatch.py - command dispatching for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import os, sys, atexit, signal, pdb, socket, errno, shlex, time
10 10 import util, commands, hg, fancyopts, extensions, hook, error
11 11 import cmdutil, encoding
12 12 import ui as _ui
13 13
14 14 def run():
15 15 "run the command in sys.argv"
16 16 sys.exit(dispatch(sys.argv[1:]))
17 17
18 18 def dispatch(args):
19 19 "run the command specified in args"
20 20 try:
21 21 u = _ui.ui()
22 22 if '--traceback' in args:
23 23 u.setconfig('ui', 'traceback', 'on')
24 24 except util.Abort, inst:
25 25 sys.stderr.write(_("abort: %s\n") % inst)
26 26 return -1
27 27 return _runcatch(u, args)
28 28
29 29 def _runcatch(ui, args):
30 30 def catchterm(*args):
31 31 raise error.SignalInterrupt
32 32
33 33 for name in 'SIGBREAK', 'SIGHUP', 'SIGTERM':
34 34 num = getattr(signal, name, None)
35 35 if num: signal.signal(num, catchterm)
36 36
37 37 try:
38 38 try:
39 39 # enter the debugger before command execution
40 40 if '--debugger' in args:
41 41 pdb.set_trace()
42 42 try:
43 43 return _dispatch(ui, args)
44 44 finally:
45 45 ui.flush()
46 46 except:
47 47 # enter the debugger when we hit an exception
48 48 if '--debugger' in args:
49 49 pdb.post_mortem(sys.exc_info()[2])
50 ui.print_exc()
50 ui.traceback()
51 51 raise
52 52
53 53 # Global exception handling, alphabetically
54 54 # Mercurial-specific first, followed by built-in and library exceptions
55 55 except error.AmbiguousCommand, inst:
56 56 ui.warn(_("hg: command '%s' is ambiguous:\n %s\n") %
57 57 (inst.args[0], " ".join(inst.args[1])))
58 58 except error.ConfigError, inst:
59 59 ui.warn(_("hg: %s\n") % inst.args[0])
60 60 except error.LockHeld, inst:
61 61 if inst.errno == errno.ETIMEDOUT:
62 62 reason = _('timed out waiting for lock held by %s') % inst.locker
63 63 else:
64 64 reason = _('lock held by %s') % inst.locker
65 65 ui.warn(_("abort: %s: %s\n") % (inst.desc or inst.filename, reason))
66 66 except error.LockUnavailable, inst:
67 67 ui.warn(_("abort: could not lock %s: %s\n") %
68 68 (inst.desc or inst.filename, inst.strerror))
69 69 except error.ParseError, inst:
70 70 if inst.args[0]:
71 71 ui.warn(_("hg %s: %s\n") % (inst.args[0], inst.args[1]))
72 72 commands.help_(ui, inst.args[0])
73 73 else:
74 74 ui.warn(_("hg: %s\n") % inst.args[1])
75 75 commands.help_(ui, 'shortlist')
76 76 except error.RepoError, inst:
77 77 ui.warn(_("abort: %s!\n") % inst)
78 78 except error.ResponseError, inst:
79 79 ui.warn(_("abort: %s") % inst.args[0])
80 80 if not isinstance(inst.args[1], basestring):
81 81 ui.warn(" %r\n" % (inst.args[1],))
82 82 elif not inst.args[1]:
83 83 ui.warn(_(" empty string\n"))
84 84 else:
85 85 ui.warn("\n%r\n" % util.ellipsis(inst.args[1]))
86 86 except error.RevlogError, inst:
87 87 ui.warn(_("abort: %s!\n") % inst)
88 88 except error.SignalInterrupt:
89 89 ui.warn(_("killed!\n"))
90 90 except error.UnknownCommand, inst:
91 91 ui.warn(_("hg: unknown command '%s'\n") % inst.args[0])
92 92 commands.help_(ui, 'shortlist')
93 93 except util.Abort, inst:
94 94 ui.warn(_("abort: %s\n") % inst)
95 95 except ImportError, inst:
96 96 m = str(inst).split()[-1]
97 97 ui.warn(_("abort: could not import module %s!\n") % m)
98 98 if m in "mpatch bdiff".split():
99 99 ui.warn(_("(did you forget to compile extensions?)\n"))
100 100 elif m in "zlib".split():
101 101 ui.warn(_("(is your Python install correct?)\n"))
102 102 except IOError, inst:
103 103 if hasattr(inst, "code"):
104 104 ui.warn(_("abort: %s\n") % inst)
105 105 elif hasattr(inst, "reason"):
106 106 try: # usually it is in the form (errno, strerror)
107 107 reason = inst.reason.args[1]
108 108 except: # it might be anything, for example a string
109 109 reason = inst.reason
110 110 ui.warn(_("abort: error: %s\n") % reason)
111 111 elif hasattr(inst, "args") and inst.args[0] == errno.EPIPE:
112 112 if ui.debugflag:
113 113 ui.warn(_("broken pipe\n"))
114 114 elif getattr(inst, "strerror", None):
115 115 if getattr(inst, "filename", None):
116 116 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
117 117 else:
118 118 ui.warn(_("abort: %s\n") % inst.strerror)
119 119 else:
120 120 raise
121 121 except OSError, inst:
122 122 if getattr(inst, "filename", None):
123 123 ui.warn(_("abort: %s: %s\n") % (inst.strerror, inst.filename))
124 124 else:
125 125 ui.warn(_("abort: %s\n") % inst.strerror)
126 126 except KeyboardInterrupt:
127 127 try:
128 128 ui.warn(_("interrupted!\n"))
129 129 except IOError, inst:
130 130 if inst.errno == errno.EPIPE:
131 131 if ui.debugflag:
132 132 ui.warn(_("\nbroken pipe\n"))
133 133 else:
134 134 raise
135 135 except MemoryError:
136 136 ui.warn(_("abort: out of memory\n"))
137 137 except SystemExit, inst:
138 138 # Commands shouldn't sys.exit directly, but give a return code.
139 139 # Just in case catch this and and pass exit code to caller.
140 140 return inst.code
141 141 except socket.error, inst:
142 142 ui.warn(_("abort: %s\n") % inst.args[-1])
143 143 except:
144 144 ui.warn(_("** unknown exception encountered, details follow\n"))
145 145 ui.warn(_("** report bug details to "
146 146 "http://www.selenic.com/mercurial/bts\n"))
147 147 ui.warn(_("** or mercurial@selenic.com\n"))
148 148 ui.warn(_("** Mercurial Distributed SCM (version %s)\n")
149 149 % util.version())
150 150 ui.warn(_("** Extensions loaded: %s\n")
151 151 % ", ".join([x[0] for x in extensions.extensions()]))
152 152 raise
153 153
154 154 return -1
155 155
156 156 def _findrepo(p):
157 157 while not os.path.isdir(os.path.join(p, ".hg")):
158 158 oldp, p = p, os.path.dirname(p)
159 159 if p == oldp:
160 160 return None
161 161
162 162 return p
163 163
164 164 def _parse(ui, args):
165 165 options = {}
166 166 cmdoptions = {}
167 167
168 168 try:
169 169 args = fancyopts.fancyopts(args, commands.globalopts, options)
170 170 except fancyopts.getopt.GetoptError, inst:
171 171 raise error.ParseError(None, inst)
172 172
173 173 if args:
174 174 cmd, args = args[0], args[1:]
175 175 aliases, i = cmdutil.findcmd(cmd, commands.table,
176 176 ui.config("ui", "strict"))
177 177 cmd = aliases[0]
178 178 defaults = ui.config("defaults", cmd)
179 179 if defaults:
180 180 args = shlex.split(defaults) + args
181 181 c = list(i[1])
182 182 else:
183 183 cmd = None
184 184 c = []
185 185
186 186 # combine global options into local
187 187 for o in commands.globalopts:
188 188 c.append((o[0], o[1], options[o[1]], o[3]))
189 189
190 190 try:
191 191 args = fancyopts.fancyopts(args, c, cmdoptions, True)
192 192 except fancyopts.getopt.GetoptError, inst:
193 193 raise error.ParseError(cmd, inst)
194 194
195 195 # separate global options back out
196 196 for o in commands.globalopts:
197 197 n = o[1]
198 198 options[n] = cmdoptions[n]
199 199 del cmdoptions[n]
200 200
201 201 return (cmd, cmd and i[0] or None, args, options, cmdoptions)
202 202
203 203 def _parseconfig(ui, config):
204 204 """parse the --config options from the command line"""
205 205 for cfg in config:
206 206 try:
207 207 name, value = cfg.split('=', 1)
208 208 section, name = name.split('.', 1)
209 209 if not section or not name:
210 210 raise IndexError
211 211 ui.setconfig(section, name, value)
212 212 except (IndexError, ValueError):
213 213 raise util.Abort(_('malformed --config option: %s') % cfg)
214 214
215 215 def _earlygetopt(aliases, args):
216 216 """Return list of values for an option (or aliases).
217 217
218 218 The values are listed in the order they appear in args.
219 219 The options and values are removed from args.
220 220 """
221 221 try:
222 222 argcount = args.index("--")
223 223 except ValueError:
224 224 argcount = len(args)
225 225 shortopts = [opt for opt in aliases if len(opt) == 2]
226 226 values = []
227 227 pos = 0
228 228 while pos < argcount:
229 229 if args[pos] in aliases:
230 230 if pos + 1 >= argcount:
231 231 # ignore and let getopt report an error if there is no value
232 232 break
233 233 del args[pos]
234 234 values.append(args.pop(pos))
235 235 argcount -= 2
236 236 elif args[pos][:2] in shortopts:
237 237 # short option can have no following space, e.g. hg log -Rfoo
238 238 values.append(args.pop(pos)[2:])
239 239 argcount -= 1
240 240 else:
241 241 pos += 1
242 242 return values
243 243
244 244 def runcommand(lui, repo, cmd, fullargs, ui, options, d):
245 245 # run pre-hook, and abort if it fails
246 246 ret = hook.hook(lui, repo, "pre-%s" % cmd, False, args=" ".join(fullargs))
247 247 if ret:
248 248 return ret
249 249 ret = _runcommand(ui, options, cmd, d)
250 250 # run post-hook, passing command result
251 251 hook.hook(lui, repo, "post-%s" % cmd, False, args=" ".join(fullargs),
252 252 result = ret)
253 253 return ret
254 254
255 255 _loaded = {}
256 256 def _dispatch(ui, args):
257 257 # read --config before doing anything else
258 258 # (e.g. to change trust settings for reading .hg/hgrc)
259 259 _parseconfig(ui, _earlygetopt(['--config'], args))
260 260
261 261 # check for cwd
262 262 cwd = _earlygetopt(['--cwd'], args)
263 263 if cwd:
264 264 os.chdir(cwd[-1])
265 265
266 266 # read the local repository .hgrc into a local ui object
267 267 path = _findrepo(os.getcwd()) or ""
268 268 if not path:
269 269 lui = ui
270 270 if path:
271 271 try:
272 272 lui = ui.copy()
273 273 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
274 274 except IOError:
275 275 pass
276 276
277 277 # now we can expand paths, even ones in .hg/hgrc
278 278 rpath = _earlygetopt(["-R", "--repository", "--repo"], args)
279 279 if rpath:
280 280 path = lui.expandpath(rpath[-1])
281 281 lui = ui.copy()
282 282 lui.readconfig(os.path.join(path, ".hg", "hgrc"))
283 283
284 284 extensions.loadall(lui)
285 285 for name, module in extensions.extensions():
286 286 if name in _loaded:
287 287 continue
288 288
289 289 # setup extensions
290 290 # TODO this should be generalized to scheme, where extensions can
291 291 # redepend on other extensions. then we should toposort them, and
292 292 # do initialization in correct order
293 293 extsetup = getattr(module, 'extsetup', None)
294 294 if extsetup:
295 295 extsetup()
296 296
297 297 cmdtable = getattr(module, 'cmdtable', {})
298 298 overrides = [cmd for cmd in cmdtable if cmd in commands.table]
299 299 if overrides:
300 300 ui.warn(_("extension '%s' overrides commands: %s\n")
301 301 % (name, " ".join(overrides)))
302 302 commands.table.update(cmdtable)
303 303 _loaded[name] = 1
304 304 # check for fallback encoding
305 305 fallback = lui.config('ui', 'fallbackencoding')
306 306 if fallback:
307 307 encoding.fallbackencoding = fallback
308 308
309 309 fullargs = args
310 310 cmd, func, args, options, cmdoptions = _parse(lui, args)
311 311
312 312 if options["config"]:
313 313 raise util.Abort(_("Option --config may not be abbreviated!"))
314 314 if options["cwd"]:
315 315 raise util.Abort(_("Option --cwd may not be abbreviated!"))
316 316 if options["repository"]:
317 317 raise util.Abort(_(
318 318 "Option -R has to be separated from other options (i.e. not -qR) "
319 319 "and --repository may only be abbreviated as --repo!"))
320 320
321 321 if options["encoding"]:
322 322 encoding.encoding = options["encoding"]
323 323 if options["encodingmode"]:
324 324 encoding.encodingmode = options["encodingmode"]
325 325 if options["time"]:
326 326 def get_times():
327 327 t = os.times()
328 328 if t[4] == 0.0: # Windows leaves this as zero, so use time.clock()
329 329 t = (t[0], t[1], t[2], t[3], time.clock())
330 330 return t
331 331 s = get_times()
332 332 def print_time():
333 333 t = get_times()
334 334 ui.warn(_("Time: real %.3f secs (user %.3f+%.3f sys %.3f+%.3f)\n") %
335 335 (t[4]-s[4], t[0]-s[0], t[2]-s[2], t[1]-s[1], t[3]-s[3]))
336 336 atexit.register(print_time)
337 337
338 338 if options['verbose'] or options['debug'] or options['quiet']:
339 339 ui.setconfig('ui', 'verbose', str(bool(options['verbose'])))
340 340 ui.setconfig('ui', 'debug', str(bool(options['debug'])))
341 341 ui.setconfig('ui', 'quiet', str(bool(options['quiet'])))
342 342 if options['traceback']:
343 343 ui.setconfig('ui', 'traceback', 'on')
344 344 if options['noninteractive']:
345 345 ui.setconfig('ui', 'interactive', 'off')
346 346
347 347 if options['help']:
348 348 return commands.help_(ui, cmd, options['version'])
349 349 elif options['version']:
350 350 return commands.version_(ui)
351 351 elif not cmd:
352 352 return commands.help_(ui, 'shortlist')
353 353
354 354 repo = None
355 355 if cmd not in commands.norepo.split():
356 356 try:
357 357 repo = hg.repository(ui, path=path)
358 358 ui = repo.ui
359 359 if not repo.local():
360 360 raise util.Abort(_("repository '%s' is not local") % path)
361 361 ui.setconfig("bundle", "mainreporoot", repo.root)
362 362 except error.RepoError:
363 363 if cmd not in commands.optionalrepo.split():
364 364 if args and not path: # try to infer -R from command args
365 365 repos = map(_findrepo, args)
366 366 guess = repos[0]
367 367 if guess and repos.count(guess) == len(repos):
368 368 return _dispatch(ui, ['--repository', guess] + fullargs)
369 369 if not path:
370 370 raise error.RepoError(_("There is no Mercurial repository"
371 371 " here (.hg not found)"))
372 372 raise
373 373 args.insert(0, repo)
374 374 elif rpath:
375 375 ui.warn("warning: --repository ignored\n")
376 376
377 377 d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
378 378 return runcommand(lui, repo, cmd, fullargs, ui, options, d)
379 379
380 380 def _runcommand(ui, options, cmd, cmdfunc):
381 381 def checkargs():
382 382 try:
383 383 return cmdfunc()
384 384 except error.SignatureError:
385 385 raise error.ParseError(cmd, _("invalid arguments"))
386 386
387 387 if options['profile']:
388 388 format = ui.config('profiling', 'format', default='text')
389 389
390 390 if not format in ['text', 'kcachegrind']:
391 391 ui.warn(_("unrecognized profiling format '%s'"
392 392 " - Ignored\n") % format)
393 393 format = 'text'
394 394
395 395 output = ui.config('profiling', 'output')
396 396
397 397 if output:
398 398 path = os.path.expanduser(output)
399 399 path = ui.expandpath(path)
400 400 ostream = open(path, 'wb')
401 401 else:
402 402 ostream = sys.stderr
403 403
404 404 try:
405 405 from mercurial import lsprof
406 406 except ImportError:
407 407 raise util.Abort(_(
408 408 'lsprof not available - install from '
409 409 'http://codespeak.net/svn/user/arigo/hack/misc/lsprof/'))
410 410 p = lsprof.Profiler()
411 411 p.enable(subcalls=True)
412 412 try:
413 413 return checkargs()
414 414 finally:
415 415 p.disable()
416 416
417 417 if format == 'kcachegrind':
418 418 import lsprofcalltree
419 419 calltree = lsprofcalltree.KCacheGrind(p)
420 420 calltree.output(ostream)
421 421 else:
422 422 # format == 'text'
423 423 stats = lsprof.Stats(p.getstats())
424 424 stats.sort()
425 425 stats.pprint(top=10, file=ostream, climit=5)
426 426
427 427 if output:
428 428 ostream.close()
429 429 else:
430 430 return checkargs()
@@ -1,119 +1,119
1 1 # extensions.py - extension handling for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 import imp, os
9 9 import util, cmdutil
10 10 from i18n import _
11 11
12 12 _extensions = {}
13 13 _order = []
14 14
15 15 def extensions():
16 16 for name in _order:
17 17 module = _extensions[name]
18 18 if module:
19 19 yield name, module
20 20
21 21 def find(name):
22 22 '''return module with given extension name'''
23 23 try:
24 24 return _extensions[name]
25 25 except KeyError:
26 26 for k, v in _extensions.iteritems():
27 27 if k.endswith('.' + name) or k.endswith('/' + name):
28 28 return v
29 29 raise KeyError(name)
30 30
31 31 def loadpath(path, module_name):
32 32 module_name = module_name.replace('.', '_')
33 33 path = os.path.expanduser(path)
34 34 if os.path.isdir(path):
35 35 # module/__init__.py style
36 36 d, f = os.path.split(path.rstrip('/'))
37 37 fd, fpath, desc = imp.find_module(f, [d])
38 38 return imp.load_module(module_name, fd, fpath, desc)
39 39 else:
40 40 return imp.load_source(module_name, path)
41 41
42 42 def load(ui, name, path):
43 43 if name.startswith('hgext.') or name.startswith('hgext/'):
44 44 shortname = name[6:]
45 45 else:
46 46 shortname = name
47 47 if shortname in _extensions:
48 48 return
49 49 _extensions[shortname] = None
50 50 if path:
51 51 # the module will be loaded in sys.modules
52 52 # choose an unique name so that it doesn't
53 53 # conflicts with other modules
54 54 mod = loadpath(path, 'hgext.%s' % name)
55 55 else:
56 56 def importh(name):
57 57 mod = __import__(name)
58 58 components = name.split('.')
59 59 for comp in components[1:]:
60 60 mod = getattr(mod, comp)
61 61 return mod
62 62 try:
63 63 mod = importh("hgext.%s" % name)
64 64 except ImportError:
65 65 mod = importh(name)
66 66 _extensions[shortname] = mod
67 67 _order.append(shortname)
68 68
69 69 uisetup = getattr(mod, 'uisetup', None)
70 70 if uisetup:
71 71 uisetup(ui)
72 72
73 73 def loadall(ui):
74 74 result = ui.configitems("extensions")
75 75 for (name, path) in result:
76 76 if path:
77 77 if path[0] == '!':
78 78 continue
79 79 try:
80 80 load(ui, name, path)
81 81 except KeyboardInterrupt:
82 82 raise
83 83 except Exception, inst:
84 84 if path:
85 85 ui.warn(_("*** failed to import extension %s from %s: %s\n")
86 86 % (name, path, inst))
87 87 else:
88 88 ui.warn(_("*** failed to import extension %s: %s\n")
89 89 % (name, inst))
90 if ui.print_exc():
90 if ui.traceback():
91 91 return 1
92 92
93 93 def wrapcommand(table, command, wrapper):
94 94 aliases, entry = cmdutil.findcmd(command, table)
95 95 for alias, e in table.iteritems():
96 96 if e is entry:
97 97 key = alias
98 98 break
99 99
100 100 origfn = entry[0]
101 101 def wrap(*args, **kwargs):
102 102 return util.checksignature(wrapper)(
103 103 util.checksignature(origfn), *args, **kwargs)
104 104
105 105 wrap.__doc__ = getattr(origfn, '__doc__')
106 106 wrap.__module__ = getattr(origfn, '__module__')
107 107
108 108 newentry = list(entry)
109 109 newentry[0] = wrap
110 110 table[key] = tuple(newentry)
111 111 return entry
112 112
113 113 def wrapfunction(container, funcname, wrapper):
114 114 def wrap(*args, **kwargs):
115 115 return wrapper(origfn, *args, **kwargs)
116 116
117 117 origfn = getattr(container, funcname)
118 118 setattr(container, funcname, wrap)
119 119 return origfn
@@ -1,127 +1,127
1 1 # hook.py - hook support for mercurial
2 2 #
3 3 # Copyright 2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import util, os, sys
10 10 from mercurial import extensions
11 11
12 12 def _pythonhook(ui, repo, name, hname, funcname, args, throw):
13 13 '''call python hook. hook is callable object, looked up as
14 14 name in python module. if callable returns "true", hook
15 15 fails, else passes. if hook raises exception, treated as
16 16 hook failure. exception propagates if throw is "true".
17 17
18 18 reason for "true" meaning "hook failed" is so that
19 19 unmodified commands (e.g. mercurial.commands.update) can
20 20 be run as hooks without wrappers to convert return values.'''
21 21
22 22 ui.note(_("calling hook %s: %s\n") % (hname, funcname))
23 23 obj = funcname
24 24 if not callable(obj):
25 25 d = funcname.rfind('.')
26 26 if d == -1:
27 27 raise util.Abort(_('%s hook is invalid ("%s" not in '
28 28 'a module)') % (hname, funcname))
29 29 modname = funcname[:d]
30 30 try:
31 31 obj = __import__(modname)
32 32 except ImportError:
33 33 try:
34 34 # extensions are loaded with hgext_ prefix
35 35 obj = __import__("hgext_%s" % modname)
36 36 except ImportError:
37 37 raise util.Abort(_('%s hook is invalid '
38 38 '(import of "%s" failed)') %
39 39 (hname, modname))
40 40 try:
41 41 for p in funcname.split('.')[1:]:
42 42 obj = getattr(obj, p)
43 43 except AttributeError:
44 44 raise util.Abort(_('%s hook is invalid '
45 45 '("%s" is not defined)') %
46 46 (hname, funcname))
47 47 if not callable(obj):
48 48 raise util.Abort(_('%s hook is invalid '
49 49 '("%s" is not callable)') %
50 50 (hname, funcname))
51 51 try:
52 52 r = obj(ui=ui, repo=repo, hooktype=name, **args)
53 53 except KeyboardInterrupt:
54 54 raise
55 55 except Exception, exc:
56 56 if isinstance(exc, util.Abort):
57 57 ui.warn(_('error: %s hook failed: %s\n') %
58 58 (hname, exc.args[0]))
59 59 else:
60 60 ui.warn(_('error: %s hook raised an exception: '
61 61 '%s\n') % (hname, exc))
62 62 if throw:
63 63 raise
64 ui.print_exc()
64 ui.traceback()
65 65 return True
66 66 if r:
67 67 if throw:
68 68 raise util.Abort(_('%s hook failed') % hname)
69 69 ui.warn(_('warning: %s hook failed\n') % hname)
70 70 return r
71 71
72 72 def _exthook(ui, repo, name, cmd, args, throw):
73 73 ui.note(_("running hook %s: %s\n") % (name, cmd))
74 74
75 75 env = {}
76 76 for k, v in args.iteritems():
77 77 if callable(v):
78 78 v = v()
79 79 env['HG_' + k.upper()] = v
80 80
81 81 if repo:
82 82 cwd = repo.root
83 83 else:
84 84 cwd = os.getcwd()
85 85 r = util.system(cmd, environ=env, cwd=cwd)
86 86 if r:
87 87 desc, r = util.explain_exit(r)
88 88 if throw:
89 89 raise util.Abort(_('%s hook %s') % (name, desc))
90 90 ui.warn(_('warning: %s hook %s\n') % (name, desc))
91 91 return r
92 92
93 93 _redirect = False
94 94 def redirect(state):
95 95 global _redirect
96 96 _redirect = state
97 97
98 98 def hook(ui, repo, name, throw=False, **args):
99 99 r = False
100 100
101 101 if _redirect:
102 102 # temporarily redirect stdout to stderr
103 103 oldstdout = os.dup(sys.__stdout__.fileno())
104 104 os.dup2(sys.__stderr__.fileno(), sys.__stdout__.fileno())
105 105
106 106 try:
107 107 for hname, cmd in util.sort(ui.configitems('hooks')):
108 108 if hname.split('.')[0] != name or not cmd:
109 109 continue
110 110 if callable(cmd):
111 111 r = _pythonhook(ui, repo, name, hname, cmd, args, throw) or r
112 112 elif cmd.startswith('python:'):
113 113 if cmd.count(':') == 2:
114 114 path, cmd = cmd[7:].split(':')
115 115 mod = extensions.loadpath(path, 'hgkook.%s' % hname)
116 116 hookfn = getattr(mod, cmd)
117 117 else:
118 118 hookfn = cmd[7:].strip()
119 119 r = _pythonhook(ui, repo, name, hname, hookfn, args, throw) or r
120 120 else:
121 121 r = _exthook(ui, repo, hname, cmd, args, throw) or r
122 122 finally:
123 123 if _redirect:
124 124 os.dup2(oldstdout, sys.__stdout__.fileno())
125 125 os.close(oldstdout)
126 126
127 127 return r
@@ -1,244 +1,244
1 1 # httprepo.py - HTTP repository proxy classes for mercurial
2 2 #
3 3 # Copyright 2005, 2006 Matt Mackall <mpm@selenic.com>
4 4 # Copyright 2006 Vadim Gelfer <vadim.gelfer@gmail.com>
5 5 #
6 6 # This software may be used and distributed according to the terms
7 7 # of the GNU General Public License, incorporated herein by reference.
8 8
9 9 from node import bin, hex, nullid
10 10 from i18n import _
11 11 import repo, os, urllib, urllib2, urlparse, zlib, util, httplib
12 12 import errno, socket, changegroup, statichttprepo, error, url
13 13
14 14 def zgenerator(f):
15 15 zd = zlib.decompressobj()
16 16 try:
17 17 for chunk in util.filechunkiter(f):
18 18 yield zd.decompress(chunk)
19 19 except httplib.HTTPException:
20 20 raise IOError(None, _('connection ended unexpectedly'))
21 21 yield zd.flush()
22 22
23 23 class httprepository(repo.repository):
24 24 def __init__(self, ui, path):
25 25 self.path = path
26 26 self.caps = None
27 27 self.handler = None
28 28 scheme, netloc, urlpath, query, frag = urlparse.urlsplit(path)
29 29 if query or frag:
30 30 raise util.Abort(_('unsupported URL component: "%s"') %
31 31 (query or frag))
32 32
33 33 # urllib cannot handle URLs with embedded user or passwd
34 34 self._url, authinfo = url.getauthinfo(path)
35 35
36 36 self.ui = ui
37 37 self.ui.debug(_('using %s\n') % self._url)
38 38
39 39 self.urlopener = url.opener(ui, authinfo)
40 40
41 41 def __del__(self):
42 42 for h in self.urlopener.handlers:
43 43 h.close()
44 44 if hasattr(h, "close_all"):
45 45 h.close_all()
46 46
47 47 def url(self):
48 48 return self.path
49 49
50 50 # look up capabilities only when needed
51 51
52 52 def get_caps(self):
53 53 if self.caps is None:
54 54 try:
55 55 self.caps = set(self.do_read('capabilities').split())
56 56 except error.RepoError:
57 57 self.caps = set()
58 58 self.ui.debug(_('capabilities: %s\n') %
59 59 (' '.join(self.caps or ['none'])))
60 60 return self.caps
61 61
62 62 capabilities = property(get_caps)
63 63
64 64 def lock(self):
65 65 raise util.Abort(_('operation not supported over http'))
66 66
67 67 def do_cmd(self, cmd, **args):
68 68 data = args.pop('data', None)
69 69 headers = args.pop('headers', {})
70 70 self.ui.debug(_("sending %s command\n") % cmd)
71 71 q = {"cmd": cmd}
72 72 q.update(args)
73 73 qs = '?%s' % urllib.urlencode(q)
74 74 cu = "%s%s" % (self._url, qs)
75 75 try:
76 76 if data:
77 77 self.ui.debug(_("sending %s bytes\n") % len(data))
78 78 resp = self.urlopener.open(urllib2.Request(cu, data, headers))
79 79 except urllib2.HTTPError, inst:
80 80 if inst.code == 401:
81 81 raise util.Abort(_('authorization failed'))
82 82 raise
83 83 except httplib.HTTPException, inst:
84 84 self.ui.debug(_('http error while sending %s command\n') % cmd)
85 self.ui.print_exc()
85 self.ui.traceback()
86 86 raise IOError(None, inst)
87 87 except IndexError:
88 88 # this only happens with Python 2.3, later versions raise URLError
89 89 raise util.Abort(_('http error, possibly caused by proxy setting'))
90 90 # record the url we got redirected to
91 91 resp_url = resp.geturl()
92 92 if resp_url.endswith(qs):
93 93 resp_url = resp_url[:-len(qs)]
94 94 if self._url != resp_url:
95 95 self.ui.status(_('real URL is %s\n') % resp_url)
96 96 self._url = resp_url
97 97 try:
98 98 proto = resp.getheader('content-type')
99 99 except AttributeError:
100 100 proto = resp.headers['content-type']
101 101
102 102 safeurl = url.hidepassword(self._url)
103 103 # accept old "text/plain" and "application/hg-changegroup" for now
104 104 if not (proto.startswith('application/mercurial-') or
105 105 proto.startswith('text/plain') or
106 106 proto.startswith('application/hg-changegroup')):
107 107 self.ui.debug(_("requested URL: '%s'\n") % url.hidepassword(cu))
108 108 raise error.RepoError(_("'%s' does not appear to be an hg repository")
109 109 % safeurl)
110 110
111 111 if proto.startswith('application/mercurial-'):
112 112 try:
113 113 version = proto.split('-', 1)[1]
114 114 version_info = tuple([int(n) for n in version.split('.')])
115 115 except ValueError:
116 116 raise error.RepoError(_("'%s' sent a broken Content-Type "
117 117 "header (%s)") % (safeurl, proto))
118 118 if version_info > (0, 1):
119 119 raise error.RepoError(_("'%s' uses newer protocol %s") %
120 120 (safeurl, version))
121 121
122 122 return resp
123 123
124 124 def do_read(self, cmd, **args):
125 125 fp = self.do_cmd(cmd, **args)
126 126 try:
127 127 return fp.read()
128 128 finally:
129 129 # if using keepalive, allow connection to be reused
130 130 fp.close()
131 131
132 132 def lookup(self, key):
133 133 self.requirecap('lookup', _('look up remote revision'))
134 134 d = self.do_cmd("lookup", key = key).read()
135 135 success, data = d[:-1].split(' ', 1)
136 136 if int(success):
137 137 return bin(data)
138 138 raise error.RepoError(data)
139 139
140 140 def heads(self):
141 141 d = self.do_read("heads")
142 142 try:
143 143 return map(bin, d[:-1].split(" "))
144 144 except:
145 145 raise error.ResponseError(_("unexpected response:"), d)
146 146
147 147 def branches(self, nodes):
148 148 n = " ".join(map(hex, nodes))
149 149 d = self.do_read("branches", nodes=n)
150 150 try:
151 151 br = [ tuple(map(bin, b.split(" "))) for b in d.splitlines() ]
152 152 return br
153 153 except:
154 154 raise error.ResponseError(_("unexpected response:"), d)
155 155
156 156 def between(self, pairs):
157 157 batch = 8 # avoid giant requests
158 158 r = []
159 159 for i in xrange(0, len(pairs), batch):
160 160 n = " ".join(["-".join(map(hex, p)) for p in pairs[i:i + batch]])
161 161 d = self.do_read("between", pairs=n)
162 162 try:
163 163 r += [ l and map(bin, l.split(" ")) or [] for l in d.splitlines() ]
164 164 except:
165 165 raise error.ResponseError(_("unexpected response:"), d)
166 166 return r
167 167
168 168 def changegroup(self, nodes, kind):
169 169 n = " ".join(map(hex, nodes))
170 170 f = self.do_cmd("changegroup", roots=n)
171 171 return util.chunkbuffer(zgenerator(f))
172 172
173 173 def changegroupsubset(self, bases, heads, source):
174 174 self.requirecap('changegroupsubset', _('look up remote changes'))
175 175 baselst = " ".join([hex(n) for n in bases])
176 176 headlst = " ".join([hex(n) for n in heads])
177 177 f = self.do_cmd("changegroupsubset", bases=baselst, heads=headlst)
178 178 return util.chunkbuffer(zgenerator(f))
179 179
180 180 def unbundle(self, cg, heads, source):
181 181 # have to stream bundle to a temp file because we do not have
182 182 # http 1.1 chunked transfer.
183 183
184 184 type = ""
185 185 types = self.capable('unbundle')
186 186 # servers older than d1b16a746db6 will send 'unbundle' as a
187 187 # boolean capability
188 188 try:
189 189 types = types.split(',')
190 190 except AttributeError:
191 191 types = [""]
192 192 if types:
193 193 for x in types:
194 194 if x in changegroup.bundletypes:
195 195 type = x
196 196 break
197 197
198 198 tempname = changegroup.writebundle(cg, None, type)
199 199 fp = url.httpsendfile(tempname, "rb")
200 200 try:
201 201 try:
202 202 resp = self.do_read(
203 203 'unbundle', data=fp,
204 204 headers={'Content-Type': 'application/octet-stream'},
205 205 heads=' '.join(map(hex, heads)))
206 206 resp_code, output = resp.split('\n', 1)
207 207 try:
208 208 ret = int(resp_code)
209 209 except ValueError, err:
210 210 raise error.ResponseError(
211 211 _('push failed (unexpected response):'), resp)
212 212 self.ui.write(output)
213 213 return ret
214 214 except socket.error, err:
215 215 if err[0] in (errno.ECONNRESET, errno.EPIPE):
216 216 raise util.Abort(_('push failed: %s') % err[1])
217 217 raise util.Abort(err[1])
218 218 finally:
219 219 fp.close()
220 220 os.unlink(tempname)
221 221
222 222 def stream_out(self):
223 223 return self.do_cmd('stream_out')
224 224
225 225 class httpsrepository(httprepository):
226 226 def __init__(self, ui, path):
227 227 if not url.has_https:
228 228 raise util.Abort(_('Python support for SSL and HTTPS '
229 229 'is not installed'))
230 230 httprepository.__init__(self, ui, path)
231 231
232 232 def instance(ui, path, create):
233 233 if create:
234 234 raise util.Abort(_('cannot create new http repository'))
235 235 try:
236 236 if path.startswith('https:'):
237 237 inst = httpsrepository(ui, path)
238 238 else:
239 239 inst = httprepository(ui, path)
240 240 inst.between([(nullid, nullid)])
241 241 return inst
242 242 except error.RepoError:
243 243 ui.note('(falling back to static-http)\n')
244 244 return statichttprepo.instance(ui, "static-" + path, create)
@@ -1,350 +1,350
1 1 # ui.py - user interface bits for mercurial
2 2 #
3 3 # Copyright 2005-2007 Matt Mackall <mpm@selenic.com>
4 4 #
5 5 # This software may be used and distributed according to the terms
6 6 # of the GNU General Public License, incorporated herein by reference.
7 7
8 8 from i18n import _
9 9 import errno, getpass, os, re, socket, sys, tempfile
10 10 import config, traceback, util, error
11 11
12 12 _booleans = {'1':True, 'yes':True, 'true':True, 'on':True,
13 13 '0':False, 'no':False, 'false':False, 'off':False}
14 14
15 15 class ui(object):
16 16 def __init__(self, src=None):
17 17 self._buffers = []
18 18 self.quiet = self.verbose = self.debugflag = self._traceback = False
19 19 self.interactive = self._reportuntrusted = True
20 20 self._ocfg = config.config() # overlay
21 21 self._tcfg = config.config() # trusted
22 22 self._ucfg = config.config() # untrusted
23 23 self._trustusers = {}
24 24 self._trustgroups = {}
25 25
26 26 if src:
27 27 self._tcfg = src._tcfg.copy()
28 28 self._ucfg = src._ucfg.copy()
29 29 self._ocfg = src._ocfg.copy()
30 30 self._trustusers = src._trustusers.copy()
31 31 self._trustgroups = src._trustgroups.copy()
32 32 self.fixconfig()
33 33 else:
34 34 # we always trust global config files
35 35 for f in util.rcpath():
36 36 self.readconfig(f, trust=True)
37 37 def copy(self):
38 38 return ui(self)
39 39
40 40 _isatty = None
41 41 def isatty(self):
42 42 if ui._isatty is None:
43 43 try:
44 44 ui._isatty = sys.stdin.isatty()
45 45 except AttributeError: # not a real file object
46 46 ui._isatty = False
47 47 except IOError:
48 48 # access to stdin is unsafe in a WSGI environment
49 49 ui._isatty = False
50 50 return ui._isatty
51 51
52 52 def _is_trusted(self, fp, f):
53 53 st = util.fstat(fp)
54 54 if util.isowner(fp, st):
55 55 return True
56 56
57 57 tusers, tgroups = self._trustusers, self._trustgroups
58 58 if '*' in tusers or '*' in tgroups:
59 59 return True
60 60
61 61 user = util.username(st.st_uid)
62 62 group = util.groupname(st.st_gid)
63 63 if user in tusers or group in tgroups or user == util.username():
64 64 return True
65 65
66 66 if self._reportuntrusted:
67 67 self.warn(_('Not trusting file %s from untrusted '
68 68 'user %s, group %s\n') % (f, user, group))
69 69 return False
70 70
71 71 def readconfig(self, filename, root=None, trust=False,
72 72 sections = None):
73 73 try:
74 74 fp = open(filename)
75 75 except IOError:
76 76 if not sections: # ignore unless we were looking for something
77 77 return
78 78 raise
79 79
80 80 cfg = config.config()
81 81 trusted = sections or trust or self._is_trusted(fp, filename)
82 82
83 83 try:
84 84 cfg.read(filename, fp, sections=sections)
85 85 except error.ConfigError, inst:
86 86 if trusted:
87 87 raise
88 88 self.warn(_("Ignored: %s\n") % str(inst))
89 89
90 90 if trusted:
91 91 self._tcfg.update(cfg)
92 92 self._tcfg.update(self._ocfg)
93 93 self._ucfg.update(cfg)
94 94 self._ucfg.update(self._ocfg)
95 95
96 96 if root is None:
97 97 root = os.path.expanduser('~')
98 98 self.fixconfig(root=root)
99 99
100 100 def fixconfig(self, root=None):
101 101 # translate paths relative to root (or home) into absolute paths
102 102 root = root or os.getcwd()
103 103 for c in self._tcfg, self._ucfg, self._ocfg:
104 104 for n, p in c.items('paths'):
105 105 if p and "://" not in p and not os.path.isabs(p):
106 106 c.set("paths", n, os.path.normpath(os.path.join(root, p)))
107 107
108 108 # update ui options
109 109 self.debugflag = self.configbool('ui', 'debug')
110 110 self.verbose = self.debugflag or self.configbool('ui', 'verbose')
111 111 self.quiet = not self.debugflag and self.configbool('ui', 'quiet')
112 112 if self.verbose and self.quiet:
113 113 self.quiet = self.verbose = False
114 114 self._reportuntrusted = self.configbool("ui", "report_untrusted", True)
115 115 self.interactive = self.configbool("ui", "interactive", self.isatty())
116 116 self._traceback = self.configbool('ui', 'traceback', False)
117 117
118 118 # update trust information
119 119 for user in self.configlist('trusted', 'users'):
120 120 self._trustusers[user] = 1
121 121 for group in self.configlist('trusted', 'groups'):
122 122 self._trustgroups[group] = 1
123 123
124 124 def setconfig(self, section, name, value):
125 125 for cfg in (self._ocfg, self._tcfg, self._ucfg):
126 126 cfg.set(section, name, value)
127 127 self.fixconfig()
128 128
129 129 def _data(self, untrusted):
130 130 return untrusted and self._ucfg or self._tcfg
131 131
132 132 def configsource(self, section, name, untrusted=False):
133 133 return self._data(untrusted).source(section, name) or 'none'
134 134
135 135 def config(self, section, name, default=None, untrusted=False):
136 136 value = self._data(untrusted).get(section, name, default)
137 137 if self.debugflag and not untrusted and self._reportuntrusted:
138 138 uvalue = self._ucfg.get(section, name)
139 139 if uvalue is not None and uvalue != value:
140 140 self.debug(_("ignoring untrusted configuration option "
141 141 "%s.%s = %s\n") % (section, name, uvalue))
142 142 return value
143 143
144 144 def configbool(self, section, name, default=False, untrusted=False):
145 145 v = self.config(section, name, None, untrusted)
146 146 if v == None:
147 147 return default
148 148 if v.lower() not in _booleans:
149 149 raise error.ConfigError(_("%s.%s not a boolean ('%s')")
150 150 % (section, name, v))
151 151 return _booleans[v.lower()]
152 152
153 153 def configlist(self, section, name, default=None, untrusted=False):
154 154 """Return a list of comma/space separated strings"""
155 155 result = self.config(section, name, untrusted=untrusted)
156 156 if result is None:
157 157 result = default or []
158 158 if isinstance(result, basestring):
159 159 result = result.replace(",", " ").split()
160 160 return result
161 161
162 162 def has_section(self, section, untrusted=False):
163 163 '''tell whether section exists in config.'''
164 164 return section in self._data(untrusted)
165 165
166 166 def configitems(self, section, untrusted=False):
167 167 items = self._data(untrusted).items(section)
168 168 if self.debugflag and not untrusted and self._reportuntrusted:
169 169 for k,v in self._ucfg.items(section):
170 170 if self._tcfg.get(section, k) != v:
171 171 self.debug(_("ignoring untrusted configuration option "
172 172 "%s.%s = %s\n") % (section, k, v))
173 173 return items
174 174
175 175 def walkconfig(self, untrusted=False):
176 176 cfg = self._data(untrusted)
177 177 for section in cfg.sections():
178 178 for name, value in self.configitems(section, untrusted):
179 179 yield section, name, str(value).replace('\n', '\\n')
180 180
181 181 def username(self):
182 182 """Return default username to be used in commits.
183 183
184 184 Searched in this order: $HGUSER, [ui] section of hgrcs, $EMAIL
185 185 and stop searching if one of these is set.
186 186 If not found and ui.askusername is True, ask the user, else use
187 187 ($LOGNAME or $USER or $LNAME or $USERNAME) + "@full.hostname".
188 188 """
189 189 user = os.environ.get("HGUSER")
190 190 if user is None:
191 191 user = self.config("ui", "username")
192 192 if user is None:
193 193 user = os.environ.get("EMAIL")
194 194 if user is None and self.configbool("ui", "askusername"):
195 195 user = self.prompt(_("enter a commit username:"), default=None)
196 196 if user is None:
197 197 try:
198 198 user = '%s@%s' % (util.getuser(), socket.getfqdn())
199 199 self.warn(_("No username found, using '%s' instead\n") % user)
200 200 except KeyError:
201 201 pass
202 202 if not user:
203 203 raise util.Abort(_("Please specify a username."))
204 204 if "\n" in user:
205 205 raise util.Abort(_("username %s contains a newline\n") % repr(user))
206 206 return user
207 207
208 208 def shortuser(self, user):
209 209 """Return a short representation of a user name or email address."""
210 210 if not self.verbose: user = util.shortuser(user)
211 211 return user
212 212
213 213 def _path(self, loc):
214 214 p = self.config('paths', loc)
215 215 if p and '%%' in p:
216 216 ui.warn('(deprecated \'\%\%\' in path %s=%s from %s)\n' %
217 217 (loc, p, self.configsource('paths', loc)))
218 218 p = p.replace('%%', '%')
219 219 return p
220 220
221 221 def expandpath(self, loc, default=None):
222 222 """Return repository location relative to cwd or from [paths]"""
223 223 if "://" in loc or os.path.isdir(os.path.join(loc, '.hg')):
224 224 return loc
225 225
226 226 path = self._path(loc)
227 227 if not path and default is not None:
228 228 path = self._path(default)
229 229 return path or loc
230 230
231 231 def pushbuffer(self):
232 232 self._buffers.append([])
233 233
234 234 def popbuffer(self):
235 235 return "".join(self._buffers.pop())
236 236
237 237 def write(self, *args):
238 238 if self._buffers:
239 239 self._buffers[-1].extend([str(a) for a in args])
240 240 else:
241 241 for a in args:
242 242 sys.stdout.write(str(a))
243 243
244 244 def write_err(self, *args):
245 245 try:
246 246 if not sys.stdout.closed: sys.stdout.flush()
247 247 for a in args:
248 248 sys.stderr.write(str(a))
249 249 # stderr may be buffered under win32 when redirected to files,
250 250 # including stdout.
251 251 if not sys.stderr.closed: sys.stderr.flush()
252 252 except IOError, inst:
253 253 if inst.errno != errno.EPIPE:
254 254 raise
255 255
256 256 def flush(self):
257 257 try: sys.stdout.flush()
258 258 except: pass
259 259 try: sys.stderr.flush()
260 260 except: pass
261 261
262 262 def _readline(self, prompt=''):
263 263 if self.isatty():
264 264 try:
265 265 # magically add command line editing support, where
266 266 # available
267 267 import readline
268 268 # force demandimport to really load the module
269 269 readline.read_history_file
270 270 # windows sometimes raises something other than ImportError
271 271 except Exception:
272 272 pass
273 273 line = raw_input(prompt)
274 274 # When stdin is in binary mode on Windows, it can cause
275 275 # raw_input() to emit an extra trailing carriage return
276 276 if os.linesep == '\r\n' and line and line[-1] == '\r':
277 277 line = line[:-1]
278 278 return line
279 279
280 280 def prompt(self, msg, pat=None, default="y"):
281 281 """Prompt user with msg, read response, and ensure it matches pat
282 282
283 283 If not interactive -- the default is returned
284 284 """
285 285 if not self.interactive:
286 286 self.note(msg, ' ', default, "\n")
287 287 return default
288 288 while True:
289 289 try:
290 290 r = self._readline(msg + ' ')
291 291 if not r:
292 292 return default
293 293 if not pat or re.match(pat, r):
294 294 return r
295 295 else:
296 296 self.write(_("unrecognized response\n"))
297 297 except EOFError:
298 298 raise util.Abort(_('response expected'))
299 299
300 300 def getpass(self, prompt=None, default=None):
301 301 if not self.interactive: return default
302 302 try:
303 303 return getpass.getpass(prompt or _('password: '))
304 304 except EOFError:
305 305 raise util.Abort(_('response expected'))
306 306 def status(self, *msg):
307 307 if not self.quiet: self.write(*msg)
308 308 def warn(self, *msg):
309 309 self.write_err(*msg)
310 310 def note(self, *msg):
311 311 if self.verbose: self.write(*msg)
312 312 def debug(self, *msg):
313 313 if self.debugflag: self.write(*msg)
314 314 def edit(self, text, user):
315 315 (fd, name) = tempfile.mkstemp(prefix="hg-editor-", suffix=".txt",
316 316 text=True)
317 317 try:
318 318 f = os.fdopen(fd, "w")
319 319 f.write(text)
320 320 f.close()
321 321
322 322 editor = self.geteditor()
323 323
324 324 util.system("%s \"%s\"" % (editor, name),
325 325 environ={'HGUSER': user},
326 326 onerr=util.Abort, errprefix=_("edit failed"))
327 327
328 328 f = open(name)
329 329 t = f.read()
330 330 f.close()
331 331 t = re.sub("(?m)^HG:.*\n", "", t)
332 332 finally:
333 333 os.unlink(name)
334 334
335 335 return t
336 336
337 def print_exc(self):
337 def traceback(self):
338 338 '''print exception traceback if traceback printing enabled.
339 339 only to call in exception handler. returns true if traceback
340 340 printed.'''
341 341 if self._traceback:
342 342 traceback.print_exc()
343 343 return self._traceback
344 344
345 345 def geteditor(self):
346 346 '''return editor to use'''
347 347 return (os.environ.get("HGEDITOR") or
348 348 self.config("ui", "editor") or
349 349 os.environ.get("VISUAL") or
350 350 os.environ.get("EDITOR", "vi"))
General Comments 0
You need to be logged in to leave comments. Login now