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,59 +1002,6 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 |
|
|||
849 | # history it is interested in. This has awful (quadratic-looking) |
|
|||
850 | # performance, so we use iterators that walk forwards through |
|
|||
851 | # windows of revisions, yielding revisions in reverse order, while |
|
|||
852 | # walking the windows backwards. |
|
|||
853 | cwd = repo.getcwd() |
|
|||
854 | if not pats and cwd: |
|
|||
855 | 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']] |
|
|||
857 | files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', |
|
|||
858 | pats, opts) |
|
|||
859 | revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) |
|
|||
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 |
|
|
1005 | class dui: | |
902 |
|
|
1006 | # Implement and delegate some ui protocol. Save hunks of | |
903 |
|
|
1007 | # output for later display in the desired order. | |
@@ -915,26 +1019,25 b' def log(ui, repo, *pats, **opts):' | |||||
915 |
|
|
1019 | self.hunk[self.rev].append(args) | |
916 |
|
|
1020 | def __getattr__(self, key): | |
917 |
|
|
1021 | return getattr(self.ui, key) | |
918 | for i in xrange(0, len(revs), window): |
|
1022 | cwd = repo.getcwd() | |
919 | nrevs = [rev for rev in revs[i : min(i + window, len(revs))] |
|
1023 | if not pats and cwd: | |
920 | if rev in wanted] |
|
1024 | opts['include'] = [os.path.join(cwd, i) for i in opts['include']] | |
921 | srevs = list(nrevs) |
|
1025 | opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] | |
922 | srevs.sort() |
|
1026 | for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats, | |
|
1027 | opts): | |||
|
1028 | if st == 'window': | |||
923 | du = dui(ui) |
|
1029 | du = dui(ui) | |
924 | for rev in srevs: |
|
1030 | elif st == 'add': | |
925 |
|
|
1031 | du.bump(rev) | |
926 | yield rev, du |
|
1032 | show_changeset(du, repo, rev) | |
927 | for rev in nrevs: |
|
|||
928 | for args in du.hunk[rev]: |
|
|||
929 | ui.write(*args) |
|
|||
930 |
|
||||
931 | for rev, dui in changerevgen(): |
|
|||
932 | show_changeset(dui, repo, rev) |
|
|||
933 | if opts['patch']: |
|
1033 | if opts['patch']: | |
934 | changenode = repo.changelog.node(rev) |
|
1034 | changenode = repo.changelog.node(rev) | |
935 | prev, other = repo.changelog.parents(changenode) |
|
1035 | prev, other = repo.changelog.parents(changenode) | |
936 |
dodiff(du |
|
1036 | dodiff(du, du, repo, prev, changenode, fns) | |
937 |
du |
|
1037 | du.write("\n\n") | |
|
1038 | elif st == 'iter': | |||
|
1039 | for args in du.hunk[rev]: | |||
|
1040 | ui.write(*args) | |||
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