# HG changeset patch # User Thomas Arendsen Hein # Date 1173467293 -3600 # Node ID ac9e891f2c0f6a43639f9708e4e17cf7cfa19e4b # Parent c0271aba6abe6f5c9da103bafb10821a84dc6140# Parent 0182cb2e4aacf303a3a3a1164a01432a2024e981 merge with crew-stable diff --git a/Makefile b/Makefile --- a/Makefile +++ b/Makefile @@ -67,10 +67,10 @@ dist-notests: doc MANIFEST TAR_OPTIONS="--owner=root --group=root --mode=u+w,go-w,a+rX-s" $(PYTHON) setup.py -q sdist tests: - cd tests && $(PYTHON) run-tests.py + cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) test-%: - cd tests && $(PYTHON) run-tests.py $@ + cd tests && $(PYTHON) run-tests.py $(TESTFLAGS) $@ .PHONY: help all local build doc clean install install-bin install-doc \ diff --git a/README b/README --- a/README +++ b/README @@ -1,99 +1,10 @@ -MERCURIAL QUICK-START - -Setting up Mercurial: - - Note: some distributions fails to include bits of distutils by - default, you'll need python-dev to install. You'll also need a C - compiler and a 3-way merge tool like merge, tkdiff, or kdiff3. - - First, unpack the source: - - $ tar xvzf mercurial-.tar.gz - $ cd mercurial- - - When installing, change python to python2.3 or python2.4 if 2.2 is the - default on your system. - - To install system-wide: - - $ python setup.py install --force - - To install in your home directory (~/bin and ~/lib, actually), run: - - $ python setup.py install --home=${HOME} --force - $ export PYTHONPATH=${HOME}/lib/python # (or lib64/ on some systems) - $ export PATH=${HOME}/bin:$PATH # add these to your .bashrc - - And finally: - - $ hg debuginstall # run some basic tests - $ hg # show help - - If you get complaints about missing modules, you probably haven't set - PYTHONPATH correctly. - -Setting up a Mercurial project: - - $ hg init project # creates project directory - $ cd project - # copy files in, edit them - $ hg add # add all unknown files - $ hg commit # commit all changes, edit changelog entry - - Mercurial will look for a file named .hgignore in the root of your - repository which contains a set of regular expressions to ignore in - file paths. - -Branching and merging: +Basic install: - $ hg clone project project-work # create a new branch - $ cd project-work - $ - $ hg commit - $ cd ../project - $ hg pull ../project-work # pull changesets from project-work - $ hg merge # merge the new tip from project-work into - # our working directory - $ hg commit # commit the result of the merge - -Importing patches: - - Simple: - $ patch < ../p/foo.patch - $ hg commit -A - - Fast: - $ cat ../p/patchlist | xargs hg import -p1 -b ../p - -Exporting a patch: - - (make changes) - $ hg commit - $ hg export tip > foo.patch # export latest change + $ make # see install targets + $ make install # do a system-wide install + $ hg debuginstall # sanity-check setup + $ hg # see help -Network support: - - # pull from the primary Mercurial repo - foo$ hg clone http://selenic.com/hg/ - foo$ cd hg - - # make your current repo available via http://server:8000/ - foo$ hg serve - - # pushing and pulling changes to/from a remote repo with SSH - foo$ hg push ssh://user@example.com/my/repository - foo$ hg pull ssh://user@example.com//home/somebody/his/repository +See http://www.selenic.com/mercurial/ for detailed installation +instructions, platform-specific notes, and Mercurial user information. - # merge changes from a remote machine (e.g. running 'hg serve') - bar$ hg pull http://foo:8000/ - bar$ hg merge # merge changes into your working directory - bar$ hg commit # commit merge in to your local repository - - # Set up a CGI server on your webserver - foo$ cp hgweb.cgi ~/public_html/hg/index.cgi - foo$ emacs ~/public_html/hg/index.cgi # adjust the defaults - -For more info: - - Documentation in doc/ - Mercurial website at http://selenic.com/mercurial diff --git a/contrib/bash_completion b/contrib/bash_completion --- a/contrib/bash_completion +++ b/contrib/bash_completion @@ -145,6 +145,7 @@ shopt -s extglob # global options case "$prev" in -R|--repository) + _hg_paths _hg_repos return ;; @@ -477,3 +478,25 @@ complete -o bashdefault -o default -F _h { _hg_tags } + + +# transplant +_hg_cmd_transplant() +{ + case "$prev" in + -s|--source) + _hg_paths + _hg_repos + return + ;; + --filter) + # standard filename completion + return + ;; + esac + + # all other transplant options values and command parameters are revisions + _hg_tags + return +} + diff --git a/contrib/churn.py b/contrib/churn.py --- a/contrib/churn.py +++ b/contrib/churn.py @@ -11,10 +11,9 @@ # # -from mercurial.demandload import * +import sys from mercurial.i18n import gettext as _ -demandload(globals(), 'time sys signal os') -demandload(globals(), 'mercurial:hg,mdiff,fancyopts,cmdutil,ui,util,templater,node') +from mercurial import hg, mdiff, cmdutil, ui, util, templater, node def __gather(ui, repo, node1, node2): def dirtywork(f, mmap1, mmap2): diff --git a/contrib/convert-repo b/contrib/convert-repo --- a/contrib/convert-repo +++ b/contrib/convert-repo @@ -3,29 +3,49 @@ # This is a generalized framework for converting between SCM # repository formats. # -# In its current form, it's hardcoded to convert incrementally between -# git and Mercurial. -# # To use, run: # -# convert-repo +# convert-repo [ []] # -# (don't forget to create the repository beforehand) +# Currently accepted source formats: git, cvs +# Currently accepted destination formats: hg # -# The is a simple text file that maps a git commit hash to -# the hash in Mercurial for that version, like so: +# If destination isn't given, a new Mercurial repo named -hg will +# be created. If isn't given, it will be put in a default +# location (/.hg/shamap by default) # -# +# The is a simple text file that maps each source commit ID to +# the destination ID for that revision, like so: +# +# # # If the file doesn't exist, it's automatically created. It's updated # on each commit copied, so convert-repo can be interrupted and can # be run repeatedly to copy new commits. -import sys, os, zlib, sha, time - +import sys, os, zlib, sha, time, re, locale, socket os.environ["HGENCODING"] = "utf-8" +from mercurial import hg, ui, util, fancyopts -from mercurial import hg, ui, util +class Abort(Exception): pass +class NoRepo(Exception): pass + +class commit: + def __init__(self, **parts): + for x in "author date desc parents".split(): + if not x in parts: + abort("commit missing field %s\n" % x) + self.__dict__.update(parts) + +quiet = 0 +def status(msg): + if not quiet: sys.stdout.write(str(msg)) + +def warn(msg): + sys.stderr.write(str(msg)) + +def abort(msg): + raise Abort(msg) def recode(s): try: @@ -36,9 +56,245 @@ def recode(s): except: return s.decode("utf-8", "replace").encode("utf-8") +# CVS conversion code inspired by hg-cvs-import and git-cvsimport +class convert_cvs: + def __init__(self, path): + self.path = path + cvs = os.path.join(path, "CVS") + if not os.path.exists(cvs): + raise NoRepo("couldn't open CVS repo %s" % path) + + self.changeset = {} + self.files = {} + self.tags = {} + self.lastbranch = {} + self.parent = {} + self.socket = None + self.cvsroot = file(os.path.join(cvs, "Root")).read()[:-1] + self.cvsrepo = file(os.path.join(cvs, "Repository")).read()[:-1] + self.encoding = locale.getpreferredencoding() + self._parse() + self._connect() + + def _parse(self): + if self.changeset: + return + + d = os.getcwd() + try: + os.chdir(self.path) + id = None + state = 0 + for l in os.popen("cvsps -A -u --cvs-direct -q"): + if state == 0: # header + if l.startswith("PatchSet"): + id = l[9:-2] + elif l.startswith("Date"): + date = util.parsedate(l[6:-1], ["%Y/%m/%d %H:%M:%S"]) + date = util.datestr(date) + elif l.startswith("Branch"): + branch = l[8:-1] + self.parent[id] = self.lastbranch.get(branch,'bad') + self.lastbranch[branch] = id + elif l.startswith("Ancestor branch"): + ancestor = l[17:-1] + self.parent[id] = self.lastbranch[ancestor] + elif l.startswith("Author"): + author = self.recode(l[8:-1]) + elif l.startswith("Tag: "): + t = l[5:-1].rstrip() + if t != "(none)": + self.tags[t] = id + elif l.startswith("Log:"): + state = 1 + log = "" + elif state == 1: # log + if l == "Members: \n": + files = {} + log = self.recode(log[:-1]) + if log.isspace(): + log = "*** empty log message ***\n" + state = 2 + else: + log += l + elif state == 2: + if l == "\n": # + state = 0 + p = [self.parent[id]] + if id == "1": + p = [] + c = commit(author=author, date=date, parents=p, + desc=log, branch=branch) + self.changeset[id] = c + self.files[id] = files + else: + file,rev = l[1:-2].rsplit(':',1) + rev = rev.split("->")[1] + files[file] = rev + + self.heads = self.lastbranch.values() + finally: + os.chdir(d) + + def _connect(self): + root = self.cvsroot + conntype = None + user, host = None, None + cmd = ['cvs', 'server'] + + status("connecting to %s\n" % root) + + if root.startswith(":pserver:"): + root = root[9:] + m = re.match(r'(?:(.*?)(?::(.*?))?@)?([^:\/]*)(?::(\d*))?(.*)', root) + if m: + conntype = "pserver" + user, passw, serv, port, root = m.groups() + if not user: + user = "anonymous" + rr = ":pserver:" + user + "@" + serv + ":" + root + if port: + rr2, port = "-", int(port) + else: + rr2, port = rr, 2401 + rr += str(port) + + if not passw: + passw = "A" + pf = open(os.path.join(os.environ["HOME"], ".cvspass")) + for l in pf: + # :pserver:cvs@mea.tmt.tele.fi:/cvsroot/zmailer Ah/dev/null' % self.path) + prefix = 'refs/tags/' + for line in fh: + line = line.strip() + if not line.endswith("^{}"): + continue + node, tag = line.split(None, 1) + if not tag.startswith(prefix): + continue + tag = tag[len(prefix):-3] + tags[tag] = node + return tags class convert_mercurial: def __init__(self, path): self.path = path u = ui.ui() - self.repo = hg.repository(u, path) + try: + self.repo = hg.repository(u, path) + except: + raise NoRepo("could open hg repo %s" % path) + + def mapfile(self): + return os.path.join(self.path, ".hg", "shamap") def getheads(self): h = self.repo.changelog.heads() return [ hg.hex(x) for x in h ] def putfile(self, f, e, data): - self.repo.wfile(f, "w").write(data) + self.repo.wwrite(f, data, e) if self.repo.dirstate.state(f) == '?': self.repo.dirstate.update([f], "a") - util.set_exec(self.repo.wjoin(f), e) - def delfile(self, f): try: os.unlink(self.repo.wjoin(f)) @@ -130,7 +402,7 @@ class convert_mercurial: except: pass - def putcommit(self, files, parents, author, dest, text): + def putcommit(self, files, parents, commit): seen = {} pl = [] for p in parents: @@ -143,11 +415,18 @@ class convert_mercurial: if len(parents) < 2: parents.append("0" * 40) p2 = parents.pop(0) + text = commit.desc + extra = {} + try: + extra["branch"] = commit.branch + except AttributeError: + pass + while parents: p1 = p2 p2 = parents.pop(0) - self.repo.rawcommit(files, text, author, dest, - hg.bin(p1), hg.bin(p2)) + a = self.repo.rawcommit(files, text, commit.author, commit.date, + hg.bin(p1), hg.bin(p2), extra=extra) text = "(octopus merge fixup)\n" p2 = hg.hex(self.repo.changelog.tip()) @@ -170,7 +449,7 @@ class convert_mercurial: newlines.sort() if newlines != oldlines: - #print "updating tags" + status("updating tags\n") f = self.repo.wfile(".hgtags", "w") f.write("".join(newlines)) f.close() @@ -180,11 +459,25 @@ class convert_mercurial: date, self.repo.changelog.tip(), hg.nullid) return hg.hex(self.repo.changelog.tip()) +converters = [convert_cvs, convert_git, convert_mercurial] + +def converter(path): + if not os.path.isdir(path): + abort("%s: not a directory\n" % path) + for c in converters: + try: + return c(path) + except NoRepo: + pass + abort("%s: unknown repository type\n" % path) + class convert: - def __init__(self, source, dest, mapfile): + def __init__(self, source, dest, mapfile, opts): + self.source = source self.dest = dest self.mapfile = mapfile + self.opts = opts self.commitcache = {} self.map = {} @@ -204,7 +497,7 @@ class convert: if n in known or n in self.map: continue known[n] = 1 self.commitcache[n] = self.source.getcommit(n) - cp = self.commitcache[n][0] + cp = self.commitcache[n].parents for p in cp: parents.setdefault(n, []).append(p) visit.append(p) @@ -247,42 +540,60 @@ class convert: if not dep: # all n's parents are in the list removed[n] = 1 - s.append(n) + if n not in self.map: + s.append(n) if n in children: for c in children[n]: visit.insert(0, c) + if opts.get('datesort'): + depth = {} + for n in s: + depth[n] = 0 + pl = [p for p in self.commitcache[n].parents if p not in self.map] + if pl: + depth[n] = max([depth[p] for p in pl]) + 1 + + s = [(depth[n], self.commitcache[n].date, n) for n in s] + s.sort() + s = [e[2] for e in s] + return s def copy(self, rev): - p, a, d, t = self.commitcache[rev] + c = self.commitcache[rev] files = self.source.getchanges(rev) - for f,v,e in files: + for f,v in files: try: data = self.source.getfile(f, v) except IOError, inst: self.dest.delfile(f) else: + e = self.source.getmode(f, v) self.dest.putfile(f, e, data) - r = [self.map[v] for v in p] - f = [f for f,v,e in files] - self.map[rev] = self.dest.putcommit(f, r, a, d, t) + r = [self.map[v] for v in c.parents] + f = [f for f,v in files] + self.map[rev] = self.dest.putcommit(f, r, c) file(self.mapfile, "a").write("%s %s\n" % (rev, self.map[rev])) def convert(self): + status("scanning source...\n") heads = self.source.getheads() parents = self.walktree(heads) + status("sorting...\n") t = self.toposort(parents) - t = [n for n in t if n not in self.map] num = len(t) c = None + status("converting...\n") for c in t: num -= 1 - desc = self.commitcache[c][3].splitlines()[0] - #print num, desc + desc = self.commitcache[c].desc + if "\n" in desc: + desc = desc.splitlines()[0] + status("%d %s\n" % (num, desc)) self.copy(c) tags = self.source.gettags() @@ -299,9 +610,41 @@ class convert: if nrev: file(self.mapfile, "a").write("%s %s\n" % (c, nrev)) -gitpath, hgpath, mapfile = sys.argv[1:] -if os.path.isdir(gitpath + "/.git"): - gitpath += "/.git" +def command(src, dest=None, mapfile=None, **opts): + srcc = converter(src) + if not hasattr(srcc, "getcommit"): + abort("%s: can't read from this repo type\n" % src) + + if not dest: + dest = src + "-hg" + status("assuming destination %s\n" % dest) + if not os.path.isdir(dest): + status("creating repository %s\n" % dest) + os.system("hg init " + dest) + destc = converter(dest) + if not hasattr(destc, "putcommit"): + abort("%s: can't write to this repo type\n" % src) -c = convert(convert_git(gitpath), convert_mercurial(hgpath), mapfile) -c.convert() + if not mapfile: + try: + mapfile = destc.mapfile() + except: + mapfile = os.path.join(destc, "map") + + c = convert(srcc, destc, mapfile, opts) + c.convert() + +options = [('q', 'quiet', None, 'suppress output'), + ('', 'datesort', None, 'try to sort changesets by date')] +opts = {} +args = fancyopts.fancyopts(sys.argv[1:], options, opts) + +if opts['quiet']: + quiet = 1 + +try: + command(*args, **opts) +except Abort, inst: + warn(inst) +except KeyboardInterrupt: + status("interrupted\n") diff --git a/contrib/hgk b/contrib/hgk --- a/contrib/hgk +++ b/contrib/hgk @@ -43,7 +43,9 @@ proc getcommits {rargs} { } if [catch { set parse_args [concat --default HEAD $revargs] - set parsed_args [split [eval exec hg debug-rev-parse $parse_args] "\n"] + set parse_temp [eval exec hg debug-rev-parse $parse_args] + regsub -all "\r\n" $parse_temp "\n" parse_temp + set parsed_args [split $parse_temp "\n"] } err] { # if git-rev-parse failed for some reason... if {$rargs == {}} { @@ -108,6 +110,7 @@ to allow selection of commits to be disp set leftover {} } set start [expr {$i + 1}] + regsub -all "\r\n" $cmit "\n" cmit set j [string first "\n" $cmit] set ok 0 if {$j >= 0} { @@ -209,6 +212,7 @@ proc parsecommit {id contents listed old incr ncleft($p) } } + regsub -all "\r\n" $contents "\n" contents foreach line [split $contents "\n"] { if {$inhdr} { set line [split $line] @@ -257,7 +261,8 @@ proc readrefs {} { global tagids idtags headids idheads tagcontents set tags [exec hg tags] - set lines [split $tags '\n'] + regsub -all "\r\n" $tags "\n" tags + set lines [split $tags "\n"] foreach f $lines { set f [regexp -all -inline {\S+} $f] set direct [lindex $f 0] @@ -2856,6 +2861,7 @@ proc getblobdiffline {bdf ids} { if {$ids != $diffids || $bdf != $blobdifffd($ids)} { return } + regsub -all "\r" $line "" line $ctext conf -state normal if {[regexp {^diff --git a/(.*) b/(.*)} $line match fname newname]} { # start of a new file @@ -2914,7 +2920,7 @@ proc getblobdiffline {bdf ids} { } elseif {$diffinhdr || $x == "\\"} { # e.g. "\ No newline at end of file" $ctext insert end "$line\n" filesep - } else { + } elseif {$line != ""} { # Something else we don't recognize if {$curdifftag != "Comments"} { $ctext insert end "\n" diff --git a/contrib/purge/README b/contrib/purge/README deleted file mode 100644 --- a/contrib/purge/README +++ /dev/null @@ -1,60 +0,0 @@ -What is "hg purge"? -=================== -"purge" is a simple extension for the Mercurial source control management -system (http://www.selenic.com/mercurial). -This extension adds a "purge" command to "hg" that removes files not known -to Mercurial, this is useful to test local and uncommitted changes in the -otherwise clean source tree. - -This means that Mercurial will delete: - - Unknown files: files marked with "?" by "hg status" - - Ignored files: files usually ignored by Mercurial because they match a - pattern in a ".hgignore" file - - Empty directories: infact Mercurial ignores directories unless they - contain files under source control managment -But it will leave untouched: - - Unmodified files tracked by Mercurial - - Modified files tracked by Mercurial - - New files added to the repository (with "hg add") - -Be careful with "hg purge", you could irreversibly delete some files you -forgot to add to the repository. If you only want to print the list of -files that this program would delete use: - hg purge --print - -To get the most recent version of "hg purge" visit its home page: - http://www.barisione.org/apps.html#hg-purge - -This program was inspired by the "cvspurge" script contained in CVS utilities -(http://www.red-bean.com/cvsutils/). - - -How to install -============== -The purge extension is distributed with Mercurial, to activate it you need to -put these lines in your ~/.hgrc: - - [extensions] - hgext.purge= - -For more information on the configuration files see the man page for "hgrc": - man 5 hgrc - - -How to use "hg purge" -==================== -For help on the usage of "hg purge" use: - hg help purge - - -License -======= -Copyright (C) 2006 - Marco Barisione - -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -A copy of the GNU General Public License is distributed along -with Mercurial in the file COPYING. diff --git a/contrib/purge/purge.py b/contrib/purge/purge.py --- a/contrib/purge/purge.py +++ b/contrib/purge/purge.py @@ -3,6 +3,16 @@ # This is a small extension for Mercurial (http://www.selenic.com/mercurial) # that removes files not known to mercurial # +# This program was inspired by the "cvspurge" script contained in CVS utilities +# (http://www.red-bean.com/cvsutils/). +# +# To enable the "purge" extension put these lines in your ~/.hgrc: +# [extensions] +# purge = /path/to/purge.py +# +# For help on the usage of "hg purge" use: +# hg help purge +# # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or @@ -18,118 +28,46 @@ # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. from mercurial import hg, util +from mercurial.i18n import _ import os -def _(s): - return s - -class Purge(object): - def __init__(self, act=True, abort_on_err=False, eol='\n'): - self._repo = None - self._ui = None - self._hg_root = None - self._act = act - self._abort_on_err = abort_on_err - self._eol = eol - - def purge(self, ui, repo, dirs=None): - self._repo = repo - self._ui = ui - self._hg_root = self._split_path(repo.root) - - if not dirs: - dirs = [repo.root] - - for path in dirs: - path = os.path.abspath(path) - for root, dirs, files in os.walk(path, topdown=False): - if '.hg' in self._split_path(root): - # Skip files in the .hg directory. - # Note that if the repository is in a directory - # called .hg this command does not work. - continue - for name in files: - self._remove_file(os.path.join(root, name)) - if not os.listdir(root): - # Remove this directory if it is empty. - self._remove_dir(root) - - self._repo = None - self._ui = None - self._hg_root = None - - def _error(self, msg): - if self._abort_on_err: +def dopurge(ui, repo, dirs=None, act=True, abort_on_err=False, eol='\n'): + def error(msg): + if abort_on_err: raise util.Abort(msg) else: - self._ui.warn(_('warning: %s\n') % msg) + ui.warn(_('warning: %s\n') % msg) - def _remove_file(self, name): - relative_name = self._relative_name(name) - # dirstate.state() requires a path relative to the root - # directory. - if self._repo.dirstate.state(relative_name) != '?': - return - self._ui.note(_('Removing file %s\n') % name) - if self._act: + def remove(remove_func, name): + if act: try: - os.remove(name) + remove_func(os.path.join(repo.root, name)) except OSError, e: - self._error(_('%s cannot be removed') % name) + error(_('%s cannot be removed') % name) else: - self._ui.write('%s%s' % (name, self._eol)) - - def _remove_dir(self, name): - self._ui.note(_('Removing directory %s\n') % name) - if self._act: - try: - os.rmdir(name) - except OSError, e: - self._error(_('%s cannot be removed') % name) - else: - self._ui.write('%s%s' % (name, self._eol)) + ui.write('%s%s' % (name, eol)) - def _relative_name(self, path): - ''' - Returns "path" but relative to the root directory of the - repository and with '\\' replaced with '/'. - This is needed because this is the format required by - self._repo.dirstate.state(). - ''' - splitted_path = self._split_path(path)[len(self._hg_root):] - # Even on Windows self._repo.dirstate.state() wants '/'. - return self._join_path(splitted_path).replace('\\', '/') + directories = [] + files = [] + roots, match, anypats = util.cmdmatcher(repo.root, repo.getcwd(), dirs) + for src, f, st in repo.dirstate.statwalk(files=roots, match=match, + ignored=True, directories=True): + if src == 'd': + directories.append(f) + elif src == 'f' and f not in repo.dirstate: + files.append(f) - def _split_path(self, path): - ''' - Returns a list of the single files/directories in "path". - For instance: - '/home/user/test' -> ['/', 'home', 'user', 'test'] - 'C:\\Mercurial' -> ['C:\\', 'Mercurial'] - ''' - ret = [] - while True: - head, tail = os.path.split(path) - if tail: - ret.append(tail) - if head == path: - ret.append(head) - break - path = head - ret.reverse() - return ret + directories.sort() - def _join_path(self, splitted_path): - ''' - Joins a list returned by _split_path(). - ''' - ret = '' - for part in splitted_path: - if ret: - ret = os.path.join(ret, part) - else: - ret = part - return ret + for f in files: + if f not in repo.dirstate: + ui.note(_('Removing file %s\n') % f) + remove(os.remove, f) + + for f in directories[::-1]: + if not os.listdir(repo.wjoin(f)): + ui.note(_('Removing directory %s\n') % f) + remove(os.rmdir, f) def purge(ui, repo, *dirs, **opts): @@ -162,8 +100,7 @@ def purge(ui, repo, *dirs, **opts): if eol == '\0': # --print0 implies --print act = False - p = Purge(act, abort_on_err, eol) - p.purge(ui, repo, dirs) + dopurge(ui, repo, dirs, act, abort_on_err, eol) cmdtable = { diff --git a/doc/Makefile b/doc/Makefile --- a/doc/Makefile +++ b/doc/Makefile @@ -2,7 +2,7 @@ SOURCES=$(wildcard *.[0-9].txt) MAN=$(SOURCES:%.txt=%) HTML=$(SOURCES:%.txt=%.html) PREFIX=/usr/local -MANDIR=$(PREFIX)/man +MANDIR=$(PREFIX)/share/man INSTALL=install -c all: man html @@ -36,8 +36,8 @@ MANIFEST: man html install: man for i in $(MAN) ; do \ subdir=`echo $$i | sed -n 's/..*\.\([0-9]\)$$/man\1/p'` ; \ - mkdir -p $(MANDIR)/$$subdir ; \ - $(INSTALL) $$i $(MANDIR)/$$subdir ; \ + mkdir -p $(DESTDIR)/$(MANDIR)/$$subdir ; \ + $(INSTALL) $$i $(DESTDIR)/$(MANDIR)/$$subdir ; \ done clean: diff --git a/doc/hgrc.5.txt b/doc/hgrc.5.txt --- a/doc/hgrc.5.txt +++ b/doc/hgrc.5.txt @@ -215,6 +215,15 @@ extensions:: # (this extension will get loaded from the file specified) myfeature = ~/.hgext/myfeature.py +format:: + + usestore;; + Enable or disable the "store" repository format which improves + compatibility with systems that fold case or otherwise mangle + filenames. Enabled by default. Disabling this option will allow + you to store longer filenames in some situations at the expense of + compatibility. + hooks:: Commands or Python functions that get automatically executed by various actions such as starting or finishing a commit. Multiple @@ -507,6 +516,11 @@ web:: push_ssl;; Whether to require that inbound pushes be transported over SSL to prevent password sniffing. Default is true. + staticurl;; + Base URL to use for static files. If unset, static files (e.g. + the hgicon.png favicon) will be served by the CGI script itself. + Use this setting to serve them directly with the HTTP server. + Example: "http://hgserver/static/" stripes;; How many lines a "zebra stripe" should span in multiline output. Default is 1; set to 0 to disable. diff --git a/hg b/hg --- a/hg +++ b/hg @@ -7,6 +7,5 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial import commands - -commands.run() +import mercurial.commands +mercurial.commands.run() diff --git a/hgext/acl.py b/hgext/acl.py --- a/hgext/acl.py +++ b/hgext/acl.py @@ -45,10 +45,10 @@ # glob pattern = user4, user5 # ** = user6 -from mercurial.demandload import * -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ from mercurial.node import * -demandload(globals(), 'getpass mercurial:util') +from mercurial import util +import getpass class checker(object): '''acl checker.''' @@ -91,7 +91,7 @@ class checker(object): def check(self, node): '''return if access allowed, raise exception if not.''' - files = self.repo.changelog.read(node)[3] + files = self.repo.changectx(node).files() if self.deniable: for f in files: if self.deny(f): diff --git a/hgext/bugzilla.py b/hgext/bugzilla.py --- a/hgext/bugzilla.py +++ b/hgext/bugzilla.py @@ -52,10 +52,10 @@ # [usermap] # committer_email = bugzilla_user_name -from mercurial.demandload import * -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ from mercurial.node import * -demandload(globals(), 'mercurial:cmdutil,templater,util os re time') +from mercurial import cmdutil, templater, util +import os, re, time MySQLdb = None @@ -222,7 +222,7 @@ class bugzilla(object): _bug_re = None _split_re = None - def find_bug_ids(self, node, desc): + def find_bug_ids(self, ctx): '''find valid bug ids that are referred to in changeset comments and that do not already have references to this changeset.''' @@ -235,7 +235,7 @@ class bugzilla(object): start = 0 ids = {} while True: - m = bugzilla._bug_re.search(desc, start) + m = bugzilla._bug_re.search(ctx.description(), start) if not m: break start = m.end() @@ -246,10 +246,10 @@ class bugzilla(object): if ids: ids = self.filter_real_bug_ids(ids) if ids: - ids = self.filter_unknown_bug_ids(node, ids) + ids = self.filter_unknown_bug_ids(ctx.node(), ids) return ids - def update(self, bugid, node, changes): + def update(self, bugid, ctx): '''update bugzilla bug with reference to changeset.''' def webroot(root): @@ -268,7 +268,7 @@ class bugzilla(object): mapfile = self.ui.config('bugzilla', 'style') tmpl = self.ui.config('bugzilla', 'template') t = cmdutil.changeset_templater(self.ui, self.repo, - False, None, mapfile, False) + False, mapfile, False) if not mapfile and not tmpl: tmpl = _('changeset {node|short} in repo {root} refers ' 'to bug {bug}.\ndetails:\n\t{desc|tabindent}') @@ -276,13 +276,13 @@ class bugzilla(object): tmpl = templater.parsestring(tmpl, quoted=False) t.use_template(tmpl) self.ui.pushbuffer() - t.show(changenode=node, changes=changes, + t.show(changenode=ctx.node(), changes=ctx.changeset(), bug=str(bugid), hgweb=self.ui.config('web', 'baseurl'), root=self.repo.root, webroot=webroot(self.repo.root)) data = self.ui.popbuffer() - self.add_comment(bugid, data, templater.email(changes[1])) + self.add_comment(bugid, data, templater.email(ctx.user())) def hook(ui, repo, hooktype, node=None, **kwargs): '''add comment to bugzilla for each changeset that refers to a @@ -300,12 +300,11 @@ def hook(ui, repo, hooktype, node=None, hooktype) try: bz = bugzilla(ui, repo) - bin_node = bin(node) - changes = repo.changelog.read(bin_node) - ids = bz.find_bug_ids(bin_node, changes[4]) + ctx = repo.changctx(node) + ids = bz.find_bug_ids(ctx) if ids: for id in ids: - bz.update(id, bin_node, changes) + bz.update(id, ctx) bz.notify(ids) except MySQLdb.MySQLError, err: raise util.Abort(_('database error: %s') % err[1]) diff --git a/hgext/extdiff.py b/hgext/extdiff.py --- a/hgext/extdiff.py +++ b/hgext/extdiff.py @@ -48,16 +48,15 @@ # needed files, so running the external diff program will actually be # pretty fast (at least faster than having to compare the entire tree). -from mercurial.demandload import demandload -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ from mercurial.node import * -demandload(globals(), 'mercurial:cmdutil,util os shutil tempfile') +from mercurial import cmdutil, util +import os, shutil, tempfile def dodiff(ui, repo, diffcmd, diffopts, pats, opts): def snapshot_node(files, node): '''snapshot files as of some revision''' - changes = repo.changelog.read(node) - mf = repo.manifest.read(changes[0]) + mf = repo.changectx(node).manifest() dirname = os.path.basename(repo.root) if dirname == "": dirname = "root" @@ -77,7 +76,8 @@ def dodiff(ui, repo, diffcmd, diffopts, destdir = os.path.dirname(dest) if not os.path.isdir(destdir): os.makedirs(destdir) - repo.wwrite(wfn, repo.file(fn).read(mf[fn]), open(dest, 'wb')) + data = repo.wwritedata(wfn, repo.file(wfn).read(mf[wfn])) + open(dest, 'wb').write(data) return dirname def snapshot_wdir(files): diff --git a/hgext/fetch.py b/hgext/fetch.py --- a/hgext/fetch.py +++ b/hgext/fetch.py @@ -5,10 +5,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import * -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ from mercurial.node import * -demandload(globals(), 'mercurial:commands,hg,node,util') +from mercurial import commands, hg, node, util def fetch(ui, repo, source='default', **opts): '''Pull changes from a remote repository, merge new changes if needed. diff --git a/hgext/gpg.py b/hgext/gpg.py --- a/hgext/gpg.py +++ b/hgext/gpg.py @@ -8,7 +8,7 @@ import os, tempfile, binascii from mercurial import util from mercurial import node as hgnode -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ class gpg: def __init__(self, path, key=None): diff --git a/hgext/hbisect.py b/hgext/hbisect.py --- a/hgext/hbisect.py +++ b/hgext/hbisect.py @@ -6,9 +6,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.i18n import gettext as _ -from mercurial.demandload import demandload -demandload(globals(), "os sys sets mercurial:hg,util,commands,cmdutil") +from mercurial.i18n import _ +from mercurial import hg, util, commands, cmdutil +import os, sys, sets versionstr = "0.0.3" diff --git a/hgext/hgk.py b/hgext/hgk.py --- a/hgext/hgk.py +++ b/hgext/hgk.py @@ -5,26 +5,18 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import * -demandload(globals(), 'time sys signal os') -demandload(globals(), 'mercurial:hg,fancyopts,commands,ui,util,patch,revlog') +import sys, os +from mercurial import hg, fancyopts, commands, ui, util, patch, revlog def difftree(ui, repo, node1=None, node2=None, *files, **opts): """diff trees from two commits""" def __difftree(repo, node1, node2, files=[]): - if node2: - change = repo.changelog.read(node2) - mmap2 = repo.manifest.read(change[0]) - status = repo.status(node1, node2, files=files)[:5] - modified, added, removed, deleted, unknown = status - else: - status = repo.status(node1, files=files)[:5] - modified, added, removed, deleted, unknown = status - if not node1: - node1 = repo.dirstate.parents()[0] + assert node2 is not None + mmap = repo.changectx(node1).manifest() + mmap2 = repo.changectx(node2).manifest() + status = repo.status(node1, node2, files=files)[:5] + modified, added, removed, deleted, unknown = status - change = repo.changelog.read(node1) - mmap = repo.manifest.read(change[0]) empty = hg.short(hg.nullid) for f in modified: @@ -70,32 +62,30 @@ def difftree(ui, repo, node1=None, node2 if not opts['stdin']: break -def catcommit(repo, n, prefix, changes=None): +def catcommit(repo, n, prefix, ctx=None): nlprefix = '\n' + prefix; - (p1, p2) = repo.changelog.parents(n) - (h, h1, h2) = map(hg.short, (n, p1, p2)) - (i1, i2) = map(repo.changelog.rev, (p1, p2)) - if not changes: - changes = repo.changelog.read(n) - print "tree %s" % (hg.short(changes[0])) - if i1 != hg.nullrev: print "parent %s" % (h1) - if i2 != hg.nullrev: print "parent %s" % (h2) - date_ar = changes[2] - date = int(float(date_ar[0])) - lines = changes[4].splitlines() + if ctx is None: + ctx = repo.changectx(n) + (p1, p2) = ctx.parents() + print "tree %s" % (hg.short(ctx.changeset()[0])) # use ctx.node() instead ?? + if p1: print "parent %s" % (hg.short(p1.node())) + if p2: print "parent %s" % (hg.short(p2.node())) + date = ctx.date() + description = ctx.description().replace("\0", "") + lines = description.splitlines() if lines and lines[-1].startswith('committer:'): committer = lines[-1].split(': ')[1].rstrip() else: - committer = changes[1] + committer = ctx.user() - print "author %s %s %s" % (changes[1], date, date_ar[1]) - print "committer %s %s %s" % (committer, date, date_ar[1]) - print "revision %d" % repo.changelog.rev(n) + print "author %s %s %s" % (ctx.user(), int(date[0]), date[1]) + print "committer %s %s %s" % (committer, int(date[0]), date[1]) + print "revision %d" % ctx.rev() print "" if prefix != "": - print "%s%s" % (prefix, changes[4].replace('\n', nlprefix).strip()) + print "%s%s" % (prefix, description.replace('\n', nlprefix).strip()) else: - print changes[4] + print description if prefix: sys.stdout.write('\0') @@ -146,8 +136,7 @@ def catfile(ui, repo, type=None, r=None, # you can specify a commit to stop at by starting the sha1 with ^ def revtree(args, repo, full="tree", maxnr=0, parents=False): def chlogwalk(): - ch = repo.changelog - count = ch.count() + count = repo.changelog.count() i = count l = [0] * 100 chunk = 100 @@ -163,7 +152,8 @@ def revtree(args, repo, full="tree", max l[chunk - x:] = [0] * (chunk - x) break if full != None: - l[x] = ch.read(ch.node(i + x)) + l[x] = repo.changectx(i + x) + l[x].changeset() # force reading else: l[x] = 1 for x in xrange(chunk-1, -1, -1): @@ -217,7 +207,7 @@ def revtree(args, repo, full="tree", max # walk the repository looking for commits that are in our # reachability graph - for i, changes in chlogwalk(): + for i, ctx in chlogwalk(): n = repo.changelog.node(i) mask = is_reachable(want_sha1, reachable, n) if mask: @@ -232,13 +222,13 @@ def revtree(args, repo, full="tree", max print hg.short(n) + parentstr elif full == "commit": print hg.short(n) + parentstr - catcommit(repo, n, ' ', changes) + catcommit(repo, n, ' ', ctx) else: (p1, p2) = repo.changelog.parents(n) (h, h1, h2) = map(hg.short, (n, p1, p2)) (i1, i2) = map(repo.changelog.rev, (p1, p2)) - date = changes[2][0] + date = ctx.date()[0] print "%s %s:%s" % (date, h, mask), mask = is_reachable(want_sha1, reachable, p1) if i1 != hg.nullrev and mask > 0: diff --git a/hgext/mq.py b/hgext/mq.py --- a/hgext/mq.py +++ b/hgext/mq.py @@ -29,14 +29,16 @@ remove patch from applied stack refresh contents of top applied patch qrefresh ''' -from mercurial.demandload import * -from mercurial.i18n import gettext as _ -from mercurial import commands -demandload(globals(), "os sys re struct traceback errno bz2") -demandload(globals(), "mercurial:cmdutil,hg,patch,revlog,util,changegroup") +from mercurial.i18n import _ +from mercurial import commands, cmdutil, hg, patch, revlog, util, changegroup +import os, sys, re, errno commands.norepo += " qclone qversion" +# Patch names looks like unix-file names. +# They must be joinable with queue directory and result in the patch path. +normname = util.normpath + class statusentry: def __init__(self, rev, name=None): if not name: @@ -328,11 +330,12 @@ class queue: hg.clean(repo, head, wlock=wlock) self.strip(repo, n, update=False, backup='strip', wlock=wlock) - c = repo.changelog.read(rev) + ctx = repo.changectx(rev) ret = hg.merge(repo, rev, wlock=wlock) if ret: raise util.Abort(_("update returned %d") % ret) - n = repo.commit(None, c[4], c[1], force=1, wlock=wlock) + n = repo.commit(None, ctx.description(), ctx.user(), + force=1, wlock=wlock) if n == None: raise util.Abort(_("repo commit failed")) try: @@ -614,15 +617,12 @@ class queue: self.ui.warn("saving bundle to %s\n" % name) return changegroup.writebundle(cg, name, "HG10BZ") - def stripall(rev, revnum): - cl = repo.changelog - c = cl.read(rev) - mm = repo.manifest.read(c[0]) + def stripall(revnum): + mm = repo.changectx(rev).manifest() seen = {} - for x in xrange(revnum, cl.count()): - c = cl.read(cl.node(x)) - for f in c[3]: + for x in xrange(revnum, repo.changelog.count()): + for f in repo.changectx(x).files(): if f in seen: continue seen[f] = 1 @@ -704,7 +704,7 @@ class queue: backupch = repo.changegroupsubset(savebases.keys(), saveheads, 'strip') chgrpfile = bundle(backupch) - stripall(rev, revnum) + stripall(revnum) change = chlog.read(rev) chlog.strip(revnum, revnum) @@ -802,10 +802,29 @@ class queue: if not wlock: wlock = repo.wlock() patch = self.lookup(patch) - if patch and self.isapplied(patch): - raise util.Abort(_("patch %s is already applied") % patch) + # Suppose our series file is: A B C and the current 'top' patch is B. + # qpush C should be performed (moving forward) + # qpush B is a NOP (no change) + # qpush A is an error (can't go backwards with qpush) + if patch: + info = self.isapplied(patch) + if info: + if info[0] < len(self.applied) - 1: + raise util.Abort(_("cannot push to a previous patch: %s") % + patch) + if info[0] < len(self.series) - 1: + self.ui.warn(_('qpush: %s is already at the top\n') % patch) + else: + self.ui.warn(_('all patches are currently applied\n')) + return + + # Following the above example, starting at 'top' of B: + # qpush should be performed (pushes C), but a subsequent qpush without + # an argument is an error (nothing to apply). This allows a loop + # of "...while hg qpush..." to work as it detects an error when done if self.series_end() == len(self.series): - raise util.Abort(_("patch series fully applied")) + self.ui.warn(_('patch series already fully applied\n')) + return 1 if not force: self.check_localchanges(repo) @@ -835,14 +854,7 @@ class queue: wlock=None): def getfile(f, rev): t = repo.file(f).read(rev) - try: - repo.wfile(f, "w").write(t) - except IOError: - try: - os.makedirs(os.path.dirname(repo.wjoin(f))) - except OSError, err: - if err.errno != errno.EEXIST: raise - repo.wfile(f, "w").write(t) + repo.wfile(f, "w").write(t) if not wlock: wlock = repo.wlock() @@ -854,8 +866,12 @@ class queue: info = self.isapplied(patch) if not info: raise util.Abort(_("patch %s is not applied") % patch) + if len(self.applied) == 0: - raise util.Abort(_("no patches applied")) + # Allow qpop -a to work repeatedly, + # but not qpop without an argument + self.ui.warn(_("no patches applied\n")) + return not all if not update: parents = repo.dirstate.parents() @@ -1089,9 +1105,13 @@ class queue: self.push(repo, force=True, wlock=wlock) def init(self, repo, create=False): - if os.path.isdir(self.path): + if not create and os.path.isdir(self.path): raise util.Abort(_("patch queue directory already exists")) - os.mkdir(self.path) + try: + os.mkdir(self.path) + except OSError, inst: + if inst.errno != errno.EEXIST or not create: + raise if create: return self.qrepo(create=True) @@ -1347,7 +1367,7 @@ class queue: lastparent = p1 if not patchname: - patchname = '%d.diff' % r + patchname = normname('%d.diff' % r) checkseries(patchname) checkfile(patchname) self.full_series.insert(0, patchname) @@ -1369,7 +1389,7 @@ class queue: if filename == '-': raise util.Abort(_('-e is incompatible with import from -')) if not patchname: - patchname = filename + patchname = normname(filename) if not os.path.isfile(self.join(patchname)): raise util.Abort(_("patch %s does not exist") % patchname) else: @@ -1383,7 +1403,7 @@ class queue: except IOError: raise util.Abort(_("unable to read %s") % patchname) if not patchname: - patchname = os.path.basename(filename) + patchname = normname(os.path.basename(filename)) checkfile(patchname) patchf = self.opener(patchname, "w") patchf.write(text) @@ -1474,13 +1494,16 @@ def init(ui, repo, **opts): r = q.init(repo, create=opts['create_repo']) q.save_dirty() if r: - fp = r.wopener('.hgignore', 'w') - print >> fp, 'syntax: glob' - print >> fp, 'status' - print >> fp, 'guards' - fp.close() - r.wopener('series', 'w').close() + if not os.path.exists(r.wjoin('.hgignore')): + fp = r.wopener('.hgignore', 'w') + fp.write('syntax: glob\n') + fp.write('status\n') + fp.write('guards\n') + fp.close() + if not os.path.exists(r.wjoin('series')): + r.wopener('series', 'w').close() r.add(['.hgignore', 'series']) + commands.add(ui, r) return 0 def clone(ui, source, dest=None, **opts): @@ -1595,6 +1618,9 @@ def refresh(ui, repo, *pats, **opts): If any file patterns are provided, the refreshed patch will contain only the modifications that match those patterns; the remaining modifications will remain in the working directory. + + hg add/remove/copy/rename work as usual, though you might want to use + git-style patches (--git or [diff] git=1) to track copies and renames. """ q = repo.mq message = commands.logmessage(opts) @@ -1766,7 +1792,8 @@ def push(ui, repo, patch=None, **opts): if opts['all']: if not q.series: - raise util.Abort(_('no patches in series')) + ui.warn(_('no patches in series\n')) + return 0 patch = q.series[-1] if opts['merge']: if opts['name']: @@ -1792,9 +1819,10 @@ def pop(ui, repo, patch=None, **opts): localupdate = False else: q = repo.mq - q.pop(repo, patch, force=opts['force'], update=localupdate, all=opts['all']) + ret = q.pop(repo, patch, force=opts['force'], update=localupdate, + all=opts['all']) q.save_dirty() - return 0 + return ret def rename(ui, repo, patch, name=None, **opts): """rename a patch @@ -1817,7 +1845,7 @@ def rename(ui, repo, patch, name=None, * patch = q.lookup('qtip') absdest = q.join(name) if os.path.isdir(absdest): - name = os.path.join(name, os.path.basename(patch)) + name = normname(os.path.join(name, os.path.basename(patch))) absdest = q.join(name) if os.path.exists(absdest): raise util.Abort(_('%s already exists') % absdest) @@ -2022,7 +2050,7 @@ def reposetup(ui, repo): return super(mqrepo, self).commit(*args, **opts) def push(self, remote, force=False, revs=None): - if self.mq.applied and not force: + if self.mq.applied and not force and not revs: raise util.Abort(_('source has mq patches applied')) return super(mqrepo, self).push(remote, force, revs) diff --git a/hgext/notify.py b/hgext/notify.py --- a/hgext/notify.py +++ b/hgext/notify.py @@ -65,11 +65,10 @@ # if you like, you can put notify config file in repo that users can # push changes to, they can manage their own subscriptions. -from mercurial.demandload import * -from mercurial.i18n import gettext as _ +from mercurial.i18n import _ from mercurial.node import * -demandload(globals(), 'mercurial:patch,cmdutil,templater,util,mail') -demandload(globals(), 'email.Parser fnmatch socket time') +from mercurial import patch, cmdutil, templater, util, mail +import email.Parser, fnmatch, socket, time # template for single changeset can include email headers. single_template = ''' @@ -113,7 +112,7 @@ class notifier(object): template = (self.ui.config('notify', hooktype) or self.ui.config('notify', 'template')) self.t = cmdutil.changeset_templater(self.ui, self.repo, - False, None, mapfile, False) + False, mapfile, False) if not mapfile and not template: template = deftemplates.get(hooktype) or single_template if template: diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py --- a/hgext/patchbomb.py +++ b/hgext/patchbomb.py @@ -63,11 +63,10 @@ # # That should be all. Now your patchbomb is on its way out. -from mercurial.demandload import * -demandload(globals(), '''email.MIMEMultipart email.MIMEText email.Utils - mercurial:cmdutil,commands,hg,mail,ui,patch,util - os errno popen2 socket sys tempfile''') -from mercurial.i18n import gettext as _ +import os, errno, socket +import email.MIMEMultipart, email.MIMEText, email.Utils +from mercurial import cmdutil, commands, hg, mail, ui, patch, util +from mercurial.i18n import _ from mercurial.node import * try: @@ -217,8 +216,6 @@ def patchbomb(ui, repo, *revs, **opts): bcc = [a.strip() for a in bcc if a.strip()] if len(patches) > 1: - ui.write(_('\nWrite the introductory message for the patch series.\n\n')) - tlen = len(str(len(patches))) subj = '[PATCH %0*d of %d] %s' % ( @@ -228,21 +225,13 @@ def patchbomb(ui, repo, *revs, **opts): prompt('Subject:', rest = ' [PATCH %0*d of %d] ' % (tlen, 0, len(patches)))) - ui.write(_('Finish with ^D or a dot on a line by itself.\n\n')) - - body = [] - - while True: - try: l = raw_input() - except EOFError: break - if l == '.': break - body.append(l) - + body = '' if opts['diffstat']: d = cdiffstat(_('Final summary:\n'), jumbo) - if d: body.append('\n' + d) + if d: body = '\n' + d - body = '\n'.join(body) + '\n' + ui.write(_('\nWrite the introductory message for the patch series.\n\n')) + body = ui.edit(body, sender) msg = email.MIMEText.MIMEText(body) msg['Subject'] = subj diff --git a/hgext/transplant.py b/hgext/transplant.py --- a/hgext/transplant.py +++ b/hgext/transplant.py @@ -5,11 +5,10 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import * -from mercurial.i18n import gettext as _ -demandload(globals(), 'os tempfile') -demandload(globals(), 'mercurial:bundlerepo,cmdutil,commands,hg,merge,patch') -demandload(globals(), 'mercurial:revlog,util') +from mercurial.i18n import _ +import os, tempfile +from mercurial import bundlerepo, changegroup, cmdutil, commands, hg, merge +from mercurial import patch, revlog, util '''patch transplanting tool @@ -120,7 +119,8 @@ class transplanter: if pulls: if source != repo: repo.pull(source, heads=pulls, lock=lock) - merge.update(repo, pulls[-1], wlock=wlock) + merge.update(repo, pulls[-1], False, False, None, + wlock=wlock) p1, p2 = repo.dirstate.parents() pulls = [] @@ -162,7 +162,7 @@ class transplanter: os.unlink(patchfile) if pulls: repo.pull(source, heads=pulls, lock=lock) - merge.update(repo, pulls[-1], wlock=wlock) + merge.update(repo, pulls[-1], False, False, None, wlock=wlock) finally: self.saveseries(revmap, merges) self.transplants.write() @@ -473,7 +473,7 @@ def transplant(ui, repo, *revs, **opts): bundle = None if not source.local(): cg = source.changegroup(incoming, 'incoming') - bundle = commands.write_bundle(cg, compress=False) + bundle = changegroup.writebundle(cg, None, 'HG10UN') source = bundlerepo.bundlerepository(ui, repo.root, bundle) return (source, incoming, bundle) @@ -575,6 +575,7 @@ def transplant(ui, repo, *revs, **opts): tp.apply(repo, source, revmap, merges, opts) finally: if bundle: + source.close() os.unlink(bundle) cmdtable = { @@ -588,5 +589,5 @@ cmdtable = { ('', 'log', None, _('append transplant info to log message')), ('c', 'continue', None, _('continue last transplant session after repair')), ('', 'filter', '', _('filter changesets through FILTER'))], - _('hg transplant [-s REPOSITORY] [-b BRANCH] [-p REV] [-m REV] [-n] REV...')) + _('hg transplant [-s REPOSITORY] [-b BRANCH [-a]] [-p REV] [-m REV] [REV]...')) } diff --git a/mercurial/appendfile.py b/mercurial/appendfile.py --- a/mercurial/appendfile.py +++ b/mercurial/appendfile.py @@ -5,8 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import * -demandload(globals(), "cStringIO changelog errno manifest os tempfile util") +import cStringIO, changelog, errno, manifest, os, tempfile, util # writes to metadata files are ordered. reads: changelog, manifest, # normal files. writes: normal files, manifest, changelog. diff --git a/mercurial/archival.py b/mercurial/archival.py --- a/mercurial/archival.py +++ b/mercurial/archival.py @@ -5,10 +5,9 @@ # This software may be used and distributed according to the terms of # the GNU General Public License, incorporated herein by reference. -from demandload import * -from i18n import gettext as _ +from i18n import _ from node import * -demandload(globals(), 'cStringIO os stat tarfile time util zipfile') +import cStringIO, os, stat, tarfile, time, util, zipfile def tidyprefix(dest, prefix, suffixes): '''choose prefix to use for names in archive. make sure prefix is @@ -155,15 +154,12 @@ def archive(repo, dest, node, kind, deco def write(name, mode, data): if matchfn and not matchfn(name): return if decode: - fp = cStringIO.StringIO() - repo.wwrite(name, data, fp) - data = fp.getvalue() + data = repo.wwritedata(name, data) archiver.addfile(name, mode, data) - change = repo.changelog.read(node) - mn = change[0] - archiver = archivers[kind](dest, prefix, mtime or change[2][0]) - m = repo.manifest.read(mn) + ctx = repo.changectx(node) + archiver = archivers[kind](dest, prefix, mtime or ctx.date()[0]) + m = ctx.manifest() items = m.items() items.sort() write('.hg_archival.txt', 0644, diff --git a/mercurial/bdiff.c b/mercurial/bdiff.c --- a/mercurial/bdiff.c +++ b/mercurial/bdiff.c @@ -33,7 +33,11 @@ static uint32_t htonl(uint32_t x) } #else #include +#ifdef __BEOS__ +#include +#else #include +#endif #include #endif diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py +++ b/mercurial/bundlerepo.py @@ -11,9 +11,8 @@ of the GNU General Public License, incor """ from node import * -from i18n import gettext as _ -from demandload import demandload -demandload(globals(), "changegroup util os struct bz2 tempfile") +from i18n import _ +import changegroup, util, os, struct, bz2, tempfile import localrepo, changelog, manifest, filelog, revlog @@ -50,7 +49,7 @@ class bundlerevlog(revlog.revlog): continue for p in (p1, p2): if not p in self.nodemap: - raise revlog.RevlogError(_("unknown parent %s") % short(p1)) + raise revlog.LookupError(_("unknown parent %s") % short(p1)) if linkmapper is None: link = n else: diff --git a/mercurial/changegroup.py b/mercurial/changegroup.py --- a/mercurial/changegroup.py +++ b/mercurial/changegroup.py @@ -6,9 +6,9 @@ changegroup.py - Mercurial changegroup m 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(), "struct os bz2 zlib util tempfile") + +from i18n import _ +import struct, os, bz2, zlib, util, tempfile def getchunk(source): """get a chunk from a changegroup""" @@ -67,8 +67,6 @@ def writebundle(cg, filename, bundletype cleanup = None try: if filename: - if os.path.exists(filename): - raise util.Abort(_("file '%s' already exists") % filename) fh = open(filename, "wb") else: fd, filename = tempfile.mkstemp(prefix="hg-bundle-", suffix=".hg") diff --git a/mercurial/changelog.py b/mercurial/changelog.py --- a/mercurial/changelog.py +++ b/mercurial/changelog.py @@ -6,9 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. from revlog import * -from i18n import gettext as _ -from demandload import demandload -demandload(globals(), "os time util") +from i18n import _ +import os, time, util def _string_escape(text): """ diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -5,11 +5,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload from node import * -from i18n import gettext as _ -demandload(globals(), 'os sys') -demandload(globals(), 'mdiff util templater patch') +from i18n import _ +import os, sys, mdiff, bdiff, util, templater, patch revrangesep = ':' @@ -148,21 +146,29 @@ def walk(repo, pats=[], opts={}, node=No yield src, fn, util.pathto(repo.getcwd(), fn), fn in exact def findrenames(repo, added=None, removed=None, threshold=0.5): + '''find renamed files -- yields (before, after, score) tuples''' if added is None or removed is None: added, removed = repo.status()[1:3] - changes = repo.changelog.read(repo.dirstate.parents()[0]) - mf = repo.manifest.read(changes[0]) + ctx = repo.changectx() for a in added: aa = repo.wread(a) - bestscore, bestname = None, None + bestname, bestscore = None, threshold for r in removed: - rr = repo.file(r).read(mf[r]) - delta = mdiff.textdiff(aa, rr) - if len(delta) < len(aa): - myscore = 1.0 - (float(len(delta)) / len(aa)) - if bestscore is None or myscore > bestscore: - bestscore, bestname = myscore, r - if bestname and bestscore >= threshold: + rr = ctx.filectx(r).data() + + # bdiff.blocks() returns blocks of matching lines + # count the number of bytes in each + equal = 0 + alines = mdiff.splitnewlines(aa) + matches = bdiff.blocks(aa, rr) + for x1,x2,y1,y2 in matches: + for line in alines[x1:x2]: + equal += len(line) + + myscore = equal*2.0 / (len(aa)+len(rr)) + if myscore >= bestscore: + bestname, bestscore = r, myscore + if bestname: yield bestname, a, bestscore def addremove(repo, pats=[], opts={}, wlock=None, dry_run=None, @@ -179,7 +185,8 @@ def addremove(repo, pats=[], opts={}, wl mapping[abs] = rel, exact if repo.ui.verbose or not exact: repo.ui.status(_('adding %s\n') % ((pats and rel) or abs)) - if repo.dirstate.state(abs) != 'r' and not os.path.exists(rel): + islink = os.path.islink(rel) + if repo.dirstate.state(abs) != 'r' and not islink and not os.path.exists(rel): remove.append(abs) mapping[abs] = rel, exact if repo.ui.verbose or not exact: @@ -201,12 +208,11 @@ def addremove(repo, pats=[], opts={}, wl class changeset_printer(object): '''show changeset information when templating not requested.''' - def __init__(self, ui, repo, patch, brinfo, buffered): + def __init__(self, ui, repo, patch, buffered): self.ui = ui self.repo = repo self.buffered = buffered self.patch = patch - self.brinfo = brinfo self.header = {} self.hunk = {} self.lastheader = None @@ -270,11 +276,6 @@ class changeset_printer(object): for parent in parents: self.ui.write(_("parent: %d:%s\n") % parent) - if self.brinfo: - br = self.repo.branchlookup([changenode]) - if br: - self.ui.write(_("branch: %s\n") % " ".join(br[changenode])) - if self.ui.debugflag: self.ui.write(_("manifest: %d:%s\n") % (self.repo.manifest.rev(changes[0]), hex(changes[0]))) @@ -322,8 +323,8 @@ class changeset_printer(object): class changeset_templater(changeset_printer): '''format changeset information.''' - def __init__(self, ui, repo, patch, brinfo, mapfile, buffered): - changeset_printer.__init__(self, ui, repo, patch, brinfo, buffered) + def __init__(self, ui, repo, patch, mapfile, buffered): + changeset_printer.__init__(self, ui, repo, patch, buffered) self.t = templater.templater(mapfile, templater.common_filters, cache={'parent': '{rev}:{node|short} ', 'manifest': '{rev}:{node|short}', @@ -409,12 +410,6 @@ class changeset_templater(changeset_prin if branch: branch = util.tolocal(branch) return showlist('branch', [branch], plural='branches', **args) - # add old style branches if requested - if self.brinfo: - br = self.repo.branchlookup([changenode]) - if changenode in br: - return showlist('branch', br[changenode], - plural='branches', **args) def showparents(**args): parents = [[('rev', log.rev(p)), ('node', hex(p))] @@ -528,11 +523,6 @@ def show_changeset(ui, repo, opts, buffe if opts.get('patch'): patch = matchfn or util.always - br = None - if opts.get('branches'): - ui.warn(_("the --branches option is deprecated, " - "please use 'hg branches' instead\n")) - br = True tmpl = opts.get('template') mapfile = None if tmpl: @@ -554,12 +544,12 @@ def show_changeset(ui, repo, opts, buffe or templater.templatepath(mapfile)) if mapname: mapfile = mapname try: - t = changeset_templater(ui, repo, patch, br, mapfile, buffered) + t = changeset_templater(ui, repo, patch, mapfile, buffered) except SyntaxError, inst: raise util.Abort(inst.args[0]) if tmpl: t.use_template(tmpl) return t - return changeset_printer(ui, repo, patch, br, buffered) + return changeset_printer(ui, repo, patch, buffered) def finddate(ui, repo, date): """Find the tipmost changeset that matches the given date spec""" diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -5,14 +5,14 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload +import demandimport; demandimport.enable() from node import * -from i18n import gettext as _ -demandload(globals(), "bisect os re sys signal imp urllib pdb shlex stat") -demandload(globals(), "fancyopts ui hg util lock revlog bundlerepo") -demandload(globals(), "difflib patch time help mdiff tempfile") -demandload(globals(), "traceback errno version atexit socket") -demandload(globals(), "archival changegroup cmdutil hgweb.server sshserver") +from i18n import _ +import bisect, os, re, sys, signal, imp, urllib, pdb, shlex, stat +import fancyopts, ui, hg, util, lock, revlog, bundlerepo +import difflib, patch, time, help, mdiff, tempfile +import traceback, errno, version, atexit, socket +import archival, changegroup, cmdutil, hgweb.server, sshserver class UnknownCommand(Exception): """Exception raised if command is not in the command table.""" @@ -240,8 +240,7 @@ def backout(ui, repo, rev, **opts): if op1 != node: if opts['merge']: ui.status(_('merging with changeset %s\n') % nice(op1)) - n = _lookup(repo, hex(op1)) - hg.merge(repo, n) + hg.merge(repo, hex(op1)) else: ui.status(_('the backout changeset is a new head - ' 'do not forget to merge\n')) @@ -685,15 +684,12 @@ def debugcomplete(ui, cmd='', **opts): clist.sort() ui.write("%s\n" % "\n".join(clist)) -def debugrebuildstate(ui, repo, rev=None): +def debugrebuildstate(ui, repo, rev=""): """rebuild the dirstate as it would look like for the given revision""" - if not rev: + if rev == "": rev = repo.changelog.tip() - else: - rev = repo.lookup(rev) - change = repo.changelog.read(rev) - n = change[0] - files = repo.manifest.read(n) + ctx = repo.changectx(rev) + files = ctx.manifest() wlock = repo.wlock() repo.dirstate.rebuild(rev, files) @@ -704,10 +700,8 @@ def debugcheckstate(ui, repo): dc = repo.dirstate.map keys = dc.keys() keys.sort() - m1n = repo.changelog.read(parent1)[0] - m2n = repo.changelog.read(parent2)[0] - m1 = repo.manifest.read(m1n) - m2 = repo.manifest.read(m2n) + m1 = repo.changectx(parent1).manifest() + m2 = repo.changectx(parent2).manifest() errors = 0 for f in dc: state = repo.dirstate.state(f) @@ -1156,13 +1150,14 @@ def grep(ui, repo, pattern, *pats, **opt prev = {} def display(fn, rev, states, prevstates): - counts = {'-': 0, '+': 0} + found = False filerevmatches = {} - if incrementing or not opts['all']: - a, b, r = prevstates, states, rev + r = prev.get(fn, -1) + if opts['all']: + iter = difflinestates(states, prevstates) else: - a, b, r = states, prevstates, prev.get(fn, -1) - for change, l in difflinestates(a, b): + iter = [('', l) for l in prevstates] + for change, l in iter: cols = [fn, str(r)] if opts['line_number']: cols.append(str(l.linenum)) @@ -1178,19 +1173,17 @@ def grep(ui, repo, pattern, *pats, **opt else: cols.append(l.line) ui.write(sep.join(cols), eol) - counts[change] += 1 - return counts['+'], counts['-'] + found = True + return found fstate = {} skip = {} get = util.cachefunc(lambda r: repo.changectx(r).changeset()) changeiter, matchfn = cmdutil.walkchangerevs(ui, repo, pats, get, opts) - count = 0 - incrementing = False + found = False follow = opts.get('follow') for st, rev, fns in changeiter: if st == 'window': - incrementing = rev matches.clear() elif st == 'add': mf = repo.changectx(rev).manifest() @@ -1216,10 +1209,10 @@ def grep(ui, repo, pattern, *pats, **opt if copy: skip[copy] = True continue - if incrementing or not opts['all'] or fstate[fn]: - pos, neg = display(fn, rev, m, fstate[fn]) - count += pos + neg - if pos and not opts['all']: + if fn in prev or fstate[fn]: + r = display(fn, rev, m, fstate[fn]) + found = found or r + if r and not opts['all']: skip[fn] = True if copy: skip[copy] = True @@ -1228,15 +1221,14 @@ def grep(ui, repo, pattern, *pats, **opt fstate[copy] = m prev[fn] = rev - if not incrementing: - fstate = fstate.items() - fstate.sort() - for fn, state in fstate: - if fn in skip: - continue - if fn not in copies.get(prev[fn], {}): - display(fn, rev, {}, state) - return (count == 0 and 1) or 0 + fstate = fstate.items() + fstate.sort() + for fn, state in fstate: + if fn in skip: + continue + if fn not in copies.get(prev[fn], {}): + found = display(fn, rev, {}, state) or found + return (not found and 1) or 0 def heads(ui, repo, **opts): """show current repository heads @@ -1543,10 +1535,15 @@ def incoming(ui, repo, source="default", setremoteconfig(ui, opts) other = hg.repository(ui, source) + ui.status(_('comparing with %s\n') % source) incoming = repo.findincoming(other, force=opts["force"]) if not incoming: + try: + os.unlink(opts["bundle"]) + except: + pass ui.status(_("no changes found\n")) - return + return 1 cleanup = None try: @@ -1711,7 +1708,6 @@ def log(ui, repo, *pats, **opts): if opts["date"]: df = util.matchdate(opts["date"]) - displayer = cmdutil.show_changeset(ui, repo, opts, True, matchfn) for st, rev, fns in changeiter: if st == 'add': @@ -1778,7 +1774,7 @@ def manifest(ui, repo, rev=None): ui.write("%3s " % (m.execf(f) and "755" or "644")) ui.write("%s\n" % f) -def merge(ui, repo, node=None, force=None, branch=None): +def merge(ui, repo, node=None, force=None): """merge working directory with another revision Merge the contents of the current working directory and the @@ -1792,9 +1788,7 @@ def merge(ui, repo, node=None, force=Non revision to merge with must be provided. """ - if node or branch: - node = _lookup(repo, node, branch) - else: + if not node: heads = repo.heads() if len(heads) > 2: raise util.Abort(_('repo has %d heads - ' @@ -1826,10 +1820,11 @@ def outgoing(ui, repo, dest=None, **opts revs = [repo.lookup(rev) for rev in opts['rev']] other = hg.repository(ui, dest) + ui.status(_('comparing with %s\n') % dest) o = repo.findoutgoing(other, force=opts['force']) if not o: ui.status(_("no changes found\n")) - return + return 1 o = repo.changelog.nodesbetween(o, revs)[0] if opts['newest_first']: o.reverse() @@ -2142,8 +2137,9 @@ def revert(ui, repo, *pats, **opts): if not opts['rev'] and p2 != nullid: raise util.Abort(_('uncommitted merge - please provide a ' 'specific revision')) - node = repo.changectx(opts['rev']).node() - mf = repo.manifest.read(repo.changelog.read(node)[0]) + ctx = repo.changectx(opts['rev']) + node = ctx.node() + mf = ctx.manifest() if node == parent: pmf = mf else: @@ -2233,7 +2229,7 @@ def revert(ui, repo, *pats, **opts): if pmf is None: # only need parent manifest in this unlikely case, # so do not read by default - pmf = repo.manifest.read(repo.changelog.read(parent)[0]) + pmf = repo.changectx(parent).manifest() if abs in pmf: if mfentry: # if version of file is same in parent and target @@ -2467,9 +2463,10 @@ def tags(ui, repo): hexfunc = ui.debugflag and hex or short for t, n in l: try: + hn = hexfunc(n) r = "%5d:%s" % (repo.changelog.rev(n), hexfunc(n)) - except KeyError: - r = " ?:?" + except revlog.LookupError: + r = " ?:%s" % hn if ui.quiet: ui.write("%s\n" % t) else: @@ -2497,7 +2494,7 @@ def unbundle(ui, repo, fname, **opts): modheads = repo.addchangegroup(gen, 'unbundle', 'bundle:' + fname) return postincoming(ui, repo, modheads, opts['update']) -def update(ui, repo, node=None, clean=False, branch=None, date=None): +def update(ui, repo, node=None, clean=False, date=None): """update working directory Update the working directory to the specified revision. @@ -2517,36 +2514,11 @@ def update(ui, repo, node=None, clean=Fa raise util.Abort(_("you can't specify a revision and a date")) node = cmdutil.finddate(ui, repo, date) - node = _lookup(repo, node, branch) if clean: return hg.clean(repo, node) else: return hg.update(repo, node) -def _lookup(repo, node, branch=None): - if branch: - repo.ui.warn(_("the --branch option is deprecated, " - "please use 'hg branch' instead\n")) - br = repo.branchlookup(branch=branch) - found = [] - for x in br: - if branch in br[x]: - found.append(x) - if len(found) > 1: - repo.ui.warn(_("Found multiple heads for %s\n") % branch) - for x in found: - cmdutil.show_changeset(ui, repo, {}).show(changenode=x) - raise util.Abort("") - if len(found) == 1: - node = found[0] - repo.ui.warn(_("Using head %s for branch %s\n") - % (short(node), branch)) - else: - raise util.Abort(_("branch %s not found") % branch) - else: - node = node and repo.lookup(node) or repo.changelog.tip() - return node - def verify(ui, repo): """verify the integrity of the repository @@ -2752,8 +2724,7 @@ table = { _('hg grep [OPTION]... PATTERN [FILE]...')), "heads": (heads, - [('b', 'branches', None, _('show branches (DEPRECATED)')), - ('', 'style', '', _('display using template map file')), + [('', 'style', '', _('display using template map file')), ('r', 'rev', '', _('show only heads which are descendants of rev')), ('', 'template', '', _('display with template'))], _('hg heads [-r REV]')), @@ -2764,7 +2735,7 @@ table = { [('p', 'strip', 1, _('directory strip option for patch. This has the same\n' 'meaning as the corresponding patch option')), - ('b', 'base', '', _('base path (DEPRECATED)')), + ('b', 'base', '', _('base path')), ('f', 'force', None, _('skip check for outstanding uncommitted changes'))] + commitopts, _('hg import [-p NUM] [-m MESSAGE] [-f] PATCH...')), @@ -2796,8 +2767,7 @@ table = { _('hg locate [OPTION]... [PATTERN]...')), "^log|history": (log, - [('b', 'branches', None, _('show branches (DEPRECATED)')), - ('f', 'follow', None, + [('f', 'follow', None, _('follow changeset history, or file history across copies and renames')), ('', 'follow-first', None, _('only follow the first parent of merge changesets')), @@ -2818,8 +2788,7 @@ table = { "manifest": (manifest, [], _('hg manifest [REV]')), "^merge": (merge, - [('b', 'branch', '', _('merge with head of a specific branch (DEPRECATED)')), - ('f', 'force', None, _('force a merge with outstanding changes'))], + [('f', 'force', None, _('force a merge with outstanding changes'))], _('hg merge [-f] [REV]')), "outgoing|out": (outgoing, [('M', 'no-merges', None, _('do not show merges')), @@ -2834,8 +2803,7 @@ table = { _('hg outgoing [-M] [-p] [-n] [-f] [-r REV]... [DEST]')), "^parents": (parents, - [('b', 'branches', None, _('show branches (DEPRECATED)')), - ('r', 'rev', '', _('show parents from the specified rev')), + [('r', 'rev', '', _('show parents from the specified rev')), ('', 'style', '', _('display using template map file')), ('', 'template', '', _('display with template'))], _('hg parents [-r REV] [FILE]')), @@ -2938,8 +2906,7 @@ table = { "tags": (tags, [], _('hg tags')), "tip": (tip, - [('b', 'branches', None, _('show branches (DEPRECATED)')), - ('', 'style', '', _('display using template map file')), + [('', 'style', '', _('display using template map file')), ('p', 'patch', None, _('show patch')), ('', 'template', '', _('display with template'))], _('hg tip [-p]')), @@ -2950,9 +2917,7 @@ table = { _('hg unbundle [-u] FILE')), "^update|up|checkout|co": (update, - [('b', 'branch', '', - _('checkout the head of a specific branch (DEPRECATED)')), - ('C', 'clean', None, _('overwrite locally modified files')), + [('C', 'clean', None, _('overwrite locally modified files')), ('d', 'date', '', _('tipmost revision matching date'))], _('hg update [-C] [-d DATE] [REV]')), "verify": (verify, [], _('hg verify')), @@ -3109,9 +3074,10 @@ def load_extensions(ui): if reposetup: hg.repo_setup_hooks.append(reposetup) cmdtable = getattr(mod, 'cmdtable', {}) - for t in cmdtable: - if t in table: - ui.warn(_("module %s overrides %s\n") % (name, t)) + overrides = [cmd for cmd in cmdtable if cmd in table] + if overrides: + ui.warn(_("extension '%s' overrides commands: %s\n") + % (name, " ".join(overrides))) table.update(cmdtable) def parseconfig(config): diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -6,9 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. from node import * -from i18n import gettext as _ -from demandload import demandload -demandload(globals(), "ancestor bdiff repo revlog util os") +from i18n import _ +import ancestor, bdiff, repo, revlog, util, os, errno class changectx(object): """A changecontext object makes access to data related to a particular @@ -84,21 +83,22 @@ class changectx(object): try: return self._manifest[path] except KeyError: - raise repo.LookupError(_("'%s' not found in manifest") % path) + raise revlog.LookupError(_("'%s' not found in manifest") % path) if '_manifestdelta' in self.__dict__ or path in self.files(): if path in self._manifestdelta: return self._manifestdelta[path] node, flag = self._repo.manifest.find(self._changeset[0], path) if not node: - raise repo.LookupError(_("'%s' not found in manifest") % path) + raise revlog.LookupError(_("'%s' not found in manifest") % path) return node - def filectx(self, path, fileid=None): + def filectx(self, path, fileid=None, filelog=None): """get a file context from this changeset""" if fileid is None: fileid = self.filenode(path) - return filectx(self._repo, path, fileid=fileid, changectx=self) + return filectx(self._repo, path, fileid=fileid, + changectx=self, filelog=filelog) def filectxs(self): """generate a file context for each file in this changeset's @@ -126,16 +126,18 @@ class filectx(object): self._repo = repo self._path = path - assert changeid is not None or fileid is not None + assert (changeid is not None + or fileid is not None + or changectx is not None) if filelog: self._filelog = filelog - if changectx: - self._changectx = changectx - self._changeid = changectx.node() if fileid is None: - self._changeid = changeid + if changectx is None: + self._changeid = changeid + else: + self._changectx = changectx else: self._fileid = fileid @@ -150,13 +152,10 @@ class filectx(object): self._changeid = self._filelog.linkrev(self._filenode) return self._changeid elif name == '_filenode': - try: - if '_fileid' in self.__dict__: - self._filenode = self._filelog.lookup(self._fileid) - else: - self._filenode = self._changectx.filenode(self._path) - except revlog.RevlogError, inst: - raise repo.LookupError(str(inst)) + if '_fileid' in self.__dict__: + self._filenode = self._filelog.lookup(self._fileid) + else: + self._filenode = self._changectx.filenode(self._path) return self._filenode elif name == '_filerev': self._filerev = self._filelog.rev(self._filenode) @@ -168,7 +167,7 @@ class filectx(object): try: n = self._filenode return True - except repo.LookupError: + except revlog.LookupError: # file is missing return False @@ -379,13 +378,15 @@ class workingctx(changectx): """generate a manifest corresponding to the working directory""" man = self._parents[0].manifest().copy() + is_exec = util.execfunc(self._repo.root, man.execf) + is_link = util.linkfunc(self._repo.root, man.linkf) copied = self._repo.dirstate.copies() modified, added, removed, deleted, unknown = self._status[:5] for i, l in (("a", added), ("m", modified), ("u", unknown)): for f in l: man[f] = man.get(copied.get(f, f), nullid) + i try: - man.set(f, util.is_exec(self._repo.wjoin(f), man.execf(f))) + man.set(f, is_exec(f), is_link(f)) except OSError: pass @@ -424,9 +425,10 @@ class workingctx(changectx): def children(self): return [] - def filectx(self, path): + def filectx(self, path, filelog=None): """get a file context from the working directory""" - return workingfilectx(self._repo, path, workingctx=self) + return workingfilectx(self._repo, path, workingctx=self, + filelog=filelog) def ancestor(self, c2): """return the ancestor context of self and c2""" @@ -484,7 +486,7 @@ class workingfilectx(filectx): rp = self._repopath if rp == self._path: return None - return rp, self._workingctx._parents._manifest.get(rp, nullid) + return rp, self._changectx._parents[0]._manifest.get(rp, nullid) def parents(self): '''return parent filectxs, following copies if necessary''' @@ -505,5 +507,12 @@ class workingfilectx(filectx): return [] def size(self): return os.stat(self._repo.wjoin(self._path)).st_size + def date(self): + t, tz = self._changectx.date() + try: + return (int(os.lstat(self._repo.wjoin(self._path)).st_mtime), tz) + except OSError, err: + if err.errno != errno.ENOENT: raise + return (t, tz) def cmp(self, text): return self._repo.wread(self._path) == text diff --git a/mercurial/demandimport.py b/mercurial/demandimport.py new file mode 100644 --- /dev/null +++ b/mercurial/demandimport.py @@ -0,0 +1,115 @@ +# demandimport.py - global demand-loading of modules 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. + +''' +demandimport - automatic demandloading of modules + +To enable this module, do: + + import demandimport; demandimport.enable() + +Imports of the following forms will be demand-loaded: + + import a, b.c + import a.b as c + from a import b,c # a will be loaded immediately + +These imports will not be delayed: + + from a import * + b = __import__(a) +''' + +_origimport = __import__ + +class _demandmod(object): + """module demand-loader and proxy""" + def __init__(self, name, globals, locals): + if '.' in name: + head, rest = name.split('.', 1) + after = [rest] + else: + head = name + after = [] + object.__setattr__(self, "_data", (head, globals, locals, after)) + object.__setattr__(self, "_module", None) + def _extend(self, name): + """add to the list of submodules to load""" + self._data[3].append(name) + def _load(self): + if not self._module: + head, globals, locals, after = self._data + mod = _origimport(head, globals, locals) + # load submodules + def subload(mod, p): + h, t = p, None + if '.' in p: + h, t = p.split('.', 1) + if not hasattr(mod, h): + setattr(mod, h, _demandmod(p, mod.__dict__, mod.__dict__)) + elif t: + subload(getattr(mod, h), t) + + for x in after: + subload(mod, x) + + # are we in the locals dictionary still? + if locals and locals.get(head) == self: + locals[head] = mod + object.__setattr__(self, "_module", mod) + def __repr__(self): + return "" % self._data[0] + def __call__(self, *args, **kwargs): + raise TypeError("'unloaded module' object is not callable") + def __getattribute__(self, attr): + if attr in ('_data', '_extend', '_load', '_module'): + return object.__getattribute__(self, attr) + self._load() + return getattr(self._module, attr) + def __setattr__(self, attr, val): + self._load() + setattr(self._module, attr, val) + +def _demandimport(name, globals=None, locals=None, fromlist=None): + if not locals or name in ignore or fromlist == ('*',): + # these cases we can't really delay + return _origimport(name, globals, locals, fromlist) + elif not fromlist: + # import a [as b] + if '.' in name: # a.b + base, rest = name.split('.', 1) + # email.__init__ loading email.mime + if globals and globals.get('__name__', None) == base: + return _origimport(name, globals, locals, fromlist) + # if a is already demand-loaded, add b to its submodule list + if base in locals: + if isinstance(locals[base], _demandmod): + locals[base]._extend(rest) + return locals[base] + return _demandmod(name, globals, locals) + else: + # from a import b,c,d + mod = _origimport(name, globals, locals) + # recurse down the module chain + for comp in name.split('.')[1:]: + mod = getattr(mod, comp) + for x in fromlist: + # set requested submodules for demand load + if not(hasattr(mod, x)): + setattr(mod, x, _demandmod(x, mod.__dict__, mod.__dict__)) + return mod + +ignore = ['_hashlib', '_xmlplus', 'fcntl', 'win32com.gen_py'] + +def enable(): + "enable global demand-loading of modules" + __builtins__["__import__"] = _demandimport + +def disable(): + "disable global demand-loading of modules" + __builtins__["__import__"] = _origimport + diff --git a/mercurial/demandload.py b/mercurial/demandload.py deleted file mode 100644 --- a/mercurial/demandload.py +++ /dev/null @@ -1,135 +0,0 @@ -'''Demand load modules when used, not when imported.''' - -__author__ = '''Copyright 2006 Vadim Gelfer . -This software may be used and distributed according to the terms -of the GNU General Public License, incorporated herein by reference.''' - -# this is based on matt's original demandload module. it is a -# complete rewrite. some time, we may need to support syntax of -# "import foo as bar". - -class _importer(object): - '''import a module. it is not imported until needed, and is - imported at most once per scope.''' - - def __init__(self, scope, modname, fromlist): - '''scope is context (globals() or locals()) in which import - should be made. modname is name of module to import. - fromlist is list of modules for "from foo import ..." - emulation.''' - - self.scope = scope - self.modname = modname - self.fromlist = fromlist - self.mod = None - - def module(self): - '''import the module if needed, and return.''' - if self.mod is None: - self.mod = __import__(self.modname, self.scope, self.scope, - self.fromlist) - del self.modname, self.fromlist - return self.mod - -class _replacer(object): - '''placeholder for a demand loaded module. demandload puts this in - a target scope. when an attribute of this object is looked up, - this object is replaced in the target scope with the actual - module. - - we use __getattribute__ to avoid namespace clashes between - placeholder object and real module.''' - - def __init__(self, importer, target): - self.importer = importer - self.target = target - # consider case where we do this: - # demandload(globals(), 'foo.bar foo.quux') - # foo will already exist in target scope when we get to - # foo.quux. so we remember that we will need to demandload - # quux into foo's scope when we really load it. - self.later = [] - - def module(self): - return object.__getattribute__(self, 'importer').module() - - def __getattribute__(self, key): - '''look up an attribute in a module and return it. replace the - name of the module in the caller\'s dict with the actual - module.''' - - module = object.__getattribute__(self, 'module')() - target = object.__getattribute__(self, 'target') - importer = object.__getattribute__(self, 'importer') - later = object.__getattribute__(self, 'later') - - if later: - demandload(module.__dict__, ' '.join(later)) - - importer.scope[target] = module - - return getattr(module, key) - -class _replacer_from(_replacer): - '''placeholder for a demand loaded module. used for "from foo - import ..." emulation. semantics of this are different than - regular import, so different implementation needed.''' - - def module(self): - importer = object.__getattribute__(self, 'importer') - target = object.__getattribute__(self, 'target') - - return getattr(importer.module(), target) - - def __call__(self, *args, **kwargs): - target = object.__getattribute__(self, 'module')() - return target(*args, **kwargs) - -def demandload(scope, modules): - '''import modules into scope when each is first used. - - scope should be the value of globals() in the module calling this - function, or locals() in the calling function. - - modules is a string listing module names, separated by white - space. names are handled like this: - - foo import foo - foo bar import foo, bar - foo@bar import foo as bar - foo.bar import foo.bar - foo:bar from foo import bar - foo:bar,quux from foo import bar, quux - foo.bar:quux from foo.bar import quux''' - - for mod in modules.split(): - col = mod.find(':') - if col >= 0: - fromlist = mod[col+1:].split(',') - mod = mod[:col] - else: - fromlist = [] - as_ = None - if '@' in mod: - mod, as_ = mod.split("@") - importer = _importer(scope, mod, fromlist) - if fromlist: - for name in fromlist: - scope[name] = _replacer_from(importer, name) - else: - dot = mod.find('.') - if dot >= 0: - basemod = mod[:dot] - val = scope.get(basemod) - # if base module has already been demandload()ed, - # remember to load this submodule into its namespace - # when needed. - if isinstance(val, _replacer): - later = object.__getattribute__(val, 'later') - later.append(mod[dot+1:]) - continue - else: - basemod = mod - if not as_: - as_ = basemod - scope[as_] = _replacer(importer, as_) diff --git a/mercurial/dirstate.py b/mercurial/dirstate.py --- a/mercurial/dirstate.py +++ b/mercurial/dirstate.py @@ -8,9 +8,8 @@ of the GNU General Public License, incor """ from node import * -from i18n import gettext as _ -from demandload import * -demandload(globals(), "struct os time bisect stat strutil util re errno") +from i18n import _ +import struct, os, time, bisect, stat, strutil, util, re, errno class dirstate(object): format = ">cllll" @@ -338,14 +337,13 @@ class dirstate(object): return ret def supported_type(self, f, st, verbose=False): - if stat.S_ISREG(st.st_mode): + if stat.S_ISREG(st.st_mode) or stat.S_ISLNK(st.st_mode): return True if verbose: kind = 'unknown' if stat.S_ISCHR(st.st_mode): kind = _('character device') elif stat.S_ISBLK(st.st_mode): kind = _('block device') elif stat.S_ISFIFO(st.st_mode): kind = _('fifo') - elif stat.S_ISLNK(st.st_mode): kind = _('symbolic link') elif stat.S_ISSOCK(st.st_mode): kind = _('socket') elif stat.S_ISDIR(st.st_mode): kind = _('directory') self.ui.warn(_('%s: unsupported file type (type is %s)\n') % ( @@ -359,7 +357,7 @@ class dirstate(object): yield src, f def statwalk(self, files=None, match=util.always, ignored=False, - badmatch=None): + badmatch=None, directories=False): ''' walk recursively through the directory tree, finding all files matched by the match function @@ -367,6 +365,7 @@ class dirstate(object): results are yielded in a tuple (src, filename, st), where src is one of: 'f' the file was found in the directory tree + 'd' the file is a directory of the tree 'm' the file was only in the dirstate and not in the tree 'b' file was not found and matched badmatch @@ -396,6 +395,8 @@ class dirstate(object): # recursion free walker, faster than os.walk. def findfiles(s): work = [s] + if directories: + yield 'd', util.normpath(s[common_prefix_len:]), os.lstat(s) while work: top = work.pop() names = os.listdir(top) @@ -423,6 +424,8 @@ class dirstate(object): ds = util.pconvert(os.path.join(nd, f +'/')) if imatch(ds): work.append(p) + if directories: + yield 'd', np, st if imatch(np) and np in dc: yield 'm', np, st elif imatch(np): diff --git a/mercurial/filelog.py b/mercurial/filelog.py --- a/mercurial/filelog.py +++ b/mercurial/filelog.py @@ -6,8 +6,7 @@ # of the GNU General Public License, incorporated herein by reference. from revlog import * -from demandload import * -demandload(globals(), "os") +import os class filelog(revlog): def __init__(self, opener, path, defversion=REVLOG_DEFAULT_VERSION): diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -8,10 +8,11 @@ from node import * from repo import * -from demandload import * -from i18n import gettext as _ -demandload(globals(), "localrepo bundlerepo httprepo sshrepo statichttprepo") -demandload(globals(), "errno lock os shutil util merge@_merge verify@_verify") +from i18n import _ +import localrepo, bundlerepo, httprepo, sshrepo, statichttprepo +import errno, lock, os, shutil, util +import merge as _merge +import verify as _verify def _local(path): return (os.path.isfile(util.drop_scheme('file', path)) and diff --git a/mercurial/hgweb/__init__.py b/mercurial/hgweb/__init__.py --- a/mercurial/hgweb/__init__.py +++ b/mercurial/hgweb/__init__.py @@ -6,6 +6,11 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import demandload -demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") -demandload(globals(), "mercurial.hgweb.hgwebdir_mod:hgwebdir") +import hgweb_mod, hgwebdir_mod + +def hgweb(*args, **kwargs): + return hgweb_mod.hgweb(*args, **kwargs) + +def hgwebdir(*args, **kwargs): + return hgwebdir_mod.hgwebdir(*args, **kwargs) + diff --git a/mercurial/hgweb/common.py b/mercurial/hgweb/common.py --- a/mercurial/hgweb/common.py +++ b/mercurial/hgweb/common.py @@ -7,7 +7,6 @@ # of the GNU General Public License, incorporated herein by reference. import os, mimetypes -import os.path def get_mtime(repo_path): store_path = os.path.join(repo_path, ".hg") @@ -39,7 +38,7 @@ def staticfile(directory, fname, req): os.stat(path) ct = mimetypes.guess_type(path)[0] or "text/plain" req.header([('Content-type', ct), - ('Content-length', os.path.getsize(path))]) + ('Content-length', str(os.path.getsize(path)))]) return file(path, 'rb').read() except (TypeError, OSError): # illegal fname or unreadable file 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 @@ -6,17 +6,13 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os -import os.path -import mimetypes -from mercurial.demandload import demandload -demandload(globals(), "re zlib ConfigParser mimetools cStringIO sys tempfile") -demandload(globals(), 'urllib bz2') -demandload(globals(), "mercurial:mdiff,ui,hg,util,archival,streamclone,patch") -demandload(globals(), "mercurial:revlog,templater") -demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map") +import os, mimetypes, re, zlib, mimetools, cStringIO, sys +import tempfile, urllib, bz2 from mercurial.node import * from mercurial.i18n import gettext as _ +from mercurial import mdiff, ui, hg, util, archival, streamclone, patch +from mercurial import revlog, templater +from common import get_mtime, staticfile, style_map def _up(p): if p[0] != "/": @@ -172,14 +168,10 @@ class hgweb(object): yield self.t("diffline", line=l) r = self.repo - cl = r.changelog - mf = r.manifest - change1 = cl.read(node1) - change2 = cl.read(node2) - mmap1 = mf.read(change1[0]) - mmap2 = mf.read(change2[0]) - date1 = util.datestr(change1[2]) - date2 = util.datestr(change2[2]) + c1 = r.changectx(node1) + c2 = r.changectx(node2) + date1 = util.datestr(c1.date()) + date2 = util.datestr(c2.date()) modified, added, removed, deleted, unknown = r.status(node1, node2)[:5] if files: @@ -188,17 +180,17 @@ class hgweb(object): diffopts = patch.diffopts(self.repo.ui, untrusted=True) for f in modified: - to = r.file(f).read(mmap1[f]) - tn = r.file(f).read(mmap2[f]) + to = c1.filectx(f).data() + tn = c2.filectx(f).data() yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) for f in added: to = None - tn = r.file(f).read(mmap2[f]) + tn = c2.filectx(f).data() yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) for f in removed: - to = r.file(f).read(mmap1[f]) + to = c1.filectx(f).data() tn = None yield diffblock(mdiff.unidiff(to, date1, tn, date2, f, opts=diffopts), f, tn) @@ -497,8 +489,6 @@ class hgweb(object): archives=self.archivelist(hex(node))) def tags(self): - cl = self.repo.changelog - i = self.repo.tagslist() i.reverse() @@ -509,7 +499,7 @@ class hgweb(object): continue yield {"parity": self.stripes(parity), "tag": k, - "date": cl.read(n)[2], + "date": self.repo.changectx(n).date(), "node": hex(n)} parity += 1 @@ -519,8 +509,6 @@ class hgweb(object): entriesnotip=lambda **x: entries(True, **x)) def summary(self): - cl = self.repo.changelog - i = self.repo.tagslist() i.reverse() @@ -535,14 +523,11 @@ class hgweb(object): if count > 10: # limit to 10 tags break; - c = cl.read(n) - t = c[2] - yield self.t("tagentry", - parity = self.stripes(parity), - tag = k, - node = hex(n), - date = t) + parity=self.stripes(parity), + tag=k, + node=hex(n), + date=self.repo.changectx(n).date()) parity += 1 def heads(**map): @@ -564,40 +549,38 @@ class hgweb(object): def changelist(**map): parity = 0 - cl = self.repo.changelog l = [] # build a list in forward order for efficiency for i in xrange(start, end): - n = cl.node(i) - changes = cl.read(n) - hn = hex(n) - t = changes[2] + ctx = self.repo.changectx(i) + hn = hex(ctx.node()) l.insert(0, self.t( 'shortlogentry', - parity = parity, - author = changes[1], - desc = changes[4], - date = t, - rev = i, - node = hn)) + parity=parity, + author=ctx.user(), + desc=ctx.description(), + date=ctx.date(), + rev=i, + node=hn)) parity = 1 - parity yield l + cl = self.repo.changelog count = cl.count() start = max(0, count - self.maxchanges) end = min(count, start + self.maxchanges) yield self.t("summary", - desc = self.config("web", "description", "unknown"), - owner = (self.config("ui", "username") or # preferred - self.config("web", "contact") or # deprecated - self.config("web", "author", "unknown")), # also - lastchange = cl.read(cl.tip())[2], - tags = tagentries, - heads = heads, - shortlog = changelist, - node = hex(cl.tip()), + desc=self.config("web", "description", "unknown"), + owner=(self.config("ui", "username") or # preferred + self.config("web", "contact") or # deprecated + self.config("web", "author", "unknown")), # also + lastchange=cl.read(cl.tip())[2], + tags=tagentries, + heads=heads, + shortlog=changelist, + node=hex(cl.tip()), archives=self.archivelist("tip")) def filediff(self, fctx): @@ -623,9 +606,13 @@ class hgweb(object): 'zip': ('application/zip', 'zip', '.zip', None), } - def archive(self, req, cnode, type_): + def archive(self, req, id, type_): reponame = re.sub(r"\W+", "-", os.path.basename(self.reponame)) - name = "%s-%s" % (reponame, short(cnode)) + cnode = self.repo.lookup(id) + arch_version = id + if cnode == id: + arch_version = short(cnode) + name = "%s-%s" % (reponame, arch_version) mimetype, artype, extension, encoding = self.archive_specs[type_] headers = [('Content-type', mimetype), ('Content-disposition', 'attachment; filename=%s%s' % @@ -789,6 +776,9 @@ class hgweb(object): port = req.env["SERVER_PORT"] port = port != "80" and (":" + port) or "" urlbase = 'http://%s%s' % (req.env['SERVER_NAME'], port) + staticurl = self.config("web", "staticurl") or req.url + 'static/' + if not staticurl.endswith('/'): + staticurl += '/' if not self.reponame: self.reponame = (self.config("web", "name") @@ -797,6 +787,7 @@ class hgweb(object): self.t = templater.templater(mapfile, templater.common_filters, defaults={"url": req.url, + "staticurl": staticurl, "urlbase": urlbase, "repo": self.reponame, "header": header, @@ -873,7 +864,7 @@ class hgweb(object): try: req.write(self.filerevision(self.filectx(req))) return - except hg.RepoError: + except revlog.LookupError: pass req.write(self.manifest(self.changectx(req), path)) @@ -1002,12 +993,11 @@ class hgweb(object): req.write(z.flush()) def do_archive(self, req): - changeset = self.repo.lookup(req.form['node'][0]) type_ = req.form['type'][0] allowed = self.configlist("web", "allow_archive") if (type_ in self.archives and (type_ in allowed or self.configbool("web", "allow" + type_, False))): - self.archive(req, changeset, type_) + self.archive(req, req.form['node'][0], type_) return req.write(self.t("error")) diff --git a/mercurial/hgweb/hgwebdir_mod.py b/mercurial/hgweb/hgwebdir_mod.py --- a/mercurial/hgweb/hgwebdir_mod.py +++ b/mercurial/hgweb/hgwebdir_mod.py @@ -6,13 +6,12 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import os -from mercurial.demandload import demandload -demandload(globals(), "mimetools cStringIO") -demandload(globals(), "mercurial:ui,hg,util,templater") -demandload(globals(), "mercurial.hgweb.hgweb_mod:hgweb") -demandload(globals(), "mercurial.hgweb.common:get_mtime,staticfile,style_map") +from mercurial import demandimport; demandimport.enable() +import os, mimetools, cStringIO from mercurial.i18n import gettext as _ +from mercurial import ui, hg, util, templater +from common import get_mtime, staticfile, style_map +from hgweb_mod import hgweb # This is a stopgap class hgwebdir(object): @@ -31,8 +30,11 @@ class hgwebdir(object): self.repos = cleannames(config.items()) self.repos.sort() else: - cp = util.configparser() - cp.read(config) + if isinstance(config, util.configparser): + cp = config + else: + cp = util.configparser() + cp.read(config) self.repos = [] if cp.has_section('web'): if cp.has_option('web', 'motd'): @@ -86,6 +88,10 @@ class hgwebdir(object): if not url.endswith('/'): url += '/' + staticurl = config('web', 'staticurl') or url + 'static/' + if not staticurl.endswith('/'): + staticurl += '/' + style = self.style if style is None: style = config('web', 'style', '') @@ -96,7 +102,8 @@ class hgwebdir(object): defaults={"header": header, "footer": footer, "motd": motd, - "url": url}) + "url": url, + "staticurl": staticurl}) def archivelist(ui, nodeid, url): allowed = ui.configlist("web", "allow_archive", untrusted=True) diff --git a/mercurial/hgweb/request.py b/mercurial/hgweb/request.py --- a/mercurial/hgweb/request.py +++ b/mercurial/hgweb/request.py @@ -6,8 +6,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import demandload -demandload(globals(), "socket sys cgi os errno") +import socket, cgi, errno from mercurial.i18n import gettext as _ class wsgiapplication(object): diff --git a/mercurial/hgweb/server.py b/mercurial/hgweb/server.py --- a/mercurial/hgweb/server.py +++ b/mercurial/hgweb/server.py @@ -6,11 +6,11 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from mercurial.demandload import demandload -import os, sys, errno -demandload(globals(), "urllib BaseHTTPServer socket SocketServer traceback") -demandload(globals(), "mercurial:ui,hg,util,templater") -demandload(globals(), "hgweb_mod:hgweb hgwebdir_mod:hgwebdir request:wsgiapplication") +import os, sys, errno, urllib, BaseHTTPServer, socket, SocketServer, traceback +from mercurial import ui, hg, util, templater +from hgweb_mod import hgweb +from hgwebdir_mod import hgwebdir +from request import wsgiapplication from mercurial.i18n import gettext as _ def _splitURI(uri): diff --git a/mercurial/httprepo.py b/mercurial/httprepo.py --- a/mercurial/httprepo.py +++ b/mercurial/httprepo.py @@ -8,10 +8,9 @@ from node import * from remoterepo import * -from i18n import gettext as _ -from demandload import * -demandload(globals(), "hg os urllib urllib2 urlparse zlib util httplib") -demandload(globals(), "errno keepalive tempfile socket changegroup") +from i18n import _ +import hg, os, urllib, urllib2, urlparse, zlib, util, httplib +import errno, keepalive, tempfile, socket, changegroup class passwordmgr(urllib2.HTTPPasswordMgrWithDefaultRealm): def __init__(self, ui): diff --git a/mercurial/i18n.py b/mercurial/i18n.py --- a/mercurial/i18n.py +++ b/mercurial/i18n.py @@ -7,9 +7,7 @@ This software may be used and distribute of the GNU General Public License, incorporated herein by reference. """ -# the import from gettext is _really_ slow -# for now we use a dummy function -gettext = lambda x: x -#import gettext -#t = gettext.translation('hg', '/usr/share/locale', fallback=1) -#gettext = t.gettext +import gettext +t = gettext.translation('hg', fallback=1) +gettext = t.gettext +_ = gettext diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -6,13 +6,11 @@ # of the GNU General Public License, incorporated herein by reference. from node import * -from i18n import gettext as _ -from demandload import * -import repo -demandload(globals(), "appendfile changegroup") -demandload(globals(), "changelog dirstate filelog manifest context") -demandload(globals(), "re lock transaction tempfile stat mdiff errno ui") -demandload(globals(), "os revlog time util") +from i18n import _ +import repo, appendfile, changegroup +import changelog, dirstate, filelog, manifest, context +import re, lock, transaction, tempfile, stat, mdiff, errno, ui +import os, revlog, time, util class localrepository(repo.repository): capabilities = ('lookup', 'changegroupsubset') @@ -44,17 +42,19 @@ class localrepository(repo.repository): if not os.path.exists(path): os.mkdir(path) os.mkdir(self.path) - os.mkdir(os.path.join(self.path, "store")) - requirements = ("revlogv1", "store") + requirements = ["revlogv1"] + if parentui.configbool('format', 'usestore', True): + os.mkdir(os.path.join(self.path, "store")) + requirements.append("store") + # create an invalid changelog + self.opener("00changelog.i", "a").write( + '\0\0\0\2' # represents revlogv2 + ' dummy changelog to prevent using the old repo layout' + ) reqfile = self.opener("requires", "w") for r in requirements: reqfile.write("%s\n" % r) reqfile.close() - # create an invalid changelog - self.opener("00changelog.i", "a").write( - '\0\0\0\2' # represents revlogv2 - ' dummy changelog to prevent using the old repo layout' - ) else: raise repo.RepoError(_("repository %s not found") % path) elif create: @@ -120,10 +120,14 @@ class localrepository(repo.repository): self.tagscache = None self.branchcache = None self.nodetagscache = None - self.encodepats = None - self.decodepats = None + self.filterpats = {} self.transhandle = None + self._link = lambda x: False + if util.checklink(self.root): + r = self.root # avoid circular reference in lambda + self._link = lambda x: util.is_link(os.path.join(r, x)) + self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) def url(self): @@ -141,32 +145,34 @@ class localrepository(repo.repository): be run as hooks without wrappers to convert return values.''' self.ui.note(_("calling hook %s: %s\n") % (hname, funcname)) - d = funcname.rfind('.') - if d == -1: - raise util.Abort(_('%s hook is invalid ("%s" not in a module)') - % (hname, funcname)) - modname = funcname[:d] - try: - obj = __import__(modname) - except ImportError: + obj = funcname + if not callable(obj): + d = funcname.rfind('.') + if d == -1: + raise util.Abort(_('%s hook is invalid ("%s" not in ' + 'a module)') % (hname, funcname)) + modname = funcname[:d] try: - # extensions are loaded with hgext_ prefix - obj = __import__("hgext_%s" % modname) + obj = __import__(modname) except ImportError: + try: + # extensions are loaded with hgext_ prefix + obj = __import__("hgext_%s" % modname) + except ImportError: + raise util.Abort(_('%s hook is invalid ' + '(import of "%s" failed)') % + (hname, modname)) + try: + for p in funcname.split('.')[1:]: + obj = getattr(obj, p) + except AttributeError, err: raise util.Abort(_('%s hook is invalid ' - '(import of "%s" failed)') % - (hname, modname)) - try: - for p in funcname.split('.')[1:]: - obj = getattr(obj, p) - except AttributeError, err: - raise util.Abort(_('%s hook is invalid ' - '("%s" is not defined)') % - (hname, funcname)) - if not callable(obj): - raise util.Abort(_('%s hook is invalid ' - '("%s" is not callable)') % - (hname, funcname)) + '("%s" is not defined)') % + (hname, funcname)) + if not callable(obj): + raise util.Abort(_('%s hook is invalid ' + '("%s" is not callable)') % + (hname, funcname)) try: r = obj(ui=self.ui, repo=self, hooktype=name, **args) except (KeyboardInterrupt, util.SignalInterrupt): @@ -204,7 +210,9 @@ class localrepository(repo.repository): if hname.split(".", 1)[0] == name and cmd] hooks.sort() for hname, cmd in hooks: - if cmd.startswith('python:'): + if callable(cmd): + r = callhook(hname, cmd) or r + elif cmd.startswith('python:'): r = callhook(hname, cmd[7:].strip()) or r else: r = runhook(hname, cmd) or r @@ -212,6 +220,37 @@ class localrepository(repo.repository): tag_disallowed = ':\r\n' + def _tag(self, name, node, message, local, user, date, parent=None): + use_dirstate = parent is None + + for c in self.tag_disallowed: + if c in name: + raise util.Abort(_('%r cannot be used in a tag name') % c) + + self.hook('pretag', throw=True, node=hex(node), tag=name, local=local) + + if local: + # local tags are stored in the current charset + self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name)) + self.hook('tag', node=hex(node), tag=name, local=local) + return + + # committed tags are stored in UTF-8 + line = '%s %s\n' % (hex(node), util.fromlocal(name)) + if use_dirstate: + self.wfile('.hgtags', 'ab').write(line) + else: + ntags = self.filectx('.hgtags', parent).data() + self.wfile('.hgtags', 'ab').write(ntags + line) + if use_dirstate and self.dirstate.state('.hgtags') == '?': + self.add(['.hgtags']) + + tagnode = self.commit(['.hgtags'], message, user, date, p1=parent) + + self.hook('tag', node=hex(node), tag=name, local=local) + + return tagnode + def tag(self, name, node, message, local, user, date): '''tag a revision with a symbolic name. @@ -230,31 +269,13 @@ class localrepository(repo.repository): date: date tuple to use if committing''' - for c in self.tag_disallowed: - if c in name: - raise util.Abort(_('%r cannot be used in a tag name') % c) - - self.hook('pretag', throw=True, node=hex(node), tag=name, local=local) - - if local: - # local tags are stored in the current charset - self.opener('localtags', 'a').write('%s %s\n' % (hex(node), name)) - self.hook('tag', node=hex(node), tag=name, local=local) - return - for x in self.status()[:5]: if '.hgtags' in x: raise util.Abort(_('working copy of .hgtags is changed ' '(please commit .hgtags manually)')) - # committed tags are stored in UTF-8 - line = '%s %s\n' % (hex(node), util.fromlocal(name)) - self.wfile('.hgtags', 'ab').write(line) - if self.dirstate.state('.hgtags') == '?': - self.add(['.hgtags']) - self.commit(['.hgtags'], message, user, date) - self.hook('tag', node=hex(node), tag=name, local=local) + self._tag(name, node, message, local, user, date) def tags(self): '''return a mapping of tag to node''' @@ -320,7 +341,7 @@ class localrepository(repo.repository): rev = c.rev() try: fnode = c.filenode('.hgtags') - except repo.LookupError: + except revlog.LookupError: continue ret.append((rev, node, fnode)) if fnode in last: @@ -497,17 +518,15 @@ class localrepository(repo.repository): def wfile(self, f, mode='r'): return self.wopener(f, mode) - def wread(self, filename): - if self.encodepats == None: + def _filter(self, filter, filename, data): + if filter not in self.filterpats: l = [] - for pat, cmd in self.ui.configitems("encode"): + for pat, cmd in self.ui.configitems(filter): mf = util.matcher(self.root, "", [pat], [], [])[1] l.append((mf, cmd)) - self.encodepats = l + self.filterpats[filter] = l - data = self.wopener(filename, 'r').read() - - for mf, cmd in self.encodepats: + for mf, cmd in self.filterpats[filter]: if mf(filename): self.ui.debug(_("filtering %s through %s\n") % (filename, cmd)) data = util.filter(data, cmd) @@ -515,23 +534,36 @@ class localrepository(repo.repository): return data - def wwrite(self, filename, data, fd=None): - if self.decodepats == None: - l = [] - for pat, cmd in self.ui.configitems("decode"): - mf = util.matcher(self.root, "", [pat], [], [])[1] - l.append((mf, cmd)) - self.decodepats = l + def wread(self, filename): + if self._link(filename): + data = os.readlink(self.wjoin(filename)) + else: + data = self.wopener(filename, 'r').read() + return self._filter("encode", filename, data) - for mf, cmd in self.decodepats: - if mf(filename): - self.ui.debug(_("filtering %s through %s\n") % (filename, cmd)) - data = util.filter(data, cmd) - break + def wwrite(self, filename, data, flags): + data = self._filter("decode", filename, data) + if "l" in flags: + f = self.wjoin(filename) + try: + os.unlink(f) + except OSError: + pass + d = os.path.dirname(f) + if not os.path.exists(d): + os.makedirs(d) + os.symlink(data, f) + else: + try: + if self._link(filename): + os.unlink(self.wjoin(filename)) + except OSError: + pass + self.wopener(filename, 'w').write(data) + util.set_exec(self.wjoin(filename), "x" in flags) - if fd: - return fd.write(data) - return self.wopener(filename, 'w').write(data) + def wwritedata(self, filename, data): + return self._filter("decode", filename, data) def transaction(self): tr = self.transhandle @@ -671,11 +703,11 @@ class localrepository(repo.repository): changelist.append(fn) return fl.add(t, meta, transaction, linkrev, fp1, fp2) - def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None): + def rawcommit(self, files, text, user, date, p1=None, p2=None, wlock=None, extra={}): if p1 is None: p1, p2 = self.dirstate.parents() return self.commit(files=files, text=text, user=user, date=date, - p1=p1, p2=p2, wlock=wlock) + p1=p1, p2=p2, wlock=wlock, extra=extra) def commit(self, files=None, text="", user=None, date=None, match=util.always, force=False, lock=None, wlock=None, @@ -749,12 +781,14 @@ class localrepository(repo.repository): new = {} linkrev = self.changelog.count() commit.sort() + is_exec = util.execfunc(self.root, m1.execf) + is_link = util.linkfunc(self.root, m1.linkf) for f in commit: self.ui.note(f + "\n") try: new[f] = self.filecommit(f, m1, m2, linkrev, tr, changed) - m1.set(f, util.is_exec(self.wjoin(f), m1.execf(f))) - except IOError: + m1.set(f, is_exec(f), is_link(f)) + except (OSError, IOError): if use_dirstate: self.ui.warn(_("trouble committing %s!\n") % f) raise @@ -764,11 +798,13 @@ class localrepository(repo.repository): # update manifest m1.update(new) remove.sort() + removed = [] for f in remove: if f in m1: del m1[f] - mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, remove)) + removed.append(f) + mn = self.manifest.add(m1, tr, linkrev, c1[0], c2[0], (new, removed)) # add changeset new = new.keys() @@ -783,8 +819,10 @@ class localrepository(repo.repository): edittext.append("HG: user: %s" % user) if p2 != nullid: edittext.append("HG: branch merge") + if branchname: + edittext.append("HG: branch %s" % util.tolocal(branchname)) edittext.extend(["HG: changed %s" % f for f in changed]) - edittext.extend(["HG: removed %s" % f for f in remove]) + edittext.extend(["HG: removed %s" % f for f in removed]) if not changed and not remove: edittext.append("HG: no files changed") edittext.append("") @@ -802,17 +840,20 @@ class localrepository(repo.repository): text = '\n'.join(lines) if branchname: extra["branch"] = branchname - n = self.changelog.add(mn, changed + remove, text, tr, p1, p2, + n = self.changelog.add(mn, changed + removed, text, tr, p1, p2, user, date, extra) self.hook('pretxncommit', throw=True, node=hex(n), parent1=xp1, parent2=xp2) tr.close() + if self.branchcache and "branch" in extra: + self.branchcache[util.tolocal(extra["branch"])] = n + if use_dirstate or update_dirstate: self.dirstate.setparents(n) if use_dirstate: self.dirstate.update(new, "n") - self.dirstate.forget(remove) + self.dirstate.forget(removed) self.hook("commit", node=hex(n), parent1=xp1, parent2=xp2) return n @@ -859,9 +900,9 @@ class localrepository(repo.repository): If node2 is None, compare node1 with working directory. """ - def fcmp(fn, mf): + def fcmp(fn, getnode): t1 = self.wread(fn) - return self.file(fn).cmp(mf.get(fn, nullid), t1) + return self.file(fn).cmp(getnode(fn), t1) def mfmatches(node): change = self.changelog.read(node) @@ -899,9 +940,11 @@ class localrepository(repo.repository): if compareworking: if lookup: # do a full compare of any files that might have changed - mf2 = mfmatches(self.dirstate.parents()[0]) + mnode = self.changelog.read(self.dirstate.parents()[0])[0] + getnode = lambda fn: (self.manifest.find(mnode, fn)[0] or + nullid) for f in lookup: - if fcmp(f, mf2): + if fcmp(f, getnode): modified.append(f) else: clean.append(f) @@ -912,9 +955,11 @@ class localrepository(repo.repository): # generate a pseudo-manifest for the working dir # XXX: create it in dirstate.py ? mf2 = mfmatches(self.dirstate.parents()[0]) + is_exec = util.execfunc(self.root, mf2.execf) + is_link = util.linkfunc(self.root, mf2.linkf) for f in lookup + modified + added: mf2[f] = "" - mf2.set(f, execf=util.is_exec(self.wjoin(f), mf2.execf(f))) + mf2.set(f, is_exec(f), is_link(f)) for f in removed: if f in mf2: del mf2[f] @@ -930,10 +975,12 @@ class localrepository(repo.repository): # reasonable order mf2keys = mf2.keys() mf2keys.sort() + getnode = lambda fn: mf1.get(fn, nullid) for fn in mf2keys: if mf1.has_key(fn): if mf1.flags(fn) != mf2.flags(fn) or \ - (mf1[fn] != mf2[fn] and (mf2[fn] != "" or fcmp(fn, mf1))): + (mf1[fn] != mf2[fn] and (mf2[fn] != "" or + fcmp(fn, getnode))): modified.append(fn) elif list_clean: clean.append(fn) @@ -953,11 +1000,12 @@ class localrepository(repo.repository): wlock = self.wlock() for f in list: p = self.wjoin(f) - if not os.path.exists(p): + islink = os.path.islink(p) + if not islink and not os.path.exists(p): self.ui.warn(_("%s does not exist!\n") % f) - elif not os.path.isfile(p): - self.ui.warn(_("%s not added: only files supported currently\n") - % f) + elif not islink and not os.path.isfile(p): + self.ui.warn(_("%s not added: only files and symlinks " + "supported currently\n") % f) elif self.dirstate.state(f) in 'an': self.ui.warn(_("%s already tracked!\n") % f) else: @@ -1004,8 +1052,7 @@ class localrepository(repo.repository): self.ui.warn("%s not removed!\n" % f) else: t = self.file(f).read(m[f]) - self.wwrite(f, t) - util.set_exec(self.wjoin(f), m.execf(f)) + self.wwrite(f, t, m.flags(f)) self.dirstate.update([f], "n") def copy(self, source, dest, wlock=None): @@ -1028,112 +1075,6 @@ class localrepository(repo.repository): heads.sort() return [n for (r, n) in heads] - # branchlookup returns a dict giving a list of branches for - # each head. A branch is defined as the tag of a node or - # the branch of the node's parents. If a node has multiple - # branch tags, tags are eliminated if they are visible from other - # branch tags. - # - # So, for this graph: a->b->c->d->e - # \ / - # aa -----/ - # a has tag 2.6.12 - # d has tag 2.6.13 - # e would have branch tags for 2.6.12 and 2.6.13. Because the node - # for 2.6.12 can be reached from the node 2.6.13, that is eliminated - # from the list. - # - # It is possible that more than one head will have the same branch tag. - # callers need to check the result for multiple heads under the same - # branch tag if that is a problem for them (ie checkout of a specific - # branch). - # - # passing in a specific branch will limit the depth of the search - # through the parents. It won't limit the branches returned in the - # result though. - def branchlookup(self, heads=None, branch=None): - if not heads: - heads = self.heads() - headt = [ h for h in heads ] - chlog = self.changelog - branches = {} - merges = [] - seenmerge = {} - - # traverse the tree once for each head, recording in the branches - # dict which tags are visible from this head. The branches - # dict also records which tags are visible from each tag - # while we traverse. - while headt or merges: - if merges: - n, found = merges.pop() - visit = [n] - else: - h = headt.pop() - visit = [h] - found = [h] - seen = {} - while visit: - n = visit.pop() - if n in seen: - continue - pp = chlog.parents(n) - tags = self.nodetags(n) - if tags: - for x in tags: - if x == 'tip': - continue - for f in found: - branches.setdefault(f, {})[n] = 1 - branches.setdefault(n, {})[n] = 1 - break - if n not in found: - found.append(n) - if branch in tags: - continue - seen[n] = 1 - if pp[1] != nullid and n not in seenmerge: - merges.append((pp[1], [x for x in found])) - seenmerge[n] = 1 - if pp[0] != nullid: - visit.append(pp[0]) - # traverse the branches dict, eliminating branch tags from each - # head that are visible from another branch tag for that head. - out = {} - viscache = {} - for h in heads: - def visible(node): - if node in viscache: - return viscache[node] - ret = {} - visit = [node] - while visit: - x = visit.pop() - if x in viscache: - ret.update(viscache[x]) - elif x not in ret: - ret[x] = 1 - if x in branches: - visit[len(visit):] = branches[x].keys() - viscache[node] = ret - return ret - if h not in branches: - continue - # O(n^2), but somewhat limited. This only searches the - # tags visible from a specific head, not all the tags in the - # whole repo. - for b in branches[h]: - vis = False - for bb in branches[h].keys(): - if b != bb: - if b in visible(bb): - vis = True - break - if not vis: - l = out.setdefault(h, []) - l[len(l):] = self.nodetags(b) - return out - def branches(self, nodes): if not nodes: nodes = [self.changelog.tip()] diff --git a/mercurial/lock.py b/mercurial/lock.py --- a/mercurial/lock.py +++ b/mercurial/lock.py @@ -5,8 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import * -demandload(globals(), 'errno os socket time util') +import errno, os, socket, time, util class LockException(IOError): def __init__(self, errno, strerror, filename, desc): diff --git a/mercurial/mail.py b/mercurial/mail.py --- a/mercurial/mail.py +++ b/mercurial/mail.py @@ -5,9 +5,8 @@ # 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 socket") +from i18n import _ +import os, smtplib, templater, util, socket def _smtp(ui): '''send mail using smtp.''' diff --git a/mercurial/manifest.py b/mercurial/manifest.py --- a/mercurial/manifest.py +++ b/mercurial/manifest.py @@ -6,10 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. from revlog import * -from i18n import gettext as _ -from demandload import * -demandload(globals(), "array bisect struct") -demandload(globals(), "mdiff") +from i18n import _ +import array, bisect, struct, mdiff class manifestdict(dict): def __init__(self, mapping=None, flags=None): @@ -108,7 +106,7 @@ class manifest(revlog): def find(self, node, f): '''look up entry for a single file efficiently. - return (node, flag) pair if found, (None, None) if not.''' + return (node, flags) pair if found, (None, None) if not.''' if self.mapcache and node == self.mapcache[0]: return self.mapcache[1].get(f), self.mapcache[1].flags(f) text = self.revision(node) @@ -117,7 +115,7 @@ class manifest(revlog): return None, None l = text[start:end] f, n = l.split('\0') - return bin(n[:40]), n[40:-1] == 'x' + return bin(n[:40]), n[40:-1] def add(self, map, transaction, link, p1=None, p2=None, changed=None): diff --git a/mercurial/mdiff.py b/mercurial/mdiff.py --- a/mercurial/mdiff.py +++ b/mercurial/mdiff.py @@ -5,9 +5,7 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -import bdiff, mpatch -demandload(globals(), "re struct util md5") +import bdiff, mpatch, re, struct, util, md5 def splitnewlines(text): '''like str.splitlines, but only split on newlines.''' diff --git a/mercurial/merge.py b/mercurial/merge.py --- a/mercurial/merge.py +++ b/mercurial/merge.py @@ -6,9 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. from node import * -from i18n import gettext as _ -from demandload import * -demandload(globals(), "errno util os tempfile") +from i18n import _ +import errno, util, os, tempfile def filemerge(repo, fw, fo, wctx, mctx): """perform a 3-way merge in the working directory @@ -21,8 +20,9 @@ def filemerge(repo, fw, fo, wctx, mctx): def temp(prefix, ctx): pre = "%s~%s." % (os.path.basename(ctx.path()), prefix) (fd, name) = tempfile.mkstemp(prefix=pre) + data = repo.wwritedata(ctx.path(), ctx.data()) f = os.fdopen(fd, "wb") - repo.wwrite(ctx.path(), ctx.data(), f) + f.write(data) f.close() return name @@ -221,12 +221,17 @@ def manifestmerge(repo, p1, p2, pa, over copy = {} def fmerge(f, f2=None, fa=None): - """merge executable flags""" + """merge flags""" if not f2: f2 = f fa = f a, b, c = ma.execf(fa), m1.execf(f), m2.execf(f2) - return ((a^b) | (a^c)) ^ a + if ((a^b) | (a^c)) ^ a: + return 'x' + a, b, c = ma.linkf(fa), m1.linkf(f), m2.linkf(f2) + if ((a^b) | (a^c)) ^ a: + return 'l' + return '' def act(msg, m, f, *args): repo.ui.debug(" %s: %s -> %s\n" % (f, msg, m)) @@ -251,21 +256,21 @@ def manifestmerge(repo, p1, p2, pa, over # is remote's version newer? # or are we going back in time and clean? elif overwrite or m2[f] != a or (backwards and not n[20:]): - act("remote is newer", "g", f, m2.execf(f)) + act("remote is newer", "g", f, m2.flags(f)) # local is newer, not overwrite, check mode bits - elif fmerge(f) != m1.execf(f): - act("update permissions", "e", f, m2.execf(f)) + elif fmerge(f) != m1.flags(f): + act("update permissions", "e", f, m2.flags(f)) # contents same, check mode bits - elif m1.execf(f) != m2.execf(f): - if overwrite or fmerge(f) != m1.execf(f): - act("update permissions", "e", f, m2.execf(f)) + elif m1.flags(f) != m2.flags(f): + if overwrite or fmerge(f) != m1.flags(f): + act("update permissions", "e", f, m2.flags(f)) elif f in copied: continue elif f in copy: f2 = copy[f] if f2 not in m2: # directory rename act("remote renamed directory to " + f2, "d", - f, None, f2, m1.execf(f)) + f, None, f2, m1.flags(f)) elif f2 in m1: # case 2 A,B/B/B act("local copied to " + f2, "m", f, f2, f, fmerge(f, f2, f2), False) @@ -296,7 +301,7 @@ def manifestmerge(repo, p1, p2, pa, over f2 = copy[f] if f2 not in m1: # directory rename act("local renamed directory to " + f2, "d", - None, f, f2, m2.execf(f)) + None, f, f2, m2.flags(f)) elif f2 in m2: # rename case 1, A/A,B/A act("remote copied to " + f, "m", f2, f, f, fmerge(f2, f, f2), False) @@ -305,14 +310,14 @@ def manifestmerge(repo, p1, p2, pa, over f2, f, f, fmerge(f2, f, f2), True) elif f in ma: if overwrite or backwards: - act("recreating", "g", f, m2.execf(f)) + act("recreating", "g", f, m2.flags(f)) elif n != ma[f]: if repo.ui.prompt( (_("remote changed %s which local deleted\n") % f) + _("(k)eep or (d)elete?"), _("[kd]"), _("k")) == _("k"): - act("prompt recreating", "g", f, m2.execf(f)) + act("prompt recreating", "g", f, m2.flags(f)) else: - act("remote created", "g", f, m2.execf(f)) + act("remote created", "g", f, m2.flags(f)) return action @@ -336,7 +341,7 @@ def applyupdates(repo, action, wctx, mct (f, inst.strerror)) removed += 1 elif m == "m": # merge - f2, fd, flag, move = a[2:] + f2, fd, flags, move = a[2:] r = filemerge(repo, f, f2, wctx, mctx) if r > 0: unresolved += 1 @@ -347,35 +352,32 @@ def applyupdates(repo, action, wctx, mct merged += 1 if f != fd: repo.ui.debug(_("copying %s to %s\n") % (f, fd)) - repo.wwrite(fd, repo.wread(f)) + repo.wwrite(fd, repo.wread(f), flags) if move: repo.ui.debug(_("removing %s\n") % f) os.unlink(repo.wjoin(f)) - util.set_exec(repo.wjoin(fd), flag) + util.set_exec(repo.wjoin(fd), "x" in flags) elif m == "g": # get - flag = a[2] + flags = a[2] repo.ui.note(_("getting %s\n") % f) t = mctx.filectx(f).data() - repo.wwrite(f, t) - util.set_exec(repo.wjoin(f), flag) + repo.wwrite(f, t, flags) updated += 1 elif m == "d": # directory rename - f2, fd, flag = a[2:] + f2, fd, flags = a[2:] if f: repo.ui.note(_("moving %s to %s\n") % (f, fd)) t = wctx.filectx(f).data() - repo.wwrite(fd, t) - util.set_exec(repo.wjoin(fd), flag) + repo.wwrite(fd, t, flags) util.unlink(repo.wjoin(f)) if f2: repo.ui.note(_("getting %s to %s\n") % (f2, fd)) t = mctx.filectx(f2).data() - repo.wwrite(fd, t) - util.set_exec(repo.wjoin(fd), flag) + repo.wwrite(fd, t, flags) updated += 1 elif m == "e": # exec - flag = a[2] - util.set_exec(repo.wjoin(f), flag) + flags = a[2] + util.set_exec(repo.wjoin(f), flags) return updated, merged, removed, unresolved @@ -442,6 +444,9 @@ def update(repo, node, branchmerge, forc wlock = working dir lock, if already held """ + if node is None: + node = "tip" + if not wlock: wlock = repo.wlock() diff --git a/mercurial/mpatch.c b/mercurial/mpatch.c --- a/mercurial/mpatch.c +++ b/mercurial/mpatch.c @@ -42,7 +42,11 @@ static uint32_t ntohl(uint32_t x) #else /* not windows */ # include -# include +# ifdef __BEOS__ +# include +# else +# include +# endif # include #endif diff --git a/mercurial/node.py b/mercurial/node.py --- a/mercurial/node.py +++ b/mercurial/node.py @@ -7,8 +7,7 @@ This software may be used and distribute of the GNU General Public License, incorporated herein by reference. """ -from demandload import demandload -demandload(globals(), "binascii") +import binascii nullrev = -1 nullid = "\0" * 20 diff --git a/mercurial/packagescan.py b/mercurial/packagescan.py deleted file mode 100644 --- a/mercurial/packagescan.py +++ /dev/null @@ -1,131 +0,0 @@ -# packagescan.py - Helper module for identifing used modules. -# Used for the py2exe distutil. -# This module must be the first mercurial module imported in setup.py -# -# Copyright 2005, 2006 Volker Kleinfeld -# -# This software may be used and distributed according to the terms -# of the GNU General Public License, incorporated herein by reference. -import glob -import os -import sys -import ihooks -import types -import string - -# Install this module as fake demandload module -sys.modules['mercurial.demandload'] = sys.modules[__name__] - -# Requiredmodules contains the modules imported by demandload. -# Please note that demandload can be invoked before the -# mercurial.packagescan.scan method is invoked in case a mercurial -# module is imported. -requiredmodules = {} -def demandload(scope, modules): - """ fake demandload function that collects the required modules - foo import foo - foo bar import foo, bar - foo.bar import foo.bar - foo@bar import foo as bar - foo:bar from foo import bar - foo:bar,quux from foo import bar, quux - foo.bar:quux from foo.bar import quux""" - - for m in modules.split(): - mod = None - try: - module, fromlist = m.split(':') - fromlist = fromlist.split(',') - except: - module = m - fromlist = [] - as_ = None - if '@' in module: - module, as_ = module.split('@') - mod = __import__(module, scope, scope, fromlist) - if fromlist == []: - # mod is only the top package, but we need all packages - comp = module.split('.') - i = 1 - mn = comp[0] - while True: - # mn and mod.__name__ might not be the same - if not as_: - as_ = mn - scope[as_] = mod - requiredmodules[mod.__name__] = 1 - if len(comp) == i: break - mod = getattr(mod, comp[i]) - mn = string.join(comp[:i+1],'.') - i += 1 - else: - # mod is the last package in the component list - requiredmodules[mod.__name__] = 1 - for f in fromlist: - scope[f] = getattr(mod, f) - if type(scope[f]) == types.ModuleType: - requiredmodules[scope[f].__name__] = 1 - -class SkipPackage(Exception): - def __init__(self, reason): - self.reason = reason - -scan_in_progress = False - -def scan(libpath, packagename): - """ helper for finding all required modules of package """ - global scan_in_progress - scan_in_progress = True - # Use the package in the build directory - libpath = os.path.abspath(libpath) - sys.path.insert(0, libpath) - packdir = os.path.join(libpath, packagename.replace('.', '/')) - # A normal import would not find the package in - # the build directory. ihook is used to force the import. - # After the package is imported the import scope for - # the following imports is settled. - p = importfrom(packdir) - globals()[packagename] = p - sys.modules[packagename] = p - # Fetch the python modules in the package - cwd = os.getcwd() - os.chdir(packdir) - pymodulefiles = glob.glob('*.py') - extmodulefiles = glob.glob('*.pyd') - os.chdir(cwd) - # Import all python modules and by that run the fake demandload - for m in pymodulefiles: - if m == '__init__.py': continue - tmp = {} - mname, ext = os.path.splitext(m) - fullname = packagename+'.'+mname - try: - __import__(fullname, tmp, tmp) - except SkipPackage, inst: - print >> sys.stderr, 'skipping %s: %s' % (fullname, inst.reason) - continue - requiredmodules[fullname] = 1 - # Import all extension modules and by that run the fake demandload - for m in extmodulefiles: - tmp = {} - mname, ext = os.path.splitext(m) - fullname = packagename+'.'+mname - __import__(fullname, tmp, tmp) - requiredmodules[fullname] = 1 - -def getmodules(): - return requiredmodules.keys() - -def importfrom(filename): - """ - import module/package from a named file and returns the module. - It does not check on sys.modules or includes the module in the scope. - """ - loader = ihooks.BasicModuleLoader() - path, file = os.path.split(filename) - name, ext = os.path.splitext(file) - m = loader.find_module_in_dir(name, path) - if not m: - raise ImportError, name - m = loader.load_module(name, m) - return m diff --git a/mercurial/patch.py b/mercurial/patch.py --- a/mercurial/patch.py +++ b/mercurial/patch.py @@ -5,12 +5,11 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -from i18n import gettext as _ +from i18n import _ from node import * -demandload(globals(), "base85 cmdutil mdiff util") -demandload(globals(), "cStringIO email.Parser errno os popen2 re shutil sha") -demandload(globals(), "sys tempfile zlib") +import base85, cmdutil, mdiff, util, context, revlog +import cStringIO, email.Parser, os, popen2, re, sha +import sys, tempfile, zlib # helper functions @@ -378,8 +377,9 @@ def updatedir(ui, repo, patches, wlock=N dst = os.path.join(repo.root, gp.path) # patch won't create empty files if ctype == 'ADD' and not os.path.exists(dst): - repo.wwrite(gp.path, '') - util.set_exec(dst, x) + repo.wwrite(gp.path, '', x and 'x' or '') + else: + util.set_exec(dst, x) cmdutil.addremove(repo, cfiles, wlock=wlock) files = patches.keys() files.extend([r for r in removes if r not in files]) @@ -441,27 +441,25 @@ def diff(repo, node1=None, node2=None, f if not node1: node1 = repo.dirstate.parents()[0] - clcache = {} - def getchangelog(n): - if n not in clcache: - clcache[n] = repo.changelog.read(n) - return clcache[n] - mcache = {} - def getmanifest(n): - if n not in mcache: - mcache[n] = repo.manifest.read(n) - return mcache[n] - fcache = {} - def getfile(f): - if f not in fcache: - fcache[f] = repo.file(f) - return fcache[f] + ccache = {} + def getctx(r): + if r not in ccache: + ccache[r] = context.changectx(repo, r) + return ccache[r] + + flcache = {} + def getfilectx(f, ctx): + flctx = ctx.filectx(f, filelog=flcache.get(f)) + if f not in flcache: + flcache[f] = flctx._filelog + return flctx # reading the data for node1 early allows it to play nicely # with repo.status and the revlog cache. - change = getchangelog(node1) - mmap = getmanifest(change[0]) - date1 = util.datestr(change[2]) + ctx1 = context.changectx(repo, node1) + # force manifest reading + man1 = ctx1.manifest() + date1 = util.datestr(ctx1.date()) if not changes: changes = repo.status(node1, node2, files, match=match)[:5] @@ -481,67 +479,38 @@ def diff(repo, node1=None, node2=None, f if not modified and not added and not removed: return - # returns False if there was no rename between n1 and n2 - # returns None if the file was created between n1 and n2 - # returns the (file, node) present in n1 that was renamed to f in n2 - def renamedbetween(f, n1, n2): - r1, r2 = map(repo.changelog.rev, (n1, n2)) + if node2: + ctx2 = context.changectx(repo, node2) + else: + ctx2 = context.workingctx(repo) + man2 = ctx2.manifest() + + # returns False if there was no rename between ctx1 and ctx2 + # returns None if the file was created between ctx1 and ctx2 + # returns the (file, node) present in ctx1 that was renamed to f in ctx2 + def renamed(f): + startrev = ctx1.rev() + c = ctx2 + crev = c.rev() + if crev is None: + crev = repo.changelog.count() orig = f - src = None - while r2 > r1: - cl = getchangelog(n2) - if f in cl[3]: - m = getmanifest(cl[0]) + while crev > startrev: + if f in c.files(): try: - src = getfile(f).renamed(m[f]) - except KeyError: + src = getfilectx(f, c).renamed() + except revlog.LookupError: return None if src: f = src[0] - n2 = repo.changelog.parents(n2)[0] - r2 = repo.changelog.rev(n2) - cl = getchangelog(n1) - m = getmanifest(cl[0]) - if f not in m: + crev = c.parents()[0].rev() + # try to reuse + c = getctx(crev) + if f not in man1: return None if f == orig: return False - return f, m[f] - - if node2: - change = getchangelog(node2) - mmap2 = getmanifest(change[0]) - _date2 = util.datestr(change[2]) - def date2(f): - return _date2 - def read(f): - return getfile(f).read(mmap2[f]) - def renamed(f): - return renamedbetween(f, node1, node2) - else: - tz = util.makedate()[1] - _date2 = util.datestr() - def date2(f): - try: - return util.datestr((os.lstat(repo.wjoin(f)).st_mtime, tz)) - except OSError, err: - if err.errno != errno.ENOENT: raise - return _date2 - def read(f): - return repo.wread(f) - def renamed(f): - src = repo.dirstate.copied(f) - parent = repo.dirstate.parents()[0] - if src: - f = src - of = renamedbetween(f, node1, parent) - if of or of is None: - return of - elif src: - cl = getchangelog(parent)[0] - return (src, getmanifest(cl)[src]) - else: - return None + return f if repo.ui.quiet: r = None @@ -555,20 +524,21 @@ def diff(repo, node1=None, node2=None, f src = renamed(f) if src: copied[f] = src - srcs = [x[1][0] for x in copied.items()] + srcs = [x[1] for x in copied.items()] all = modified + added + removed all.sort() gone = {} + for f in all: to = None tn = None dodiff = True header = [] - if f in mmap: - to = getfile(f).read(mmap[f]) + if f in man1: + to = getfilectx(f, ctx1).data() if f not in removed: - tn = read(f) + tn = getfilectx(f, ctx2).data() if opts.git: def gitmode(x): return x and '100755' or '100644' @@ -579,13 +549,10 @@ def diff(repo, node1=None, node2=None, f a, b = f, f if f in added: - if node2: - mode = gitmode(mmap2.execf(f)) - else: - mode = gitmode(util.is_exec(repo.wjoin(f), None)) + mode = gitmode(man2.execf(f)) if f in copied: - a, arev = copied[f] - omode = gitmode(mmap.execf(a)) + a = copied[f] + omode = gitmode(man1.execf(a)) addmodehdr(header, omode, mode) if a in removed and a not in gone: op = 'rename' @@ -594,7 +561,7 @@ def diff(repo, node1=None, node2=None, f op = 'copy' header.append('%s from %s\n' % (op, a)) header.append('%s to %s\n' % (op, f)) - to = getfile(a).read(arev) + to = getfilectx(a, ctx1).data() else: header.append('new file mode %s\n' % mode) if util.binary(tn): @@ -603,14 +570,11 @@ def diff(repo, node1=None, node2=None, f if f in srcs: dodiff = False else: - mode = gitmode(mmap.execf(f)) + mode = gitmode(man1.execf(f)) header.append('deleted file mode %s\n' % mode) else: - omode = gitmode(mmap.execf(f)) - if node2: - nmode = gitmode(mmap2.execf(f)) - else: - nmode = gitmode(util.is_exec(repo.wjoin(f), mmap.execf(f))) + omode = gitmode(man1.execf(f)) + nmode = gitmode(man2.execf(f)) addmodehdr(header, omode, nmode) if util.binary(to) or util.binary(tn): dodiff = 'binary' @@ -620,7 +584,10 @@ def diff(repo, node1=None, node2=None, f if dodiff == 'binary': text = b85diff(fp, to, tn) else: - text = mdiff.unidiff(to, date1, tn, date2(f), f, r, opts=opts) + text = mdiff.unidiff(to, date1, + # ctx2 date may be dynamic + tn, util.datestr(ctx2.date()), + f, r, opts=opts) if text or len(header) > 1: fp.write(''.join(header)) fp.write(text) @@ -632,27 +599,28 @@ def export(repo, revs, template='hg-%h.p total = len(revs) revwidth = max([len(str(rev)) for rev in revs]) - def single(node, seqno, fp): - parents = [p for p in repo.changelog.parents(node) if p != nullid] + def single(rev, seqno, fp): + ctx = repo.changectx(rev) + node = ctx.node() + parents = [p.node() for p in ctx.parents() if p] if switch_parent: parents.reverse() prev = (parents and parents[0]) or nullid - change = repo.changelog.read(node) if not fp: fp = cmdutil.make_file(repo, template, node, total=total, seqno=seqno, revwidth=revwidth) - if fp not in (sys.stdout, repo.ui): + if fp != sys.stdout and hasattr(fp, 'name'): repo.ui.note("%s\n" % fp.name) fp.write("# HG changeset patch\n") - fp.write("# User %s\n" % change[1]) - fp.write("# Date %d %d\n" % change[2]) + fp.write("# User %s\n" % ctx.user()) + fp.write("# Date %d %d\n" % ctx.date()) fp.write("# Node ID %s\n" % hex(node)) fp.write("# Parent %s\n" % hex(prev)) if len(parents) > 1: fp.write("# Parent %s\n" % hex(parents[1])) - fp.write(change[4].rstrip()) + fp.write(ctx.description().rstrip()) fp.write("\n\n") diff(repo, prev, node, fp=fp, opts=opts) @@ -660,7 +628,7 @@ def export(repo, revs, template='hg-%h.p fp.close() for seqno, rev in enumerate(revs): - single(repo.lookup(rev), seqno+1, fp) + single(rev, seqno+1, fp) def diffstat(patchlines): fd, name = tempfile.mkstemp(prefix="hg-patchbomb-", suffix=".txt") diff --git a/mercurial/repo.py b/mercurial/repo.py --- a/mercurial/repo.py +++ b/mercurial/repo.py @@ -9,9 +9,6 @@ class RepoError(Exception): pass -class LookupError(RepoError): - pass - class repository(object): def capable(self, name): '''tell whether repo supports named capability. diff --git a/mercurial/revlog.py b/mercurial/revlog.py --- a/mercurial/revlog.py +++ b/mercurial/revlog.py @@ -11,10 +11,9 @@ of the GNU General Public License, incor """ from node import * -from i18n import gettext as _ -from demandload import demandload -demandload(globals(), "binascii changegroup errno ancestor mdiff os") -demandload(globals(), "sha struct util zlib") +from i18n import _ +import binascii, changegroup, errno, ancestor, mdiff, os +import sha, struct, util, zlib # revlog version strings REVLOGV0 = 0 @@ -147,6 +146,9 @@ class lazyparser(object): lend = len(data) / self.s i = blockstart / self.s off = 0 + # lazyindex supports __delitem__ + if lend > len(self.index) - i: + lend = len(self.index) - i for x in xrange(lend): if self.index[i + x] == None: b = data[off : off + self.s] @@ -282,6 +284,7 @@ class lazymap(object): del self.p.map[key] class RevlogError(Exception): pass +class LookupError(RevlogError): pass class revlog(object): """ @@ -472,7 +475,7 @@ class revlog(object): try: return self.nodemap[node] except KeyError: - raise RevlogError(_('%s: no node %s') % (self.indexfile, hex(node))) + raise LookupError(_('%s: no node %s') % (self.indexfile, hex(node))) def linkrev(self, node): return (node == nullid) and nullrev or self.index[self.rev(node)][-4] def parents(self, node): @@ -767,7 +770,7 @@ class revlog(object): node = id r = self.rev(node) # quick search the index return node - except RevlogError: + except LookupError: pass # may be partial hex id try: # str(rev) @@ -796,7 +799,7 @@ class revlog(object): for n in self.nodemap: if n.startswith(bin_id) and hex(n).startswith(id): if node is not None: - raise RevlogError(_("Ambiguous identifier")) + raise LookupError(_("Ambiguous identifier")) node = n if node is not None: return node @@ -816,7 +819,7 @@ class revlog(object): if n: return n - raise RevlogError(_("No match found")) + raise LookupError(_("No match found")) def cmp(self, node, text): """compare text with a given file revision""" @@ -1156,13 +1159,13 @@ class revlog(object): for p in (p1, p2): if not p in self.nodemap: - raise RevlogError(_("unknown parent %s") % short(p)) + raise LookupError(_("unknown parent %s") % short(p)) if not chain: # retrieve the parent revision of the delta chain chain = p1 if not chain in self.nodemap: - raise RevlogError(_("unknown base %s") % short(chain[:4])) + raise LookupError(_("unknown base %s") % short(chain[:4])) # full versions are inserted when the needed deltas become # comparable to the uncompressed text or when the previous diff --git a/mercurial/sshrepo.py b/mercurial/sshrepo.py --- a/mercurial/sshrepo.py +++ b/mercurial/sshrepo.py @@ -7,9 +7,8 @@ from node import * from remoterepo import * -from i18n import gettext as _ -from demandload import * -demandload(globals(), "hg os re stat util") +from i18n import _ +import hg, os, re, stat, util class sshrepository(remoterepository): def __init__(self, ui, path, create=0): diff --git a/mercurial/sshserver.py b/mercurial/sshserver.py --- a/mercurial/sshserver.py +++ b/mercurial/sshserver.py @@ -6,10 +6,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -from i18n import gettext as _ +from i18n import _ from node import * -demandload(globals(), "os streamclone sys tempfile util") +import os, streamclone, sys, tempfile, util class sshserver(object): def __init__(self, ui, repo): diff --git a/mercurial/statichttprepo.py b/mercurial/statichttprepo.py --- a/mercurial/statichttprepo.py +++ b/mercurial/statichttprepo.py @@ -7,10 +7,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import * -from i18n import gettext as _ -demandload(globals(), "changelog filelog httprangereader") -demandload(globals(), "repo localrepo manifest os urllib urllib2 util") +from i18n import _ +import changelog, filelog, httprangereader +import repo, localrepo, manifest, os, urllib, urllib2, util class rangereader(httprangereader.httprangereader): def read(self, size=None): diff --git a/mercurial/streamclone.py b/mercurial/streamclone.py --- a/mercurial/streamclone.py +++ b/mercurial/streamclone.py @@ -5,9 +5,8 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -from i18n import gettext as _ -demandload(globals(), "os stat util lock") +from i18n import _ +import os, stat, util, lock # if server supports streaming clone, it advertises "stream" # capability with value that is version+flags of repo it is serving. diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -5,10 +5,9 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -from i18n import gettext as _ +from i18n import _ from node import * -demandload(globals(), "cgi re sys os time urllib util textwrap") +import cgi, re, sys, os, time, urllib, util, textwrap def parsestring(s, quoted=True): '''parse a string using simple c-like syntax. diff --git a/mercurial/transaction.py b/mercurial/transaction.py --- a/mercurial/transaction.py +++ b/mercurial/transaction.py @@ -11,9 +11,8 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -from demandload import demandload -from i18n import gettext as _ -demandload(globals(), 'os') +from i18n import _ +import os class transaction(object): def __init__(self, report, opener, journal, after=None): diff --git a/mercurial/ui.py b/mercurial/ui.py --- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -5,10 +5,9 @@ # 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(), "errno getpass os re socket sys tempfile") -demandload(globals(), "ConfigParser traceback util") +from i18n import _ +import errno, getpass, os, re, socket, sys, tempfile +import ConfigParser, traceback, util def dupconfig(orig): new = util.configparser(orig.defaults()) @@ -310,7 +309,7 @@ class ui(object): sections.sort() for section in sections: for name, value in self.configitems(section, untrusted): - yield section, name, value.replace('\n', '\\n') + yield section, name, str(value).replace('\n', '\\n') def extensions(self): result = self.configitems("extensions") @@ -388,6 +387,9 @@ class ui(object): if not sys.stdout.closed: sys.stdout.flush() for a in args: sys.stderr.write(str(a)) + # stderr may be buffered under win32 when redirected to files, + # including stdout. + if not sys.stderr.closed: sys.stderr.flush() except IOError, inst: if inst.errno != errno.EPIPE: raise diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -12,10 +12,9 @@ This contains helper routines that are i platform-specific details from the core. """ -from i18n import gettext as _ -from demandload import * -demandload(globals(), "cStringIO errno getpass popen2 re shutil sys tempfile") -demandload(globals(), "os threading time calendar ConfigParser locale glob") +from i18n import _ +import cStringIO, errno, getpass, popen2, re, shutil, sys, tempfile +import os, threading, time, calendar, ConfigParser, locale, glob try: _encoding = os.environ.get("HGENCODING") or locale.getpreferredencoding() \ @@ -117,11 +116,23 @@ extendeddateformats = defaultdateformats class SignalInterrupt(Exception): """Exception raised on SIGTERM and SIGHUP.""" -# like SafeConfigParser but with case-sensitive keys +# differences from SafeConfigParser: +# - case-sensitive keys +# - allows values that are not strings (this means that you may not +# be able to save the configuration to a file) class configparser(ConfigParser.SafeConfigParser): def optionxform(self, optionstr): return optionstr + def set(self, section, option, value): + return ConfigParser.ConfigParser.set(self, section, option, value) + + def _interpolate(self, section, option, rawval, vars): + if not isinstance(rawval, basestring): + return rawval + return ConfigParser.SafeConfigParser._interpolate(self, section, + option, rawval, vars) + def cachefunc(func): '''cache the result of function calls''' # XXX doesn't handle keywords args @@ -715,9 +726,47 @@ def checkfolding(path): except: return True +def checkexec(path): + """ + Check whether the given path is on a filesystem with UNIX-like exec flags + + Requires a directory (like /foo/.hg) + """ + fh, fn = tempfile.mkstemp("", "", path) + os.close(fh) + m = os.stat(fn).st_mode + os.chmod(fn, m ^ 0111) + r = (os.stat(fn).st_mode != m) + os.unlink(fn) + return r + +def execfunc(path, fallback): + '''return an is_exec() function with default to fallback''' + if checkexec(path): + return lambda x: is_exec(os.path.join(path, x)) + return fallback + +def checklink(path): + """check whether the given path is on a symlink-capable filesystem""" + # mktemp is not racy because symlink creation will fail if the + # file already exists + name = tempfile.mktemp(dir=path) + try: + os.symlink(".", name) + os.unlink(name) + return True + except (OSError, AttributeError): + return False + +def linkfunc(path, fallback): + '''return an is_link() function with default to fallback''' + if checklink(path): + return lambda x: is_link(os.path.join(path, x)) + return fallback + # Platform specific variants if os.name == 'nt': - demandload(globals(), "msvcrt") + import msvcrt nulldev = 'NUL:' class winstdout: @@ -758,19 +807,18 @@ if os.name == 'nt': except: return [r'c:\mercurial\mercurial.ini'] - def os_rcpath(): - '''return default os-specific hgrc search path''' - path = system_rcpath() - path.append(user_rcpath()) + def user_rcpath(): + '''return os-specific hgrc search path to the user dir''' + try: + userrc = user_rcpath_win32() + except: + userrc = os.path.join(os.path.expanduser('~'), 'mercurial.ini') + path = [userrc] userprofile = os.environ.get('USERPROFILE') if userprofile: path.append(os.path.join(userprofile, 'mercurial.ini')) return path - def user_rcpath(): - '''return os-specific hgrc search path to the user dir''' - return os.path.join(os.path.expanduser('~'), 'mercurial.ini') - def parse_patch_output(output_line): """parses the output produced by patch and returns the file name""" pf = output_line[14:] @@ -782,10 +830,10 @@ if os.name == 'nt': '''return False if pid dead, True if running or not known''' return True - def is_exec(f, last): - return last + def set_exec(f, mode): + pass - def set_exec(f, mode): + def set_link(f, mode): pass def set_binary(fd): @@ -842,6 +890,8 @@ if os.name == 'nt': else: nulldev = '/dev/null' + _umask = os.umask(0) + os.umask(_umask) def rcfiles(path): rcs = [os.path.join(path, 'hgrc')] @@ -853,18 +903,18 @@ else: pass return rcs - def os_rcpath(): - '''return default os-specific hgrc search path''' + def system_rcpath(): path = [] # old mod_python does not set sys.argv if len(getattr(sys, 'argv', [])) > 0: path.extend(rcfiles(os.path.dirname(sys.argv[0]) + '/../etc/mercurial')) path.extend(rcfiles('/etc/mercurial')) - path.append(os.path.expanduser('~/.hgrc')) - path = [os.path.normpath(f) for f in path] return path + def user_rcpath(): + return [os.path.expanduser('~/.hgrc')] + def parse_patch_output(output_line): """parses the output produced by patch and returns the file name""" pf = output_line[14:] @@ -872,7 +922,7 @@ else: pf = pf[1:-1] # Remove the quotes return pf - def is_exec(f, last): + def is_exec(f): """check whether a file is executable""" return (os.lstat(f).st_mode & 0100 != 0) @@ -883,12 +933,34 @@ else: if mode: # Turn on +x for every +r bit when making a file executable # and obey umask. - umask = os.umask(0) - os.umask(umask) - os.chmod(f, s | (s & 0444) >> 2 & ~umask) + os.chmod(f, s | (s & 0444) >> 2 & ~_umask) else: os.chmod(f, s & 0666) + def is_link(f): + """check whether a file is a symlink""" + return (os.lstat(f).st_mode & 0120000 == 0120000) + + def set_link(f, mode): + """make a file a symbolic link/regular file + + if a file is changed to a link, its contents become the link data + if a link is changed to a file, its link data become its contents + """ + + m = is_link(f) + if m == bool(mode): + return + + if mode: # switch file to link + data = file(f).read() + os.unlink(f) + os.symlink(data, f) + else: + data = os.readlink(f) + os.unlink(f) + file(f, "w").write(data) + def set_binary(fd): pass @@ -1317,6 +1389,13 @@ def walkrepos(path): _rcpath = None +def os_rcpath(): + '''return default os-specific hgrc search path''' + path = system_rcpath() + path.extend(user_rcpath()) + path = [os.path.normpath(f) for f in path] + return path + def rcpath(): '''return hgrc search path. if env var HGRCPATH is set, use it. for each item in path, if directory, use files ending in .rc, diff --git a/mercurial/util_win32.py b/mercurial/util_win32.py --- a/mercurial/util_win32.py +++ b/mercurial/util_win32.py @@ -13,10 +13,10 @@ import win32api -from demandload import * -from i18n import gettext as _ -demandload(globals(), 'errno os pywintypes win32con win32file win32process') -demandload(globals(), 'cStringIO win32com.shell:shell,shellcon winerror') +from i18n import _ +import errno, os, pywintypes, win32con, win32file, win32process +import cStringIO, winerror +from win32com.shell import shell,shellcon class WinError: winerror_map = { @@ -187,7 +187,7 @@ def system_rcpath_win32(): filename = win32api.GetModuleFileName(0) return [os.path.join(os.path.dirname(filename), 'mercurial.ini')] -def user_rcpath(): +def user_rcpath_win32(): '''return os-specific hgrc search path to the user dir''' userdir = os.path.expanduser('~') if userdir == '~': diff --git a/mercurial/verify.py b/mercurial/verify.py --- a/mercurial/verify.py +++ b/mercurial/verify.py @@ -6,7 +6,7 @@ # of the GNU General Public License, incorporated herein by reference. from node import * -from i18n import gettext as _ +from i18n import _ import revlog, mdiff def verify(repo): diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -13,9 +13,11 @@ import os from distutils.core import setup, Extension from distutils.command.install_data import install_data -# mercurial.packagescan must be the first mercurial module imported -import mercurial.packagescan import mercurial.version +import mercurial.demandimport +mercurial.demandimport.enable = lambda: None + +extra = {} # py2exe needs to be installed to work try: @@ -35,34 +37,10 @@ try: except ImportError: pass - # Due to the use of demandload py2exe is not finding the modules. - # packagescan.getmodules creates a list of modules included in - # the mercurial package plus dependant modules. - from py2exe.build_exe import py2exe as build_exe + extra['console'] = ['hg'] - class py2exe_for_demandload(build_exe): - """ overwrites the py2exe command class for getting the build - directory and for setting the 'includes' option.""" - def initialize_options(self): - self.build_lib = None - build_exe.initialize_options(self) - def finalize_options(self): - # Get the build directory, ie. where to search for modules. - self.set_undefined_options('build', - ('build_lib', 'build_lib')) - # Sets the 'includes' option with the list of needed modules - if not self.includes: - self.includes = [] - else: - self.includes = self.includes.split(',') - mercurial.packagescan.scan(self.build_lib, 'mercurial') - mercurial.packagescan.scan(self.build_lib, 'mercurial.hgweb') - mercurial.packagescan.scan(self.build_lib, 'hgext') - self.includes += mercurial.packagescan.getmodules() - build_exe.finalize_options(self) except ImportError: - py2exe_for_demandload = None - + pass # specify version string, otherwise 'hg identify' will be used: version = '' @@ -75,10 +53,6 @@ class install_package_data(install_data) mercurial.version.remember_version(version) cmdclass = {'install_data': install_package_data} -py2exe_opts = {} -if py2exe_for_demandload is not None: - cmdclass['py2exe'] = py2exe_for_demandload - py2exe_opts['console'] = ['hg'] setup(name='mercurial', version=mercurial.version.get_version(), @@ -100,4 +74,4 @@ setup(name='mercurial', license='COPYING', readme='contrib/macosx/Readme.html', welcome='contrib/macosx/Welcome.html')), - **py2exe_opts) + **extra) diff --git a/templates/gitweb/header.tmpl b/templates/gitweb/header.tmpl --- a/templates/gitweb/header.tmpl +++ b/templates/gitweb/header.tmpl @@ -4,7 +4,7 @@ Content-type: text/html; charset={encodi - + - + diff --git a/templates/gitweb/map b/templates/gitweb/map --- a/templates/gitweb/map +++ b/templates/gitweb/map @@ -48,7 +48,7 @@ filelogparent = 'p filediffchild = 'child {rev}:{node|short}' filelogchild = 'child #rev#: #node|short#' shortlog = shortlog.tmpl -shortlogentry = '#date|age# ago#author##desc|strip|firstline|escape#changeset | manifest' +shortlogentry = '#date|age# ago#author##desc|strip|firstline|escape#changeset | manifest' filelogentry = '#date|age# ago#desc|strip|firstline|escape#file | diff | annotate #rename%filelogrename#' archiveentry = ' | #type|escape# ' indexentry = '#name|escape##description##contact|obfuscate##lastchange|age# ago #archives%archiveentry#' diff --git a/templates/header.tmpl b/templates/header.tmpl --- a/templates/header.tmpl +++ b/templates/header.tmpl @@ -3,6 +3,6 @@ Content-type: text/html; charset={encodi - + - + diff --git a/templates/raw/index.tmpl b/templates/raw/index.tmpl new file mode 100644 --- /dev/null +++ b/templates/raw/index.tmpl @@ -0,0 +1,2 @@ +#header# +#entries%indexentry# diff --git a/templates/raw/manifest.tmpl b/templates/raw/manifest.tmpl new file mode 100644 --- /dev/null +++ b/templates/raw/manifest.tmpl @@ -0,0 +1,3 @@ +{header} +{dentries%manifestdirentry}{fentries%manifestfileentry} +{footer} diff --git a/templates/raw/map b/templates/raw/map --- a/templates/raw/map +++ b/templates/raw/map @@ -14,3 +14,8 @@ diffblock = '#lines#' filediff = filediff.tmpl fileannotate = fileannotate.tmpl annotateline = '#author#@#rev#: #line#' +manifest = manifest.tmpl +manifestdirentry = 'drwxr-xr-x {basename}\n' +manifestfileentry = '{permissions|permissions} {size} {basename}\n' +index = index.tmpl +indexentry = '#url#\n' diff --git a/tests/README b/tests/README --- a/tests/README +++ b/tests/README @@ -1,93 +1,7 @@ -A simple testing framework - To run the tests, do: cd tests/ python run-tests.py -This finds all scripts in the test directory named test-* and executes -them. The scripts can be either shell scripts or Python. Each test is -run in a temporary directory that is removed when the test is complete. - -A test- succeeds if the script returns success and its output -matches test-.out. If the new output doesn't match, it is stored in -test-.err. - -There are some tricky points here that you should be aware of when -writing tests: - -- hg commit and hg merge want user interaction - - for commit use -m "text" - for hg merge, set HGMERGE to something noninteractive (like true or merge) - -- changeset hashes will change based on user and date which make - things like hg history output change - - use commit -m "test" -u test -d "1000000 0" - -- diff and export may show the current time - - use -D/--nodates to strip the dates - -- You can append your own hgrc settings to the file that the environment - variable HGRCPATH points to. This file is cleared before running a test. - -You also need to be careful that the tests are portable from one platform -to another. You're probably working on Linux, where the GNU toolchain has -more (or different) functionality than on MacOS, *BSD, Solaris, AIX, etc. -While testing on all platforms is the only sure-fire way to make sure that -you've written portable code, here's a list of problems that have been -found and fixed in the tests. Another, more comprehensive list may be -found in the GNU Autoconf manual, online here: - - http://www.gnu.org/software/autoconf/manual/html_node/Portable-Shell.html - -sh: - -The Bourne shell is a very basic shell. /bin/sh on Linux is typically -bash, which even in Bourne-shell mode has many features that Bourne shells -on other Unix systems don't have (and even on Linux /bin/sh isn't -guaranteed to be bash). You'll need to be careful about constructs that -seem ubiquitous, but are actually not available in the least common -denominator. While using another shell (ksh, bash explicitly, posix shell, -etc.) explicitly may seem like another option, these may not exist in a -portable location, and so are generally probably not a good idea. You may -find that rewriting the test in python will be easier. - -- don't use pushd/popd; save the output of "pwd" and use "cd" in place of - the pushd, and cd back to the saved pwd instead of popd. - -- don't use math expressions like let, (( ... )), or $(( ... )); use "expr" - instead. - -grep: - -- don't use the -q option; redirect stdout to /dev/null instead. - -- don't use extended regular expressions with grep; use egrep instead, and - don't escape any regex operators. - -sed: - -- make sure that the beginning-of-line matcher ("^") is at the very - beginning of the expression -- it may not be supported inside parens. - -echo: - -- echo may interpret "\n" and print a newline; use printf instead if you - want a literal "\n" (backslash + n). - -false: - -- false is guaranteed only to return a non-zero value; you cannot depend on - it being 1. On Solaris in particular, /bin/false returns 255. Rewrite - your test to not depend on a particular return value, or create a - temporary "false" executable, and call that instead. - -diff: - -- don't use the -N option. There's no particularly good workaround short - of writing a reasonably complicated replacement script, but substituting - gdiff for diff if you can't rewrite the test not to need -N will probably - do. +See http://www.selenic.com/mercurial/wiki/index.cgi/WritingTests for +more information on writing tests. diff --git a/tests/test-abort-checkin b/tests/test-abort-checkin --- a/tests/test-abort-checkin +++ b/tests/test-abort-checkin @@ -1,12 +1,19 @@ #!/bin/sh +cat > abortcommit.py <> $HGRCPATH echo "mq=" >> $HGRCPATH -cat > $HGTMP/false <> $HGRCPATH hg init foo cd foo @@ -15,7 +22,7 @@ hg add foo # mq may keep a reference to the repository so __del__ will not be called # and .hg/journal.dirstate will not be deleted: -HGEDITOR=$HGTMP/false hg ci -HGEDITOR=$HGTMP/false hg ci +hg ci -m foo +hg ci -m foo exit 0 diff --git a/tests/test-abort-checkin.out b/tests/test-abort-checkin.out --- a/tests/test-abort-checkin.out +++ b/tests/test-abort-checkin.out @@ -1,6 +1,8 @@ -abort: edit failed: false exited with status 1 +error: pretxncommit.nocommits hook failed: no commits allowed +abort: no commits allowed transaction abort! rollback completed -abort: edit failed: false exited with status 1 +error: pretxncommit.nocommits hook failed: no commits allowed +abort: no commits allowed transaction abort! rollback completed diff --git a/tests/test-acl b/tests/test-acl --- a/tests/test-acl +++ b/tests/test-acl @@ -26,7 +26,7 @@ mkdir foo foo/Bar quux echo 'in foo' > foo/file.txt echo 'in foo/Bar' > foo/Bar/file.txt echo 'in quux' > quux/file.py -hg add +hg add -q hg ci -m 'add files' -d '1000000 0' echo >> foo/file.txt hg ci -m 'change foo/file' -d '1000001 0' diff --git a/tests/test-acl.out b/tests/test-acl.out --- a/tests/test-acl.out +++ b/tests/test-acl.out @@ -1,6 +1,3 @@ -adding foo/Bar/file.txt -adding foo/file.txt -adding quux/file.py 3:911600dab2ae requesting all changes adding changesets diff --git a/tests/test-addremove-similar b/tests/test-addremove-similar new file mode 100755 --- /dev/null +++ b/tests/test-addremove-similar @@ -0,0 +1,37 @@ +#!/bin/sh + +hg init rep; cd rep + +touch empty-file +python -c 'for x in range(10000): print x' > large-file + +hg addremove + +hg commit -m A + +rm large-file empty-file +python -c 'for x in range(10,10000): print x' > another-file + +hg addremove -s50 + +hg commit -m B + +cd .. + +hg init rep2; cd rep2 + +python -c 'for x in range(10000): print x' > large-file +python -c 'for x in range(50): print x' > tiny-file + +hg addremove + +hg commit -m A + +python -c 'for x in range(70): print x' > small-file +rm tiny-file +rm large-file + +hg addremove -s50 + +hg commit -m B + diff --git a/tests/test-addremove-similar.out b/tests/test-addremove-similar.out new file mode 100644 --- /dev/null +++ b/tests/test-addremove-similar.out @@ -0,0 +1,12 @@ +adding empty-file +adding large-file +adding another-file +removing empty-file +removing large-file +recording removal of large-file as rename to another-file (99% similar) +adding large-file +adding tiny-file +adding small-file +removing large-file +removing tiny-file +recording removal of tiny-file as rename to small-file (82% similar) diff --git a/tests/test-bad-extension b/tests/test-bad-extension --- a/tests/test-bad-extension +++ b/tests/test-bad-extension @@ -1,9 +1,11 @@ #!/bin/sh -echo 'syntax error' > badext.py +echo 'raise Exception("bit bucket overflow")' > badext.py abspath=`pwd`/badext.py echo '[extensions]' >> $HGRCPATH +echo "gpg =" >> $HGRCPATH +echo "hgext.gpg =" >> $HGRCPATH echo "badext = $abspath" >> $HGRCPATH hg -q help help diff --git a/tests/test-bad-extension.out b/tests/test-bad-extension.out --- a/tests/test-bad-extension.out +++ b/tests/test-bad-extension.out @@ -1,4 +1,5 @@ -*** failed to import extension badext: invalid syntax (badext.py, line 1) +*** failed to import extension badext: bit bucket overflow +extension 'hgext.gpg' overrides commands: sigs sigcheck sign hg help [COMMAND] show help for a command, extension, or list of commands diff --git a/tests/test-branch b/tests/test-branch deleted file mode 100755 --- a/tests/test-branch +++ /dev/null @@ -1,34 +0,0 @@ -#!/bin/sh -# -# test for branch handling -# -# XXX: need more tests - -hg init -echo a > a -echo b > b -hg ci -A -m 0 -d "1000000 0" -echo aa > a -echo bb > b -hg ci -m 1 -d "1000000 0" -hg tag -l foo -hg update 0 -hg parents -b - -# test update -hg update -b foo -hg parents - -# test merge -hg update 0 -echo c > c -hg ci -A -m 0.0 -d "1000000 0" -hg merge -b foo -hg parents -b - -# re-test with more branches -hg update -C 0 -echo d > d -hg ci -A -m 0.0 -d "1000000 0" -hg merge -b foo -hg parents -b diff --git a/tests/test-branch.out b/tests/test-branch.out deleted file mode 100644 --- a/tests/test-branch.out +++ /dev/null @@ -1,61 +0,0 @@ -adding a -adding b -2 files updated, 0 files merged, 0 files removed, 0 files unresolved -the --branches option is deprecated, please use 'hg branches' instead -changeset: 0:b544c4ac4389 -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 0 - -the --branch option is deprecated, please use 'hg branch' instead -Using head f4ac749470f2 for branch foo -2 files updated, 0 files merged, 0 files removed, 0 files unresolved -changeset: 1:f4ac749470f2 -tag: foo -tag: tip -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 1 - -2 files updated, 0 files merged, 0 files removed, 0 files unresolved -adding c -the --branch option is deprecated, please use 'hg branch' instead -Using head f4ac749470f2 for branch foo -2 files updated, 0 files merged, 0 files removed, 0 files unresolved -(branch merge, don't forget to commit) -the --branches option is deprecated, please use 'hg branches' instead -changeset: 2:1505d56ee00e -tag: tip -parent: 0:b544c4ac4389 -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 0.0 - -changeset: 1:f4ac749470f2 -tag: foo -branch: foo -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 1 - -2 files updated, 0 files merged, 1 files removed, 0 files unresolved -adding d -the --branch option is deprecated, please use 'hg branch' instead -Using head f4ac749470f2 for branch foo -2 files updated, 0 files merged, 0 files removed, 0 files unresolved -(branch merge, don't forget to commit) -the --branches option is deprecated, please use 'hg branches' instead -changeset: 3:53b72df12ae5 -tag: tip -parent: 0:b544c4ac4389 -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 0.0 - -changeset: 1:f4ac749470f2 -tag: foo -branch: foo -user: test -date: Mon Jan 12 13:46:40 1970 +0000 -summary: 1 - diff --git a/tests/test-bundle.out b/tests/test-bundle.out --- a/tests/test-bundle.out +++ b/tests/test-bundle.out @@ -150,6 +150,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 0.0 +comparing with bundle://../full.hg searching for changes changeset: 4:5f4f3ceb285e parent: 0:5649c9d34dd8 @@ -179,6 +180,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 0.3m +comparing with ../partial2 searching for changes changeset: 4:5f4f3ceb285e parent: 0:5649c9d34dd8 @@ -211,6 +213,7 @@ summary: 0.3m abort: No such file or directory: ../does-not-exist.hg 1 files updated, 0 files merged, 0 files removed, 0 files unresolved searching for changes +comparing with ../bundle.hg searching for changes changeset: 2:ed1b79f46b9a tag: tip diff --git a/tests/test-commit.out b/tests/test-commit.out --- a/tests/test-commit.out +++ b/tests/test-commit.out @@ -21,8 +21,7 @@ abort: no match under directory .../test dir/file does-not-exist: No such file or directory abort: file .../test/does-not-exist not found! -baz: unsupported file type (type is symbolic link) -abort: can't commit .../test/baz: unsupported file type! +abort: file .../test/baz not tracked! abort: file .../test/quux not tracked! dir/file % partial subdir commit test diff --git a/tests/test-context.py b/tests/test-context.py new file mode 100644 --- /dev/null +++ b/tests/test-context.py @@ -0,0 +1,20 @@ +import os +from mercurial import hg, ui, commands + +u = ui.ui() + +repo = hg.repository(u, 'test1', create=1) +os.chdir('test1') +repo = hg.repository(u, '.') # FIXME: can't lock repo without doing this + +# create 'foo' with fixed time stamp +f = file('foo', 'w') +f.write('foo\n') +f.close() +os.utime('foo', (1000, 1000)) + +# add+commit 'foo' +repo.add(['foo']) +repo.commit(text='commit1', date="0 0") + +print "workingfilectx.date =", repo.workingctx().filectx('foo').date() diff --git a/tests/test-context.py.out b/tests/test-context.py.out new file mode 100644 --- /dev/null +++ b/tests/test-context.py.out @@ -0,0 +1,1 @@ +workingfilectx.date = (1000, 0) diff --git a/tests/test-empty-group.out b/tests/test-empty-group.out --- a/tests/test-empty-group.out +++ b/tests/test-empty-group.out @@ -21,6 +21,7 @@ adding manifests adding file changes added 4 changesets with 3 changes to 3 files 3 files updated, 0 files merged, 0 files removed, 0 files unresolved +comparing with b searching for changes changeset: 4:fdb3c546e859 tag: tip @@ -30,6 +31,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: m2 +comparing with c searching for changes changeset: 3:f40f830c0024 parent: 2:de997049e034 @@ -38,6 +40,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: m1 +comparing with c searching for changes changeset: 3:f40f830c0024 tag: tip @@ -47,6 +50,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: m1 +comparing with b searching for changes changeset: 3:fdb3c546e859 tag: tip diff --git a/tests/test-git-export b/tests/test-git-export diff --git a/tests/test-grep b/tests/test-grep --- a/tests/test-grep +++ b/tests/test-grep @@ -17,8 +17,11 @@ hg commit -m 3 -u eggs -d '3 0' head -n 3 port > port1 mv port1 port hg commit -m 4 -u spam -d '4 0' +echo % simple hg grep port port +echo % all hg grep --all -nu port port +echo % other hg grep import port hg cp port port2 @@ -28,3 +31,22 @@ hg grep -f 'import$' port2 echo deport >> port2 hg commit -m 5 -u eggs -d '6 0' hg grep -f --all -nu port port2 + +cd .. +hg init t2 +cd t2 +hg grep foobar foo +hg grep foobar +echo blue >> color +echo black >> color +hg add color +hg ci -m 0 -d '0 0' +echo orange >> color +hg ci -m 1 -d '0 0' +echo black > color +hg ci -m 2 -d '0 0' +echo orange >> color +echo blue >> color +hg ci -m 3 -d '0 0' +hg grep orange +hg grep --all orange diff --git a/tests/test-grep.out b/tests/test-grep.out --- a/tests/test-grep.out +++ b/tests/test-grep.out @@ -1,6 +1,8 @@ +% simple port:4:export port:4:vaportight port:4:import/export +% all port:4:4:-:spam:import/export port:3:4:+:eggs:import/export port:2:1:-:spam:import @@ -10,6 +12,7 @@ port:2:2:+:spam:vaportight port:2:3:+:spam:import/export port:1:2:+:eggs:export port:0:1:+:spam:import +% other port:4:import/export % follow port:0:import @@ -23,3 +26,7 @@ port:2:2:+:spam:vaportight port:2:3:+:spam:import/export port:1:2:+:eggs:export port:0:1:+:spam:import +color:3:orange +color:3:+:orange +color:2:-:orange +color:1:+:orange diff --git a/tests/test-hgweb b/tests/test-hgweb new file mode 100755 --- /dev/null +++ b/tests/test-hgweb @@ -0,0 +1,13 @@ +#!/bin/sh + +hg init test +cd test +mkdir da +echo foo > da/foo +echo foo > foo +hg ci -Ambase -d '0 0' +hg serve -p 20060 -d --pid-file=hg.pid +echo % manifest +("$TESTDIR/get-with-headers.py" localhost:20060 '/file/tip/?style=raw') +("$TESTDIR/get-with-headers.py" localhost:20060 '/file/tip/da?style=raw') +kill `cat hg.pid` diff --git a/tests/test-hgweb.out b/tests/test-hgweb.out new file mode 100644 --- /dev/null +++ b/tests/test-hgweb.out @@ -0,0 +1,16 @@ +adding da/foo +adding foo +% manifest +200 Script output follows + + +drwxr-xr-x da +-rw-r--r-- 4 foo + + +200 Script output follows + + +-rw-r--r-- 4 foo + + diff --git a/tests/test-hook b/tests/test-hook --- a/tests/test-hook +++ b/tests/test-hook @@ -183,4 +183,26 @@ echo 'commit.abort = python:hooktests.ab echo a >> a hg --traceback commit -A -m a 2>&1 | grep '^Traceback' +cd .. +hg init c +cd c + +cat > hookext.py <> .hg/hgrc +echo 'hookext = hookext.py' >> .hg/hgrc + +touch foo +hg add foo +hg ci -m 'add foo' +echo >> foo +hg ci --debug -m 'change foo' | sed -e 's/ at .*>/>/' + +hg showconfig hooks | sed -e 's/ at .*>/>/' + exit 0 diff --git a/tests/test-hook.out b/tests/test-hook.out --- a/tests/test-hook.out +++ b/tests/test-hook.out @@ -138,3 +138,8 @@ added 1 changesets with 1 changes to 1 f (run 'hg update' to get a working copy) # make sure --traceback works Traceback (most recent call last): +Automatically installed hook +foo +calling hook commit.auto: +Automatically installed hook +hooks.commit.auto= diff --git a/tests/test-incoming-outgoing.out b/tests/test-incoming-outgoing.out --- a/tests/test-incoming-outgoing.out +++ b/tests/test-incoming-outgoing.out @@ -4,6 +4,7 @@ checking manifests crosschecking files in changesets and manifests checking files 1 files, 9 changesets, 9 total revisions +comparing with http://localhost:20059/ changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -50,6 +51,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 8 +comparing with http://localhost:20059/ changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -75,6 +77,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 4 +comparing with test changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -121,6 +124,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 8 +comparing with test changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -146,6 +150,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 4 +comparing with http://localhost:20059/ changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -192,6 +197,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 8 +comparing with test changeset: 0:9cb21d99fe27 user: test date: Mon Jan 12 13:46:40 1970 +0000 @@ -266,6 +272,7 @@ checking manifests crosschecking files in changesets and manifests checking files 1 files, 14 changesets, 14 total revisions +comparing with test searching for changes changeset: 9:3741c3ad1096 user: test @@ -293,6 +300,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 13 +comparing with http://localhost:20059/ searching for changes changeset: 9:3741c3ad1096 user: test @@ -320,6 +328,7 @@ user: test date: Mon Jan 12 13:46:40 1970 +0000 summary: 13 +comparing with http://localhost:20059/ searching for changes changeset: 9:3741c3ad1096 user: test diff --git a/tests/test-init b/tests/test-init --- a/tests/test-init +++ b/tests/test-init @@ -22,11 +22,31 @@ echo Got arguments 1:$1 2:$2 3:$3 4:$4 5 EOF chmod +x dummyssh +checknewrepo() +{ + name=$1 + + if [ -d $name/.hg/store ]; then + echo store created + fi + + if [ -f $name/.hg/00changelog.i ]; then + echo 00changelog.i created + fi + + cat $name/.hg/requires +} + echo "# creating 'local'" hg init local +checknewrepo local echo this > local/foo hg ci --cwd local -A -m "init" -d "1000000 0" +echo "# creating repo with old format" +hg --config format.usestore=false init old +checknewrepo old + echo "#test failure" hg init local diff --git a/tests/test-init.out b/tests/test-init.out --- a/tests/test-init.out +++ b/tests/test-init.out @@ -1,8 +1,15 @@ # creating 'local' +store created +00changelog.i created +revlogv1 +store adding foo +# creating repo with old format +revlogv1 #test failure abort: repository local already exists! # init+push to remote2 +comparing with local changeset: 0:c4e059d443be tag: tip user: test diff --git a/tests/test-mq b/tests/test-mq --- a/tests/test-mq +++ b/tests/test-mq @@ -40,6 +40,40 @@ echo % qnew implies add hg -R c qnew test.patch hg -R c/.hg/patches st +echo '% qinit; qinit -c' +hg init d +cd d +hg qinit +hg qinit -c +# qinit -c should create both files if they don't exist +echo ' .hgignore:' +cat .hg/patches/.hgignore +echo ' series:' +cat .hg/patches/series +hg qinit -c 2>&1 | sed -e 's/repository.*already/repository already/' +cd .. + +echo '% qinit; ; qinit -c' +hg init e +cd e +hg qnew A +echo foo > foo +hg add foo +hg qrefresh +hg qnew B +echo >> foo +hg qrefresh +echo status >> .hg/patches/.hgignore +echo bleh >> .hg/patches/.hgignore +hg qinit -c +hg -R .hg/patches status +# qinit -c shouldn't touch these files if they already exist +echo ' .hgignore:' +cat .hg/patches/.hgignore +echo ' series:' +cat .hg/patches/series +cd .. + cd a echo % qnew -m @@ -135,6 +169,46 @@ echo % push should succeed hg qpop -a hg push ../../k +echo % qpush/qpop error codes +errorcode() +{ + hg "$@" && echo " $@ succeeds" || echo " $@ fails" +} + +# we want to start with some patches applied +hg qpush -a +echo " % pops all patches and succeeds" +errorcode qpop -a +echo " % does nothing and succeeds" +errorcode qpop -a +echo " % fails - nothing else to pop" +errorcode qpop +echo " % pushes a patch and succeeds" +errorcode qpush +echo " % pops a patch and succeeds" +errorcode qpop +echo " % pushes up to test1b.patch and succeeds" +errorcode qpush test1b.patch +echo " % does nothing and succeeds" +errorcode qpush test1b.patch +echo " % does nothing and succeeds" +errorcode qpop test1b.patch +echo " % fails - can't push to this patch" +errorcode qpush test.patch +echo " % fails - can't pop to this patch" +errorcode qpop test2.patch +echo " % pops up to test.patch and succeeds" +errorcode qpop test.patch +echo " % pushes all patches and succeeds" +errorcode qpush -a +echo " % does nothing and succeeds" +errorcode qpush -a +echo " % fails - nothing else to push" +errorcode qpush +echo " % does nothing and succeeds" +errorcode qpush test2.patch + + echo % strip cd ../../b echo x>x diff --git a/tests/test-mq.out b/tests/test-mq.out --- a/tests/test-mq.out +++ b/tests/test-mq.out @@ -60,6 +60,26 @@ A series A .hgignore A series A test.patch +% qinit; qinit -c + .hgignore: +syntax: glob +status +guards + series: +abort: repository already exists! +% qinit; ; qinit -c +adding A +adding B +A .hgignore +A A +A B +A series + .hgignore: +status +bleh + series: +A +B % qnew -m foo bar % qrefresh @@ -136,6 +156,61 @@ adding changesets adding manifests adding file changes added 1 changesets with 1 changes to 1 files +% qpush/qpop error codes +applying test.patch +applying test1b.patch +applying test2.patch +Now at: test2.patch + % pops all patches and succeeds +Patch queue now empty + qpop -a succeeds + % does nothing and succeeds +no patches applied + qpop -a succeeds + % fails - nothing else to pop +no patches applied + qpop fails + % pushes a patch and succeeds +applying test.patch +Now at: test.patch + qpush succeeds + % pops a patch and succeeds +Patch queue now empty + qpop succeeds + % pushes up to test1b.patch and succeeds +applying test.patch +applying test1b.patch +Now at: test1b.patch + qpush test1b.patch succeeds + % does nothing and succeeds +qpush: test1b.patch is already at the top + qpush test1b.patch succeeds + % does nothing and succeeds +qpop: test1b.patch is already at the top + qpop test1b.patch succeeds + % fails - can't push to this patch +abort: cannot push to a previous patch: test.patch + qpush test.patch fails + % fails - can't pop to this patch +abort: patch test2.patch is not applied + qpop test2.patch fails + % pops up to test.patch and succeeds +Now at: test.patch + qpop test.patch succeeds + % pushes all patches and succeeds +applying test1b.patch +applying test2.patch +Now at: test2.patch + qpush -a succeeds + % does nothing and succeeds +all patches are currently applied + qpush -a succeeds + % fails - nothing else to push +patch series already fully applied + qpush fails + % does nothing and succeeds +all patches are currently applied + qpush test2.patch succeeds % strip adding x 0 files updated, 0 files merged, 1 files removed, 0 files unresolved diff --git a/tests/test-purge b/tests/test-purge new file mode 100755 --- /dev/null +++ b/tests/test-purge @@ -0,0 +1,76 @@ +#!/bin/sh + +cat <> $HGRCPATH +[extensions] +purge=${TESTDIR}/../contrib/purge/purge.py +EOF + +echo % init +hg init t +cd t + +echo % setup +echo r1 > r1 +hg ci -qAmr1 -d'0 0' +mkdir directory +echo r2 > directory/r2 +hg ci -qAmr2 -d'1 0' +echo 'ignored' > .hgignore +hg ci -qAmr3 -d'2 0' + +echo % delete an empty directory +mkdir empty_dir +hg purge -p +hg purge -v +ls + +echo % delete an untracked directory +mkdir untracked_dir +touch untracked_dir/untracked_file1 +touch untracked_dir/untracked_file2 +hg purge -p +hg purge -v +ls + +echo % delete an untracked file +touch untracked_file +hg purge -p +hg purge -v +ls + +echo % delete an untracked file in a tracked directory +touch directory/untracked_file +hg purge -p +hg purge -v +ls + +echo % delete nested directories +mkdir -p untracked_directory/nested_directory +hg purge -p +hg purge -v +ls + +echo % delete nested directories from a subdir +mkdir -p untracked_directory/nested_directory +cd directory +hg purge -p +hg purge -v +cd .. +ls + +echo % delete only part of the tree +mkdir -p untracked_directory/nested_directory +touch directory/untracked_file +cd directory +hg purge -p ../untracked_directory +hg purge -v ../untracked_directory +cd .. +ls +ls directory/untracked_file +rm directory/untracked_file + +echo % delete ignored files +touch ignored +hg purge -p +hg purge -v +ls diff --git a/tests/test-purge.out b/tests/test-purge.out new file mode 100644 --- /dev/null +++ b/tests/test-purge.out @@ -0,0 +1,49 @@ +% init +% setup +% delete an empty directory +empty_dir +Removing directory empty_dir +directory +r1 +% delete an untracked directory +untracked_dir/untracked_file1 +untracked_dir/untracked_file2 +Removing file untracked_dir/untracked_file1 +Removing file untracked_dir/untracked_file2 +Removing directory untracked_dir +directory +r1 +% delete an untracked file +untracked_file +Removing file untracked_file +directory +r1 +% delete an untracked file in a tracked directory +directory/untracked_file +Removing file directory/untracked_file +directory +r1 +% delete nested directories +untracked_directory/nested_directory +Removing directory untracked_directory/nested_directory +Removing directory untracked_directory +directory +r1 +% delete nested directories from a subdir +untracked_directory/nested_directory +Removing directory untracked_directory/nested_directory +Removing directory untracked_directory +directory +r1 +% delete only part of the tree +untracked_directory/nested_directory +Removing directory untracked_directory/nested_directory +Removing directory untracked_directory +directory +r1 +directory/untracked_file +% delete ignored files +ignored +Removing file ignored +directory +r1 diff --git a/tests/test-ssh.out b/tests/test-ssh.out --- a/tests/test-ssh.out +++ b/tests/test-ssh.out @@ -33,6 +33,7 @@ no changes found # local change # updating rc # find outgoing +comparing with ssh://user@dummy/remote searching for changes changeset: 1:572896fe480d tag: tip @@ -41,6 +42,7 @@ date: Mon Jan 12 13:46:40 1970 +0 summary: add # find incoming on the remote side +comparing with ssh://user@dummy/local searching for changes changeset: 1:572896fe480d tag: tip diff --git a/tests/test-symlinks b/tests/test-symlinks --- a/tests/test-symlinks +++ b/tests/test-symlinks @@ -55,3 +55,18 @@ echo '# try symlink outside repo to file ln -s x/f ../z # this should fail hg status ../z && { echo hg mistakenly exited with status 0; exit 1; } || : + +cd .. ; rm -r test +hg init test; cd test; + +echo '# try cloning symlink in a subdir' +echo '1. commit a symlink' +mkdir -p a/b/c +cd a/b/c +ln -s /path/to/symlink/source demo +cd ../../.. +hg stat +hg commit -A -m 'add symlink in a/b/c subdir' +echo '2. clone it' +cd .. +hg clone test testclone diff --git a/tests/test-symlinks.out b/tests/test-symlinks.out --- a/tests/test-symlinks.out +++ b/tests/test-symlinks.out @@ -1,11 +1,12 @@ +adding bar adding foo adding bomb adding a.c adding dir/a.o adding dir/b.o +M dir/b.o ! a.c ! dir/a.o -! dir/b.o ? .hgignore a.c: unsupported file type (type is fifo) ! a.c @@ -13,3 +14,9 @@ a.c: unsupported file type (type is fifo A f # try symlink outside repo to file inside abort: ../z not under root +# try cloning symlink in a subdir +1. commit a symlink +? a/b/c/demo +adding a/b/c/demo +2. clone it +1 files updated, 0 files merged, 0 files removed, 0 files unresolved diff --git a/tests/test-transplant b/tests/test-transplant --- a/tests/test-transplant +++ b/tests/test-transplant @@ -22,28 +22,25 @@ hg ci -Amb3 -d '2 0' hg log --template '{rev} {parents} {desc}\n' -cd .. -hg clone t rebase -cd rebase +hg clone . ../rebase +cd ../rebase hg up -C 1 echo '% rebase b onto r1' hg transplant -a -b tip hg log --template '{rev} {parents} {desc}\n' -cd .. -hg clone t prune -cd prune +hg clone ../t ../prune +cd ../prune hg up -C 1 echo '% rebase b onto r1, skipping b2' hg transplant -a -b tip -p 3 hg log --template '{rev} {parents} {desc}\n' -cd .. echo '% remote transplant' -hg clone -r 1 t remote -cd remote +hg clone -r 1 ../t ../remote +cd ../remote hg transplant --log -s ../t 2 4 hg log --template '{rev} {parents} {desc}\n' @@ -54,11 +51,19 @@ hg log --template '{rev} {parents} {desc echo '% skip local changes transplanted to the source' echo b4 > b4 hg ci -Amb4 -d '3 0' -cd .. -hg clone t pullback -cd pullback +hg clone ../t ../pullback +cd ../pullback hg transplant -s ../remote -a -b tip +echo '% remote transplant with pull' +hg -R ../t serve -p 20062 -d --pid-file=../t.pid +cat ../t.pid >> $DAEMON_PIDS + +hg clone -r 0 ../t ../rp +cd ../rp +hg transplant -s http://localhost:20062/ 2 4 +hg log --template '{rev} {parents} {desc}\n' + echo '% transplant --continue' hg init ../tc cd ../tc diff --git a/tests/test-transplant.out b/tests/test-transplant.out --- a/tests/test-transplant.out +++ b/tests/test-transplant.out @@ -75,6 +75,24 @@ 4 files updated, 0 files merged, 0 files searching for changes applying 4333daefcb15 4333daefcb15 transplanted to 5f42c04e07cc +% remote transplant with pull +requesting all changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +1 files updated, 0 files merged, 0 files removed, 0 files unresolved +searching for changes +searching for changes +adding changesets +adding manifests +adding file changes +added 1 changesets with 1 changes to 1 files +applying a53251cdf717 +a53251cdf717 transplanted to 8d9279348abb +2 b3 +1 b1 +0 r1 % transplant --continue adding foo adding toremove diff --git a/tests/test-ui-config b/tests/test-ui-config --- a/tests/test-ui-config +++ b/tests/test-ui-config @@ -1,5 +1,6 @@ #!/usr/bin/env python +import ConfigParser from mercurial import ui, util, commands testui = ui.ui() @@ -70,3 +71,21 @@ try: except util.Abort, inst: print inst print "---" + +cp = util.configparser() +cp.add_section('foo') +cp.set('foo', 'bar', 'baz') +try: + # should fail - keys are case-sensitive + cp.get('foo', 'Bar') +except ConfigParser.NoOptionError, inst: + print inst + +def function(): + pass + +cp.add_section('hook') +# values that aren't strings should work +cp.set('hook', 'commit', function) +f = cp.get('hook', 'commit') +print "f %s= function" % (f == function and '=' or '!') diff --git a/tests/test-ui-config.out b/tests/test-ui-config.out --- a/tests/test-ui-config.out +++ b/tests/test-ui-config.out @@ -43,3 +43,5 @@ bad interpolation variable reference '%( Error in configuration section [interpolation] parameter 'value5': '%' must be followed by '%' or '(', found: '%bad2' --- +No option 'Bar' in section: 'foo' +f == function