Mercurial > hg > mercurial-crew-with-dirclash
comparison mercurial/commands.py @ 1057:2fd15d743b3b
Add grep command.
It currently searches all revs of every matching file. I'll change
this soon so that it can still do this, but it will not be the default
behaviour.
Many options are unimplemented. There's only one output mode. Binary
files are not handled yet.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Thu, 25 Aug 2005 02:00:03 -0700 |
parents | 23f9d71ab9ae |
children | 402279974aea |
comparison
equal
deleted
inserted
replaced
1056:34be48b4ca85 | 1057:2fd15d743b3b |
---|---|
43 return files, matchfn, walk() | 43 return files, matchfn, walk() |
44 | 44 |
45 def walk(repo, pats, opts, head = ''): | 45 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 | |
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 | |
48 | 122 |
49 revrangesep = ':' | 123 revrangesep = ':' |
50 | 124 |
51 def revrange(ui, repo, revs, revlog=None): | 125 def revrange(ui, repo, revs, revlog=None): |
52 if revlog is None: | 126 if revlog is None: |
714 if repo.dirstate.state(abs) == 'a': | 788 if repo.dirstate.state(abs) == 'a': |
715 forget.append(abs) | 789 forget.append(abs) |
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() |
722 br = None | 879 br = None |
723 if opts['branches']: | 880 if opts['branches']: |
843 else: | 1000 else: |
844 ui.write(rel, end) | 1001 ui.write(rel, end) |
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""" |
941 if rev: | 1044 if rev: |
942 try: | 1045 try: |
1406 "forget": | 1509 "forget": |
1407 (forget, | 1510 (forget, |
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')], |
1414 'hg [-b] heads'), | 1532 'hg [-b] heads'), |
1415 "help": (help_, [], 'hg help [COMMAND]'), | 1533 "help": (help_, [], 'hg help [COMMAND]'), |