##// END OF EJS Templates
redo merge with mpm....
Vadim Gelfer -
r2921:addb58e3 merge default
parent child Browse files
Show More
@@ -0,0 +1,68 b''
1 # mail.py - mail sending bits for mercurial
2 #
3 # Copyright 2006 Matt Mackall <mpm@selenic.com>
4 #
5 # This software may be used and distributed according to the terms
6 # of the GNU General Public License, incorporated herein by reference.
7
8 from i18n import gettext as _
9 from demandload import *
10 demandload(globals(), "os re smtplib templater util")
11
12 def _smtp(ui):
13 '''send mail using smtp.'''
14
15 local_hostname = ui.config('smtp', 'local_hostname')
16 s = smtplib.SMTP(local_hostname=local_hostname)
17 mailhost = ui.config('smtp', 'host')
18 if not mailhost:
19 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
20 mailport = int(ui.config('smtp', 'port', 25))
21 self.note(_('sending mail: smtp host %s, port %s\n') %
22 (mailhost, mailport))
23 s.connect(host=mailhost, port=mailport)
24 if ui.configbool('smtp', 'tls'):
25 ui.note(_('(using tls)\n'))
26 s.ehlo()
27 s.starttls()
28 s.ehlo()
29 username = ui.config('smtp', 'username')
30 password = ui.config('smtp', 'password')
31 if username and password:
32 ui.note(_('(authenticating to mail server as %s)\n') %
33 (username))
34 s.login(username, password)
35 return s
36
37 class _sendmail(object):
38 '''send mail using sendmail.'''
39
40 def __init__(self, ui, program):
41 self.ui = ui
42 self.program = program
43
44 def sendmail(self, sender, recipients, msg):
45 cmdline = '%s -f %s %s' % (
46 self.program, templater.email(sender),
47 ' '.join(map(templater.email, recipients)))
48 self.ui.note(_('sending mail: %s\n') % cmdline)
49 fp = os.popen(cmdline, 'w')
50 fp.write(msg)
51 ret = fp.close()
52 if ret:
53 raise util.Abort('%s %s' % (
54 os.path.basename(self.program.split(None, 1)[0]),
55 util.explain_exit(ret)[0]))
56
57 def connect(ui):
58 '''make a mail connection. object returned has one method, sendmail.
59 call as sendmail(sender, list-of-recipients, msg).'''
60
61 method = ui.config('email', 'method', 'smtp')
62 if method == 'smtp':
63 return smtp(ui)
64
65 return sendmail(ui, method)
66
67 def sendmail(ui, sender, recipients, msg):
68 return connect(ui).sendmail(sender, recipients, msg)
@@ -77,7 +77,7 b' class queue:'
77
77
78 def diffopts(self):
78 def diffopts(self):
79 if self._diffopts is None:
79 if self._diffopts is None:
80 self._diffopts = self.ui.diffopts()
80 self._diffopts = patch.diffopts(self.ui)
81 return self._diffopts
81 return self._diffopts
82
82
83 def join(self, *p):
83 def join(self, *p):
@@ -67,8 +67,8 b''
67 from mercurial.demandload import *
67 from mercurial.demandload import *
68 from mercurial.i18n import gettext as _
68 from mercurial.i18n import gettext as _
69 from mercurial.node import *
69 from mercurial.node import *
70 demandload(globals(), 'email.Parser mercurial:commands,patch,templater,util')
70 demandload(globals(), 'mercurial:commands,patch,templater,util,mail')
71 demandload(globals(), 'fnmatch socket time')
71 demandload(globals(), 'email.Parser fnmatch socket time')
72
72
73 # template for single changeset can include email headers.
73 # template for single changeset can include email headers.
74 single_template = '''
74 single_template = '''
@@ -229,8 +229,8 b' class notifier(object):'
229 else:
229 else:
230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
230 self.ui.status(_('notify: sending %d subscribers %d changes\n') %
231 (len(self.subs), count))
231 (len(self.subs), count))
232 mail = self.ui.sendmail()
232 mail.sendmail(self.ui, templater.email(msg['From']),
233 mail.sendmail(templater.email(msg['From']), self.subs, msgtext)
233 self.subs, msgtext)
234
234
235 def diff(self, node, ref):
235 def diff(self, node, ref):
236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
236 maxdiff = int(self.ui.config('notify', 'maxdiff', 300))
@@ -241,7 +241,7 b' def patchbomb(ui, repo, *revs, **opts):'
241 ui.write('\n')
241 ui.write('\n')
242
242
243 if not opts['test'] and not opts['mbox']:
243 if not opts['test'] and not opts['mbox']:
244 mail = ui.sendmail()
244 mailer = mail.connect(ui)
245 parent = None
245 parent = None
246
246
247 # Calculate UTC offset
247 # Calculate UTC offset
@@ -290,7 +290,7 b' def patchbomb(ui, repo, *revs, **opts):'
290 ui.status('Sending ', m['Subject'], ' ...\n')
290 ui.status('Sending ', m['Subject'], ' ...\n')
291 # Exim does not remove the Bcc field
291 # Exim does not remove the Bcc field
292 del m['Bcc']
292 del m['Bcc']
293 mail.sendmail(sender, to + bcc + cc, m.as_string(0))
293 mailer.sendmail(sender, to + bcc + cc, m.as_string(0))
294
294
295 cmdtable = {
295 cmdtable = {
296 'email':
296 'email':
@@ -1347,7 +1347,7 b' def diff(ui, repo, *pats, **opts):'
1347 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1347 fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts)
1348
1348
1349 patch.diff(repo, node1, node2, fns, match=matchfn,
1349 patch.diff(repo, node1, node2, fns, match=matchfn,
1350 opts=ui.diffopts(opts))
1350 opts=patch.diffopts(ui, opts))
1351
1351
1352 def export(ui, repo, *changesets, **opts):
1352 def export(ui, repo, *changesets, **opts):
1353 """dump the header and diffs for one or more changesets
1353 """dump the header and diffs for one or more changesets
@@ -1384,7 +1384,8 b' def export(ui, repo, *changesets, **opts'
1384 else:
1384 else:
1385 ui.note(_('exporting patch:\n'))
1385 ui.note(_('exporting patch:\n'))
1386 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1386 patch.export(repo, map(repo.lookup, revs), template=opts['output'],
1387 switch_parent=opts['switch_parent'], opts=ui.diffopts(opts))
1387 switch_parent=opts['switch_parent'],
1388 opts=patch.diffopts(ui, opts))
1388
1389
1389 def forget(ui, repo, *pats, **opts):
1390 def forget(ui, repo, *pats, **opts):
1390 """don't add the specified files on the next commit (DEPRECATED)
1391 """don't add the specified files on the next commit (DEPRECATED)
@@ -65,26 +65,25 b' class filelog(revlog):'
65 return (m["copy"], bin(m["copyrev"]))
65 return (m["copy"], bin(m["copyrev"]))
66 return False
66 return False
67
67
68 def size(self, rev):
69 """return the size of a given revision"""
70
71 # for revisions with renames, we have to go the slow way
72 node = self.node(rev)
73 if self.renamed(node):
74 return len(self.read(node))
75
76 return revlog.size(self, rev)
77
68 def cmp(self, node, text):
78 def cmp(self, node, text):
69 """compare text with a given file revision"""
79 """compare text with a given file revision"""
70
80
71 # for renames, we have to go the slow way
81 # for renames, we have to go the slow way
72 if self.renamed(node):
82 if self.renamed(node):
73 t2 = self.read(node)
83 t2 = self.read(node)
74 return t2 == text
84 return t2 != text
75
76 p1, p2 = self.parents(node)
77 h = hash(text, p1, p2)
78
79 return h != node
80
85
81 def makenode(self, node, text):
86 return revlog.cmp(self, node, text)
82 """calculate a file nodeid for text, descended or possibly
83 unchanged from node"""
84
85 if self.cmp(node, text):
86 return hash(text, node, nullid)
87 return node
88
87
89 def annotate(self, node):
88 def annotate(self, node):
90
89
@@ -11,7 +11,7 b' import os.path'
11 import mimetypes
11 import mimetypes
12 from mercurial.demandload import demandload
12 from mercurial.demandload import demandload
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
13 demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone")
14 demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch")
15 demandload(globals(), "mercurial:templater")
15 demandload(globals(), "mercurial:templater")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
16 demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile")
17 from mercurial.node import *
17 from mercurial.node import *
@@ -134,7 +134,7 b' class hgweb(object):'
134 modified, added, removed = map(lambda x: filterfiles(files, x),
134 modified, added, removed = map(lambda x: filterfiles(files, x),
135 (modified, added, removed))
135 (modified, added, removed))
136
136
137 diffopts = self.repo.ui.diffopts()
137 diffopts = patch.diffopts(ui)
138 for f in modified:
138 for f in modified:
139 to = r.file(f).read(mmap1[f])
139 to = r.file(f).read(mmap1[f])
140 tn = r.file(f).read(mmap2[f])
140 tn = r.file(f).read(mmap2[f])
@@ -10,6 +10,11 b' from i18n import gettext as _'
10 from demandload import *
10 from demandload import *
11 demandload(globals(), "util os tempfile")
11 demandload(globals(), "util os tempfile")
12
12
13 def fmerge(f, local, other, ancestor):
14 """merge executable flags"""
15 a, b, c = ancestor.execf(f), local.execf(f), other.execf(f)
16 return ((a^b) | (a^c)) ^ a
17
13 def merge3(repo, fn, my, other, p1, p2):
18 def merge3(repo, fn, my, other, p1, p2):
14 """perform a 3-way merge in the working directory"""
19 """perform a 3-way merge in the working directory"""
15
20
@@ -90,9 +95,7 b' def update(repo, node, branchmerge=False'
90 if not force:
95 if not force:
91 for f in unknown:
96 for f in unknown:
92 if f in m2:
97 if f in m2:
93 t1 = repo.wread(f)
98 if repo.file(f).cmp(m2[f], repo.wread(f)):
94 t2 = repo.file(f).read(m2[f])
95 if cmp(t1, t2) != 0:
96 raise util.Abort(_("'%s' already exists in the working"
99 raise util.Abort(_("'%s' already exists in the working"
97 " dir and differs from remote") % f)
100 " dir and differs from remote") % f)
98
101
@@ -100,13 +103,14 b' def update(repo, node, branchmerge=False'
100 # we care about merging
103 # we care about merging
101 repo.ui.note(_("resolving manifests\n"))
104 repo.ui.note(_("resolving manifests\n"))
102 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
105 repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") %
103 (overwrite, branchmerge, partial and True or False, linear_path))
106 (overwrite, branchmerge, bool(partial), linear_path))
104 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
107 repo.ui.debug(_(" ancestor %s local %s remote %s\n") %
105 (short(man), short(m1n), short(m2n)))
108 (short(man), short(m1n), short(m2n)))
106
109
107 merge = {}
110 merge = {}
108 get = {}
111 get = {}
109 remove = []
112 remove = []
113 forget = []
110
114
111 # construct a working dir manifest
115 # construct a working dir manifest
112 mw = m1.copy()
116 mw = m1.copy()
@@ -114,6 +118,11 b' def update(repo, node, branchmerge=False'
114
118
115 for f in added + modified + unknown:
119 for f in added + modified + unknown:
116 mw[f] = ""
120 mw[f] = ""
121 # is the wfile new and matches m2?
122 if (f not in m1 and f in m2 and
123 not repo.file(f).cmp(m2[f], repo.wread(f))):
124 mw[f] = m2[f]
125
117 mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f)))
126 mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f)))
118
127
119 for f in deleted + removed:
128 for f in deleted + removed:
@@ -125,8 +134,8 b' def update(repo, node, branchmerge=False'
125 # the file, then we need to remove it from the dirstate, to
134 # the file, then we need to remove it from the dirstate, to
126 # prevent the dirstate from listing the file when it is no
135 # prevent the dirstate from listing the file when it is no
127 # longer in the manifest.
136 # longer in the manifest.
128 if not partial and linear_path and f not in m2:
137 if linear_path and f not in m2:
129 repo.dirstate.forget((f,))
138 forget.append(f)
130
139
131 # Compare manifests
140 # Compare manifests
132 for f, n in mw.iteritems():
141 for f, n in mw.iteritems():
@@ -135,25 +144,13 b' def update(repo, node, branchmerge=False'
135 if f in m2:
144 if f in m2:
136 s = 0
145 s = 0
137
146
138 # is the wfile new since m1, and match m2?
139 if f not in m1:
140 t1 = repo.wread(f)
141 t2 = repo.file(f).read(m2[f])
142 if cmp(t1, t2) == 0:
143 n = m2[f]
144 del t1, t2
145
146 # are files different?
147 # are files different?
147 if n != m2[f]:
148 if n != m2[f]:
148 a = ma.get(f, nullid)
149 a = ma.get(f, nullid)
149 # are both different from the ancestor?
150 # are both different from the ancestor?
150 if n != a and m2[f] != a:
151 if n != a and m2[f] != a:
151 repo.ui.debug(_(" %s versions differ, resolve\n") % f)
152 repo.ui.debug(_(" %s versions differ, resolve\n") % f)
152 # merge executable bits
153 merge[f] = (fmerge(f, mw, m2, ma), m1.get(f, nullid), m2[f])
153 # "if we changed or they changed, change in merge"
154 a, b, c = ma.execf(f), mw.execf(f), m2.execf(f)
155 mode = ((a^b) | (a^c)) ^ a
156 merge[f] = (mode, m1.get(f, nullid), m2[f])
157 s = 1
154 s = 1
158 # are we clobbering?
155 # are we clobbering?
159 # is remote's version newer?
156 # is remote's version newer?
@@ -172,9 +169,7 b' def update(repo, node, branchmerge=False'
172 repo.ui.debug(_(" updating permissions for %s\n") % f)
169 repo.ui.debug(_(" updating permissions for %s\n") % f)
173 util.set_exec(repo.wjoin(f), m2.execf(f))
170 util.set_exec(repo.wjoin(f), m2.execf(f))
174 else:
171 else:
175 a, b, c = ma.execf(f), mw.execf(f), m2.execf(f)
172 if fmerge(f, mw, m2, ma) != mw.execf(f):
176 mode = ((a^b) | (a^c)) ^ a
177 if mode != b:
178 repo.ui.debug(_(" updating permissions for %s\n")
173 repo.ui.debug(_(" updating permissions for %s\n")
179 % f)
174 % f)
180 util.set_exec(repo.wjoin(f), mode)
175 util.set_exec(repo.wjoin(f), mode)
@@ -230,6 +225,8 b' def update(repo, node, branchmerge=False'
230
225
231 del mw, m1, m2, ma
226 del mw, m1, m2, ma
232
227
228 ### apply phase
229
233 if overwrite:
230 if overwrite:
234 for f in merge:
231 for f in merge:
235 get[f] = merge[f][:2]
232 get[f] = merge[f][:2]
@@ -257,11 +254,6 b' def update(repo, node, branchmerge=False'
257 t = repo.file(f).read(node)
254 t = repo.file(f).read(node)
258 repo.wwrite(f, t)
255 repo.wwrite(f, t)
259 util.set_exec(repo.wjoin(f), flag)
256 util.set_exec(repo.wjoin(f), flag)
260 if not partial:
261 if branchmerge:
262 repo.dirstate.update([f], 'n', st_mtime=-1)
263 else:
264 repo.dirstate.update([f], 'n')
265
257
266 # merge the tricky bits
258 # merge the tricky bits
267 unresolved = []
259 unresolved = []
@@ -274,19 +266,6 b' def update(repo, node, branchmerge=False'
274 if ret:
266 if ret:
275 unresolved.append(f)
267 unresolved.append(f)
276 util.set_exec(repo.wjoin(f), flag)
268 util.set_exec(repo.wjoin(f), flag)
277 if not partial:
278 if branchmerge:
279 # We've done a branch merge, mark this file as merged
280 # so that we properly record the merger later
281 repo.dirstate.update([f], 'm')
282 else:
283 # We've update-merged a locally modified file, so
284 # we set the dirstate to emulate a normal checkout
285 # of that file some time in the past. Thus our
286 # merge will appear as a normal local file
287 # modification.
288 f_len = len(repo.file(f).read(other))
289 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
290
269
291 remove.sort()
270 remove.sort()
292 for f in remove:
271 for f in remove:
@@ -298,14 +277,40 b' def update(repo, node, branchmerge=False'
298 if inst.errno != errno.ENOENT:
277 if inst.errno != errno.ENOENT:
299 repo.ui.warn(_("update failed to remove %s: %s!\n") %
278 repo.ui.warn(_("update failed to remove %s: %s!\n") %
300 (f, inst.strerror))
279 (f, inst.strerror))
280
281 # update dirstate
301 if not partial:
282 if not partial:
283 repo.dirstate.setparents(p1, p2)
284 repo.dirstate.forget(forget)
302 if branchmerge:
285 if branchmerge:
303 repo.dirstate.update(remove, 'r')
286 repo.dirstate.update(remove, 'r')
304 else:
287 else:
305 repo.dirstate.forget(remove)
288 repo.dirstate.forget(remove)
306
289
307 if not partial:
290 files = get.keys()
308 repo.dirstate.setparents(p1, p2)
291 files.sort()
292 for f in files:
293 if branchmerge:
294 repo.dirstate.update([f], 'n', st_mtime=-1)
295 else:
296 repo.dirstate.update([f], 'n')
297
298 files = merge.keys()
299 files.sort()
300 for f in files:
301 if branchmerge:
302 # We've done a branch merge, mark this file as merged
303 # so that we properly record the merger later
304 repo.dirstate.update([f], 'm')
305 else:
306 # We've update-merged a locally modified file, so
307 # we set the dirstate to emulate a normal checkout
308 # of that file some time in the past. Thus our
309 # merge will appear as a normal local file
310 # modification.
311 fl = repo.file(f)
312 f_len = fl.size(fl.rev(other))
313 repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1)
309
314
310 if show_stats:
315 if show_stats:
311 stats = ((len(get), _("updated")),
316 stats = ((len(get), _("updated")),
@@ -267,6 +267,20 b' def patch(patchname, ui, strip=1, cwd=No'
267
267
268 return (files, fuzz)
268 return (files, fuzz)
269
269
270 def diffopts(ui, opts={}):
271 return mdiff.diffopts(
272 text=opts.get('text'),
273 git=(opts.get('git') or
274 ui.configbool('diff', 'git', None)),
275 showfunc=(opts.get('show_function') or
276 ui.configbool('diff', 'showfunc', None)),
277 ignorews=(opts.get('ignore_all_space') or
278 ui.configbool('diff', 'ignorews', None)),
279 ignorewsamount=(opts.get('ignore_space_change') or
280 ui.configbool('diff', 'ignorewsamount', None)),
281 ignoreblanklines=(opts.get('ignore_blank_lines') or
282 ui.configbool('diff', 'ignoreblanklines', None)))
283
270 def diff(repo, node1=None, node2=None, files=None, match=util.always,
284 def diff(repo, node1=None, node2=None, files=None, match=util.always,
271 fp=None, changes=None, opts=None):
285 fp=None, changes=None, opts=None):
272 '''print diff of changes to files between two nodes, or node and
286 '''print diff of changes to files between two nodes, or node and
@@ -766,6 +766,19 b' class revlog(object):'
766
766
767 raise RevlogError(_("No match found"))
767 raise RevlogError(_("No match found"))
768
768
769 def cmp(self, node, text):
770 """compare text with a given file revision"""
771 p1, p2 = self.parents(node)
772 return hash(text, p1, p2) != node
773
774 def makenode(self, node, text):
775 """calculate a file nodeid for text, descended or possibly
776 unchanged from node"""
777
778 if self.cmp(node, text):
779 return hash(text, node, nullid)
780 return node
781
769 def diff(self, a, b):
782 def diff(self, a, b):
770 """return a delta between two revisions"""
783 """return a delta between two revisions"""
771 return mdiff.textdiff(a, b)
784 return mdiff.textdiff(a, b)
@@ -7,7 +7,7 b''
7
7
8 from i18n import gettext as _
8 from i18n import gettext as _
9 from demandload import *
9 from demandload import *
10 demandload(globals(), "errno getpass os re smtplib socket sys tempfile")
10 demandload(globals(), "errno getpass os re socket sys tempfile")
11 demandload(globals(), "ConfigParser mdiff templater traceback util")
11 demandload(globals(), "ConfigParser mdiff templater traceback util")
12
12
13 class ui(object):
13 class ui(object):
@@ -169,20 +169,6 b' class ui(object):'
169 result[key.lower()] = value
169 result[key.lower()] = value
170 return result
170 return result
171
171
172 def diffopts(self, opts={}):
173 return mdiff.diffopts(
174 text=opts.get('text'),
175 showfunc=(opts.get('show_function') or
176 self.configbool('diff', 'showfunc', None)),
177 git=(opts.get('git') or
178 self.configbool('diff', 'git', None)),
179 ignorews=(opts.get('ignore_all_space') or
180 self.configbool('diff', 'ignorews', None)),
181 ignorewsamount=(opts.get('ignore_space_change') or
182 self.configbool('diff', 'ignorewsamount', None)),
183 ignoreblanklines=(opts.get('ignore_blank_lines') or
184 self.configbool('diff', 'ignoreblanklines', None)))
185
186 def username(self):
172 def username(self):
187 """Return default username to be used in commits.
173 """Return default username to be used in commits.
188
174
@@ -295,62 +281,6 b' class ui(object):'
295
281
296 return t
282 return t
297
283
298 def sendmail(self):
299 '''send mail message. object returned has one method, sendmail.
300 call as sendmail(sender, list-of-recipients, msg).'''
301
302 def smtp():
303 '''send mail using smtp.'''
304
305 local_hostname = self.config('smtp', 'local_hostname')
306 s = smtplib.SMTP(local_hostname=local_hostname)
307 mailhost = self.config('smtp', 'host')
308 if not mailhost:
309 raise util.Abort(_('no [smtp]host in hgrc - cannot send mail'))
310 mailport = int(self.config('smtp', 'port', 25))
311 self.note(_('sending mail: smtp host %s, port %s\n') %
312 (mailhost, mailport))
313 s.connect(host=mailhost, port=mailport)
314 if self.configbool('smtp', 'tls'):
315 self.note(_('(using tls)\n'))
316 s.ehlo()
317 s.starttls()
318 s.ehlo()
319 username = self.config('smtp', 'username')
320 password = self.config('smtp', 'password')
321 if username and password:
322 self.note(_('(authenticating to mail server as %s)\n') %
323 (username))
324 s.login(username, password)
325 return s
326
327 class sendmail(object):
328 '''send mail using sendmail.'''
329
330 def __init__(self, ui, program):
331 self.ui = ui
332 self.program = program
333
334 def sendmail(self, sender, recipients, msg):
335 cmdline = '%s -f %s %s' % (
336 self.program, templater.email(sender),
337 ' '.join(map(templater.email, recipients)))
338 self.ui.note(_('sending mail: %s\n') % cmdline)
339 fp = os.popen(cmdline, 'w')
340 fp.write(msg)
341 ret = fp.close()
342 if ret:
343 raise util.Abort('%s %s' % (
344 os.path.basename(self.program.split(None, 1)[0]),
345 util.explain_exit(ret)[0]))
346
347 method = self.config('email', 'method', 'smtp')
348 if method == 'smtp':
349 mail = smtp()
350 else:
351 mail = sendmail(self, method)
352 return mail
353
354 def print_exc(self):
284 def print_exc(self):
355 '''print exception traceback if traceback printing enabled.
285 '''print exception traceback if traceback printing enabled.
356 only to call in exception handler. returns true if traceback
286 only to call in exception handler. returns true if traceback
General Comments 0
You need to be logged in to leave comments. Login now