# HG changeset patch # User mpm@selenic.com # Date 1121907629 18000 # Node ID d2422f10c136ca37bdddc19e519189a4036dd6e6 # Parent e5b39ce2c3c98accc12b5de060bce234b3c6f0c8# Parent 36edb39e8e8cc8034d242f31784be727b4322eeb Merge from BOS manifest hash: 2276dbd96bb4221e579c871a1de2403c92c85659 diff -r e5b39ce2c3c9 -r d2422f10c136 .hgignore diff -r e5b39ce2c3c9 -r d2422f10c136 MANIFEST.in diff -r e5b39ce2c3c9 -r d2422f10c136 contrib/hgit diff -r e5b39ce2c3c9 -r d2422f10c136 doc/FAQ.txt diff -r e5b39ce2c3c9 -r d2422f10c136 doc/hg.1.txt --- a/doc/hg.1.txt Wed Jul 20 11:40:27 2005 -0500 +++ b/doc/hg.1.txt Wed Jul 20 20:00:29 2005 -0500 @@ -33,7 +33,8 @@ ---------------- files ...:: - indicates one or more filename or relative path filenames + indicates one or more filename or relative path filenames; see + "FILE NAME PATTERNS" for information on pattern matching path:: indicates a path on the local machine @@ -51,11 +52,14 @@ COMMANDS -------- -add [files ...]:: +add [options] [files ...]:: Schedule files to be version controlled and added to the repository. 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. + addremove:: Add all new files and remove all missing files from the repository. @@ -69,6 +73,8 @@ place. options: + -I, --include include directories matching the given patterns + -X, --exclude exclude directories matching the given patterns -r, --revision annotate the specified revision -u, --user list the author -c, --changeset list the changeset @@ -129,6 +135,10 @@ revisions are specified, the working directory files are compared to its parent. + options: + -I, --include include directories matching the given patterns + -X, --exclude exclude directories matching the given patterns + export [-o filespec] [revision] ...:: Print the changeset header and diffs for one or more revisions. @@ -183,14 +193,10 @@ init:: Initialize a new repository in the current directory. -locate [options] [patterns]:: - Print all files under Mercurial control whose basenames match the +locate [options] [files]:: + Print all files under Mercurial control whose names match the given patterns. - Patterns are shell-style globs. To restrict searches to specific - directories, use the "-i " option. To eliminate particular - directories from searching, use the "-x " option. - This command searches the current directory and its subdirectories. To search an entire repository, move to the root of the repository. @@ -207,9 +213,9 @@ -0, --print0 end filenames with NUL, for use with xargs -f, --fullpath print complete paths from the filesystem root - -i, --include include directories matching the given globs + -I, --include include directories matching the given patterns -r, --rev search the repository as it stood at rev - -x, --exclude exclude directories matching the given globs + -X, --exclude exclude directories matching the given patterns log [-r revision ...] [-p] [file]:: Print the revision history of the specified file or the entire project. @@ -319,8 +325,10 @@ -n, --name name to show in web pages (default: working dir) -t, --templatedir web templates to use -status:: - Show changed files in the working directory. +status [options] [files]:: + 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. The codes used to show the status of files are: @@ -329,6 +337,11 @@ R = removed ? = not tracked + options: + + -I, --include include directories matching the given patterns + -X, --exclude exclude directories matching the given patterns + tag [-l -t -d -u ] [revision]:: Name a particular revision using . @@ -398,6 +411,52 @@ the changelog, manifest, and tracked files, as well as the integrity of their crosslinks and indices. +FILE NAME PATTERNS +------------------ + + Mercurial accepts several notations for identifying one or more + file at a time. + + By default, Mercurial treats file names as shell-style extended + glob patterns. + + Alternate pattern notations must be specified explicitly. + + To use a plain path name without any pattern matching, start a + name with "path:". These path names must match completely, from + the root of the current repository. + + To use an extended glob, start a name with "glob:". Globs are + rooted at the current directory; a glob such as "*.c" will match + files ending in ".c" in the current directory only. + + The supported glob syntax extensions are "**" to match any string + across path separators, and "{a,b}" to mean "a or b". + + To use a Perl/Python regular expression, start a name with "re:". + Regexp pattern matching is anchored at the root of the repository. + + Plain examples: + + path:foo/bar a name bar in a directory named foo in the root of + the repository + path:path:name a file or directory named "path:name" + + Glob examples: + + glob:*.c any name ending in ".c" in the current directory + *.c any name ending in ".c" in the current directory + **.c any name ending in ".c" in the current directory, or + any subdirectory + foo/*.c any name ending in ".c" in the directory foo + foo/**.c any name ending in ".c" in the directory foo, or any + subdirectory + + Regexp examples: + + re:.*\.c$ any name ending in ".c", anywhere in the repsitory + + SPECIFYING SINGLE REVISIONS --------------------------- diff -r e5b39ce2c3c9 -r d2422f10c136 mercurial/commands.py --- a/mercurial/commands.py Wed Jul 20 11:40:27 2005 -0500 +++ b/mercurial/commands.py Wed Jul 20 20:00:29 2005 -0500 @@ -14,6 +14,9 @@ class UnknownCommand(Exception): """Exception raised if command is not in the command table.""" +class Abort(Exception): + """Raised if a command needs to print an error and exit.""" + def filterfiles(filters, files): l = [x for x in files if x in filters] @@ -36,6 +39,41 @@ for x in args] return args +def matchpats(ui, cwd, pats = [], opts = {}, emptyok = True): + if not pats and not emptyok: + raise Abort('at least one file name or pattern required') + head = '' + if opts.get('rootless'): head = '(?:.*/|)' + def reify(name, tail): + if name.startswith('re:'): + return name[3:] + elif name.startswith('glob:'): + return head + util.globre(name[5:], '', tail) + elif name.startswith('path:'): + return '^' + re.escape(name[5:]) + '$' + return head + util.globre(name, '', tail) + cwdsep = cwd + os.sep + def under(fn): + if not cwd or fn.startswith(cwdsep): return True + def matchfn(pats, tail, ifempty = util.always): + if not pats: return ifempty + pat = '(?:%s)' % '|'.join([reify(p, tail) for p in pats]) + if cwd: pat = re.escape(cwd + os.sep) + pat + ui.debug('regexp: %s\n' % pat) + return re.compile(pat).match + patmatch = matchfn(pats, '$') + incmatch = matchfn(opts.get('include'), '(?:/|$)', under) + excmatch = matchfn(opts.get('exclude'), '(?:/|$)', util.never) + return lambda fn: (incmatch(fn) and not excmatch(fn) and + (fn.endswith('/') or patmatch(fn))) + +def walk(repo, pats, opts, emptyok = True): + cwd = repo.getcwd() + if cwd: c = len(cwd) + 1 + for src, fn in repo.walk(match = matchpats(repo.ui, cwd, pats, opts, emptyok)): + if cwd: yield src, fn, fn[c:] + else: yield src, fn, fn + revrangesep = ':' def revrange(ui, repo, revs, revlog=None): @@ -60,8 +98,7 @@ try: num = revlog.rev(revlog.lookup(val)) except KeyError: - ui.warn('abort: invalid revision identifier %s\n' % val) - sys.exit(1) + raise Abort('invalid revision identifier %s', val) return num for spec in revs: if spec.find(revrangesep) >= 0: @@ -91,29 +128,45 @@ 'b': lambda: os.path.basename(repo.root), } - if node: - expander.update(node_expander) - if node and revwidth is not None: - expander['r'] = lambda: str(r.rev(node)).zfill(revwidth) - if total is not None: - expander['N'] = lambda: str(total) - if seqno is not None: - expander['n'] = lambda: str(seqno) - if total is not None and seqno is not None: - expander['n'] = lambda:str(seqno).zfill(len(str(total))) + try: + if node: + expander.update(node_expander) + if node and revwidth is not None: + expander['r'] = lambda: str(r.rev(node)).zfill(revwidth) + if total is not None: + expander['N'] = lambda: str(total) + if seqno is not None: + expander['n'] = lambda: str(seqno) + if total is not None and seqno is not None: + expander['n'] = lambda:str(seqno).zfill(len(str(total))) - newname = [] - patlen = len(pat) - i = 0 - while i < patlen: - c = pat[i] - if c == '%': + newname = [] + patlen = len(pat) + i = 0 + while i < patlen: + c = pat[i] + if c == '%': + i += 1 + c = pat[i] + c = expander[c]() + newname.append(c) i += 1 - c = pat[i] - c = expander[c]() - newname.append(c) - i += 1 - return ''.join(newname) + return ''.join(newname) + except KeyError, inst: + raise Abort("invalid format spec '%%%s' in output file name", + inst.args[0]) + +def make_file(repo, r, pat, node=None, + total=None, seqno=None, revwidth=None, mode='wb'): + if not pat or pat == '-': + if 'w' in mode: return sys.stdout + else: return sys.stdin + if hasattr(pat, 'write') and 'w' in mode: + return pat + if hasattr(pat, 'read') and 'r' in mode: + return pat + return open(make_filename(repo, r, pat, node, total, seqno, revwidth), + mode) def dodiff(fp, ui, repo, files=None, node1=None, node2=None): def date(c): @@ -288,9 +341,17 @@ # Commands start here, listed alphabetically -def add(ui, repo, file1, *files): +def add(ui, repo, *pats, **opts): '''add the specified files on the next commit''' - repo.add(relpath(repo, (file1,) + files)) + names = [] + q = dict(zip(pats, pats)) + for src, abs, rel in walk(repo, pats, opts): + if rel in q or abs in q: + names.append(abs) + elif repo.dirstate.state(abs) == '?': + ui.status('adding %s\n' % rel) + names.append(abs) + repo.add(names) def addremove(ui, repo, *files): """add all new files, delete all missing files""" @@ -307,11 +368,11 @@ elif s not in 'nmai' and isfile: u.append(f) else: - (c, a, d, u) = repo.changes(None, None) + (c, a, d, u) = repo.changes() repo.add(u) repo.remove(d) -def annotate(ui, repo, file1, *files, **opts): +def annotate(ui, repo, *pats, **opts): """show changeset information per file line""" def getnode(rev): return hg.short(repo.changelog.node(rev)) @@ -342,8 +403,8 @@ node = repo.dirstate.parents()[0] change = repo.changelog.read(node) mmap = repo.manifest.read(change[0]) - for f in relpath(repo, (file1,) + files): - lines = repo.file(f).annotate(mmap[f]) + for src, abs, rel in walk(repo, pats, opts, emptyok = False): + lines = repo.file(abs).annotate(mmap[abs]) pieces = [] for o, f in opmap: @@ -362,16 +423,7 @@ n = r.lookup(rev) else: n = r.tip() - if opts['output'] and opts['output'] != '-': - try: - outname = make_filename(repo, r, opts['output'], node=n) - fp = open(outname, 'wb') - except KeyError, inst: - ui.warn("error: invlaid format spec '%%%s' in output file name\n" % - inst.args[0]) - sys.exit(1); - else: - fp = sys.stdout + fp = make_file(repo, r, opts['output'], node=n) fp.write(r.read(n)) def clone(ui, source, dest=None, **opts): @@ -475,8 +527,7 @@ ui.warn("%s in manifest1, but listed as state %s" % (f, state)) errors += 1 if errors: - ui.warn(".hg/dirstate inconsistent with current parent's manifest\n") - sys.exit(1) + raise Abort(".hg/dirstate inconsistent with current parent's manifest") def debugstate(ui, repo): """show the contents of the current dirstate""" @@ -509,21 +560,18 @@ ui.write("\t%d -> %d\n" % (r.rev(e[5]), i)) ui.write("}\n") -def diff(ui, repo, *files, **opts): +def diff(ui, repo, *pats, **opts): """diff working directory (or selected files)""" revs = [] if opts['rev']: revs = map(lambda x: repo.lookup(x), opts['rev']) if len(revs) > 2: - ui.warn("too many revisions to diff\n") - sys.exit(1) + raise Abort("too many revisions to diff") - if files: - files = relpath(repo, files) - else: - files = relpath(repo, [""]) - + files = [] + for src, abs, rel in walk(repo, pats, opts): + files.append(abs) dodiff(sys.stdout, ui, repo, files, *revs) def doexport(ui, repo, changeset, seqno, total, revwidth, opts): @@ -531,19 +579,11 @@ prev, other = repo.changelog.parents(node) change = repo.changelog.read(node) - if opts['output'] and opts['output'] != '-': - try: - outname = make_filename(repo, repo.changelog, opts['output'], - node=node, total=total, seqno=seqno, - revwidth=revwidth) - ui.note("Exporting patch to '%s'.\n" % outname) - fp = open(outname, 'wb') - except KeyError, inst: - ui.warn("error: invalid format spec '%%%s' in output file name\n" % - inst.args[0]) - sys.exit(1) - else: - fp = sys.stdout + fp = make_file(repo, repo.changelog, opts['output'], + node=node, total=total, seqno=seqno, + revwidth=revwidth) + if fp != sys.stdout: + ui.note("Exporting patch to '%s'.\n" % fp.name) fp.write("# HG changeset patch\n") fp.write("# User %s\n" % change[1]) @@ -555,12 +595,12 @@ fp.write("\n\n") dodiff(fp, ui, repo, None, prev, node) + if fp != sys.stdout: fp.close() def export(ui, repo, *changesets, **opts): """dump the header and diffs for one or more changesets""" if not changesets: - ui.warn("error: export requires at least one changeset\n") - sys.exit(1) + raise Abort("export requires at least one changeset") seqno = 0 revs = list(revrange(ui, repo, changesets)) total = len(revs) @@ -586,7 +626,7 @@ return hexfunc = ui.verbose and hg.hex or hg.short - (c, a, d, u) = repo.changes(None, None) + (c, a, d, u) = repo.changes() output = ["%s%s" % ('+'.join([hexfunc(parent) for parent in parents]), (c or a or d) and "+" or "")] @@ -654,8 +694,7 @@ files.append(pf) patcherr = f.close() if patcherr: - sys.stderr.write("patch failed") - sys.exit(1) + raise Abort("patch failed") if len(files) > 0: addremove(ui, repo, *files) @@ -665,52 +704,20 @@ """create a new repository in the current directory""" if source: - ui.warn("no longer supported: use \"hg clone\" instead\n") - sys.exit(1) + raise Abort("no longer supported: use \"hg clone\" instead") hg.repository(ui, ".", create=1) def locate(ui, repo, *pats, **opts): """locate files matching specific patterns""" - if [p for p in pats if os.sep in p]: - ui.warn("error: patterns may not contain '%s'\n" % os.sep) - ui.warn("use '-i ' instead\n") - sys.exit(1) - def compile(pats, head='^', tail=os.sep, on_empty=True): - if not pats: - class c: - def match(self, x): - return on_empty - return c() - fnpats = [fnmatch.translate(os.path.normpath(os.path.normcase(p)))[:-1] - for p in pats] - regexp = r'%s(?:%s)%s' % (head, '|'.join(fnpats), tail) - return re.compile(regexp) - exclude = compile(opts['exclude'], on_empty=False) - include = compile(opts['include']) - pat = compile(pats, head='', tail='$') - end = opts['print0'] and '\0' or '\n' - if opts['rev']: - node = repo.manifest.lookup(opts['rev']) - else: - node = repo.manifest.tip() - manifest = repo.manifest.read(node) - cwd = repo.getcwd() - cwd_plus = cwd and (cwd + os.sep) - found = [] - for f in manifest: - f = os.path.normcase(f) - if exclude.match(f) or not(include.match(f) and - f.startswith(cwd_plus) and - pat.match(os.path.basename(f))): - continue + if opts['print0']: end = '\0' + else: end = '\n' + opts['rootless'] = True + for src, abs, rel in walk(repo, pats, opts): + if repo.dirstate.state(abs) == '?': continue if opts['fullpath']: - f = os.path.join(repo.root, f) - elif cwd: - f = f[len(cwd_plus):] - found.append(f) - found.sort() - for f in found: - ui.write(f, end) + ui.write(os.path.join(repo.root, abs), end) + else: + ui.write(rel, end) def log(ui, repo, f=None, **opts): """show the revision history of the repository or a single file""" @@ -746,6 +753,11 @@ dodiff(sys.stdout, ui, repo, files, prev, changenode) ui.write("\n\n") +def ls(ui, repo, *pats, **opts): + """list files""" + for src, abs, rel in walk(repo, pats, opts): + ui.write(rel, '\n') + def manifest(ui, repo, rev=None): """output the latest or given revision of the project manifest""" if rev: @@ -978,7 +990,7 @@ ui.status('listening at http://%s/\n' % addr) httpd.serve_forever() -def status(ui, repo): +def status(ui, repo, *pats, **opts): '''show changed files in the working directory C = changed @@ -986,7 +998,8 @@ R = removed ? = not tracked''' - (c, a, d, u) = repo.changes(None, None) + (c, a, d, u) = repo.changes(match = matchpats(ui, repo.getcwd(), + pats, opts)) (c, a, d, u) = map(lambda x: relfilter(repo, x), (c, a, d, u)) for f in c: @@ -1017,7 +1030,7 @@ repo.opener("localtags", "a").write("%s %s\n" % (r, name)) return - (c, a, d, u) = repo.changes(None, None) + (c, a, d, u) = repo.changes() for x in (c, a, d, u): if ".hgtags" in x: ui.warn("abort: working copy of .hgtags is changed!\n") @@ -1088,11 +1101,16 @@ # Command options and aliases are listed here, alphabetically table = { - "^add": (add, [], "hg add FILE..."), - "addremove": (addremove, [], "hg addremove [FILE]..."), + "^add": (add, + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search')], + "hg add [OPTIONS] [FILES]"), + "addremove": (addremove, [], "hg addremove [FILES]"), "^annotate": (annotate, - [('r', 'rev', '', 'revision'), + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search'), + ('r', 'rev', '', 'revision'), ('u', 'user', None, 'show user'), ('n', 'number', None, 'show revision number'), ('c', 'changeset', None, 'show changeset')], @@ -1120,7 +1138,9 @@ "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'), "^diff": (diff, - [('r', 'rev', [], 'revision')], + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search'), + ('r', 'rev', [], 'revision')], 'hg diff [-r REV1 [-r REV2]] [FILE]...'), "^export": (export, @@ -1140,15 +1160,19 @@ (locate, [('0', 'print0', None, 'end records with NUL'), ('f', 'fullpath', None, 'print complete paths'), - ('i', 'include', [], 'include path in search'), + ('I', 'include', [], 'include path in search'), ('r', 'rev', '', 'revision'), - ('x', 'exclude', [], 'exclude path from search')], + ('X', 'exclude', [], 'exclude path from search')], 'hg locate [OPTION]... [PATTERN]...'), "^log|history": (log, [('r', 'rev', [], 'revision'), ('p', 'patch', None, 'show patch')], 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'), + "list|ls": (ls, + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search')], + "hg ls [OPTION]... [PATTERN]...."), "manifest": (manifest, [], 'hg manifest [REV]'), "parents": (parents, [], 'hg parents [REV]'), "^pull": @@ -1183,7 +1207,10 @@ ('', 'stdio', None, 'for remote clients'), ('t', 'templates', "", 'template map')], "hg serve [OPTION]..."), - "^status": (status, [], 'hg status'), + "^status": (status, + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search')], + 'hg status [OPTION]... [FILE]...'), "tag": (tag, [('l', 'local', None, 'make the tag local'), @@ -1344,6 +1371,9 @@ u.warn("abort: %s: %s\n" % (inst.strerror, inst.filename)) else: u.warn("abort: %s\n" % inst.strerror) + except Abort, inst: + u.warn('abort: ', inst.args[0] % inst.args[1:], '\n') + sys.exit(1) except TypeError, inst: # was this an argument error? tb = traceback.extract_tb(sys.exc_info()[2]) diff -r e5b39ce2c3c9 -r d2422f10c136 mercurial/hg.py --- a/mercurial/hg.py Wed Jul 20 11:40:27 2005 -0500 +++ b/mercurial/hg.py Wed Jul 20 20:00:29 2005 -0500 @@ -277,6 +277,36 @@ self.map = None self.pl = None self.copies = {} + self.ignorefunc = None + + def wjoin(self, f): + return os.path.join(self.root, f) + + def ignore(self, f): + if not self.ignorefunc: + bigpat = [] + try: + l = file(self.wjoin(".hgignore")) + for pat in l: + if pat != "\n": + p = util.pconvert(pat[:-1]) + try: + r = re.compile(p) + except: + self.ui.warn("ignoring invalid ignore" + + " regular expression '%s'\n" % p) + else: + bigpat.append(util.pconvert(pat[:-1])) + except IOError: pass + + if bigpat: + s = "(?:%s)" % (")|(?:".join(bigpat)) + r = re.compile(s) + self.ignorefunc = r.search + else: + self.ignorefunc = util.never + + return self.ignorefunc(f) def __del__(self): if self.dirty: @@ -298,8 +328,12 @@ self.read() return self.pl + def markdirty(self): + if not self.dirty: + self.dirty = 1 + def setparents(self, p1, p2 = nullid): - self.dirty = 1 + self.markdirty() self.pl = p1, p2 def state(self, key): @@ -334,7 +368,7 @@ def copy(self, source, dest): self.read() - self.dirty = 1 + self.markdirty() self.copies[dest] = source def copied(self, file): @@ -349,7 +383,7 @@ if not files: return self.read() - self.dirty = 1 + self.markdirty() for f in files: if state == "r": self.map[f] = ('r', 0, 0, 0) @@ -360,7 +394,7 @@ def forget(self, files): if not files: return self.read() - self.dirty = 1 + self.markdirty() for f in files: try: del self.map[f] @@ -370,7 +404,7 @@ def clear(self): self.map = {} - self.dirty = 1 + self.markdirty() def write(self): st = self.opener("dirstate", "w") @@ -383,34 +417,50 @@ st.write(e + f) self.dirty = 0 - def changes(self, files, ignore): + def walk(self, files = None, match = util.always): self.read() dc = self.map.copy() - lookup, changed, added, unknown = [], [], [], [] - - # compare all files by default + # walk all files by default if not files: files = [self.root] - - # recursive generator of all files listed - def walk(files): + def traverse(): for f in util.unique(files): f = os.path.join(self.root, f) if os.path.isdir(f): for dir, subdirs, fl in os.walk(f): d = dir[len(self.root) + 1:] + if d == '.hg': + subdirs[:] = [] + continue for sd in subdirs: - if ignore(os.path.join(d, sd +'/')): + ds = os.path.join(d, sd +'/') + if self.ignore(ds) or not match(ds): subdirs.remove(sd) for fn in fl: fn = util.pconvert(os.path.join(d, fn)) - yield fn + yield 'f', fn else: - yield f[len(self.root) + 1:] + yield 'f', f[len(self.root) + 1:] for k in dc.keys(): - yield k + yield 'm', k + + # yield only files that match: all in dirstate, others only if + # not in .hgignore - for fn in util.unique(walk(files)): + for src, fn in util.unique(traverse()): + if fn in dc: + del dc[fn] + elif self.ignore(fn): + continue + if match(fn): + yield src, fn + + def changes(self, files = None, match = util.always): + self.read() + dc = self.map.copy() + lookup, changed, added, unknown = [], [], [], [] + + for src, fn in self.walk(files, match): try: s = os.stat(os.path.join(self.root, fn)) except: continue @@ -429,9 +479,9 @@ elif c[1] != s.st_mode or c[3] != s.st_mtime: lookup.append(fn) else: - if not ignore(fn): unknown.append(fn) + if match(fn): unknown.append(fn) - return (lookup, changed, added, dc.keys(), unknown) + return (lookup, changed, added, filter(match, dc.keys()), unknown) # used to avoid circular references so destructors work def opener(base): @@ -493,7 +543,6 @@ self.wopener = opener(self.root) self.manifest = manifest(self.opener) self.changelog = changelog(self.opener) - self.ignorefunc = None self.tagscache = None self.nodetagscache = None @@ -503,29 +552,6 @@ self.ui.readconfig(self.opener("hgrc")) except IOError: pass - def ignore(self, f): - if not self.ignorefunc: - bigpat = ["^.hg/$"] - try: - l = file(self.wjoin(".hgignore")) - for pat in l: - if pat != "\n": - p = util.pconvert(pat[:-1]) - try: - r = re.compile(p) - except: - self.ui.warn("ignoring invalid ignore" - + " regular expression '%s'\n" % p) - else: - bigpat.append(util.pconvert(pat[:-1])) - except IOError: pass - - s = "(?:%s)" % (")|(?:".join(bigpat)) - r = re.compile(s) - self.ignorefunc = r.search - - return self.ignorefunc(f) - def hook(self, name, **args): s = self.ui.config("hooks", name) if s: @@ -738,7 +764,7 @@ else: self.ui.warn("%s not tracked!\n" % f) else: - (c, a, d, u) = self.changes(None, None) + (c, a, d, u) = self.changes() commit = c + a remove = d @@ -815,7 +841,16 @@ if not self.hook("commit", node=hex(n)): return 1 - def changes(self, node1, node2, files=None): + def walk(self, node = None, files = [], match = util.always): + if node: + for fn in self.manifest.read(self.changelog.read(node)[0]): + yield 'm', fn + else: + for src, fn in self.dirstate.walk(files, match): + yield src, fn + + def changes(self, node1 = None, node2 = None, files = [], + match = util.always): mf2, u = None, [] def fcmp(fn, mf): @@ -823,16 +858,23 @@ t2 = self.file(fn).revision(mf[fn]) return cmp(t1, t2) + def mfmatches(node): + mf = dict(self.manifest.read(node)) + for fn in mf.keys(): + if not match(fn): + del mf[fn] + return mf + # are we comparing the working directory? if not node2: - l, c, a, d, u = self.dirstate.changes(files, self.ignore) + l, c, a, d, u = self.dirstate.changes(files, match) # are we comparing working dir against its parent? if not node1: if l: # do a full compare of any files that might have changed change = self.changelog.read(self.dirstate.parents()[0]) - mf2 = self.manifest.read(change[0]) + mf2 = mfmatches(change[0]) for f in l: if fcmp(f, mf2): c.append(f) @@ -847,20 +889,20 @@ if not node2: if not mf2: change = self.changelog.read(self.dirstate.parents()[0]) - mf2 = self.manifest.read(change[0]).copy() + mf2 = mfmatches(change[0]) for f in a + c + l: mf2[f] = "" for f in d: if f in mf2: del mf2[f] else: change = self.changelog.read(node2) - mf2 = self.manifest.read(change[0]) + mf2 = mfmatches(change[0]) # flush lists from dirstate before comparing manifests c, a = [], [] change = self.changelog.read(node1) - mf1 = self.manifest.read(change[0]).copy() + mf1 = mfmatches(change[0]) for fn in mf2: if mf1.has_key(fn): @@ -885,7 +927,7 @@ self.ui.warn("%s does not exist!\n" % f) elif not os.path.isfile(p): self.ui.warn("%s not added: mercurial only supports files currently\n" % f) - elif self.dirstate.state(f) == 'n': + elif self.dirstate.state(f) in 'an': self.ui.warn("%s already tracked!\n" % f) else: self.dirstate.update([f], "a") @@ -1268,7 +1310,7 @@ ma = self.manifest.read(man) mfa = self.manifest.readflags(man) - (c, a, d, u) = self.changes(None, None) + (c, a, d, u) = self.changes() # is this a jump, or a merge? i.e. is there a linear path # from p1 to p2? diff -r e5b39ce2c3c9 -r d2422f10c136 mercurial/util.py --- a/mercurial/util.py Wed Jul 20 11:40:27 2005 -0500 +++ b/mercurial/util.py Wed Jul 20 20:00:29 2005 -0500 @@ -6,6 +6,8 @@ # of the GNU General Public License, incorporated herein by reference. import os, errno +from demandload import * +demandload(globals(), "re") def unique(g): seen = {} @@ -29,6 +31,54 @@ return "stopped by signal %d" % val, val raise ValueError("invalid exit code") +def always(fn): return True +def never(fn): return False + +def globre(pat, head = '^', tail = '$'): + "convert a glob pattern into a regexp" + i, n = 0, len(pat) + res = '' + group = False + def peek(): return i < n and pat[i] + while i < n: + c = pat[i] + i = i+1 + if c == '*': + if peek() == '*': + i += 1 + res += '.*' + else: + res += '[^/]*' + elif c == '?': + res += '.' + elif c == '[': + j = i + if j < n and pat[j] in '!]': + j += 1 + while j < n and pat[j] != ']': + j += 1 + if j >= n: + res += '\\[' + else: + stuff = pat[i:j].replace('\\','\\\\') + i = j + 1 + if stuff[0] == '!': + stuff = '^' + stuff[1:] + elif stuff[0] == '^': + stuff = '\\' + stuff + res = '%s[%s]' % (res, stuff) + elif c == '{': + group = True + res += '(?:' + elif c == '}' and group: + res += ')' + group = False + elif c == ',' and group: + res += '|' + else: + res += re.escape(c) + return head + res + tail + def system(cmd, errprefix=None): """execute a shell command that must succeed""" rc = os.system(cmd) diff -r e5b39ce2c3c9 -r d2422f10c136 templates/map diff -r e5b39ce2c3c9 -r d2422f10c136 tests/test-help.out --- a/tests/test-help.out Wed Jul 20 11:40:27 2005 -0500 +++ b/tests/test-help.out Wed Jul 20 20:00:29 2005 -0500 @@ -33,15 +33,29 @@ status show changed files in the working directory update update or merge working directory hg add: option -h not recognized -hg add FILE... +hg add [OPTIONS] [FILES] + + -I --include + include path in search + -X --exclude + exclude path from search add the specified files on the next commit hg add: option --skjdfks not recognized -hg add FILE... +hg add [OPTIONS] [FILES] + + -I --include + include path in search + -X --exclude + exclude path from search add the specified files on the next commit hg diff [-r REV1 [-r REV2]] [FILE]... + -I --include + include path in search + -X --exclude + exclude path from search -r --rev revision