# HG changeset patch # User Vadim Gelfer # Date 1134344322 28800 # Node ID b3e94785ab698e77ca22b467fc3ac58c7de57c3e # Parent 32a4e6802864007096604956b561b4c10ac79752# Parent 63799b01985c75b4583227de7cf92757ac4300c5 merge with crew diff -r 32a4e6802864 -r b3e94785ab69 contrib/bash_completion --- a/contrib/bash_completion Fri Nov 04 11:51:01 2005 -0800 +++ b/contrib/bash_completion Sun Dec 11 15:38:42 2005 -0800 @@ -2,18 +2,25 @@ _hg_commands() { - local commands="$(hg -v help | sed -e '1,/^list of commands:/d' \ - -e '/^global options:/,$d' \ - -e '/^ [^ ]/!d; s/[,:]//g;')" + local all commands result + + all=($(hg --debug help | sed -e '1,/^list of commands:/d' \ + -e '/^global options:/,$d' \ + -e '/^ [^ ]/!d; s/^ //; s/[,:]//g;')) + + commands="${all[*]##debug*}" + result=$(compgen -W "${commands[*]}" -- "$cur") # hide debug commands from users, but complete them if - # specifically asked for - if [[ "$cur" == de* ]]; then - commands="$commands debugcheckstate debugstate debugindex" - commands="$commands debugindexdot debugwalk debugdata" - commands="$commands debugancestor debugconfig debugrename" + # there is no other possible command + if [ "$result" = "" ]; then + local debug + debug=(${all[*]##!(debug*)}) + debug="${debug[*]/g/debug}" + result=$(compgen -W "$debug" -- "$cur") fi - COMPREPLY=( ${COMPREPLY[@]:-} $(compgen -W "$commands" -- "$cur") ) + + COMPREPLY=(${COMPREPLY[@]:-} $result) } _hg_paths() @@ -161,7 +168,7 @@ fi ;; *) - COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" )) + COMPREPLY=(${COMPREPLY[@]:-} $( compgen -f -- "$cur" )) ;; esac diff -r 32a4e6802864 -r b3e94785ab69 contrib/hbisect.py --- a/contrib/hbisect.py Fri Nov 04 11:51:01 2005 -0800 +++ b/contrib/hbisect.py Sun Dec 11 15:38:42 2005 -0800 @@ -26,7 +26,7 @@ ui.warn("Repository is not clean, please commit or revert\n") sys.exit(1) -class bisect: +class bisect(object): """dichotomic search in the DAG of changesets""" def __init__(self, ui, repo): self.repo = repo diff -r 32a4e6802864 -r b3e94785ab69 contrib/hg-ssh --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/contrib/hg-ssh Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,45 @@ +#!/usr/bin/env python +# +# Copyright 2005 by Intevation GmbH +# Author(s): +# Thomas Arendsen Hein +# +# This software may be used and distributed according to the terms +# of the GNU General Public License, incorporated herein by reference. + +""" +hg-ssh - a wrapper for ssh access to a limited set of mercurial repos + +To be used in ~/.ssh/authorized_keys with the "command" option, see sshd(8): +command="hg-ssh path/to/repo1 /path/to/repo2 ~/repo3 ~user/repo4" ssh-dss ... +(probably together with these other useful options: + no-port-forwarding,no-X11-forwarding,no-agent-forwarding) + +This allows pull/push over ssh to to the repositories given as arguments. + +If all your repositories are subdirectories of a common directory, you can +allow shorter paths with: +command="cd path/to/my/repositories && hg-ssh repo1 subdir/repo2" +""" + +from mercurial import commands + +import sys, os + +cwd = os.getcwd() +allowed_paths = [os.path.normpath(os.path.join(cwd, os.path.expanduser(path))) + for path in sys.argv[1:]] +orig_cmd = os.getenv('SSH_ORIGINAL_COMMAND', '?') + +if orig_cmd.startswith('hg -R ') and orig_cmd.endswith(' serve --stdio'): + path = orig_cmd[6:-14] + repo = os.path.normpath(os.path.join(cwd, os.path.expanduser(path))) + if repo in allowed_paths: + commands.dispatch(['-R', repo, 'serve', '--stdio']) + else: + sys.stderr.write("Illegal repository %r\n" % repo) + sys.exit(-1) +else: + sys.stderr.write("Illegal command %r\n" % orig_cmd) + sys.exit(-1) + diff -r 32a4e6802864 -r b3e94785ab69 contrib/zsh_completion --- a/contrib/zsh_completion Fri Nov 04 11:51:01 2005 -0800 +++ b/contrib/zsh_completion Sun Dec 11 15:38:42 2005 -0800 @@ -116,7 +116,7 @@ '*:file:_files' ;; - (status) + (status|st) _arguments $includeExclude \ '(--no-status)-n[hide status prefix]' \ '(-n)--no-status[hide status prefix]' \ diff -r 32a4e6802864 -r b3e94785ab69 doc/hg.1.txt --- a/doc/hg.1.txt Fri Nov 04 11:51:01 2005 -0800 +++ b/doc/hg.1.txt Sun Dec 11 15:38:42 2005 -0800 @@ -87,7 +87,7 @@ New files are ignored if they match any of the patterns in .hgignore. As with add, these changes take effect at the next commit. -annotate [-r -u -n -c] [files ...]:: +annotate [-r -u -n -c -d] [files ...]:: List changes in files, showing the revision id responsible for each line This command is useful to discover who did a change or when a change took @@ -103,6 +103,7 @@ -X, --exclude exclude names matching the given patterns -r, --revision annotate the specified revision -u, --user list the author + -d, --date list the commit date -c, --changeset list the changeset -n, --number list the revision number (default) diff -r 32a4e6802864 -r b3e94785ab69 mercurial/bdiff.c --- a/mercurial/bdiff.c Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/bdiff.c Sun Dec 11 15:38:42 2005 -0800 @@ -147,7 +147,7 @@ break; a[i].e = j; /* use equivalence class for quick compare */ - if(h[j].len <= t) + if (h[j].len <= t) a[i].n = h[j].pos; /* point to head of match list */ else a[i].n = -1; /* too popular */ @@ -270,7 +270,7 @@ if (!l.head || !rl) goto nomem; - for(h = l.base; h != l.head; h++) { + for (h = l.base; h != l.head; h++) { m = Py_BuildValue("iiii", h->a1, h->a2, h->b1, h->b2); PyList_SetItem(rl, pos, m); pos++; @@ -305,7 +305,7 @@ goto nomem; /* calculate length of output */ - for(h = l.base; h != l.head; h++) { + for (h = l.base; h != l.head; h++) { if (h->a1 != la || h->b1 != lb) len += 12 + bl[h->b1].l - bl[lb].l; la = h->a2; @@ -320,7 +320,7 @@ rb = PyString_AsString(result); la = lb = 0; - for(h = l.base; h != l.head; h++) { + for (h = l.base; h != l.head; h++) { if (h->a1 != la || h->b1 != lb) { len = bl[h->b1].l - bl[lb].l; *(uint32_t *)(encode) = htonl(al[la].l - al->l); @@ -353,3 +353,4 @@ { Py_InitModule3("bdiff", methods, mdiff_doc); } + diff -r 32a4e6802864 -r b3e94785ab69 mercurial/commands.py --- a/mercurial/commands.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/commands.py Sun Dec 11 15:38:42 2005 -0800 @@ -15,6 +15,8 @@ class UnknownCommand(Exception): """Exception raised if command is not in the command table.""" +class AmbiguousCommand(Exception): + """Exception raised if command shortcut matches more than one command.""" def filterfiles(filters, files): l = [x for x in files if x in filters] @@ -31,25 +33,29 @@ return [util.normpath(os.path.join(cwd, x)) for x in args] return args -def matchpats(repo, cwd, pats=[], opts={}, head=''): +def matchpats(repo, pats=[], opts={}, head=''): + cwd = repo.getcwd() + if not pats and cwd: + opts['include'] = [os.path.join(cwd, i) for i in opts['include']] + opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] + cwd = '' return util.cmdmatcher(repo.root, cwd, pats or ['.'], opts.get('include'), - opts.get('exclude'), head) + opts.get('exclude'), head) + (cwd,) -def makewalk(repo, pats, opts, head=''): - cwd = repo.getcwd() - files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head) +def makewalk(repo, pats, opts, node=None, head=''): + files, matchfn, anypats, cwd = matchpats(repo, pats, opts, head) exact = dict(zip(files, files)) def walk(): - for src, fn in repo.walk(files=files, match=matchfn): + for src, fn in repo.walk(node=node, files=files, match=matchfn): yield src, fn, util.pathto(cwd, fn), fn in exact return files, matchfn, walk() -def walk(repo, pats, opts, head=''): - files, matchfn, results = makewalk(repo, pats, opts, head) +def walk(repo, pats, opts, node=None, head=''): + files, matchfn, results = makewalk(repo, pats, opts, node, head) for r in results: yield r -def walkchangerevs(ui, repo, cwd, pats, opts): +def walkchangerevs(ui, repo, pats, opts): '''Iterate over files and the revs they changed in. Callers most commonly need to iterate backwards over the history @@ -79,12 +85,7 @@ if repo.changelog.count() == 0: return [], False - cwd = repo.getcwd() - if not pats and cwd: - opts['include'] = [os.path.join(cwd, i) for i in opts['include']] - opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] - files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', - pats, opts) + files, matchfn, anypats, cwd = matchpats(repo, pats, opts) revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) wanted = {} slowpath = anypats @@ -387,7 +388,7 @@ if with_version: show_version(ui) ui.write('\n') - key, i = find(cmd) + aliases, i = find(cmd) # synopsis ui.write("%s\n\n" % i[2]) @@ -399,9 +400,8 @@ if not ui.quiet: # aliases - aliases = ', '.join(key.split('|')[1:]) - if aliases: - ui.write(_("\naliases: %s\n") % aliases) + if len(aliases) > 1: + ui.write(_("\naliases: %s\n") % ', '.join(aliases[1:])) # options if i[1]: @@ -482,8 +482,7 @@ The files will be added to the repository at the next commit. - If no names are given, add all files in the current directory and - its subdirectories. + If no names are given, add all files in the repository. """ names = [] @@ -537,11 +536,20 @@ cl = repo.changelog.read(repo.changelog.node(rev)) return trimuser(ui, cl[1], rev, ucache) + dcache = {} + def getdate(rev): + datestr = dcache.get(rev) + if datestr is None: + cl = repo.changelog.read(repo.changelog.node(rev)) + datestr = dcache[rev] = util.datestr(cl[2]) + return datestr + if not pats: raise util.Abort(_('at least one file name or pattern required')) - opmap = [['user', getname], ['number', str], ['changeset', getnode]] - if not opts['user'] and not opts['changeset']: + opmap = [['user', getname], ['number', str], ['changeset', getnode], + ['date', getdate]] + if not opts['user'] and not opts['changeset'] and not opts['date']: opts['number'] = 1 if opts['rev']: @@ -624,21 +632,16 @@ %p root-relative path name of file being printed """ mf = {} - if opts['rev']: - change = repo.changelog.read(repo.lookup(opts['rev'])) - mf = repo.manifest.read(change[0]) - for src, abs, rel, exact in walk(repo, (file1,) + pats, opts): + rev = opts['rev'] + if rev: + node = repo.lookup(rev) + else: + node = repo.changelog.tip() + change = repo.changelog.read(node) + mf = repo.manifest.read(change[0]) + for src, abs, rel, exact in walk(repo, (file1,) + pats, opts, node): r = repo.file(abs) - if opts['rev']: - try: - n = mf[abs] - except (hg.RepoError, KeyError): - try: - n = r.lookup(rev) - except KeyError, inst: - raise util.Abort(_('cannot find file %s in rev %s'), rel, rev) - else: - n = r.tip() + n = mf[abs] fp = make_file(repo, r, opts['output'], node=n, pathname=abs) fp.write(r.read(n)) @@ -667,7 +670,7 @@ dest = os.path.realpath(dest) - class Dircleanup: + class Dircleanup(object): def __init__(self, dir_): self.rmtree = shutil.rmtree self.dir_ = dir_ @@ -735,6 +738,7 @@ f = repo.opener("hgrc", "w", text=True) f.write("[paths]\n") f.write("default = %s\n" % abspath) + f.close() if not opts['noupdate']: update(ui, repo) @@ -747,7 +751,7 @@ Commit changes to the given files into the repository. If a list of files is omitted, all changes reported by "hg status" - from the root of the repository will be commited. + will be commited. The HGEDITOR or EDITOR environment variables are used to start an editor to add a commit comment. @@ -770,12 +774,7 @@ if opts['addremove']: addremove(ui, repo, *pats, **opts) - cwd = repo.getcwd() - if not pats and cwd: - opts['include'] = [os.path.join(cwd, i) for i in opts['include']] - opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] - fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '', - pats, opts) + fns, match, anypats, cwd = matchpats(repo, pats, opts) if pats: c, a, d, u = repo.changes(files=fns, match=match) files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r'] @@ -787,14 +786,10 @@ raise util.Abort(str(inst)) def docopy(ui, repo, pats, opts): - if not pats: - raise util.Abort(_('no source or destination specified')) - elif len(pats) == 1: - raise util.Abort(_('no destination specified')) - pats = list(pats) - dest = pats.pop() - sources = [] - dir2dir = len(pats) == 1 and os.path.isdir(pats[0]) + cwd = repo.getcwd() + errors = 0 + copied = [] + targets = {} def okaytocopy(abs, rel, exact): reasons = {'?': _('is not managed'), @@ -805,74 +800,133 @@ else: return True - for src, abs, rel, exact in walk(repo, pats, opts): - if okaytocopy(abs, rel, exact): - sources.append((abs, rel, exact)) - if not sources: - raise util.Abort(_('no files to copy')) - - cwd = repo.getcwd() - absdest = util.canonpath(repo.root, cwd, dest) - reldest = util.pathto(cwd, absdest) - if os.path.exists(reldest): - destisfile = not os.path.isdir(reldest) - else: - destisfile = not dir2dir and (len(sources) == 1 - or repo.dirstate.state(absdest) != '?') - - if destisfile and len(sources) > 1: - raise util.Abort(_('with multiple sources, destination must be a ' - 'directory')) - - srcpfxlen = 0 - if dir2dir: - srcpfx = util.pathto(cwd, util.canonpath(repo.root, cwd, pats[0])) - if os.path.exists(reldest): - srcpfx = os.path.split(srcpfx)[0] - if srcpfx: - srcpfx += os.sep - srcpfxlen = len(srcpfx) - - errs, copied = 0, [] - for abs, rel, exact in sources: - if destisfile: - mydest = reldest - elif dir2dir: - mydest = os.path.join(dest, rel[srcpfxlen:]) + def copy(abssrc, relsrc, target, exact): + abstarget = util.canonpath(repo.root, cwd, target) + reltarget = util.pathto(cwd, abstarget) + prevsrc = targets.get(abstarget) + if prevsrc is not None: + ui.warn(_('%s: not overwriting - %s collides with %s\n') % + (reltarget, abssrc, prevsrc)) + return + if (not opts['after'] and os.path.exists(reltarget) or + opts['after'] and repo.dirstate.state(abstarget) not in '?r'): + if not opts['force']: + ui.warn(_('%s: not overwriting - file exists\n') % + reltarget) + return + if not opts['after']: + os.unlink(reltarget) + if opts['after']: + if not os.path.exists(reltarget): + return else: - mydest = os.path.join(dest, os.path.basename(rel)) - myabsdest = util.canonpath(repo.root, cwd, mydest) - myreldest = util.pathto(cwd, myabsdest) - if not opts['force'] and repo.dirstate.state(myabsdest) not in 'a?': - ui.warn(_('%s: not overwriting - file already managed\n') % myreldest) - continue - mydestdir = os.path.dirname(myreldest) or '.' - if not opts['after']: + targetdir = os.path.dirname(reltarget) or '.' + if not os.path.isdir(targetdir): + os.makedirs(targetdir) try: - if dir2dir: os.makedirs(mydestdir) - elif not destisfile: os.mkdir(mydestdir) - except OSError, inst: - if inst.errno != errno.EEXIST: raise - if ui.verbose or not exact: - ui.status(_('copying %s to %s\n') % (rel, myreldest)) - if not opts['after']: - try: - shutil.copyfile(rel, myreldest) - shutil.copymode(rel, myreldest) + shutil.copyfile(relsrc, reltarget) + shutil.copymode(relsrc, reltarget) except shutil.Error, inst: raise util.Abort(str(inst)) except IOError, inst: if inst.errno == errno.ENOENT: - ui.warn(_('%s: deleted in working copy\n') % rel) + ui.warn(_('%s: deleted in working copy\n') % relsrc) else: - ui.warn(_('%s: cannot copy - %s\n') % (rel, inst.strerror)) - errs += 1 - continue - repo.copy(abs, myabsdest) - copied.append((abs, rel, exact)) - if errs: + ui.warn(_('%s: cannot copy - %s\n') % + (relsrc, inst.strerror)) + errors += 1 + return + if ui.verbose or not exact: + ui.status(_('copying %s to %s\n') % (relsrc, reltarget)) + targets[abstarget] = abssrc + repo.copy(abssrc, abstarget) + copied.append((abssrc, relsrc, exact)) + + def targetpathfn(pat, dest, srcs): + if os.path.isdir(pat): + if pat.endswith(os.sep): + pat = pat[:-len(os.sep)] + if destdirexists: + striplen = len(os.path.split(pat)[0]) + else: + striplen = len(pat) + if striplen: + striplen += len(os.sep) + res = lambda p: os.path.join(dest, p[striplen:]) + elif destdirexists: + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + res = lambda p: dest + return res + + def targetpathafterfn(pat, dest, srcs): + if util.patkind(pat, None)[0]: + # a mercurial pattern + res = lambda p: os.path.join(dest, os.path.basename(p)) + elif len(util.canonpath(repo.root, cwd, pat)) < len(srcs[0][0]): + # A directory. Either the target path contains the last + # component of the source path or it does not. + def evalpath(striplen): + score = 0 + for s in srcs: + t = os.path.join(dest, s[1][striplen:]) + if os.path.exists(t): + score += 1 + return score + + if pat.endswith(os.sep): + pat = pat[:-len(os.sep)] + striplen = len(pat) + len(os.sep) + if os.path.isdir(os.path.join(dest, os.path.split(pat)[1])): + score = evalpath(striplen) + striplen1 = len(os.path.split(pat)[0]) + if striplen1: + striplen1 += len(os.sep) + if evalpath(striplen1) > score: + striplen = striplen1 + res = lambda p: os.path.join(dest, p[striplen:]) + else: + # a file + if destdirexists: + res = lambda p: os.path.join(dest, os.path.basename(p)) + else: + res = lambda p: dest + return res + + + pats = list(pats) + if not pats: + raise util.Abort(_('no source or destination specified')) + if len(pats) == 1: + raise util.Abort(_('no destination specified')) + dest = pats.pop() + destdirexists = os.path.isdir(dest) + if (len(pats) > 1 or util.patkind(pats[0], None)[0]) and not destdirexists: + raise util.Abort(_('with multiple sources, destination must be an ' + 'existing directory')) + if opts['after']: + tfn = targetpathafterfn + else: + tfn = targetpathfn + copylist = [] + for pat in pats: + srcs = [] + for tag, abssrc, relsrc, exact in walk(repo, [pat], opts): + if okaytocopy(abssrc, relsrc, exact): + srcs.append((abssrc, relsrc, exact)) + if not srcs: + continue + copylist.append((tfn(pat, dest, srcs), srcs)) + if not copylist: + raise util.Abort(_('no files to copy')) + + for targetpath, srcs in copylist: + for abssrc, relsrc, exact in srcs: + copy(abssrc, relsrc, targetpath(relsrc), exact) + + if errors: ui.warn(_('(consider using --after)\n')) - return errs, copied + return errors, copied def copy(ui, repo, *pats, **opts): """mark files as copied for the next commit @@ -1007,7 +1061,7 @@ change = repo.changelog.read(n) m = repo.manifest.read(change[0]) n = m[relpath(repo, [file])[0]] - except hg.RepoError, KeyError: + except (hg.RepoError, KeyError): n = r.lookup(rev) else: n = r.tip() @@ -1030,7 +1084,7 @@ ui.write("%s\n" % line.rstrip()) def diff(ui, repo, *pats, **opts): - """diff working directory (or selected files) + """diff repository (or selected files) Show differences between revisions for the specified files. @@ -1056,7 +1110,7 @@ if len(revs) > 2: raise util.Abort(_("too many revisions to diff")) - fns, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts) + fns, matchfn, anypats, cwd = matchpats(repo, pats, opts) dodiff(sys.stdout, ui, repo, node1, node2, fns, match=matchfn, text=opts['text']) @@ -1177,7 +1231,7 @@ yield linenum, mstart - lstart, mend - lstart, body[lstart:lend] begin = lend + 1 - class linestate: + class linestate(object): def __init__(self, line, linenum, colstart, colend): self.line = line self.linenum = linenum @@ -1227,7 +1281,7 @@ fstate = {} skip = {} - changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts) + changeiter, getchange = walkchangerevs(ui, repo, pats, opts) count = 0 incrementing = False for st, rev, fns in changeiter: @@ -1275,11 +1329,14 @@ changesets. They are where development generally takes place and are the usual targets for update and merge operations. """ - heads = repo.changelog.heads() + if opts['rev']: + heads = repo.heads(repo.lookup(opts['rev'])) + else: + heads = repo.heads() br = None if opts['branches']: br = repo.branchlookup(heads) - for n in repo.changelog.heads(): + for n in heads: show_changeset(ui, repo, changenode=n, brinfo=br) def identify(ui, repo): @@ -1461,11 +1518,11 @@ Print the revision history of the specified files or the entire project. By default this command outputs: changeset id and hash, tags, - parents, user, date and time, and a summary for each commit. The - -v switch adds some more detail, such as changed files, manifest - hashes or message signatures. + non-trivial parents, user, date and time, and a summary for each + commit. When the -v/--verbose switch is used, the list of changed + files and full commit message is shown. """ - class dui: + class dui(object): # Implement and delegate some ui protocol. Save hunks of # output for later display in the desired order. def __init__(self, ui): @@ -1487,12 +1544,7 @@ self.write(*args) def __getattr__(self, key): return getattr(self.ui, key) - cwd = repo.getcwd() - if not pats and cwd: - opts['include'] = [os.path.join(cwd, i) for i in opts['include']] - opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] - changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '', - pats, opts) + changeiter, getchange = walkchangerevs(ui, repo, pats, opts) for st, rev, fns in changeiter: if st == 'window': du = dui(ui) @@ -1733,7 +1785,9 @@ This command tries to fix the repository status after an interrupted operation. It should only be necessary when Mercurial suggests it. """ - repo.recover() + if repo.recover(): + return repo.verify() + return False def remove(ui, repo, pat, *pats, **opts): """remove the specified files on the next commit @@ -1799,13 +1853,12 @@ If names are given, all files matching the names are reverted. - If no names are given, all files in the current directory and - its subdirectories are reverted. + If no arguments are given, all files in the repository are reverted. """ node = opts['rev'] and repo.lookup(opts['rev']) or \ repo.dirstate.parents()[0] - files, choose, anypats = matchpats(repo, repo.getcwd(), pats, opts) + files, choose, anypats, cwd = matchpats(repo, pats, opts) (c, a, d, u) = repo.changes(match=choose) repo.forget(a) repo.undelete(d) @@ -1928,9 +1981,8 @@ def status(ui, repo, *pats, **opts): """show changed files in the working directory - Show changed files in the working directory. If no names are - given, all files are shown. Otherwise, only files matching the - given names are shown. + Show changed files in the repository. If names are + given, only files that match are shown. The codes used to show the status of files are: M = modified @@ -1939,8 +1991,7 @@ ? = not tracked """ - cwd = repo.getcwd() - files, matchfn, anypats = matchpats(repo, cwd, pats, opts) + files, matchfn, anypats, cwd = matchpats(repo, pats, opts) (c, a, d, u) = [[util.pathto(cwd, x) for x in n] for n in repo.changes(files=files, match=matchfn)] @@ -1986,8 +2037,10 @@ else: r = hex(repo.changelog.tip()) - if name.find(revrangesep) >= 0: - raise util.Abort(_("'%s' cannot be used in a tag name") % revrangesep) + disallowed = (revrangesep, '\r', '\n') + for c in disallowed: + if name.find(c) >= 0: + raise util.Abort(_("%s cannot be used in a tag name") % repr(c)) if opts['local']: repo.opener("localtags", "a").write("%s %s\n" % (r, name)) @@ -2138,6 +2191,7 @@ [('r', 'rev', '', _('annotate the specified revision')), ('a', 'text', None, _('treat all files as text')), ('u', 'user', None, _('list the author')), + ('d', 'date', None, _('list the date')), ('n', 'number', None, _('list the revision number (default)')), ('c', 'changeset', None, _('list the changeset')), ('I', 'include', [], _('include names matching the given patterns')), @@ -2223,8 +2277,9 @@ "hg grep [OPTION]... PATTERN [FILE]..."), "heads": (heads, - [('b', 'branches', None, _('find branch info'))], - _('hg heads [-b]')), + [('b', 'branches', None, _('find branch info')), + ('r', 'rev', "", _('show only heads which are descendants of rev'))], + _('hg heads [-b] [-r ]')), "help": (help_, [], _('hg help [COMMAND]')), "identify|id": (identify, [], _('hg identify')), "import|patch": @@ -2374,17 +2429,21 @@ " debugindex debugindexdot paths") def find(cmd): - choice = [] + """Return (aliases, command table entry) for command string.""" + choice = None for e in table.keys(): aliases = e.lstrip("^").split("|") if cmd in aliases: - return e, table[e] + return aliases, table[e] for a in aliases: if a.startswith(cmd): - choice.append(e) - if len(choice) == 1: - e = choice[0] - return e, table[e] + if choice: + raise AmbiguousCommand(cmd) + else: + choice = aliases, table[e] + break + if choice: + return choice raise UnknownCommand(cmd) @@ -2411,18 +2470,11 @@ if args: cmd, args = args[0], args[1:] + aliases, i = find(cmd) + cmd = aliases[0] defaults = ui.config("defaults", cmd) if defaults: - # reparse with command defaults added - args = [cmd] + defaults.split() + args - try: - args = fancyopts.fancyopts(args, globalopts, options) - except fancyopts.getopt.GetoptError, inst: - raise ParseError(None, inst) - - cmd, args = args[0], args[1:] - - i = find(cmd)[1] + args = defaults.split() + args c = list(i[1]) else: cmd = None @@ -2460,7 +2512,7 @@ external = [] for x in u.extensions(): - def on_exception(Exception, inst): + def on_exception(exc, inst): u.warn(_("*** failed to import extension %s\n") % x[1]) u.warn("%s\n" % inst) if "--traceback" in sys.argv[1:]: @@ -2502,6 +2554,9 @@ u.warn(_("hg: %s\n") % inst.args[1]) help_(u, 'shortlist') sys.exit(-1) + except AmbiguousCommand, inst: + u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0]) + sys.exit(1) except UnknownCommand, inst: u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) help_(u, 'shortlist') @@ -2620,6 +2675,9 @@ u.debug(inst, "\n") u.warn(_("%s: invalid arguments\n") % cmd) help_(u, cmd) + except AmbiguousCommand, inst: + u.warn(_("hg: command '%s' is ambiguous.\n") % inst.args[0]) + help_(u, 'shortlist') except UnknownCommand, inst: u.warn(_("hg: unknown command '%s'\n") % inst.args[0]) help_(u, 'shortlist') @@ -2629,6 +2687,8 @@ except: u.warn(_("** unknown exception encountered, details follow\n")) u.warn(_("** report bug details to mercurial@selenic.com\n")) + u.warn(_("** Mercurial Distributed SCM (version %s)\n") + % version.get_version()) raise sys.exit(-1) diff -r 32a4e6802864 -r b3e94785ab69 mercurial/dirstate.py --- a/mercurial/dirstate.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/dirstate.py Sun Dec 11 15:38:42 2005 -0800 @@ -13,7 +13,7 @@ from demandload import * demandload(globals(), "time bisect stat util re errno") -class dirstate: +class dirstate(object): def __init__(self, opener, ui, root): self.opener = opener self.root = root @@ -101,16 +101,15 @@ try: return self.map[key] except TypeError: - self.read() + self.lazyread() return self[key] def __contains__(self, key): - if not self.map: self.read() + self.lazyread() return key in self.map def parents(self): - if not self.pl: - self.read() + self.lazyread() return self.pl def markdirty(self): @@ -118,8 +117,7 @@ self.dirty = 1 def setparents(self, p1, p2=nullid): - if not self.pl: - self.read() + self.lazyread() self.markdirty() self.pl = p1, p2 @@ -129,9 +127,11 @@ except KeyError: return "?" + def lazyread(self): + if self.map is None: + self.read() + def read(self): - if self.map is not None: return self.map - self.map = {} self.pl = [nullid, nullid] try: @@ -154,7 +154,7 @@ pos += l def copy(self, source, dest): - self.read() + self.lazyread() self.markdirty() self.copies[dest] = source @@ -169,13 +169,13 @@ a marked for addition''' if not files: return - self.read() + self.lazyread() self.markdirty() for f in files: if state == "r": self.map[f] = ('r', 0, 0, 0) else: - s = os.lstat(os.path.join(self.root, f)) + s = os.lstat(self.wjoin(f)) st_size = kw.get('st_size', s.st_size) st_mtime = kw.get('st_mtime', s.st_mtime) self.map[f] = (state, s.st_mode, st_size, st_mtime) @@ -184,7 +184,7 @@ def forget(self, files): if not files: return - self.read() + self.lazyread() self.markdirty() for f in files: try: @@ -198,7 +198,7 @@ self.markdirty() def write(self): - st = self.opener("dirstate", "w") + st = self.opener("dirstate", "w", atomic=True) st.write("".join(self.pl)) for f, e in self.map.items(): c = self.copied(f) @@ -213,7 +213,7 @@ unknown = [] for x in files: - if x is '.': + if x == '.': return self.map.copy() if x not in self.map: unknown.append(x) @@ -241,7 +241,7 @@ bs += 1 return ret - def supported_type(self, f, st, verbose=True): + def supported_type(self, f, st, verbose=False): if stat.S_ISREG(st.st_mode): return True if verbose: @@ -258,7 +258,7 @@ return False def statwalk(self, files=None, match=util.always, dc=None): - self.read() + self.lazyread() # walk all files by default if not files: @@ -296,7 +296,6 @@ def walkhelper(self, files, statmatch, dc): # recursion free walker, faster than os.walk. def findfiles(s): - retfiles = [] work = [s] while work: top = work.pop() @@ -306,7 +305,7 @@ nd = util.normpath(top[len(self.root) + 1:]) if nd == '.': nd = '' for f in names: - np = os.path.join(nd, f) + np = util.pconvert(os.path.join(nd, f)) if seen(np): continue p = os.path.join(top, f) @@ -317,12 +316,12 @@ if statmatch(ds, st): work.append(p) if statmatch(np, st) and np in dc: - yield 'm', util.pconvert(np), st + yield 'm', np, st elif statmatch(np, st): if self.supported_type(np, st): - yield 'f', util.pconvert(np), st + yield 'f', np, st elif np in dc: - yield 'm', util.pconvert(np), st + yield 'm', np, st known = {'.hg': 1} def seen(fn): @@ -332,13 +331,20 @@ # step one, find all files that match our criteria files.sort() for ff in util.unique(files): - f = os.path.join(self.root, ff) + f = self.wjoin(ff) try: st = os.lstat(f) except OSError, inst: - if ff not in dc: self.ui.warn('%s: %s\n' % ( - util.pathto(self.getcwd(), ff), - inst.strerror)) + nf = util.normpath(ff) + found = False + for fn in dc: + if nf == fn or (fn.startswith(nf) and fn[len(nf)] == '/'): + found = True + break + if not found: + self.ui.warn('%s: %s\n' % ( + util.pathto(self.getcwd(), ff), + inst.strerror)) continue if stat.S_ISDIR(st.st_mode): cmp1 = (lambda x, y: cmp(x[1], y[1])) @@ -352,7 +358,7 @@ continue self.blockignore = True if statmatch(ff, st): - if self.supported_type(ff, st): + if self.supported_type(ff, st, verbose=True): yield 'f', ff, st elif ff in dc: yield 'm', ff, st @@ -380,7 +386,7 @@ nonexistent = True if not st: try: - f = os.path.join(self.root, fn) + f = self.wjoin(fn) st = os.lstat(f) except OSError, inst: if inst.errno != errno.ENOENT: diff -r 32a4e6802864 -r b3e94785ab69 mercurial/fancyopts.py --- a/mercurial/fancyopts.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/fancyopts.py Sun Dec 11 15:38:42 2005 -0800 @@ -1,10 +1,10 @@ import getopt def fancyopts(args, options, state): - long=[] - short='' - map={} - dt={} + long = [] + short = '' + map = {} + dt = {} for s, l, d, c in options: pl = l.replace('-', '_') diff -r 32a4e6802864 -r b3e94785ab69 mercurial/filelog.py --- a/mercurial/filelog.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/filelog.py Sun Dec 11 15:38:42 2005 -0800 @@ -54,11 +54,11 @@ mt = "" if meta: mt = [ "%s: %s\n" % (k, v) for k,v in meta.items() ] - text = "\1\n" + "".join(mt) + "\1\n" + text + text = "\1\n%s\1\n%s" % ("".join(mt), text) return self.addrevision(text, transaction, link, p1, p2) def renamed(self, node): - if 0 and self.parents(node)[0] != nullid: + if 0 and self.parents(node)[0] != nullid: # XXX return False m = self.readmeta(node) if m and m.has_key("copy"): diff -r 32a4e6802864 -r b3e94785ab69 mercurial/hgweb.py --- a/mercurial/hgweb.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/hgweb.py Sun Dec 11 15:38:42 2005 -0800 @@ -71,7 +71,7 @@ else: return os.stat(hg_path).st_mtime -class hgrequest: +class hgrequest(object): def __init__(self, inp=None, out=None, env=None): self.inp = inp or sys.stdin self.out = out or sys.stdout @@ -104,7 +104,7 @@ headers.append(('Content-length', str(size))) self.header(headers) -class templater: +class templater(object): def __init__(self, mapfile, filters={}, defaults={}): self.cache = {} self.map = {} @@ -165,7 +165,6 @@ common_filters = { "escape": cgi.escape, "strip": lambda x: x.strip(), - "rstrip": lambda x: x.rstrip(), "age": age, "date": lambda x: util.datestr(x), "addbreaks": nl2br, @@ -176,7 +175,7 @@ "rfc822date": lambda x: util.datestr(x, "%a, %d %b %Y %H:%M:%S"), } -class hgweb: +class hgweb(object): def __init__(self, repo, name=None): if type(repo) == type(""): self.repo = hg.repository(ui.ui(), repo) @@ -952,14 +951,8 @@ else: return BaseHTTPServer.HTTPServer((address, port), hgwebhandler) -def server(path, name, templates, address, port, use_ipv6=False, - accesslog=sys.stdout, errorlog=sys.stderr): - httpd = create_server(path, name, templates, address, port, use_ipv6, - accesslog, errorlog) - httpd.serve_forever() - # This is a stopgap -class hgwebdir: +class hgwebdir(object): def __init__(self, config): def cleannames(items): return [(name.strip('/'), path) for name, path in items] @@ -1000,7 +993,10 @@ .replace("//", "/")) # update time with local timezone - d = (get_mtime(path), util.makedate()[1]) + try: + d = (get_mtime(path), util.makedate()[1]) + except OSError: + continue yield dict(contact=(get("ui", "username") or # preferred get("web", "contact") or # deprecated @@ -1017,7 +1013,12 @@ if virtual: real = dict(self.repos).get(virtual) if real: - hgweb(real).run(req) + try: + hgweb(real).run(req) + except IOError, inst: + req.write(tmpl("error", error=inst.strerror)) + except hg.RepoError, inst: + req.write(tmpl("error", error=str(inst))) else: req.write(tmpl("notfound", repo=virtual)) else: diff -r 32a4e6802864 -r b3e94785ab69 mercurial/httprangereader.py --- a/mercurial/httprangereader.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/httprangereader.py Sun Dec 11 15:38:42 2005 -0800 @@ -7,7 +7,7 @@ import byterange, urllib2 -class httprangereader: +class httprangereader(object): def __init__(self, url): self.url = url self.pos = 0 diff -r 32a4e6802864 -r b3e94785ab69 mercurial/localrepo.py --- a/mercurial/localrepo.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/localrepo.py Sun Dec 11 15:38:42 2005 -0800 @@ -12,7 +12,7 @@ from demandload import * demandload(globals(), "re lock transaction tempfile stat mdiff errno") -class localrepository: +class localrepository(object): def __init__(self, ui, path=None, create=0): if not path: p = os.getcwd() @@ -43,7 +43,7 @@ self.dirstate = dirstate.dirstate(self.opener, ui, self.root) try: - self.ui.readconfig(os.path.join(self.path, "hgrc")) + self.ui.readconfig(self.join("hgrc")) except IOError: pass def hook(self, name, **args): @@ -225,18 +225,20 @@ lock = self.lock() if os.path.exists(self.join("journal")): self.ui.status(_("rolling back interrupted transaction\n")) - return transaction.rollback(self.opener, self.join("journal")) + transaction.rollback(self.opener, self.join("journal")) + return True else: self.ui.warn(_("no interrupted transaction available\n")) + return False def undo(self): + wlock = self.wlock() lock = self.lock() if os.path.exists(self.join("undo")): self.ui.status(_("rolling back last transaction\n")) transaction.rollback(self.opener, self.join("undo")) - self.dirstate = None util.rename(self.join("undo.dirstate"), self.join("dirstate")) - self.dirstate = dirstate.dirstate(self.opener, self.ui, self.root) + self.dirstate.read() else: self.ui.warn(_("no undo information available\n")) @@ -249,6 +251,17 @@ return lock.lock(self.join("lock"), wait) raise inst + def wlock(self, wait=1): + try: + wlock = lock.lock(self.join("wlock"), 0, self.dirstate.write) + except lock.LockHeld, inst: + if not wait: + raise inst + self.ui.warn(_("waiting for lock held by %s\n") % inst.args[0]) + wlock = lock.lock(self.join("wlock"), wait, self.dirstate.write) + self.dirstate.read() + return wlock + def rawcommit(self, files, text, user, date, p1=None, p2=None): orig_parent = self.dirstate.parents()[0] or nullid p1 = p1 or self.dirstate.parents()[0] or nullid @@ -265,6 +278,8 @@ else: update_dirstate = 0 + wlock = self.wlock() + lock = self.lock() tr = self.transaction() mm = m1.copy() mfm = mf1.copy() @@ -353,6 +368,7 @@ if not self.hook("precommit"): return None + wlock = self.wlock() lock = self.lock() tr = self.transaction() @@ -446,8 +462,14 @@ def walk(self, node=None, files=[], match=util.always): if node: + fdict = dict.fromkeys(files) for fn in self.manifest.read(self.changelog.read(node)[0]): - if match(fn): yield 'm', fn + fdict.pop(fn, None) + if match(fn): + yield 'm', fn + for fn in fdict: + self.ui.warn(_('%s: No such file in rev %s\n') % ( + util.pathto(self.getcwd(), fn), short(node))) else: for src, fn in self.dirstate.walk(files, match): yield src, fn @@ -470,6 +492,10 @@ # are we comparing the working directory? if not node2: + try: + wlock = self.wlock(wait=0) + except lock.LockHeld: + wlock = None l, c, a, d, u = self.dirstate.changes(files, match) # are we comparing working dir against its parent? @@ -481,6 +507,8 @@ for f in l: if fcmp(f, mf2): c.append(f) + elif wlock is not None: + self.dirstate.update([f], "n") for l in c, a, d, u: l.sort() @@ -524,6 +552,7 @@ return (c, a, d, u) def add(self, list): + wlock = self.wlock() for f in list: p = self.wjoin(f) if not os.path.exists(p): @@ -536,6 +565,7 @@ self.dirstate.update([f], "a") def forget(self, list): + wlock = self.wlock() for f in list: if self.dirstate.state(f) not in 'ai': self.ui.warn(_("%s not added!\n") % f) @@ -549,6 +579,7 @@ util.unlink(self.wjoin(f)) except OSError, inst: if inst.errno != errno.ENOENT: raise + wlock = self.wlock() for f in list: p = self.wjoin(f) if os.path.exists(p): @@ -566,6 +597,7 @@ mn = self.changelog.read(p)[0] mf = self.manifest.readflags(mn) m = self.manifest.read(mn) + wlock = self.wlock() for f in list: if self.dirstate.state(f) not in "r": self.ui.warn("%s not removed!\n" % f) @@ -582,12 +614,17 @@ elif not os.path.isfile(p): self.ui.warn(_("copy failed: %s is not a file\n") % dest) else: + wlock = self.wlock() if self.dirstate.state(dest) == '?': self.dirstate.update([dest], "a") self.dirstate.copy(source, dest) - def heads(self): - return self.changelog.heads() + def heads(self, start=None): + heads = self.changelog.heads(start) + # sort the output in rev descending order + heads = [(-self.changelog.rev(h), h) for h in heads] + 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 @@ -1372,6 +1409,9 @@ mw[f] = "" mfw[f] = util.is_exec(self.wjoin(f), mfw.get(f, False)) + if moddirstate: + wlock = self.wlock() + for f in d: if f in mw: del mw[f] diff -r 32a4e6802864 -r b3e94785ab69 mercurial/lock.py --- a/mercurial/lock.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/lock.py Sun Dec 11 15:38:42 2005 -0800 @@ -11,11 +11,12 @@ class LockHeld(Exception): pass -class lock: - def __init__(self, file, wait=1): +class lock(object): + def __init__(self, file, wait=1, releasefn=None): self.f = file self.held = 0 self.wait = wait + self.releasefn = releasefn self.lock() def __del__(self): @@ -43,6 +44,8 @@ def release(self): if self.held: self.held = 0 + if self.releasefn: + self.releasefn() try: os.unlink(self.f) except: pass diff -r 32a4e6802864 -r b3e94785ab69 mercurial/manifest.py --- a/mercurial/manifest.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/manifest.py Sun Dec 11 15:38:42 2005 -0800 @@ -5,17 +5,16 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -import sys, struct +import struct from revlog import * from i18n import gettext as _ from demandload import * -demandload(globals(), "bisect") +demandload(globals(), "bisect array") class manifest(revlog): def __init__(self, opener): self.mapcache = None self.listcache = None - self.addlist = None revlog.__init__(self, opener, "00manifest.i", "00manifest.d") def read(self, node): @@ -25,8 +24,9 @@ text = self.revision(node) map = {} flag = {} - self.listcache = (text, text.splitlines(1)) - for l in self.listcache[1]: + self.listcache = array.array('c', text) + lines = text.splitlines(1) + for l in lines: (f, n) = l.split('\0') map[f] = bin(n[:40]) flag[f] = (n[40:-1] == "x") @@ -39,57 +39,67 @@ self.read(node) return self.mapcache[2] + def diff(self, a, b): + return mdiff.textdiff(str(a), str(b)) + def add(self, map, flags, transaction, link, p1=None, p2=None, changed=None): - # directly generate the mdiff delta from the data collected during - # the bisect loop below - def gendelta(delta): - i = 0 - result = [] - while i < len(delta): - start = delta[i][2] - end = delta[i][3] - l = delta[i][4] - if l == None: - l = "" - while i < len(delta) - 1 and start <= delta[i+1][2] \ - and end >= delta[i+1][2]: - if delta[i+1][3] > end: - end = delta[i+1][3] - if delta[i+1][4]: - l += delta[i+1][4] + + # returns a tuple (start, end). If the string is found + # m[start:end] are the line containing that string. If start == end + # the string was not found and they indicate the proper sorted + # insertion point. This was taken from bisect_left, and modified + # to find line start/end as it goes along. + # + # m should be a buffer or a string + # s is a string + # + def manifestsearch(m, s, lo=0, hi=None): + def advance(i, c): + while i < lenm and m[i] != c: i += 1 - result.append(struct.pack(">lll", start, end, len(l)) + l) - i += 1 - return result + return i + lenm = len(m) + if not hi: + hi = lenm + while lo < hi: + mid = (lo + hi) // 2 + start = mid + while start > 0 and m[start-1] != '\n': + start -= 1 + end = advance(start, '\0') + if m[start:end] < s: + # we know that after the null there are 40 bytes of sha1 + # this translates to the bisect lo = mid + 1 + lo = advance(end + 40, '\n') + 1 + else: + # this translates to the bisect hi = mid + hi = start + end = advance(lo, '\0') + found = m[lo:end] + if cmp(s, found) == 0: + # we know that after the null there are 40 bytes of sha1 + end = advance(end + 40, '\n') + return (lo, end+1) + else: + return (lo, lo) # apply the changes collected during the bisect loop to our addlist - def addlistdelta(addlist, delta): - # apply the deltas to the addlist. start from the bottom up + # return a delta suitable for addrevision + def addlistdelta(addlist, x): + # start from the bottom up # so changes to the offsets don't mess things up. - i = len(delta) + i = len(x) while i > 0: i -= 1 - start = delta[i][0] - end = delta[i][1] - if delta[i][4]: - addlist[start:end] = [delta[i][4]] + start = x[i][0] + end = x[i][1] + if x[i][2]: + addlist[start:end] = array.array('c', x[i][2]) else: del addlist[start:end] - return addlist - - # calculate the byte offset of the start of each line in the - # manifest - def calcoffsets(addlist): - offsets = [0] * (len(addlist) + 1) - offset = 0 - i = 0 - while i < len(addlist): - offsets[i] = offset - offset += len(addlist[i]) - i += 1 - offsets[i] = offset - return offsets + return "".join([struct.pack(">lll", d[0], d[1], len(d[2])) + d[2] \ + for d in x ]) # if we're using the listcache, make sure it is valid and # parented by the same node we're diffing against @@ -98,15 +108,13 @@ files = map.keys() files.sort() - self.addlist = ["%s\000%s%s\n" % + text = ["%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '') for f in files] + self.listcache = array.array('c', "".join(text)) cachedelta = None else: - addlist = self.listcache[1] - - # find the starting offset for each line in the add list - offsets = calcoffsets(addlist) + addlist = self.listcache # combine the changed lists into one list for sorting work = [[x, 0] for x in changed[0]] @@ -114,45 +122,52 @@ work.sort() delta = [] - bs = 0 + dstart = None + dend = None + dline = [""] + start = 0 + # zero copy representation of addlist as a buffer + addbuf = buffer(addlist) + # start with a readonly loop that finds the offset of + # each line and creates the deltas for w in work: f = w[0] # bs will either be the index of the item or the insert point - bs = bisect.bisect(addlist, f, bs) - if bs < len(addlist): - fn = addlist[bs][:addlist[bs].index('\0')] - else: - fn = None + start, end = manifestsearch(addbuf, f, start) if w[1] == 0: l = "%s\000%s%s\n" % (f, hex(map[f]), flags[f] and "x" or '') else: - l = None - start = bs - if fn != f: - # item not found, insert a new one - end = bs - if w[1] == 1: - raise AssertionError( + l = "" + if start == end and w[1] == 1: + # item we want to delete was not found, error out + raise AssertionError( _("failed to remove %s from manifest\n") % f) + if dstart != None and dstart <= start and dend >= start: + if dend < end: + dend = end + if l: + dline.append(l) else: - # item is found, replace/delete the existing line - end = bs + 1 - delta.append([start, end, offsets[start], offsets[end], l]) + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + dstart = start + dend = end + dline = [l] - self.addlist = addlistdelta(addlist, delta) - if self.mapcache[0] == self.tip(): - cachedelta = "".join(gendelta(delta)) - else: - cachedelta = None + if dstart != None: + delta.append([dstart, dend, "".join(dline)]) + # apply the delta to the addlist, and get a delta for addrevision + cachedelta = addlistdelta(addlist, delta) - text = "".join(self.addlist) - if cachedelta and mdiff.patch(self.listcache[0], cachedelta) != text: - raise AssertionError(_("manifest delta failure\n")) - n = self.addrevision(text, transaction, link, p1, p2, cachedelta) + # the delta is only valid if we've been processing the tip revision + if self.mapcache[0] != self.tip(): + cachedelta = None + self.listcache = addlist + + n = self.addrevision(buffer(self.listcache), transaction, link, p1, \ + p2, cachedelta) self.mapcache = (n, map, flags) - self.listcache = (text, self.addlist) - self.addlist = None return n diff -r 32a4e6802864 -r b3e94785ab69 mercurial/mdiff.py --- a/mercurial/mdiff.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/mdiff.py Sun Dec 11 15:38:42 2005 -0800 @@ -32,8 +32,8 @@ l = list(difflib.unified_diff(a, b, "a/" + fn, "b/" + fn)) if not l: return "" # difflib uses a space, rather than a tab - l[0] = l[0][:-2] + "\t" + ad + "\n" - l[1] = l[1][:-2] + "\t" + bd + "\n" + l[0] = "%s\t%s\n" % (l[0][:-2], ad) + l[1] = "%s\t%s\n" % (l[1][:-2], bd) for ln in xrange(len(l)): if l[ln][-1] != '\n': diff -r 32a4e6802864 -r b3e94785ab69 mercurial/node.py --- a/mercurial/node.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/node.py Sun Dec 11 15:38:42 2005 -0800 @@ -7,7 +7,7 @@ of the GNU General Public License, incorporated herein by reference. """ -import sha, binascii +import binascii nullid = "\0" * 20 diff -r 32a4e6802864 -r b3e94785ab69 mercurial/remoterepo.py --- a/mercurial/remoterepo.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/remoterepo.py Sun Dec 11 15:38:42 2005 -0800 @@ -5,11 +5,11 @@ # This software may be used and distributed according to the terms # of the GNU General Public License, incorporated herein by reference. -class remoterepository: +class remoterepository(object): def local(self): return False -class remotelock: +class remotelock(object): def __init__(self, repo): self.repo = repo def release(self): diff -r 32a4e6802864 -r b3e94785ab69 mercurial/revlog.py --- a/mercurial/revlog.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/revlog.py Sun Dec 11 15:38:42 2005 -0800 @@ -31,15 +31,15 @@ def compress(text): """ generate a possibly-compressed representation of text """ - if not text: return text + if not text: return ("", text) if len(text) < 44: - if text[0] == '\0': return text - return 'u' + text + if text[0] == '\0': return ("", text) + return ('u', text) bin = zlib.compress(text) if len(bin) > len(text): - if text[0] == '\0': return text - return 'u' + text - return bin + if text[0] == '\0': return ("", text) + return ('u', text) + return ("", bin) def decompress(bin): """ decompress the given input """ @@ -52,7 +52,7 @@ indexformat = ">4l20s20s20s" -class lazyparser: +class lazyparser(object): """ this class avoids the need to parse the entirety of large indices @@ -71,6 +71,9 @@ self.all = 0 self.revlog = revlog + def trunc(self, pos): + self.l = pos/self.s + def load(self, pos=None): if self.all: return if pos is not None: @@ -91,7 +94,7 @@ self.map[e[6]] = i i += 1 -class lazyindex: +class lazyindex(object): """a lazy version of the index array""" def __init__(self, parser): self.p = parser @@ -104,10 +107,14 @@ return self.p.index[pos] def __getitem__(self, pos): return self.p.index[pos] or self.load(pos) + def __delitem__(self, pos): + del self.p.index[pos] def append(self, e): self.p.index.append(e) + def trunc(self, pos): + self.p.trunc(pos) -class lazymap: +class lazymap(object): """a lazy version of the node map""" def __init__(self, parser): self.p = parser @@ -140,10 +147,12 @@ raise KeyError("node " + hex(key)) def __setitem__(self, key, val): self.p.map[key] = val + def __delitem__(self, key): + del self.p.map[key] class RevlogError(Exception): pass -class revlog: +class revlog(object): """ the underlying revision storage object @@ -400,25 +409,28 @@ assert heads return (orderedout, roots, heads) - def heads(self, stop=None): - """return the list of all nodes that have no children""" - p = {} - h = [] - stoprev = 0 - if stop and stop in self.nodemap: - stoprev = self.rev(stop) + def heads(self, start=None): + """return the list of all nodes that have no children + + if start is specified, only heads that are descendants of + start will be returned - for r in range(self.count() - 1, -1, -1): + """ + if start is None: + start = nullid + reachable = {start: 1} + heads = {start: 1} + startrev = self.rev(start) + + for r in xrange(startrev + 1, self.count()): n = self.node(r) - if n not in p: - h.append(n) - if n == stop: - break - if r < stoprev: - break for pn in self.parents(n): - p[pn] = 1 - return h + if pn in reachable: + reachable[n] = 1 + heads[n] = 1 + if pn in heads: + del heads[pn] + return heads.keys() def children(self, node): """find the children of a given node""" @@ -543,14 +555,16 @@ end = self.end(t) if not d: prev = self.revision(self.tip()) - d = self.diff(prev, text) + d = self.diff(prev, str(text)) data = compress(d) - dist = end - start + len(data) + l = len(data[1]) + len(data[0]) + dist = end - start + l # full versions are inserted when the needed deltas # become comparable to the uncompressed text if not n or dist > len(text) * 2: data = compress(text) + l = len(data[1]) + len(data[0]) base = n else: base = self.base(t) @@ -559,14 +573,17 @@ if t >= 0: offset = self.end(t) - e = (offset, len(data), base, link, p1, p2, node) + e = (offset, l, base, link, p1, p2, node) self.index.append(e) self.nodemap[node] = n entry = struct.pack(indexformat, *e) transaction.add(self.datafile, e[0]) - self.opener(self.datafile, "a").write(data) + f = self.opener(self.datafile, "a") + if data[0]: + f.write(data[0]) + f.write(data[1]) transaction.add(self.indexfile, n * len(entry)) self.opener(self.indexfile, "a").write(entry) @@ -784,6 +801,10 @@ continue delta = chunk[80:] + for p in (p1, p2): + if not p in self.nodemap: + raise RevlogError(_("unknown parent %s") % short(p1)) + if not chain: # retrieve the parent revision of the delta chain chain = p1 @@ -797,7 +818,8 @@ # current size. if chain == prev: - cdelta = compress(delta) + tempd = compress(delta) + cdelta = tempd[0] + tempd[1] if chain != prev or (end - start + len(cdelta)) > measure * 2: # flush our writes here so we can read it in revision @@ -824,6 +846,36 @@ ifh.close() return node + def strip(self, rev, minlink): + if self.count() == 0 or rev >= self.count(): + return + + # When stripping away a revision, we need to make sure it + # does not actually belong to an older changeset. + # The minlink parameter defines the oldest revision + # we're allowed to strip away. + while minlink > self.index[rev][3]: + rev += 1 + if rev >= self.count(): + return + + # first truncate the files on disk + end = self.start(rev) + self.opener(self.datafile, "a").truncate(end) + end = rev * struct.calcsize(indexformat) + self.opener(self.indexfile, "a").truncate(end) + + # then reset internal state in memory to forget those revisions + self.cache = None + for p in self.index[rev:]: + del self.nodemap[p[6]] + del self.index[rev:] + + # truncating the lazyindex also truncates the lazymap. + if isinstance(self.index, lazyindex): + self.index.trunc(end) + + def checksize(self): expected = 0 if self.count(): diff -r 32a4e6802864 -r b3e94785ab69 mercurial/transaction.py --- a/mercurial/transaction.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/transaction.py Sun Dec 11 15:38:42 2005 -0800 @@ -12,10 +12,9 @@ # of the GNU General Public License, incorporated herein by reference. import os -import util from i18n import gettext as _ -class transaction: +class transaction(object): def __init__(self, report, opener, journal, after=None): self.journal = None diff -r 32a4e6802864 -r b3e94785ab69 mercurial/ui.py --- a/mercurial/ui.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/ui.py Sun Dec 11 15:38:42 2005 -0800 @@ -10,7 +10,7 @@ from demandload import * demandload(globals(), "re socket sys util") -class ui: +class ui(object): def __init__(self, verbose=False, debug=False, quiet=False, interactive=True): self.overlay = {} diff -r 32a4e6802864 -r b3e94785ab69 mercurial/util.py --- a/mercurial/util.py Fri Nov 04 11:51:01 2005 -0800 +++ b/mercurial/util.py Sun Dec 11 15:38:42 2005 -0800 @@ -106,6 +106,13 @@ def always(fn): return True def never(fn): return False +def patkind(name, dflt_pat='glob'): + """Split a string into an optional pattern kind prefix and the + actual pattern.""" + for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': + if name.startswith(prefix + ':'): return name.split(':', 1) + return dflt_pat, name + def globre(pat, head='^', tail='$'): "convert a glob pattern into a regexp" i, n = 0, len(pat) @@ -158,15 +165,20 @@ this returns a path in the form used by the local filesystem, not hg.''' if not n1: return localpath(n2) a, b = n1.split('/'), n2.split('/') - a.reverse(), b.reverse() + a.reverse() + b.reverse() while a and b and a[-1] == b[-1]: - a.pop(), b.pop() + a.pop() + b.pop() b.reverse() return os.sep.join((['..'] * len(a)) + b) def canonpath(root, cwd, myname): """return the canonical path of myname, given cwd and root""" - rootsep = root + os.sep + if root == os.sep: + rootsep = os.sep + else: + rootsep = root + os.sep name = myname if not name.startswith(os.sep): name = os.path.join(root, cwd, name) @@ -218,11 +230,6 @@ make head regex a rooted bool """ - def patkind(name, dflt_pat='glob'): - for prefix in 're', 'glob', 'path', 'relglob', 'relpath', 'relre': - if name.startswith(prefix + ':'): return name.split(':', 1) - return dflt_pat, name - def contains_glob(name): for c in name: if c in _globchars: return True @@ -253,7 +260,7 @@ try: pat = '(?:%s)' % regex(k, p, tail) matches.append(re.compile(pat).match) - except re.error, inst: + except re.error: raise Abort("invalid pattern: %s:%s" % (k, p)) def buildfn(text): @@ -362,7 +369,36 @@ remote file access from higher level code. """ p = base - def o(path, mode="r", text=False): + + def mktempcopy(name): + d, fn = os.path.split(name) + fd, temp = tempfile.mkstemp(prefix=fn, dir=d) + fp = os.fdopen(fd, "wb") + try: + fp.write(file(name, "rb").read()) + except: + try: os.unlink(temp) + except: pass + raise + fp.close() + st = os.lstat(name) + os.chmod(temp, st.st_mode) + return temp + + class atomicfile(file): + """the file will only be copied on close""" + def __init__(self, name, mode, atomic=False): + self.__name = name + self.temp = mktempcopy(name) + file.__init__(self, self.temp, mode) + def close(self): + if not self.closed: + file.close(self) + rename(self.temp, self.__name) + def __del__(self): + self.close() + + def o(path, mode="r", text=False, atomic=False): f = os.path.join(p, path) if not text: @@ -376,19 +412,10 @@ if not os.path.isdir(d): os.makedirs(d) else: + if atomic: + return atomicfile(f, mode) if nlink > 1: - d, fn = os.path.split(f) - fd, temp = tempfile.mkstemp(prefix=fn, dir=d) - fp = os.fdopen(fd, "wb") - try: - fp.write(file(f, "rb").read()) - except: - try: os.unlink(temp) - except: pass - raise - fp.close() - rename(temp, f) - + rename(mktempcopy(f), f) return file(f, mode) return o @@ -484,6 +511,7 @@ nulldev = '/dev/null' def rcfiles(path): + print 'checking', path rcs = [os.path.join(path, 'hgrc')] rcdir = os.path.join(path, 'hgrc.d') try: diff -r 32a4e6802864 -r b3e94785ab69 setup.py --- a/setup.py Fri Nov 04 11:51:01 2005 -0800 +++ b/setup.py Sun Dec 11 15:38:42 2005 -0800 @@ -72,8 +72,10 @@ try: 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(), author='Matt Mackall', @@ -90,6 +92,6 @@ glob.glob('templates/*.tmpl'))], cmdclass=cmdclass, scripts=['hg', 'hgmerge'], - console = ['hg']) + **py2exe_opts) finally: mercurial.version.forget_version() diff -r 32a4e6802864 -r b3e94785ab69 templates/changelogentry-rss.tmpl --- a/templates/changelogentry-rss.tmpl Fri Nov 04 11:51:01 2005 -0800 +++ b/templates/changelogentry-rss.tmpl Sun Dec 11 15:38:42 2005 -0800 @@ -1,5 +1,5 @@ - #desc|strip|firstline|rstrip|escape# + #desc|strip|firstline|strip|escape# #url#?cs=#node|short# #author|obfuscate# diff -r 32a4e6802864 -r b3e94785ab69 templates/error.tmpl --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/templates/error.tmpl Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,15 @@ +#header# +Mercurial Error + + + +

Mercurial Error

+ +

+An error occured while processing your request: +

+

+#error|escape# +

+ +#footer# diff -r 32a4e6802864 -r b3e94785ab69 templates/filelogentry-rss.tmpl --- a/templates/filelogentry-rss.tmpl Fri Nov 04 11:51:01 2005 -0800 +++ b/templates/filelogentry-rss.tmpl Sun Dec 11 15:38:42 2005 -0800 @@ -1,5 +1,5 @@ - #desc|strip|firstline|rstrip|escape# + #desc|strip|firstline|strip|escape# #url#?f=#filenode|short#;file=#file# #author|obfuscate# diff -r 32a4e6802864 -r b3e94785ab69 templates/map --- a/templates/map Fri Nov 04 11:51:01 2005 -0800 +++ b/templates/map Sun Dec 11 15:38:42 2005 -0800 @@ -39,3 +39,4 @@ index = index.tmpl archiveentry = "#type# " notfound = notfound.tmpl +error = error.tmpl diff -r 32a4e6802864 -r b3e94785ab69 templates/notfound.tmpl --- a/templates/notfound.tmpl Fri Nov 04 11:51:01 2005 -0800 +++ b/templates/notfound.tmpl Sun Dec 11 15:38:42 2005 -0800 @@ -5,7 +5,7 @@

Mercurial Repositories

-The specified repository "#repo#" is unknown, sorry. +The specified repository "#repo|escape#" is unknown, sorry. Please go back to the main repository list page. diff -r 32a4e6802864 -r b3e94785ab69 templates/tags.tmpl --- a/templates/tags.tmpl Fri Nov 04 11:51:01 2005 -0800 +++ b/templates/tags.tmpl Sun Dec 11 15:38:42 2005 -0800 @@ -1,5 +1,5 @@ #header# -#repo#: tags +#repo|escape#: tags diff -r 32a4e6802864 -r b3e94785ab69 tests/run-tests --- a/tests/run-tests Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/run-tests Sun Dec 11 15:38:42 2005 -0800 @@ -40,16 +40,11 @@ } TESTDIR="$PWD" - -if [ -d /usr/lib64 ]; then - lib=lib64 -else - lib=lib -fi - INST="$HGTMP/install" +PYTHONDIR="$INST/lib/python" cd .. -if ${PYTHON-python} setup.py install --home="$INST" > tests/install.err 2>&1 +if ${PYTHON-python} setup.py install --home="$INST" \ + --install-lib="$PYTHONDIR" > tests/install.err 2>&1 then rm tests/install.err else @@ -59,8 +54,7 @@ cd "$TESTDIR" PATH="$INST/bin:$PATH"; export PATH -PYTHONPATH="$INST/$lib/python"; export PYTHONPATH - +PYTHONPATH="$PYTHONDIR"; export PYTHONPATH run_one() { rm -f "$1.err" diff -r 32a4e6802864 -r b3e94785ab69 tests/test-cat --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-cat Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,18 @@ +#!/bin/sh +# +mkdir t +cd t +hg init +echo 0 > a +echo 0 > b +hg ci -A -m m -d "0 0" +hg rm a +hg cat a +sleep 1 # make sure mtime is changed +echo 1 > b +hg ci -m m -d "0 0" +echo 2 > b +hg cat -r 0 a +hg cat -r 0 b +hg cat -r 1 a +hg cat -r 1 b diff -r 32a4e6802864 -r b3e94785ab69 tests/test-cat.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-cat.out Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,7 @@ +adding a +adding b +0 +0 +0 +a: No such file in rev 551e7cb14b32 +1 diff -r 32a4e6802864 -r b3e94785ab69 tests/test-grep --- a/tests/test-grep Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-grep Sun Dec 11 15:38:42 2005 -0800 @@ -14,7 +14,7 @@ hg commit -m 2 -u spam -d '2 0' echo 'import/export' >> port hg commit -m 3 -u eggs -d '3 0' -head -3 port > port1 +head -n 3 port > port1 mv port1 port hg commit -m 4 -u spam -d '4 0' hg grep port port diff -r 32a4e6802864 -r b3e94785ab69 tests/test-help.out --- a/tests/test-help.out Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-help.out Sun Dec 11 15:38:42 2005 -0800 @@ -6,7 +6,7 @@ annotate show changeset information per file line clone make a copy of an existing repository commit commit the specified files or all outstanding changes - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets init create a new repository in the given directory log show revision history of entire repository or files @@ -22,7 +22,7 @@ annotate show changeset information per file line clone make a copy of an existing repository commit commit the specified files or all outstanding changes - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets init create a new repository in the given directory log show revision history of entire repository or files @@ -46,7 +46,7 @@ clone make a copy of an existing repository commit commit the specified files or all outstanding changes copy mark files as copied for the next commit - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget don't add the specified files on the next commit grep search for a pattern in specified files and revisions @@ -88,7 +88,7 @@ clone make a copy of an existing repository commit commit the specified files or all outstanding changes copy mark files as copied for the next commit - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets forget don't add the specified files on the next commit grep search for a pattern in specified files and revisions @@ -130,8 +130,7 @@ The files will be added to the repository at the next commit. - If no names are given, add all files in the current directory and - its subdirectories. + If no names are given, add all files in the repository. options: @@ -146,8 +145,7 @@ The files will be added to the repository at the next commit. - If no names are given, add all files in the current directory and - its subdirectories. + If no names are given, add all files in the repository. options: @@ -155,7 +153,7 @@ -X --exclude exclude names matching the given patterns hg diff [-a] [-I] [-X] [-r REV1 [-r REV2]] [FILE]... -diff working directory (or selected files) +diff repository (or selected files) Show differences between revisions for the specified files. @@ -181,9 +179,8 @@ show changed files in the working directory - Show changed files in the working directory. If no names are - given, all files are shown. Otherwise, only files matching the - given names are shown. + Show changed files in the repository. If names are + given, only files that match are shown. The codes used to show the status of files are: M = modified @@ -191,6 +188,8 @@ R = removed ? = not tracked +aliases: st + options: -m --modified show only modified files @@ -213,7 +212,7 @@ annotate show changeset information per file line clone make a copy of an existing repository commit commit the specified files or all outstanding changes - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets init create a new repository in the given directory log show revision history of entire repository or files @@ -234,7 +233,7 @@ annotate show changeset information per file line clone make a copy of an existing repository commit commit the specified files or all outstanding changes - diff diff working directory (or selected files) + diff diff repository (or selected files) export dump the header and diffs for one or more changesets init create a new repository in the given directory log show revision history of entire repository or files diff -r 32a4e6802864 -r b3e94785ab69 tests/test-hgignore --- a/tests/test-hgignore Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-hgignore Sun Dec 11 15:38:42 2005 -0800 @@ -42,4 +42,4 @@ echo "--" ; hg status cd dir -echo "--" ; hg status +echo "--" ; hg status . diff -r 32a4e6802864 -r b3e94785ab69 tests/test-rename --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rename Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,136 @@ +#!/bin/sh + +hg init +mkdir d1 d1/d11 d2 +echo d1/a > d1/a +echo d1/ba > d1/ba +echo d1/a1 > d1/d11/a1 +echo d1/b > d1/b +echo d2/b > d2/b +hg add d1/a d1/b d1/ba d1/d11/a1 d2/b +hg commit -m "1" -d "0 0" + +echo "# rename a single file" +hg rename d1/d11/a1 d2/c +hg status +hg update -C + +echo "# rename --after a single file" +mv d1/d11/a1 d2/c +hg rename --after d1/d11/a1 d2/c +hg status +hg update -C + +echo "# move a single file to an existing directory" +hg rename d1/d11/a1 d2 +hg status +hg update -C + +echo "# move --after a single file to an existing directory" +mv d1/d11/a1 d2 +hg rename --after d1/d11/a1 d2 +hg status +hg update -C + +echo "# rename a file using a relative path" +(cd d1/d11; hg rename ../../d2/b e) +hg status +hg update -C + +echo "# rename --after a file using a relative path" +(cd d1/d11; mv ../../d2/b e; hg rename --after ../../d2/b e) +hg status +hg update -C + +echo "# rename directory d1 as d3" +hg rename d1/ d3 +hg status +hg update -C + +echo "# rename --after directory d1 as d3" +mv d1 d3 +hg rename --after d1 d3 +hg status +hg update -C + +echo "# move a directory using a relative path" +(cd d2; mkdir d3; hg rename ../d1/d11 d3) +hg status +hg update -C + +echo "# move --after a directory using a relative path" +(cd d2; mkdir d3; mv ../d1/d11 d3; hg rename --after ../d1/d11 d3) +hg status +hg update -C + +echo "# move directory d1/d11 to an existing directory d2 (removes empty d1)" +hg rename d1/d11/ d2 +hg status +hg update -C + +echo "# move directories d1 and d2 to a new directory d3" +mkdir d3 +hg rename d1 d2 d3 +hg status +hg update -C + +echo "# move --after directories d1 and d2 to a new directory d3" +mkdir d3 +mv d1 d2 d3 +hg rename --after d1 d2 d3 +hg status +hg update -C + +echo "# move everything under directory d1 to existing directory d2, do not" +echo "# overwrite existing files (d2/b)" +hg rename d1/* d2 +hg status +diff d1/b d2/b +hg update -C + +echo "# attempt to move potentially more than one file into a non-existent" +echo "# directory" +hg rename 'glob:d1/**' dx + +echo "# move every file under d1 to d2/d21 (glob)" +mkdir d2/d21 +hg rename 'glob:d1/**' d2/d21 +hg status +hg update -C + +echo "# move --after some files under d1 to d2/d21 (glob)" +mkdir d2/d21 +mv d1/a d1/d11/a1 d2/d21 +hg rename --after 'glob:d1/**' d2/d21 +hg status +hg update -C + +echo "# move every file under d1 starting with an 'a' to d2/d21 (regexp)" +mkdir d2/d21 +hg rename 're:d1/([^a][^/]*/)*a.*' d2/d21 +hg status +hg update -C + +echo "# attempt to overwrite an existing file" +echo "ca" > d1/ca +hg rename d1/ba d1/ca +hg status +hg update -C + +echo "# forced overwrite of an existing file" +echo "ca" > d1/ca +hg rename --force d1/ba d1/ca +hg status +hg update -C + +echo "# replace a symlink with a file" +ln -s ba d1/ca +hg rename --force d1/ba d1/ca +hg status +hg update -C + +echo "# do not copy more than one source file to the same destination file" +mkdir d3 +hg rename d1/* d2/* d3 +hg status +hg update -C diff -r 32a4e6802864 -r b3e94785ab69 tests/test-rename.out --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/tests/test-rename.out Sun Dec 11 15:38:42 2005 -0800 @@ -0,0 +1,183 @@ +# rename a single file +A d2/c +R d1/d11/a1 +# rename --after a single file +A d2/c +R d1/d11/a1 +# move a single file to an existing directory +A d2/a1 +R d1/d11/a1 +# move --after a single file to an existing directory +A d2/a1 +R d1/d11/a1 +# rename a file using a relative path +A d1/d11/e +R d2/b +# rename --after a file using a relative path +A d1/d11/e +R d2/b +# rename directory d1 as d3 +copying d1/a to d3/a +copying d1/b to d3/b +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +# rename --after directory d1 as d3 +copying d1/a to d3/a +copying d1/b to d3/b +copying d1/ba to d3/ba +copying d1/d11/a1 to d3/d11/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +# move a directory using a relative path +copying ../d1/d11/a1 to d3/d11/a1 +removing ../d1/d11/a1 +A d2/d3/d11/a1 +R d1/d11/a1 +# move --after a directory using a relative path +copying ../d1/d11/a1 to d3/d11/a1 +removing ../d1/d11/a1 +A d2/d3/d11/a1 +R d1/d11/a1 +# move directory d1/d11 to an existing directory d2 (removes empty d1) +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/d11/a1 +R d1/d11/a1 +# move directories d1 and d2 to a new directory d3 +copying d1/a to d3/d1/a +copying d1/b to d3/d1/b +copying d1/ba to d3/d1/ba +copying d1/d11/a1 to d3/d1/d11/a1 +copying d2/b to d3/d2/b +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +removing d2/b +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +A d3/d2/b +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +R d2/b +# move --after directories d1 and d2 to a new directory d3 +copying d1/a to d3/d1/a +copying d1/b to d3/d1/b +copying d1/ba to d3/d1/ba +copying d1/d11/a1 to d3/d1/d11/a1 +copying d2/b to d3/d2/b +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +removing d2/b +A d3/d1/a +A d3/d1/b +A d3/d1/ba +A d3/d1/d11/a1 +A d3/d2/b +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +R d2/b +# move everything under directory d1 to existing directory d2, do not +# overwrite existing files (d2/b) +d2/b: not overwriting - file exists +copying d1/d11/a1 to d2/d11/a1 +removing d1/d11/a1 +A d2/a +A d2/ba +A d2/d11/a1 +R d1/a +R d1/ba +R d1/d11/a1 +1c1 +< d1/b +--- +> d2/b +# attempt to move potentially more than one file into a non-existent +# directory +abort: with multiple sources, destination must be an existing directory +# move every file under d1 to d2/d21 (glob) +copying d1/a to d2/d21/a +copying d1/b to d2/d21/b +copying d1/ba to d2/d21/ba +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/b +removing d1/ba +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +A d2/d21/b +A d2/d21/ba +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 +# move --after some files under d1 to d2/d21 (glob) +copying d1/a to d2/d21/a +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +R d1/a +R d1/d11/a1 +# move every file under d1 starting with an 'a' to d2/d21 (regexp) +copying d1/a to d2/d21/a +copying d1/d11/a1 to d2/d21/a1 +removing d1/a +removing d1/d11/a1 +A d2/d21/a +A d2/d21/a1 +R d1/a +R d1/d11/a1 +# attempt to overwrite an existing file +d1/ca: not overwriting - file exists +? d1/ca +# forced overwrite of an existing file +A d1/ca +R d1/ba +# replace a symlink with a file +A d1/ca +R d1/ba +# do not copy more than one source file to the same destination file +copying d1/d11/a1 to d3/d11/a1 +d3/b: not overwriting - d2/b collides with d1/b +removing d1/d11/a1 +A d3/a +A d3/b +A d3/ba +A d3/d11/a1 +R d1/a +R d1/b +R d1/ba +R d1/d11/a1 diff -r 32a4e6802864 -r b3e94785ab69 tests/test-symlinks --- a/tests/test-symlinks Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-symlinks Sun Dec 11 15:38:42 2005 -0800 @@ -39,3 +39,4 @@ mkfifo a.c # it should show a.c, dir/a.o and dir/b.o removed hg status +hg status a.c diff -r 32a4e6802864 -r b3e94785ab69 tests/test-symlinks.out --- a/tests/test-symlinks.out Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-symlinks.out Sun Dec 11 15:38:42 2005 -0800 @@ -1,15 +1,11 @@ -bar: unsupported file type (type is symbolic link) adding foo -bar: unsupported file type (type is symbolic link) -bar: unsupported file type (type is symbolic link) adding bomb -bar: unsupported file type (type is symbolic link) adding a.c adding dir/a.o adding dir/b.o -a.c: unsupported file type (type is fifo) -dir/b.o: unsupported file type (type is symbolic link) R a.c R dir/a.o R dir/b.o ? .hgignore +a.c: unsupported file type (type is fifo) +R a.c diff -r 32a4e6802864 -r b3e94785ab69 tests/test-tag --- a/tests/test-tag Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-tag Sun Dec 11 15:38:42 2005 -0800 @@ -11,3 +11,7 @@ echo foo >> .hgtags hg tag -d "0 0" "bleah2" || echo "failed" +hg tag -l 'xx +newline' +hg tag -l 'xx:xx' +true diff -r 32a4e6802864 -r b3e94785ab69 tests/test-tag.out --- a/tests/test-tag.out Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-tag.out Sun Dec 11 15:38:42 2005 -0800 @@ -18,3 +18,5 @@ abort: working copy of .hgtags is changed (please commit .hgtags manually) failed +abort: '\n' cannot be used in a tag name +abort: ':' cannot be used in a tag name diff -r 32a4e6802864 -r b3e94785ab69 tests/test-walk --- a/tests/test-walk Fri Nov 04 11:51:01 2005 -0800 +++ b/tests/test-walk Sun Dec 11 15:38:42 2005 -0800 @@ -20,14 +20,14 @@ hg commit -m "commit #0" -d "0 0" hg debugwalk cd mammals -hg debugwalk +hg debugwalk . hg debugwalk Procyonidae cd Procyonidae -hg debugwalk +hg debugwalk . hg debugwalk .. cd .. hg debugwalk ../beans -hg debugwalk +hg debugwalk . cd .. hg debugwalk -Ibeans hg debugwalk 'glob:mammals/../beans/b*'