diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -77,7 +77,7 @@ class queue: def diffopts(self): if self._diffopts is None: - self._diffopts = self.ui.diffopts() + self._diffopts = patch.diffopts(self.ui) return self._diffopts def join(self, *p): @@ -400,15 +400,39 @@ class queue: '''Apply patchfile to the working directory. patchfile: file name of patch''' try: - (files, fuzz) = patch.patch(patchfile, self.ui, strip=1, - cwd=repo.root) - except Exception, inst: - self.ui.note(str(inst) + '\n') - if not self.ui.verbose: - self.ui.warn("patch failed, unable to continue (try -v)\n") - return (False, [], False) + pp = util.find_in_path('gpatch', os.environ.get('PATH', ''), 'patch') + f = os.popen("%s -d %s -p1 --no-backup-if-mismatch < %s" % + (pp, util.shellquote(repo.root), util.shellquote(patchfile))) + except: + self.ui.warn("patch failed, unable to continue (try -v)\n") + return (None, [], False) + files = [] + fuzz = False + for l in f: + l = l.rstrip('\r\n'); + if self.ui.verbose: + self.ui.warn(l + "\n") + if l[:14] == 'patching file ': + pf = os.path.normpath(util.parse_patch_output(l)) + if pf not in files: + files.append(pf) + printed_file = False + file_str = l + elif l.find('with fuzz') >= 0: + if not printed_file: + self.ui.warn(file_str + '\n') + printed_file = True + self.ui.warn(l + '\n') + fuzz = True + elif l.find('saving rejects to file') >= 0: + self.ui.warn(l + '\n') + elif l.find('FAILED') >= 0: + if not printed_file: + self.ui.warn(file_str + '\n') + printed_file = True + self.ui.warn(l + '\n') - return (True, files.keys(), fuzz) + return (not f.close(), files, fuzz) def apply(self, repo, series, list=False, update_status=True, strict=False, patchdir=None, merge=None, wlock=None): @@ -482,28 +506,21 @@ class queue: tr.close() return (err, n) - def delete(self, repo, patches, keep=False): - realpatches = [] - for patch in patches: - patch = self.lookup(patch, strict=True) - info = self.isapplied(patch) - if info: - raise util.Abort(_("cannot delete applied patch %s") % patch) - if patch not in self.series: - raise util.Abort(_("patch %s not in series file") % patch) - realpatches.append(patch) - - if not keep: + def delete(self, repo, patch, force=False): + patch = self.lookup(patch, strict=True) + info = self.isapplied(patch) + if info: + raise util.Abort(_("cannot delete applied patch %s") % patch) + if patch not in self.series: + raise util.Abort(_("patch %s not in series file") % patch) + if force: r = self.qrepo() if r: - r.remove(realpatches, True) + r.remove([patch], True) else: os.unlink(self.join(patch)) - - indices = [self.find_series(p) for p in realpatches] - indices.sort() - for i in indices[-1::-1]: - del self.full_series[i] + i = self.find_series(patch) + del self.full_series[i] self.parse_series() self.series_dirty = 1 @@ -1283,13 +1300,13 @@ class queue: if qrepo: qrepo.add(added) -def delete(ui, repo, patch, *patches, **opts): - """remove patches from queue +def delete(ui, repo, patch, **opts): + """remove a patch from the series file - The patches must not be applied. - With -k, the patch files are preserved in the patch directory.""" + The patch must not be applied. + With -f, deletes the patch file as well as the series entry.""" q = repo.mq - q.delete(repo, (patch,) + patches, keep=opts.get('keep')) + q.delete(repo, patch, force=opts.get('force')) q.save_dirty() return 0 @@ -1447,7 +1464,7 @@ def fold(ui, repo, *files, **opts): applied to the current patch in the order given. If all the patches apply successfully, the current patch will be refreshed with the new cumulative patch, and the folded patches will - be deleted. With -k/--keep, the folded patch files will not + be deleted. With -f/--force, the folded patch files will be removed afterwards. The header for each folded patch will be concatenated with @@ -1497,7 +1514,7 @@ def fold(ui, repo, *files, **opts): q.refresh(repo, msg=message) for patch in patches: - q.delete(repo, patch, keep=opts['keep']) + q.delete(repo, patch, force=opts['force']) q.save_dirty() @@ -1886,14 +1903,14 @@ cmdtable = { commands.table["^commit|ci"][1], 'hg qcommit [OPTION]... [FILE]...'), "^qdiff": (diff, [], 'hg qdiff [FILE]...'), - "qdelete|qremove|qrm": + "qdelete": (delete, - [('k', 'keep', None, _('keep patch file'))], - 'hg qdelete [-k] PATCH'), + [('f', 'force', None, _('delete patch file'))], + 'hg qdelete [-f] PATCH'), 'qfold': (fold, [('e', 'edit', None, _('edit patch header')), - ('k', 'keep', None, _('keep folded patch files')), + ('f', 'force', None, _('delete folded patch files')), ('m', 'message', '', _('set patch header to ')), ('l', 'logfile', '', _('set patch header to contents of '))], 'hg qfold [-e] [-m ] [-l self.startrev: + # forward: all descendants + if not self.roots: + self.roots.append(self.startrev) + for parent in realparents(rev): + if parent in self.roots: + self.roots.append(rev) + return True else: - return filter(lambda x: x != -1, repo.changelog.parentrevs(rev)) - - if self.startrev == -1: - self.startrev = rev - return True - - if rev > self.startrev: - # forward: all descendants - if not self.roots: - self.roots.append(self.startrev) - for parent in realparents(rev): - if parent in self.roots: - self.roots.append(rev) + # backwards: all parents + if not self.roots: + self.roots.extend(realparents(self.startrev)) + if rev in self.roots: + self.roots.remove(rev) + self.roots.extend(realparents(rev)) return True - else: - # backwards: all parents - if not self.roots: - self.roots.extend(realparents(self.startrev)) - if rev in self.roots: - self.roots.remove(rev) - self.roots.extend(realparents(rev)) - return True - - return False - - # it might be worthwhile to do this in the iterator if the rev range - # is descending and the prune args are all within that range - for rev in opts.get('prune', ()): - rev = repo.changelog.rev(repo.lookup(rev)) - ff = followfilter() - stop = min(revs[0], revs[-1]) - for x in range(rev, stop-1, -1): - if ff.match(x) and wanted.has_key(x): - del wanted[x] - - def iterate(): + + return False + if follow and not files: ff = followfilter(onlyfirst=opts.get('follow_first')) def want(rev): - if ff.match(rev) and rev in wanted: - return True - return False + if rev not in wanted: + return False + return ff.match(rev) else: def want(rev): return rev in wanted @@ -1347,7 +1337,7 @@ def diff(ui, repo, *pats, **opts): fns, matchfn, anypats = cmdutil.matchpats(repo, pats, opts) patch.diff(repo, node1, node2, fns, match=matchfn, - opts=ui.diffopts(opts)) + opts=patch.diffopts(ui, opts)) def export(ui, repo, *changesets, **opts): """dump the header and diffs for one or more changesets @@ -1384,7 +1374,8 @@ def export(ui, repo, *changesets, **opts else: ui.note(_('exporting patch:\n')) patch.export(repo, map(repo.lookup, revs), template=opts['output'], - switch_parent=opts['switch_parent'], opts=ui.diffopts(opts)) + switch_parent=opts['switch_parent'], + opts=patch.diffopts(ui, opts)) def forget(ui, repo, *pats, **opts): """don't add the specified files on the next commit (DEPRECATED) @@ -1681,7 +1672,7 @@ def import_(ui, repo, patch1, *patches, message = None ui.debug(_('message:\n%s\n') % message) - files, fuzz = patch.patch(tmpname, ui, strip=strip, cwd=repo.root) + files = patch.patch(strip, tmpname, ui, cwd=repo.root) removes = [] if len(files) > 0: cfiles = files.keys() @@ -1969,29 +1960,9 @@ def merge(ui, repo, node=None, force=Non requested revision. Files that changed between either parent are marked as changed for the next commit and a commit must be performed before any further updates are allowed. - - If no revision is specified, the working directory's parent is a - head revision, and the repository contains exactly one other head, - the other head is merged with by default. Otherwise, an explicit - revision to merge with must be provided. """ - if node: - node = _lookup(repo, node, branch) - else: - heads = repo.heads() - if len(heads) > 2: - raise util.Abort(_('repo has %d heads - ' - 'please merge with an explicit rev') % - len(heads)) - if len(heads) == 1: - raise util.Abort(_('there is nothing to merge - ' - 'use "hg update" instead')) - parent = repo.dirstate.parents()[0] - if parent not in heads: - raise util.Abort(_('working dir not at a head rev - ' - 'use "hg update" or merge with an explicit rev')) - node = parent == heads[0] and heads[-1] or heads[0] + node = _lookup(repo, node, branch) return hg.merge(repo, node, force=force) def outgoing(ui, repo, dest=None, **opts): @@ -2897,7 +2868,6 @@ table = { ('a', 'text', None, _('treat all files as text')), ('p', 'show-function', None, _('show which function each change is in')), - ('g', 'git', None, _('use git extended diff format')), ('w', 'ignore-all-space', None, _('ignore white space when comparing lines')), ('b', 'ignore-space-change', None, @@ -2997,7 +2967,6 @@ table = { ('', 'style', '', _('display using template map file')), ('m', 'only-merges', None, _('show only merges')), ('p', 'patch', None, _('show patch')), - ('P', 'prune', [], _('do not display revision or any of its ancestors')), ('', 'template', '', _('display with template')), ('I', 'include', [], _('include names matching the given patterns')), ('X', 'exclude', [], _('exclude names matching the given patterns'))], diff --git a/mercurial/filelog.py b/mercurial/filelog.py --- a/mercurial/filelog.py +++ b/mercurial/filelog.py @@ -65,26 +65,25 @@ class filelog(revlog): return (m["copy"], bin(m["copyrev"])) return False + def size(self, rev): + """return the size of a given revision""" + + # for revisions with renames, we have to go the slow way + node = self.node(rev) + if self.renamed(node): + return len(self.read(node)) + + return revlog.size(self, rev) + def cmp(self, node, text): """compare text with a given file revision""" # for renames, we have to go the slow way if self.renamed(node): t2 = self.read(node) - return t2 == text - - p1, p2 = self.parents(node) - h = hash(text, p1, p2) - - return h != node + return t2 != text - def makenode(self, node, text): - """calculate a file nodeid for text, descended or possibly - unchanged from node""" - - if self.cmp(node, text): - return hash(text, node, nullid) - return node + return revlog.cmp(self, node, text) def annotate(self, node): diff --git a/mercurial/hgweb/hgweb_mod.py b/mercurial/hgweb/hgweb_mod.py --- a/mercurial/hgweb/hgweb_mod.py +++ b/mercurial/hgweb/hgweb_mod.py @@ -11,7 +11,7 @@ import os.path import mimetypes from mercurial.demandload import demandload demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile") -demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone") +demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch") demandload(globals(), "mercurial:templater") demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile") from mercurial.node import * @@ -134,7 +134,7 @@ class hgweb(object): modified, added, removed = map(lambda x: filterfiles(files, x), (modified, added, removed)) - diffopts = self.repo.ui.diffopts() + diffopts = patch.diffopts(ui) for f in modified: to = r.file(f).read(mmap1[f]) tn = r.file(f).read(mmap2[f]) diff --git a/mercurial/mail.py b/mercurial/mail.py new file mode 100644 --- /dev/null +++ b/mercurial/mail.py @@ -0,0 +1,68 @@ +# mail.py - mail sending bits for mercurial +# +# Copyright 2006 Matt Mackall +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +from i18n import gettext as _ +from demandload import * +demandload(globals(), "os re smtplib templater util") + +def _smtp(ui): + '''send mail using smtp.''' + + local_hostname = ui.config('smtp', 'local_hostname') + s = smtplib.SMTP(local_hostname=local_hostname) + mailhost = ui.config('smtp', 'host') + if not mailhost: + raise util.Abort(_('no [smtp]host in hgrc - cannot send mail')) + mailport = int(ui.config('smtp', 'port', 25)) + self.note(_('sending mail: smtp host %s, port %s\n') % + (mailhost, mailport)) + s.connect(host=mailhost, port=mailport) + if ui.configbool('smtp', 'tls'): + ui.note(_('(using tls)\n')) + s.ehlo() + s.starttls() + s.ehlo() + username = ui.config('smtp', 'username') + password = ui.config('smtp', 'password') + if username and password: + ui.note(_('(authenticating to mail server as %s)\n') % + (username)) + s.login(username, password) + return s + +class _sendmail(object): + '''send mail using sendmail.''' + + def __init__(self, ui, program): + self.ui = ui + self.program = program + + def sendmail(self, sender, recipients, msg): + cmdline = '%s -f %s %s' % ( + self.program, templater.email(sender), + ' '.join(map(templater.email, recipients))) + self.ui.note(_('sending mail: %s\n') % cmdline) + fp = os.popen(cmdline, 'w') + fp.write(msg) + ret = fp.close() + if ret: + raise util.Abort('%s %s' % ( + os.path.basename(self.program.split(None, 1)[0]), + util.explain_exit(ret)[0])) + +def connect(ui): + '''make a mail connection. object returned has one method, sendmail. + call as sendmail(sender, list-of-recipients, msg).''' + + method = ui.config('email', 'method', 'smtp') + if method == 'smtp': + return smtp(ui) + + return sendmail(ui, method) + +def sendmail(ui, sender, recipients, msg): + return connect(ui).sendmail(sender, recipients, msg) diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -10,6 +10,11 @@ from i18n import gettext as _ from demandload import * demandload(globals(), "util os tempfile") +def fmerge(f, local, other, ancestor): + """merge executable flags""" + a, b, c = ancestor.execf(f), local.execf(f), other.execf(f) + return ((a^b) | (a^c)) ^ a + def merge3(repo, fn, my, other, p1, p2): """perform a 3-way merge in the working directory""" @@ -90,9 +95,7 @@ def update(repo, node, branchmerge=False if not force: for f in unknown: if f in m2: - t1 = repo.wread(f) - t2 = repo.file(f).read(m2[f]) - if cmp(t1, t2) != 0: + if repo.file(f).cmp(m2[f], repo.wread(f)): raise util.Abort(_("'%s' already exists in the working" " dir and differs from remote") % f) @@ -100,13 +103,14 @@ def update(repo, node, branchmerge=False # we care about merging repo.ui.note(_("resolving manifests\n")) repo.ui.debug(_(" overwrite %s branchmerge %s partial %s linear %s\n") % - (overwrite, branchmerge, partial and True or False, linear_path)) + (overwrite, branchmerge, bool(partial), linear_path)) repo.ui.debug(_(" ancestor %s local %s remote %s\n") % (short(man), short(m1n), short(m2n))) merge = {} get = {} remove = [] + forget = [] # construct a working dir manifest mw = m1.copy() @@ -114,6 +118,11 @@ def update(repo, node, branchmerge=False for f in added + modified + unknown: mw[f] = "" + # is the wfile new and matches m2? + if (f not in m1 and f in m2 and + not repo.file(f).cmp(m2[f], repo.wread(f))): + mw[f] = m2[f] + mw.set(f, util.is_exec(repo.wjoin(f), mw.execf(f))) for f in deleted + removed: @@ -125,8 +134,8 @@ def update(repo, node, branchmerge=False # the file, then we need to remove it from the dirstate, to # prevent the dirstate from listing the file when it is no # longer in the manifest. - if not partial and linear_path and f not in m2: - repo.dirstate.forget((f,)) + if linear_path and f not in m2: + forget.append(f) # Compare manifests for f, n in mw.iteritems(): @@ -135,25 +144,13 @@ def update(repo, node, branchmerge=False if f in m2: s = 0 - # is the wfile new since m1, and match m2? - if f not in m1: - t1 = repo.wread(f) - t2 = repo.file(f).read(m2[f]) - if cmp(t1, t2) == 0: - n = m2[f] - del t1, t2 - # are files different? if n != m2[f]: a = ma.get(f, nullid) # are both different from the ancestor? if n != a and m2[f] != a: repo.ui.debug(_(" %s versions differ, resolve\n") % f) - # merge executable bits - # "if we changed or they changed, change in merge" - a, b, c = ma.execf(f), mw.execf(f), m2.execf(f) - mode = ((a^b) | (a^c)) ^ a - merge[f] = (mode, m1.get(f, nullid), m2[f]) + merge[f] = (fmerge(f, mw, m2, ma), m1.get(f, nullid), m2[f]) s = 1 # are we clobbering? # is remote's version newer? @@ -172,9 +169,7 @@ def update(repo, node, branchmerge=False repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), m2.execf(f)) else: - a, b, c = ma.execf(f), mw.execf(f), m2.execf(f) - mode = ((a^b) | (a^c)) ^ a - if mode != b: + if fmerge(f, mw, m2, ma) != mw.execf(f): repo.ui.debug(_(" updating permissions for %s\n") % f) util.set_exec(repo.wjoin(f), mode) @@ -230,6 +225,8 @@ def update(repo, node, branchmerge=False del mw, m1, m2, ma + ### apply phase + if overwrite: for f in merge: get[f] = merge[f][:2] @@ -257,11 +254,6 @@ def update(repo, node, branchmerge=False t = repo.file(f).read(node) repo.wwrite(f, t) util.set_exec(repo.wjoin(f), flag) - if not partial: - if branchmerge: - repo.dirstate.update([f], 'n', st_mtime=-1) - else: - repo.dirstate.update([f], 'n') # merge the tricky bits unresolved = [] @@ -274,19 +266,6 @@ def update(repo, node, branchmerge=False if ret: unresolved.append(f) util.set_exec(repo.wjoin(f), flag) - if not partial: - if branchmerge: - # We've done a branch merge, mark this file as merged - # so that we properly record the merger later - repo.dirstate.update([f], 'm') - else: - # We've update-merged a locally modified file, so - # we set the dirstate to emulate a normal checkout - # of that file some time in the past. Thus our - # merge will appear as a normal local file - # modification. - f_len = len(repo.file(f).read(other)) - repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) remove.sort() for f in remove: @@ -298,14 +277,40 @@ def update(repo, node, branchmerge=False if inst.errno != errno.ENOENT: repo.ui.warn(_("update failed to remove %s: %s!\n") % (f, inst.strerror)) + + # update dirstate if not partial: + repo.dirstate.setparents(p1, p2) + repo.dirstate.forget(forget) if branchmerge: repo.dirstate.update(remove, 'r') else: repo.dirstate.forget(remove) - if not partial: - repo.dirstate.setparents(p1, p2) + files = get.keys() + files.sort() + for f in files: + if branchmerge: + repo.dirstate.update([f], 'n', st_mtime=-1) + else: + repo.dirstate.update([f], 'n') + + files = merge.keys() + files.sort() + for f in files: + if branchmerge: + # We've done a branch merge, mark this file as merged + # so that we properly record the merger later + repo.dirstate.update([f], 'm') + else: + # We've update-merged a locally modified file, so + # we set the dirstate to emulate a normal checkout + # of that file some time in the past. Thus our + # merge will appear as a normal local file + # modification. + fl = repo.file(f) + f_len = fl.size(fl.rev(other)) + repo.dirstate.update([f], 'n', st_size=f_len, st_mtime=-1) if show_stats: stats = ((len(get), _("updated")), diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -215,14 +215,13 @@ def dogitpatch(patchname, gitpatches): tmpfp.close() return patchname -def patch(patchname, ui, strip=1, cwd=None): +def patch(strip, patchname, ui, cwd=None): """apply the patch to the working directory. a list of patched files is returned""" (dopatch, gitpatches) = readgitpatch(patchname) files = {} - fuzz = False if dopatch: if dopatch == 'filter': patchname = dogitpatch(patchname, gitpatches) @@ -238,25 +237,10 @@ def patch(patchname, ui, strip=1, cwd=No for line in fp: line = line.rstrip() - ui.note(line + '\n') + ui.status("%s\n" % line) if line.startswith('patching file '): pf = util.parse_patch_output(line) - printed_file = False files.setdefault(pf, (None, None)) - elif line.find('with fuzz') >= 0: - fuzz = True - if not printed_file: - ui.warn(pf + '\n') - printed_file = True - ui.warn(line + '\n') - elif line.find('saving rejects to file') >= 0: - ui.warn(line + '\n') - elif line.find('FAILED') >= 0: - if not printed_file: - ui.warn(pf + '\n') - printed_file = True - ui.warn(line + '\n') - code = fp.close() if code: raise util.Abort(_("patch command failed: %s") % @@ -265,7 +249,19 @@ def patch(patchname, ui, strip=1, cwd=No for gp in gitpatches: files[gp.path] = (gp.op, gp) - return (files, fuzz) + return files + +def diffopts(ui, opts={}): + return mdiff.diffopts( + text=opts.get('text'), + showfunc=(opts.get('show_function') or + ui.configbool('diff', 'showfunc', None)), + ignorews=(opts.get('ignore_all_space') or + ui.configbool('diff', 'ignorews', None)), + ignorewsamount=(opts.get('ignore_space_change') or + ui.configbool('diff', 'ignorewsamount', None)), + ignoreblanklines=(opts.get('ignore_blank_lines') or + ui.configbool('diff', 'ignoreblanklines', None))) def diff(repo, node1=None, node2=None, files=None, match=util.always, fp=None, changes=None, opts=None): @@ -314,9 +310,6 @@ def diff(repo, node1=None, node2=None, f return _date2 def read(f): return repo.file(f).read(mmap2[f]) - def renamed(f): - src = repo.file(f).renamed(mmap2[f]) - return src and src[0] or None else: tz = util.makedate()[1] _date2 = util.datestr() @@ -328,8 +321,6 @@ def diff(repo, node1=None, node2=None, f return _date2 def read(f): return repo.wread(f) - def renamed(f): - return repo.dirstate.copies.get(f) if repo.ui.quiet: r = None @@ -337,65 +328,16 @@ def diff(repo, node1=None, node2=None, f hexfunc = repo.ui.verbose and hex or short r = [hexfunc(node) for node in [node1, node2] if node] - if opts.git: - copied = {} - for f in added: - src = renamed(f) - if src: - copied[f] = src - srcs = [x[1] for x in copied.items()] - all = modified + added + removed all.sort() for f in all: to = None tn = None - dodiff = True if f in mmap: to = repo.file(f).read(mmap[f]) if f not in removed: tn = read(f) - if opts.git: - def gitmode(x): - return x and '100755' or '100644' - def addmodehdr(header, omode, nmode): - if omode != nmode: - header.append('old mode %s\n' % omode) - header.append('new mode %s\n' % nmode) - - a, b = f, f - header = [] - if f in added: - if node2: - mode = gitmode(mmap2.execf(f)) - else: - mode = gitmode(util.is_exec(repo.wjoin(f), None)) - if f in copied: - a = copied[f] - omode = gitmode(mmap.execf(a)) - addmodehdr(header, omode, mode) - op = a in removed and 'rename' or 'copy' - header.append('%s from %s\n' % (op, a)) - header.append('%s to %s\n' % (op, f)) - to = repo.file(a).read(mmap[a]) - else: - header.append('new file mode %s\n' % mode) - elif f in removed: - if f in srcs: - dodiff = False - else: - mode = gitmode(mmap.execf(f)) - header.append('deleted file mode %s\n' % mode) - else: - omode = gitmode(mmap.execf(f)) - nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f))) - addmodehdr(header, omode, nmode) - r = None - if dodiff: - header.insert(0, 'diff --git a/%s b/%s\n' % (a, b)) - fp.write(''.join(header)) - if dodiff: - fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)) + fp.write(mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts)) def export(repo, revs, template='hg-%h.patch', fp=None, switch_parent=False, opts=None): diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -766,6 +766,19 @@ class revlog(object): raise RevlogError(_("No match found")) + def cmp(self, node, text): + """compare text with a given file revision""" + p1, p2 = self.parents(node) + return hash(text, p1, p2) != node + + def makenode(self, node, text): + """calculate a file nodeid for text, descended or possibly + unchanged from node""" + + if self.cmp(node, text): + return hash(text, node, nullid) + return node + def diff(self, a, b): """return a delta between two revisions""" return mdiff.textdiff(a, b) diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -7,7 +7,7 @@ from i18n import gettext as _ from demandload import * -demandload(globals(), "errno getpass os re smtplib socket sys tempfile") +demandload(globals(), "errno getpass os re socket sys tempfile") demandload(globals(), "ConfigParser mdiff templater traceback util") class ui(object): @@ -169,20 +169,6 @@ class ui(object): result[key.lower()] = value return result - def diffopts(self, opts={}): - return mdiff.diffopts( - text=opts.get('text'), - showfunc=(opts.get('show_function') or - self.configbool('diff', 'showfunc', None)), - git=(opts.get('git') or - self.configbool('diff', 'git', None)), - ignorews=(opts.get('ignore_all_space') or - self.configbool('diff', 'ignorews', None)), - ignorewsamount=(opts.get('ignore_space_change') or - self.configbool('diff', 'ignorewsamount', None)), - ignoreblanklines=(opts.get('ignore_blank_lines') or - self.configbool('diff', 'ignoreblanklines', None))) - def username(self): """Return default username to be used in commits. @@ -295,62 +281,6 @@ class ui(object): return t - def sendmail(self): - '''send mail message. object returned has one method, sendmail. - call as sendmail(sender, list-of-recipients, msg).''' - - def smtp(): - '''send mail using smtp.''' - - local_hostname = self.config('smtp', 'local_hostname') - s = smtplib.SMTP(local_hostname=local_hostname) - mailhost = self.config('smtp', 'host') - if not mailhost: - raise util.Abort(_('no [smtp]host in hgrc - cannot send mail')) - mailport = int(self.config('smtp', 'port', 25)) - self.note(_('sending mail: smtp host %s, port %s\n') % - (mailhost, mailport)) - s.connect(host=mailhost, port=mailport) - if self.configbool('smtp', 'tls'): - self.note(_('(using tls)\n')) - s.ehlo() - s.starttls() - s.ehlo() - username = self.config('smtp', 'username') - password = self.config('smtp', 'password') - if username and password: - self.note(_('(authenticating to mail server as %s)\n') % - (username)) - s.login(username, password) - return s - - class sendmail(object): - '''send mail using sendmail.''' - - def __init__(self, ui, program): - self.ui = ui - self.program = program - - def sendmail(self, sender, recipients, msg): - cmdline = '%s -f %s %s' % ( - self.program, templater.email(sender), - ' '.join(map(templater.email, recipients))) - self.ui.note(_('sending mail: %s\n') % cmdline) - fp = os.popen(cmdline, 'w') - fp.write(msg) - ret = fp.close() - if ret: - raise util.Abort('%s %s' % ( - os.path.basename(self.program.split(None, 1)[0]), - util.explain_exit(ret)[0])) - - method = self.config('email', 'method', 'smtp') - if method == 'smtp': - mail = smtp() - else: - mail = sendmail(self, method) - return mail - def print_exc(self): '''print exception traceback if traceback printing enabled. only to call in exception handler. returns true if traceback