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: |
643 |
717 |
644 def debugwalk(ui, repo, *pats, **opts): |
718 def debugwalk(ui, repo, *pats, **opts): |
645 """show how files match on given patterns""" |
719 """show how files match on given patterns""" |
646 items = list(walk(repo, pats, opts)) |
720 items = list(walk(repo, pats, opts)) |
647 if not items: return |
721 if not items: return |
648 fmt = '%%s %%-%ds %%-%ds %%s' % ( |
722 fmt = '%%s %%-%ds %%-%ds %%s\n' % ( |
649 max([len(abs) for (src, abs, rel, exact) in items]), |
723 max([len(abs) for (src, abs, rel, exact) in items]), |
650 max([len(rel) for (src, abs, rel, exact) in items])) |
724 max([len(rel) for (src, abs, rel, exact) in items])) |
651 exactly = {True: 'exact', False: ''} |
725 exactly = {True: 'exact', False: ''} |
652 for src, abs, rel, exact in items: |
726 for src, abs, rel, exact in items: |
653 print fmt % (src, abs, rel, exactly[exact]) |
727 ui.write(fmt % (src, abs, rel, exactly[exact])) |
654 |
728 |
655 def diff(ui, repo, *pats, **opts): |
729 def diff(ui, repo, *pats, **opts): |
656 """diff working directory (or selected files)""" |
730 """diff working directory (or selected files)""" |
657 node1, node2 = None, None |
731 node1, node2 = None, None |
658 revs = [repo.lookup(x) for x in opts['rev']] |
732 revs = [repo.lookup(x) for x in opts['rev']] |
716 for src, abs, rel, exact in walk(repo, pats, opts): |
790 for src, abs, rel, exact in walk(repo, pats, opts): |
717 if repo.dirstate.state(abs) == 'a': |
791 if repo.dirstate.state(abs) == 'a': |
718 forget.append(abs) |
792 forget.append(abs) |
719 if not exact: ui.status('forgetting ', rel, '\n') |
793 if not exact: ui.status('forgetting ', rel, '\n') |
720 repo.forget(forget) |
794 repo.forget(forget) |
|
795 |
|
796 def grep(ui, repo, pattern = None, *pats, **opts): |
|
797 """search for a pattern in specified files and revisions""" |
|
798 if pattern is None: pattern = opts['regexp'] |
|
799 if not pattern: raise util.Abort('no pattern to search for') |
|
800 reflags = 0 |
|
801 if opts['ignore_case']: reflags |= re.I |
|
802 regexp = re.compile(pattern, reflags) |
|
803 sep, end = ':', '\n' |
|
804 if opts['null'] or opts['print0']: sep = end = '\0' |
|
805 |
|
806 fcache = {} |
|
807 def getfile(fn): |
|
808 if fn not in fcache: |
|
809 fcache[fn] = repo.file(fn) |
|
810 return fcache[fn] |
|
811 |
|
812 def matchlines(body): |
|
813 begin = 0 |
|
814 linenum = 0 |
|
815 while True: |
|
816 match = regexp.search(body, begin) |
|
817 if not match: break |
|
818 mstart, mend = match.span() |
|
819 linenum += body.count('\n', begin, mstart) + 1 |
|
820 lstart = body.rfind('\n', begin, mstart) + 1 or begin |
|
821 lend = body.find('\n', mend) |
|
822 yield linenum, mstart - lstart, mend - lstart, body[lstart:lend] |
|
823 begin = lend + 1 |
|
824 |
|
825 class linestate: |
|
826 def __init__(self, line, linenum, colstart, colend): |
|
827 self.line = line |
|
828 self.linenum = linenum |
|
829 self.colstart = colstart |
|
830 self.colend = colend |
|
831 def __eq__(self, other): return self.line == other.line |
|
832 def __hash__(self): return hash(self.line) |
|
833 |
|
834 matches = {} |
|
835 def grepbody(fn, rev, body): |
|
836 matches[rev].setdefault(fn, {}) |
|
837 m = matches[rev][fn] |
|
838 for lnum, cstart, cend, line in matchlines(body): |
|
839 s = linestate(line, lnum, cstart, cend) |
|
840 m[s] = s |
|
841 |
|
842 prev = {} |
|
843 def display(fn, rev, states, prevstates): |
|
844 diff = list(set(states).symmetric_difference(set(prevstates))) |
|
845 diff.sort(lambda x, y: cmp(x.linenum, y.linenum)) |
|
846 for l in diff: |
|
847 if incrementing: |
|
848 change = ((l in prevstates) and '-') or '+' |
|
849 r = rev |
|
850 else: |
|
851 change = ((l in states) and '-') or '+' |
|
852 r = prev[fn] |
|
853 ui.write('%s:%s:%s:%s%s\n' % (fn, r, l.linenum, change, l.line)) |
|
854 |
|
855 fstate = {} |
|
856 for st, rev, fns in walkchangerevs(ui, repo, repo.getcwd(), pats, opts): |
|
857 if st == 'window': |
|
858 incrementing = rev |
|
859 matches.clear() |
|
860 elif st == 'add': |
|
861 change = repo.changelog.read(repo.lookup(str(rev))) |
|
862 mf = repo.manifest.read(change[0]) |
|
863 matches[rev] = {} |
|
864 for fn in fns: |
|
865 fstate.setdefault(fn, {}) |
|
866 try: |
|
867 grepbody(fn, rev, getfile(fn).read(mf[fn])) |
|
868 except KeyError: |
|
869 pass |
|
870 elif st == 'iter': |
|
871 states = matches[rev].items() |
|
872 states.sort() |
|
873 for fn, m in states: |
|
874 if incrementing or fstate[fn]: |
|
875 display(fn, rev, m, fstate[fn]) |
|
876 fstate[fn] = m |
|
877 prev[fn] = rev |
|
878 |
|
879 if not incrementing: |
|
880 fstate = fstate.items() |
|
881 fstate.sort() |
|
882 for fn, state in fstate: |
|
883 display(fn, rev, {}, state) |
721 |
884 |
722 def heads(ui, repo, **opts): |
885 def heads(ui, repo, **opts): |
723 """show current repository heads""" |
886 """show current repository heads""" |
724 heads = repo.changelog.heads() |
887 heads = repo.changelog.heads() |
725 br = None |
888 br = None |
846 else: |
1009 else: |
847 ui.write(rel, end) |
1010 ui.write(rel, end) |
848 |
1011 |
849 def log(ui, repo, *pats, **opts): |
1012 def log(ui, repo, *pats, **opts): |
850 """show revision history of entire repository or files""" |
1013 """show revision history of entire repository or files""" |
851 # This code most commonly needs to iterate backwards over the |
1014 class dui: |
852 # history it is interested in. This has awful (quadratic-looking) |
1015 # Implement and delegate some ui protocol. Save hunks of |
853 # performance, so we use iterators that walk forwards through |
1016 # output for later display in the desired order. |
854 # windows of revisions, yielding revisions in reverse order, while |
1017 def __init__(self, ui): |
855 # walking the windows backwards. |
1018 self.ui = ui |
|
1019 self.hunk = {} |
|
1020 def bump(self, rev): |
|
1021 self.rev = rev |
|
1022 self.hunk[rev] = [] |
|
1023 def note(self, *args): |
|
1024 if self.verbose: self.write(*args) |
|
1025 def status(self, *args): |
|
1026 if not self.quiet: self.write(*args) |
|
1027 def write(self, *args): |
|
1028 self.hunk[self.rev].append(args) |
|
1029 def __getattr__(self, key): |
|
1030 return getattr(self.ui, key) |
856 cwd = repo.getcwd() |
1031 cwd = repo.getcwd() |
857 if not pats and cwd: |
1032 if not pats and cwd: |
858 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
1033 opts['include'] = [os.path.join(cwd, i) for i in opts['include']] |
859 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
1034 opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] |
860 files, matchfn, anypats = matchpats(repo, (pats and cwd) or '', |
1035 for st, rev, fns in walkchangerevs(ui, repo, (pats and cwd) or '', pats, |
861 pats, opts) |
1036 opts): |
862 revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) |
1037 if st == 'window': |
863 wanted = {} |
|
864 slowpath = anypats |
|
865 window = 300 |
|
866 if not slowpath and not files: |
|
867 # No files, no patterns. Display all revs. |
|
868 wanted = dict(zip(revs, revs)) |
|
869 if not slowpath: |
|
870 # Only files, no patterns. Check the history of each file. |
|
871 def filerevgen(filelog): |
|
872 for i in xrange(filelog.count() - 1, -1, -window): |
|
873 revs = [] |
|
874 for j in xrange(max(0, i - window), i + 1): |
|
875 revs.append(filelog.linkrev(filelog.node(j))) |
|
876 revs.reverse() |
|
877 for rev in revs: |
|
878 yield rev |
|
879 |
|
880 minrev, maxrev = min(revs), max(revs) |
|
881 for filelog in map(repo.file, files): |
|
882 # A zero count may be a directory or deleted file, so |
|
883 # try to find matching entries on the slow path. |
|
884 if filelog.count() == 0: |
|
885 slowpath = True |
|
886 break |
|
887 for rev in filerevgen(filelog): |
|
888 if rev <= maxrev: |
|
889 if rev < minrev: break |
|
890 wanted[rev] = 1 |
|
891 if slowpath: |
|
892 # The slow path checks files modified in every changeset. |
|
893 def mfrevgen(): |
|
894 for i in xrange(repo.changelog.count() - 1, -1, -window): |
|
895 for j in xrange(max(0, i - window), i + 1): |
|
896 yield j, repo.changelog.read(repo.lookup(str(j)))[3] |
|
897 |
|
898 for rev, mf in mfrevgen(): |
|
899 if filter(matchfn, mf): |
|
900 wanted[rev] = 1 |
|
901 |
|
902 def changerevgen(): |
|
903 class dui: |
|
904 # Implement and delegate some ui protocol. Save hunks of |
|
905 # output for later display in the desired order. |
|
906 def __init__(self, ui): |
|
907 self.ui = ui |
|
908 self.hunk = {} |
|
909 def bump(self, rev): |
|
910 self.rev = rev |
|
911 self.hunk[rev] = [] |
|
912 def note(self, *args): |
|
913 if self.verbose: self.write(*args) |
|
914 def status(self, *args): |
|
915 if not self.quiet: self.write(*args) |
|
916 def write(self, *args): |
|
917 self.hunk[self.rev].append(args) |
|
918 def __getattr__(self, key): |
|
919 return getattr(self.ui, key) |
|
920 for i in xrange(0, len(revs), window): |
|
921 nrevs = [rev for rev in revs[i : min(i + window, len(revs))] |
|
922 if rev in wanted] |
|
923 srevs = list(nrevs) |
|
924 srevs.sort() |
|
925 du = dui(ui) |
1038 du = dui(ui) |
926 for rev in srevs: |
1039 elif st == 'add': |
927 du.bump(rev) |
1040 du.bump(rev) |
928 yield rev, du |
1041 show_changeset(du, repo, rev) |
929 for rev in nrevs: |
1042 if opts['patch']: |
930 for args in du.hunk[rev]: |
1043 changenode = repo.changelog.node(rev) |
931 ui.write(*args) |
1044 prev, other = repo.changelog.parents(changenode) |
932 |
1045 dodiff(du, du, repo, prev, changenode, fns) |
933 for rev, dui in changerevgen(): |
1046 du.write("\n\n") |
934 show_changeset(dui, repo, rev) |
1047 elif st == 'iter': |
935 if opts['patch']: |
1048 for args in du.hunk[rev]: |
936 changenode = repo.changelog.node(rev) |
1049 ui.write(*args) |
937 prev, other = repo.changelog.parents(changenode) |
|
938 dodiff(dui, dui, repo, prev, changenode, files) |
|
939 dui.write("\n\n") |
|
940 |
1050 |
941 def manifest(ui, repo, rev=None): |
1051 def manifest(ui, repo, rev=None): |
942 """output the latest or given revision of the project manifest""" |
1052 """output the latest or given revision of the project manifest""" |
943 if rev: |
1053 if rev: |
944 try: |
1054 try: |
1408 "forget": |
1518 "forget": |
1409 (forget, |
1519 (forget, |
1410 [('I', 'include', [], 'include path in search'), |
1520 [('I', 'include', [], 'include path in search'), |
1411 ('X', 'exclude', [], 'exclude path from search')], |
1521 ('X', 'exclude', [], 'exclude path from search')], |
1412 "hg forget [OPTION]... FILE..."), |
1522 "hg forget [OPTION]... FILE..."), |
|
1523 "grep": (grep, |
|
1524 [('0', 'print0', None, 'terminate file names with NUL'), |
|
1525 ('I', 'include', [], 'include path in search'), |
|
1526 ('X', 'exclude', [], 'include path in search'), |
|
1527 ('Z', 'null', None, 'terminate file names with NUL'), |
|
1528 ('a', 'all-revs', '', 'search all revs'), |
|
1529 ('e', 'regexp', '', 'pattern to search for'), |
|
1530 ('f', 'full-path', None, 'print complete paths'), |
|
1531 ('i', 'ignore-case', None, 'ignore case when matching'), |
|
1532 ('l', 'files-with-matches', None, 'print names of files with matches'), |
|
1533 ('n', 'line-number', '', 'print line numbers'), |
|
1534 ('r', 'rev', [], 'search in revision rev'), |
|
1535 ('s', 'no-messages', None, 'do not print error messages'), |
|
1536 ('v', 'invert-match', None, 'select non-matching lines')], |
|
1537 "hg grep [options] [pat] [files]"), |
1413 "heads": |
1538 "heads": |
1414 (heads, |
1539 (heads, |
1415 [('b', 'branches', None, 'find branch info')], |
1540 [('b', 'branches', None, 'find branch info')], |
1416 'hg heads [-b]'), |
1541 'hg heads [-b]'), |
1417 "help": (help_, [], 'hg help [COMMAND]'), |
1542 "help": (help_, [], 'hg help [COMMAND]'), |