##// END OF EJS Templates
Add grep command....
Bryan O'Sullivan -
r1057:2fd15d74 default
parent child Browse files
Show More
@@ -46,6 +46,80 b" def walk(repo, pats, opts, head = ''):"
46 files, matchfn, results = makewalk(repo, pats, opts, head)
46 files, matchfn, results = makewalk(repo, pats, opts, head)
47 for r in results: yield r
47 for r in results: yield r
48
48
49 def walkchangerevs(ui, repo, cwd, pats, opts):
50 # This code most commonly needs to iterate backwards over the
51 # history it is interested in. Doing so has awful
52 # (quadratic-looking) performance, so we use iterators in a
53 # "windowed" way. Walk forwards through a window of revisions,
54 # yielding them in the desired order, and walk the windows
55 # themselves backwards.
56 cwd = repo.getcwd()
57 if not pats and cwd:
58 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
59 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
60 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
61 pats, opts)
62 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
63 wanted = {}
64 slowpath = anypats
65 window = 300
66 fncache = {}
67 if not slowpath and not files:
68 # No files, no patterns. Display all revs.
69 wanted = dict(zip(revs, revs))
70 if not slowpath:
71 # Only files, no patterns. Check the history of each file.
72 def filerevgen(filelog):
73 for i in xrange(filelog.count() - 1, -1, -window):
74 revs = []
75 for j in xrange(max(0, i - window), i + 1):
76 revs.append(filelog.linkrev(filelog.node(j)))
77 revs.reverse()
78 for rev in revs:
79 yield rev
80
81 minrev, maxrev = min(revs), max(revs)
82 for file in files:
83 filelog = repo.file(file)
84 # A zero count may be a directory or deleted file, so
85 # try to find matching entries on the slow path.
86 if filelog.count() == 0:
87 slowpath = True
88 break
89 for rev in filerevgen(filelog):
90 if rev <= maxrev:
91 if rev < minrev: break
92 fncache.setdefault(rev, [])
93 fncache[rev].append(file)
94 wanted[rev] = 1
95 if slowpath:
96 # The slow path checks files modified in every changeset.
97 def changerevgen():
98 for i in xrange(repo.changelog.count() - 1, -1, -window):
99 for j in xrange(max(0, i - window), i + 1):
100 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
101
102 for rev, changefiles in changerevgen():
103 matches = filter(matchfn, changefiles)
104 if matches:
105 fncache[rev] = matches
106 wanted[rev] = 1
107
108 for i in xrange(0, len(revs), window):
109 yield 'window', revs[0] < revs[-1], revs[-1]
110 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
111 if rev in wanted]
112 srevs = list(nrevs)
113 srevs.sort()
114 for rev in srevs:
115 fns = fncache.get(rev)
116 if not fns:
117 fns = repo.changelog.read(repo.lookup(str(rev)))[3]
118 fns = filter(matchfn, fns)
119 yield 'add', rev, fns
120 for rev in nrevs:
121 yield 'iter', rev, None
122
49 revrangesep = ':'
123 revrangesep = ':'
50
124
51 def revrange(ui, repo, revs, revlog=None):
125 def revrange(ui, repo, revs, revlog=None):
@@ -716,6 +790,89 b' def forget(ui, repo, *pats, **opts):'
716 if not exact: ui.status('forgetting ', rel, '\n')
790 if not exact: ui.status('forgetting ', rel, '\n')
717 repo.forget(forget)
791 repo.forget(forget)
718
792
793 def grep(ui, repo, pattern = None, *pats, **opts):
794 if pattern is None: pattern = opts['regexp']
795 if not pattern: raise util.Abort('no pattern to search for')
796 reflags = 0
797 if opts['ignore_case']: reflags |= re.I
798 regexp = re.compile(pattern, reflags)
799 sep, end = ':', '\n'
800 if opts['null'] or opts['print0']: sep = end = '\0'
801
802 fcache = {}
803 def getfile(fn):
804 if fn not in fcache:
805 fcache[fn] = repo.file(fn)
806 return fcache[fn]
807
808 def matchlines(body):
809 for match in regexp.finditer(body):
810 start, end = match.span()
811 lnum = body.count('\n', 0, start) + 1
812 lstart = body.rfind('\n', 0, start) + 1
813 lend = body.find('\n', end)
814 yield lnum, start - lstart, end - lstart, body[lstart:lend]
815
816 class linestate:
817 def __init__(self, line, linenum, colstart, colend):
818 self.line = line
819 self.linenum = linenum
820 self.colstart = colstart
821 self.colend = colend
822 def __eq__(self, other): return self.line == other.line
823 def __hash__(self): return hash(self.line)
824
825 matches = {}
826 def grepbody(fn, rev, body):
827 matches[rev].setdefault(fn, {})
828 m = matches[rev][fn]
829 for lnum, cstart, cend, line in matchlines(body):
830 s = linestate(line, lnum, cstart, cend)
831 m[s] = s
832
833 prev = {}
834 def display(fn, rev, states, prevstates):
835 diff = list(set(states).symmetric_difference(set(prevstates)))
836 diff.sort(lambda x, y: cmp(x.linenum, y.linenum))
837 for l in diff:
838 if incrementing:
839 change = ((l in prevstates) and '-') or '+'
840 r = rev
841 else:
842 change = ((l in states) and '-') or '+'
843 r = prev[fn]
844 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line))
845
846 fstate = {}
847 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts):
848 if st == 'window':
849 incrementing = rev
850 matches.clear()
851 elif st == 'add':
852 change = repo.changelog.read(repo.lookup(str(rev)))
853 mf = repo.manifest.read(change[0])
854 matches[rev] = {}
855 for fn in fns:
856 fstate.setdefault(fn, {})
857 try:
858 grepbody(fn, rev, getfile(fn).read(mf[fn]))
859 except KeyError:
860 pass
861 elif st == 'iter':
862 states = matches[rev].items()
863 states.sort()
864 for fn, m in states:
865 if incrementing or fstate[fn]:
866 display(fn, rev, m, fstate[fn])
867 fstate[fn] = m
868 prev[fn] = rev
869
870 if not incrementing:
871 fstate = fstate.items()
872 fstate.sort()
873 for fn, state in fstate:
874 display(fn, rev, {}, state)
875
719 def heads(ui, repo, **opts):
876 def heads(ui, repo, **opts):
720 """show current repository heads"""
877 """show current repository heads"""
721 heads = repo.changelog.heads()
878 heads = repo.changelog.heads()
@@ -845,96 +1002,42 b' def locate(ui, repo, *pats, **opts):'
845
1002
846 def log(ui, repo, *pats, **opts):
1003 def log(ui, repo, *pats, **opts):
847 """show revision history of entire repository or files"""
1004 """show revision history of entire repository or files"""
848 # This code most commonly needs to iterate backwards over the
1005 class dui:
849 # history it is interested in. This has awful (quadratic-looking)
1006 # Implement and delegate some ui protocol. Save hunks of
850 # performance, so we use iterators that walk forwards through
1007 # output for later display in the desired order.
851 # windows of revisions, yielding revisions in reverse order, while
1008 def __init__(self, ui):
852 # walking the windows backwards.
1009 self.ui = ui
1010 self.hunk = {}
1011 def bump(self, rev):
1012 self.rev = rev
1013 self.hunk[rev] = []
1014 def note(self, *args):
1015 if self.verbose: self.write(*args)
1016 def status(self, *args):
1017 if not self.quiet: self.write(*args)
1018 def write(self, *args):
1019 self.hunk[self.rev].append(args)
1020 def __getattr__(self, key):
1021 return getattr(self.ui, key)
853 cwd = repo.getcwd()
1022 cwd = repo.getcwd()
854 if not pats and cwd:
1023 if not pats and cwd:
855 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
1024 opts['include'] = [os.path.join(cwd, i) for i in opts['include']]
856 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
1025 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']]
857 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '',
1026 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats,
858 pats, opts)
1027 opts):
859 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0']))
1028 if st == 'window':
860 wanted = {}
861 slowpath = anypats
862 window = 300
863 if not slowpath and not files:
864 # No files, no patterns. Display all revs.
865 wanted = dict(zip(revs, revs))
866 if not slowpath:
867 # Only files, no patterns. Check the history of each file.
868 def filerevgen(filelog):
869 for i in xrange(filelog.count() - 1, -1, -window):
870 print "filelog"
871 revs = []
872 for j in xrange(max(0, i - window), i + 1):
873 revs.append(filelog.linkrev(filelog.node(j)))
874 revs.reverse()
875 for rev in revs:
876 yield rev
877
878 minrev, maxrev = min(revs), max(revs)
879 for filelog in map(repo.file, files):
880 # A zero count may be a directory or deleted file, so
881 # try to find matching entries on the slow path.
882 if filelog.count() == 0:
883 slowpath = True
884 break
885 for rev in filerevgen(filelog):
886 if rev <= maxrev:
887 if rev < minrev: break
888 wanted[rev] = 1
889 if slowpath:
890 # The slow path checks files modified in every changeset.
891 def mfrevgen():
892 for i in xrange(repo.changelog.count() - 1, -1, -window):
893 for j in xrange(max(0, i - window), i + 1):
894 yield j, repo.changelog.read(repo.lookup(str(j)))[3]
895
896 for rev, mf in mfrevgen():
897 if filter(matchfn, mf):
898 wanted[rev] = 1
899
900 def changerevgen():
901 class dui:
902 # Implement and delegate some ui protocol. Save hunks of
903 # output for later display in the desired order.
904 def __init__(self, ui):
905 self.ui = ui
906 self.hunk = {}
907 def bump(self, rev):
908 self.rev = rev
909 self.hunk[rev] = []
910 def note(self, *args):
911 if self.verbose: self.write(*args)
912 def status(self, *args):
913 if not self.quiet: self.write(*args)
914 def write(self, *args):
915 self.hunk[self.rev].append(args)
916 def __getattr__(self, key):
917 return getattr(self.ui, key)
918 for i in xrange(0, len(revs), window):
919 nrevs = [rev for rev in revs[i : min(i + window, len(revs))]
920 if rev in wanted]
921 srevs = list(nrevs)
922 srevs.sort()
923 du = dui(ui)
1029 du = dui(ui)
924 for rev in srevs:
1030 elif st == 'add':
925 du.bump(rev)
1031 du.bump(rev)
926 yield rev, du
1032 show_changeset(du, repo, rev)
927 for rev in nrevs:
1033 if opts['patch']:
928 for args in du.hunk[rev]:
1034 changenode = repo.changelog.node(rev)
929 ui.write(*args)
1035 prev, other = repo.changelog.parents(changenode)
930
1036 dodiff(du, du, repo, prev, changenode, fns)
931 for rev, dui in changerevgen():
1037 du.write("\n\n")
932 show_changeset(dui, repo, rev)
1038 elif st == 'iter':
933 if opts['patch']:
1039 for args in du.hunk[rev]:
934 changenode = repo.changelog.node(rev)
1040 ui.write(*args)
935 prev, other = repo.changelog.parents(changenode)
936 dodiff(dui, dui, repo, prev, changenode, files)
937 dui.write("\n\n")
938
1041
939 def manifest(ui, repo, rev=None):
1042 def manifest(ui, repo, rev=None):
940 """output the latest or given revision of the project manifest"""
1043 """output the latest or given revision of the project manifest"""
@@ -1408,6 +1511,21 b' table = {'
1408 [('I', 'include', [], 'include path in search'),
1511 [('I', 'include', [], 'include path in search'),
1409 ('X', 'exclude', [], 'exclude path from search')],
1512 ('X', 'exclude', [], 'exclude path from search')],
1410 "hg forget [OPTION]... FILE..."),
1513 "hg forget [OPTION]... FILE..."),
1514 "grep": (grep,
1515 [('0', 'print0', None, 'terminate file names with NUL'),
1516 ('I', 'include', [], 'include path in search'),
1517 ('X', 'exclude', [], 'include path in search'),
1518 ('Z', 'null', None, 'terminate file names with NUL'),
1519 ('a', 'all-revs', '', 'search all revs'),
1520 ('e', 'regexp', '', 'pattern to search for'),
1521 ('f', 'full-path', None, 'print complete paths'),
1522 ('i', 'ignore-case', None, 'ignore case when matching'),
1523 ('l', 'files-with-matches', None, 'print names of files with matches'),
1524 ('n', 'line-number', '', 'print line numbers'),
1525 ('r', 'rev', [], 'search in revision rev'),
1526 ('s', 'no-messages', None, 'do not print error messages'),
1527 ('v', 'invert-match', None, 'select non-matching lines')],
1528 "hg grep [options] [pat] [files]"),
1411 "heads":
1529 "heads":
1412 (heads,
1530 (heads,
1413 [('b', 'branches', None, 'find branch info')],
1531 [('b', 'branches', None, 'find branch info')],
General Comments 0
You need to be logged in to leave comments. Login now