Show More
@@ -0,0 +1,22 b'' | |||
|
1 | #!/bin/sh | |
|
2 | ||
|
3 | mkdir t | |
|
4 | cd t | |
|
5 | hg init | |
|
6 | echo import > port | |
|
7 | hg add port | |
|
8 | hg commit -m 0 -u spam -d '0 0' | |
|
9 | echo export >> port | |
|
10 | hg commit -m 1 -u eggs -d '1 0' | |
|
11 | echo export > port | |
|
12 | echo vaportight >> port | |
|
13 | echo 'import/export' >> port | |
|
14 | hg commit -m 2 -u spam -d '2 0' | |
|
15 | echo 'import/export' >> port | |
|
16 | hg commit -m 3 -u eggs -d '3 0' | |
|
17 | head -3 port > port1 | |
|
18 | mv port1 port | |
|
19 | hg commit -m 4 -u spam -d '4 0' | |
|
20 | hg grep port port | |
|
21 | hg grep -enu port port | |
|
22 | hg grep import port |
@@ -207,6 +207,31 b' forget [options] [files]::' | |||
|
207 | 207 | -I, --include <pat> include names matching the given patterns |
|
208 | 208 | -X, --exclude <pat> exclude names matching the given patterns |
|
209 | 209 | |
|
210 | grep [options] pattern [files]:: | |
|
211 | Search revisions of files for a regular expression. | |
|
212 | ||
|
213 | This command behaves differently than Unix grep. It only accepts | |
|
214 | Python/Perl regexps. It searches repository history, not the | |
|
215 | working directory. It always prints the revision number in which | |
|
216 | a match appears. | |
|
217 | ||
|
218 | By default, grep only prints output for the first revision of a | |
|
219 | file in which it finds a match. To get it to print every revision | |
|
220 | that contains a change in match status ("-" for a match that | |
|
221 | becomes a non-match, or "+" for a non-match that becomes a match), | |
|
222 | use the --every-match flag. | |
|
223 | ||
|
224 | options: | |
|
225 | -0, --print0 end fields with NUL | |
|
226 | -I, --include <pat> include names matching the given patterns | |
|
227 | -X, --exclude <pat> exclude names matching the given patterns | |
|
228 | -e, --every-match print every revision that matches | |
|
229 | -i, --ignore-case ignore case when matching | |
|
230 | -l, --files-with-matches print only file names and revs that match | |
|
231 | -n, --line-number print matching line numbers | |
|
232 | -r <rev>, --rev <rev> search in given revision range | |
|
233 | -u, --user print user who committed change | |
|
234 | ||
|
210 | 235 | heads:: |
|
211 | 236 | Show all repository head changesets. |
|
212 | 237 |
@@ -49,12 +49,31 b" def walk(repo, pats, opts, head=''):" | |||
|
49 | 49 | yield r |
|
50 | 50 | |
|
51 | 51 | def walkchangerevs(ui, repo, cwd, pats, opts): |
|
52 | # This code most commonly needs to iterate backwards over the | |
|
53 | # history it is interested in. Doing so has awful | |
|
54 | # (quadratic-looking) performance, so we use iterators in a | |
|
55 | # "windowed" way. Walk forwards through a window of revisions, | |
|
56 | # yielding them in the desired order, and walk the windows | |
|
57 | # themselves backwards. | |
|
52 | '''Iterate over files and the revs they changed in. | |
|
53 | ||
|
54 | Callers most commonly need to iterate backwards over the history | |
|
55 | it is interested in. Doing so has awful (quadratic-looking) | |
|
56 | performance, so we use iterators in a "windowed" way. | |
|
57 | ||
|
58 | We walk a window of revisions in the desired order. Within the | |
|
59 | window, we first walk forwards to gather data, then in the desired | |
|
60 | order (usually backwards) to display it. | |
|
61 | ||
|
62 | This function returns an (iterator, getchange) pair. The | |
|
63 | getchange function returns the changelog entry for a numeric | |
|
64 | revision. The iterator yields 3-tuples. They will be of one of | |
|
65 | the following forms: | |
|
66 | ||
|
67 | "window", incrementing, lastrev: stepping through a window, | |
|
68 | positive if walking forwards through revs, last rev in the | |
|
69 | sequence iterated over - use to reset state for the current window | |
|
70 | ||
|
71 | "add", rev, fns: out-of-order traversal of the given file names | |
|
72 | fns, which changed during revision rev - use to gather data for | |
|
73 | possible display | |
|
74 | ||
|
75 | "iter", rev, None: in-order traversal of the revs earlier iterated | |
|
76 | over with "add" - use to display data''' | |
|
58 | 77 | cwd = repo.getcwd() |
|
59 | 78 | if not pats and cwd: |
|
60 | 79 | opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
@@ -66,6 +85,14 b' def walkchangerevs(ui, repo, cwd, pats, ' | |||
|
66 | 85 | slowpath = anypats |
|
67 | 86 | window = 300 |
|
68 | 87 | fncache = {} |
|
88 | ||
|
89 | chcache = {} | |
|
90 | def getchange(rev): | |
|
91 | ch = chcache.get(rev) | |
|
92 | if ch is None: | |
|
93 | chcache[rev] = ch = repo.changelog.read(repo.lookup(str(rev))) | |
|
94 | return ch | |
|
95 | ||
|
69 | 96 | if not slowpath and not files: |
|
70 | 97 | # No files, no patterns. Display all revs. |
|
71 | 98 | wanted = dict(zip(revs, revs)) |
@@ -100,7 +127,7 b' def walkchangerevs(ui, repo, cwd, pats, ' | |||
|
100 | 127 | def changerevgen(): |
|
101 | 128 | for i in xrange(repo.changelog.count() - 1, -1, -window): |
|
102 | 129 | for j in xrange(max(0, i - window), i + 1): |
|
103 |
yield j, |
|
|
130 | yield j, getchange(j)[3] | |
|
104 | 131 | |
|
105 | 132 | for rev, changefiles in changerevgen(): |
|
106 | 133 | matches = filter(matchfn, changefiles) |
@@ -108,20 +135,19 b' def walkchangerevs(ui, repo, cwd, pats, ' | |||
|
108 | 135 | fncache[rev] = matches |
|
109 | 136 | wanted[rev] = 1 |
|
110 | 137 | |
|
111 | for i in xrange(0, len(revs), window): | |
|
112 | yield 'window', revs[0] < revs[-1], revs[-1] | |
|
113 | nrevs = [rev for rev in revs[i:min(i+window, len(revs))] | |
|
114 | if rev in wanted] | |
|
115 | srevs = list(nrevs) | |
|
116 |
srevs |
|
|
117 |
|
|
|
118 | fns = fncache.get(rev) | |
|
119 | if not fns: | |
|
120 | fns = repo.changelog.read(repo.lookup(str(rev)))[3] | |
|
121 | fns = filter(matchfn, fns) | |
|
122 |
yield ' |
|
|
123 | for rev in nrevs: | |
|
124 | yield 'iter', rev, None | |
|
138 | def iterate(): | |
|
139 | for i in xrange(0, len(revs), window): | |
|
140 | yield 'window', revs[0] < revs[-1], revs[-1] | |
|
141 | nrevs = [rev for rev in revs[i:min(i+window, len(revs))] | |
|
142 | if rev in wanted] | |
|
143 | srevs = list(nrevs) | |
|
144 | srevs.sort() | |
|
145 | for rev in srevs: | |
|
146 | fns = fncache.get(rev) or filter(matchfn, getchange(rev)[3]) | |
|
147 | yield 'add', rev, fns | |
|
148 | for rev in nrevs: | |
|
149 | yield 'iter', rev, None | |
|
150 | return iterate(), getchange | |
|
125 | 151 | |
|
126 | 152 | revrangesep = ':' |
|
127 | 153 | |
@@ -150,6 +176,7 b' def revrange(ui, repo, revs, revlog=None' | |||
|
150 | 176 | except KeyError: |
|
151 | 177 | raise util.Abort('invalid revision identifier %s', val) |
|
152 | 178 | return num |
|
179 | seen = {} | |
|
153 | 180 | for spec in revs: |
|
154 | 181 | if spec.find(revrangesep) >= 0: |
|
155 | 182 | start, end = spec.split(revrangesep, 1) |
@@ -157,9 +184,14 b' def revrange(ui, repo, revs, revlog=None' | |||
|
157 | 184 | end = fix(end, revcount - 1) |
|
158 | 185 | step = start > end and -1 or 1 |
|
159 | 186 | for rev in xrange(start, end+step, step): |
|
187 | if rev in seen: continue | |
|
188 | seen[rev] = 1 | |
|
160 | 189 | yield str(rev) |
|
161 | 190 | else: |
|
162 |
|
|
|
191 | rev = fix(spec, None) | |
|
192 | if rev in seen: continue | |
|
193 | seen[rev] = 1 | |
|
194 | yield str(rev) | |
|
163 | 195 | |
|
164 | 196 | def make_filename(repo, r, pat, node=None, |
|
165 | 197 | total=None, seqno=None, revwidth=None): |
@@ -265,6 +297,21 b' def dodiff(fp, ui, repo, node1, node2, f' | |||
|
265 | 297 | tn = None |
|
266 | 298 | fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) |
|
267 | 299 | |
|
300 | def trimuser(ui, rev, name, revcache): | |
|
301 | """trim the name of the user who committed a change""" | |
|
302 | try: | |
|
303 | return revcache[rev] | |
|
304 | except KeyError: | |
|
305 | if not ui.verbose: | |
|
306 | f = name.find('@') | |
|
307 | if f >= 0: | |
|
308 | name = name[:f] | |
|
309 | f = name.find('<') | |
|
310 | if f >= 0: | |
|
311 | name = name[f+1:] | |
|
312 | revcache[rev] = name | |
|
313 | return name | |
|
314 | ||
|
268 | 315 | def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None): |
|
269 | 316 | """show a single changeset or file revision""" |
|
270 | 317 | log = repo.changelog |
@@ -467,25 +514,14 b' def annotate(ui, repo, *pats, **opts):' | |||
|
467 | 514 | def getnode(rev): |
|
468 | 515 | return short(repo.changelog.node(rev)) |
|
469 | 516 | |
|
517 | ucache = {} | |
|
470 | 518 | def getname(rev): |
|
471 | try: | |
|
472 | return bcache[rev] | |
|
473 | except KeyError: | |
|
474 | cl = repo.changelog.read(repo.changelog.node(rev)) | |
|
475 | name = cl[1] | |
|
476 | f = name.find('@') | |
|
477 | if f >= 0: | |
|
478 | name = name[:f] | |
|
479 | f = name.find('<') | |
|
480 | if f >= 0: | |
|
481 | name = name[f+1:] | |
|
482 | bcache[rev] = name | |
|
483 | return name | |
|
519 | cl = repo.changelog.read(repo.changelog.node(rev)) | |
|
520 | return trimuser(ui, rev, cl[1], ucache) | |
|
484 | 521 | |
|
485 | 522 | if not pats: |
|
486 | 523 | raise util.Abort('at least one file name or pattern required') |
|
487 | 524 | |
|
488 | bcache = {} | |
|
489 | 525 | opmap = [['user', getname], ['number', str], ['changeset', getnode]] |
|
490 | 526 | if not opts['user'] and not opts['changeset']: |
|
491 | 527 | opts['number'] = 1 |
@@ -826,9 +862,9 b' def grep(ui, repo, pattern, *pats, **opt' | |||
|
826 | 862 | if opts['ignore_case']: |
|
827 | 863 | reflags |= re.I |
|
828 | 864 | regexp = re.compile(pattern, reflags) |
|
829 |
sep, e |
|
|
865 | sep, eol = ':', '\n' | |
|
830 | 866 | if opts['print0']: |
|
831 |
sep = e |
|
|
867 | sep = eol = '\0' | |
|
832 | 868 | |
|
833 | 869 | fcache = {} |
|
834 | 870 | def getfile(fn): |
@@ -870,10 +906,12 b' def grep(ui, repo, pattern, *pats, **opt' | |||
|
870 | 906 | m[s] = s |
|
871 | 907 | |
|
872 | 908 | prev = {} |
|
909 | ucache = {} | |
|
873 | 910 | def display(fn, rev, states, prevstates): |
|
874 | 911 | diff = list(sets.Set(states).symmetric_difference(sets.Set(prevstates))) |
|
875 | 912 | diff.sort(lambda x, y: cmp(x.linenum, y.linenum)) |
|
876 | 913 | counts = {'-': 0, '+': 0} |
|
914 | filerevmatches = {} | |
|
877 | 915 | for l in diff: |
|
878 | 916 | if incrementing or not opts['every_match']: |
|
879 | 917 | change = ((l in prevstates) and '-') or '+' |
@@ -881,13 +919,26 b' def grep(ui, repo, pattern, *pats, **opt' | |||
|
881 | 919 | else: |
|
882 | 920 | change = ((l in states) and '-') or '+' |
|
883 | 921 | r = prev[fn] |
|
884 | ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line)) | |
|
922 | cols = [fn, str(rev)] | |
|
923 | if opts['line_number']: cols.append(str(l.linenum)) | |
|
924 | if opts['every_match']: cols.append(change) | |
|
925 | if opts['user']: cols.append(trimuser(ui, rev, getchange(rev)[1], | |
|
926 | ucache)) | |
|
927 | if opts['files_with_matches']: | |
|
928 | c = (fn, rev) | |
|
929 | if c in filerevmatches: continue | |
|
930 | filerevmatches[c] = 1 | |
|
931 | else: | |
|
932 | cols.append(l.line) | |
|
933 | ui.write(sep.join(cols), eol) | |
|
885 | 934 | counts[change] += 1 |
|
886 | 935 | return counts['+'], counts['-'] |
|
887 | 936 | |
|
888 | 937 | fstate = {} |
|
889 | 938 | skip = {} |
|
890 |
|
|
|
939 | changeiter, getchange = walkchangerevs(ui, repo, repo.getcwd(), pats, opts) | |
|
940 | count = 0 | |
|
941 | for st, rev, fns in changeiter: | |
|
891 | 942 | if st == 'window': |
|
892 | 943 | incrementing = rev |
|
893 | 944 | matches.clear() |
@@ -909,6 +960,7 b' def grep(ui, repo, pattern, *pats, **opt' | |||
|
909 | 960 | if fn in skip: continue |
|
910 | 961 | if incrementing or not opts['every_match'] or fstate[fn]: |
|
911 | 962 | pos, neg = display(fn, rev, m, fstate[fn]) |
|
963 | count += pos + neg | |
|
912 | 964 | if pos and not opts['every_match']: |
|
913 | 965 | skip[fn] = True |
|
914 | 966 | fstate[fn] = m |
@@ -920,6 +972,7 b' def grep(ui, repo, pattern, *pats, **opt' | |||
|
920 | 972 | for fn, state in fstate: |
|
921 | 973 | if fn in skip: continue |
|
922 | 974 | display(fn, rev, {}, state) |
|
975 | return (count == 0 and 1) or 0 | |
|
923 | 976 | |
|
924 | 977 | def heads(ui, repo, **opts): |
|
925 | 978 | """show current repository heads""" |
@@ -1073,8 +1126,9 b' def log(ui, repo, *pats, **opts):' | |||
|
1073 | 1126 | if not pats and cwd: |
|
1074 | 1127 | opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
|
1075 | 1128 | opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
|
1076 |
|
|
|
1077 |
opts) |
|
|
1129 | changeiter, getchange = walkchangerevs(ui, repo, (pats and cwd) or '', | |
|
1130 | pats, opts) | |
|
1131 | for st, rev, fns in changeiter: | |
|
1078 | 1132 | if st == 'window': |
|
1079 | 1133 | du = dui(ui) |
|
1080 | 1134 | elif st == 'add': |
@@ -1571,14 +1625,15 b' table = {' | |||
|
1571 | 1625 | "hg forget [OPTION]... FILE..."), |
|
1572 | 1626 | "grep": |
|
1573 | 1627 | (grep, |
|
1574 |
[('0', 'print0', None, 'end fi |
|
|
1628 | [('0', 'print0', None, 'end fields with NUL'), | |
|
1575 | 1629 | ('I', 'include', [], 'include path in search'), |
|
1576 | 1630 | ('X', 'exclude', [], 'include path in search'), |
|
1577 |
('e', 'every-match', None, 'print every match |
|
|
1631 | ('e', 'every-match', None, 'print every rev with matches'), | |
|
1578 | 1632 | ('i', 'ignore-case', None, 'ignore case when matching'), |
|
1579 | ('l', 'files-with-matches', None, 'print names of files with matches'), | |
|
1580 |
('n', 'line-number', |
|
|
1581 |
('r', 'rev', [], 'search in revision rev') |
|
|
1633 | ('l', 'files-with-matches', None, 'print names of files and revs with matches'), | |
|
1634 | ('n', 'line-number', None, 'print line numbers'), | |
|
1635 | ('r', 'rev', [], 'search in revision rev'), | |
|
1636 | ('u', 'user', None, 'print user who made change')], | |
|
1582 | 1637 | "hg grep [OPTION]... PATTERN [FILE]..."), |
|
1583 | 1638 | "heads": |
|
1584 | 1639 | (heads, |
General Comments 0
You need to be logged in to leave comments.
Login now