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]'), |