670 return changesetprinter(ui, repo, *postargs) |
674 return changesetprinter(ui, repo, *postargs) |
671 |
675 |
672 return changesettemplater(ui, repo, spec, *postargs) |
676 return changesettemplater(ui, repo, spec, *postargs) |
673 |
677 |
674 |
678 |
675 def _makematcher(repo, revs, pats, opts): |
679 @attr.s |
|
680 class walkopts(object): |
|
681 """Options to configure a set of revisions and file matcher factory |
|
682 to scan revision/file history |
|
683 """ |
|
684 |
|
685 # raw command-line parameters, which a matcher will be built from |
|
686 pats = attr.ib() # type: List[bytes] |
|
687 opts = attr.ib() # type: Dict[bytes, Any] |
|
688 |
|
689 |
|
690 def parseopts(ui, pats, opts): |
|
691 # type: (Any, List[bytes], Dict[bytes, Any]) -> walkopts |
|
692 """Parse log command options into walkopts |
|
693 |
|
694 The returned walkopts will be passed in to getrevs(). |
|
695 """ |
|
696 return walkopts(pats=pats, opts=opts) |
|
697 |
|
698 |
|
699 def _makematcher(repo, revs, wopts): |
676 """Build matcher and expanded patterns from log options |
700 """Build matcher and expanded patterns from log options |
677 |
701 |
678 If --follow, revs are the revisions to follow from. |
702 If --follow, revs are the revisions to follow from. |
679 |
703 |
680 Returns (match, pats, slowpath) where |
704 Returns (match, pats, slowpath) where |
685 # pats/include/exclude are passed to match.match() directly in |
709 # pats/include/exclude are passed to match.match() directly in |
686 # _matchfiles() revset but walkchangerevs() builds its matcher with |
710 # _matchfiles() revset but walkchangerevs() builds its matcher with |
687 # scmutil.match(). The difference is input pats are globbed on |
711 # scmutil.match(). The difference is input pats are globbed on |
688 # platforms without shell expansion (windows). |
712 # platforms without shell expansion (windows). |
689 wctx = repo[None] |
713 wctx = repo[None] |
690 match, pats = scmutil.matchandpats(wctx, pats, opts) |
714 match, pats = scmutil.matchandpats(wctx, wopts.pats, wopts.opts) |
691 slowpath = match.anypats() or (not match.always() and opts.get(b'removed')) |
715 slowpath = match.anypats() or ( |
|
716 not match.always() and wopts.opts.get(b'removed') |
|
717 ) |
692 if not slowpath: |
718 if not slowpath: |
693 follow = opts.get(b'follow') or opts.get(b'follow_first') |
719 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first') |
694 if follow and opts.get(b'rev'): |
720 if follow and wopts.opts.get(b'rev'): |
695 # There may be the case that a path doesn't exist in some (but |
721 # There may be the case that a path doesn't exist in some (but |
696 # not all) of the specified start revisions, but let's consider |
722 # not all) of the specified start revisions, but let's consider |
697 # the path is valid. Missing files will be warned by the matcher. |
723 # the path is valid. Missing files will be warned by the matcher. |
698 startctxs = [repo[r] for r in revs] |
724 startctxs = [repo[r] for r in revs] |
699 for f in match.files(): |
725 for f in match.files(): |
798 b'prune': (b'ancestors(%s)', b'not %lr'), |
824 b'prune': (b'ancestors(%s)', b'not %lr'), |
799 b'user': (b'user(%s)', b'%lr'), |
825 b'user': (b'user(%s)', b'%lr'), |
800 } |
826 } |
801 |
827 |
802 |
828 |
803 def _makerevset(repo, pats, slowpath, opts): |
829 def _makerevset(repo, wopts, slowpath): |
804 """Return a revset string built from log options and file patterns""" |
830 """Return a revset string built from log options and file patterns""" |
805 opts = dict(opts) |
831 opts = dict(wopts.opts) |
806 # follow or not follow? |
832 # follow or not follow? |
807 follow = opts.get(b'follow') or opts.get(b'follow_first') |
833 follow = opts.get(b'follow') or opts.get(b'follow_first') |
808 |
834 |
809 # branch and only_branch are really aliases and must be handled at |
835 # branch and only_branch are really aliases and must be handled at |
810 # the same time |
836 # the same time |
819 # level. For instance "-I a -X b" matches a revision touching |
845 # level. For instance "-I a -X b" matches a revision touching |
820 # "a" and "b" while "file(a) and not file(b)" does |
846 # "a" and "b" while "file(a) and not file(b)" does |
821 # not. Besides, filesets are evaluated against the working |
847 # not. Besides, filesets are evaluated against the working |
822 # directory. |
848 # directory. |
823 matchargs = [b'r:', b'd:relpath'] |
849 matchargs = [b'r:', b'd:relpath'] |
824 for p in pats: |
850 for p in wopts.pats: |
825 matchargs.append(b'p:' + p) |
851 matchargs.append(b'p:' + p) |
826 for p in opts.get(b'include', []): |
852 for p in opts.get(b'include', []): |
827 matchargs.append(b'i:' + p) |
853 matchargs.append(b'i:' + p) |
828 for p in opts.get(b'exclude', []): |
854 for p in opts.get(b'exclude', []): |
829 matchargs.append(b'x:' + p) |
855 matchargs.append(b'x:' + p) |
830 opts[b'_matchfiles'] = matchargs |
856 opts[b'_matchfiles'] = matchargs |
831 elif not follow: |
857 elif not follow: |
832 opts[b'_patslog'] = list(pats) |
858 opts[b'_patslog'] = list(wopts.pats) |
833 |
859 |
834 expr = [] |
860 expr = [] |
835 for op, val in sorted(pycompat.iteritems(opts)): |
861 for op, val in sorted(pycompat.iteritems(opts)): |
836 if not val: |
862 if not val: |
837 continue |
863 continue |
852 else: |
878 else: |
853 expr = None |
879 expr = None |
854 return expr |
880 return expr |
855 |
881 |
856 |
882 |
857 def _initialrevs(repo, opts): |
883 def _initialrevs(repo, wopts): |
858 """Return the initial set of revisions to be filtered or followed""" |
884 """Return the initial set of revisions to be filtered or followed""" |
859 follow = opts.get(b'follow') or opts.get(b'follow_first') |
885 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first') |
860 if opts.get(b'rev'): |
886 if wopts.opts.get(b'rev'): |
861 revs = scmutil.revrange(repo, opts[b'rev']) |
887 revs = scmutil.revrange(repo, wopts.opts[b'rev']) |
862 elif follow and repo.dirstate.p1() == nullid: |
888 elif follow and repo.dirstate.p1() == nullid: |
863 revs = smartset.baseset() |
889 revs = smartset.baseset() |
864 elif follow: |
890 elif follow: |
865 revs = repo.revs(b'.') |
891 revs = repo.revs(b'.') |
866 else: |
892 else: |
867 revs = smartset.spanset(repo) |
893 revs = smartset.spanset(repo) |
868 revs.reverse() |
894 revs.reverse() |
869 return revs |
895 return revs |
870 |
896 |
871 |
897 |
872 def getrevs(repo, pats, opts): |
898 def getrevs(repo, wopts): |
873 # type: (Any, Any, Any) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]] |
899 # type: (Any, walkopts) -> Tuple[smartset.abstractsmartset, Optional[changesetdiffer]] |
874 """Return (revs, differ) where revs is a smartset |
900 """Return (revs, differ) where revs is a smartset |
875 |
901 |
876 differ is a changesetdiffer with pre-configured file matcher. |
902 differ is a changesetdiffer with pre-configured file matcher. |
877 """ |
903 """ |
878 follow = opts.get(b'follow') or opts.get(b'follow_first') |
904 follow = wopts.opts.get(b'follow') or wopts.opts.get(b'follow_first') |
879 followfirst = opts.get(b'follow_first') |
905 followfirst = wopts.opts.get(b'follow_first') |
880 limit = getlimit(opts) |
906 limit = getlimit(wopts.opts) |
881 revs = _initialrevs(repo, opts) |
907 revs = _initialrevs(repo, wopts) |
882 if not revs: |
908 if not revs: |
883 return smartset.baseset(), None |
909 return smartset.baseset(), None |
884 match, pats, slowpath = _makematcher(repo, revs, pats, opts) |
910 match, pats, slowpath = _makematcher(repo, revs, wopts) |
|
911 wopts = attr.evolve(wopts, pats=pats) |
|
912 |
885 filematcher = None |
913 filematcher = None |
886 if follow: |
914 if follow: |
887 if slowpath or match.always(): |
915 if slowpath or match.always(): |
888 revs = dagop.revancestors(repo, revs, followfirst=followfirst) |
916 revs = dagop.revancestors(repo, revs, followfirst=followfirst) |
889 else: |
917 else: |
890 revs, filematcher = _fileancestors(repo, revs, match, followfirst) |
918 revs, filematcher = _fileancestors(repo, revs, match, followfirst) |
891 revs.reverse() |
919 revs.reverse() |
892 if filematcher is None: |
920 if filematcher is None: |
893 filematcher = _makenofollowfilematcher(repo, pats, opts) |
921 filematcher = _makenofollowfilematcher(repo, wopts.pats, wopts.opts) |
894 if filematcher is None: |
922 if filematcher is None: |
895 |
923 |
896 def filematcher(ctx): |
924 def filematcher(ctx): |
897 return match |
925 return match |
898 |
926 |
899 expr = _makerevset(repo, pats, slowpath, opts) |
927 expr = _makerevset(repo, wopts, slowpath) |
900 if opts.get(b'graph'): |
928 if wopts.opts.get(b'graph'): |
901 if repo.ui.configbool(b'experimental', b'log.topo'): |
929 if repo.ui.configbool(b'experimental', b'log.topo'): |
902 if not revs.istopo(): |
930 if not revs.istopo(): |
903 revs = dagop.toposort(revs, repo.changelog.parentrevs) |
931 revs = dagop.toposort(revs, repo.changelog.parentrevs) |
904 # TODO: try to iterate the set lazily |
932 # TODO: try to iterate the set lazily |
905 revs = revset.baseset(list(revs), istopo=True) |
933 revs = revset.baseset(list(revs), istopo=True) |