diff --git a/mercurial/hg.py b/mercurial/hg.py --- a/mercurial/hg.py +++ b/mercurial/hg.py @@ -277,6 +277,36 @@ class dirstate: 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 @@ class dirstate: 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 @@ class dirstate: def copy(self, source, dest): self.read() - self.dirty = 1 + self.markdirty() self.copies[dest] = source def copied(self, file): @@ -349,7 +383,7 @@ class dirstate: 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 @@ class dirstate: 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 @@ class dirstate: def clear(self): self.map = {} - self.dirty = 1 + self.markdirty() def write(self): st = self.opener("dirstate", "w") @@ -383,34 +417,50 @@ class dirstate: 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 @@ class dirstate: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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 @@ class localrepository: 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?