Bug 1313194 - Add --outgoing argument to mozlint. r?alhal draft
authorFrancesco Pischedda <francesco.pischedda@gmail.com>
Thu, 26 Jan 2017 17:50:34 +0100
changeset 467183 76b5c1038a3a1aef3e50c55e634a13f91fe1eba4
parent 466816 d92fd6b6d6bfc5b566222ae2957e55772d60151a
child 543627 e6325ef37ac5a2bffb9232c5330dad90525f50fb
push id43112
push userbmo:francesco.pischedda@gmail.com
push dateFri, 27 Jan 2017 08:19:09 +0000
reviewersalhal
bugs1313194
milestone54.0a1
Bug 1313194 - Add --outgoing argument to mozlint. r?alhal Was: Replace mozlint's --rev with a --outgoing argument MozReview-Commit-ID: 5YcRWbc1dR4
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/roller.py
python/mozlint/mozlint/vcs.py
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -1,18 +1,17 @@
 # This Source Code Form is subject to the terms of the Mozilla Public
 # License, v. 2.0. If a copy of the MPL was not distributed with this
 # file, You can obtain one at http://mozilla.org/MPL/2.0/.
 
 from __future__ import print_function, unicode_literals
 
 import os
 import sys
-from argparse import ArgumentParser, REMAINDER
-
+from argparse import REMAINDER, ArgumentParser
 
 SEARCH_PATHS = []
 
 
 class MozlintParser(ArgumentParser):
     arguments = [
         [['paths'],
          {'nargs': '*',
@@ -41,16 +40,23 @@ class MozlintParser(ArgumentParser):
                   "testing a directory that otherwise wouldn't be run, "
                   "without needing to modify the config file.",
           }],
         [['-r', '--rev'],
          {'default': None,
           'help': "Lint files touched by the given revision(s). Works with "
                   "mercurial or git."
           }],
+        [['-o', '--outgoing'],
+         {'const': 'default',
+          'nargs': '?',
+          'help': "Lint files touched by commits that are not on the remote repository."
+                  "If you are using git please specify which remote you want to compare to."
+                  "Works with mercurial or git."
+          }],
         [['-w', '--workdir'],
          {'default': False,
           'action': 'store_true',
           'help': "Lint files touched by changes in the working directory "
                   "(i.e haven't been committed yet). Works with mercurial or git.",
           }],
         [['extra_args'],
          {'nargs': REMAINDER,
@@ -86,24 +92,25 @@ def find_linters(linters=None):
 
             if linters and name not in linters:
                 continue
 
             lints.append(os.path.join(search_path, f))
     return lints
 
 
-def run(paths, linters, fmt, rev, workdir, **lintargs):
+def run(paths, linters, fmt, rev, outgoing, workdir, **lintargs):
     from mozlint import LintRoller, formatters
 
     lint = LintRoller(**lintargs)
     lint.read(find_linters(linters))
 
     # run all linters
-    results = lint.roll(paths, rev=rev, workdir=workdir)
+    results = lint.roll(paths, rev=rev, outgoing=outgoing,
+                        workdir=workdir)
 
     formatter = formatters.get(fmt)
 
     # Encode output with 'replace' to avoid UnicodeEncodeErrors on
     # environments that aren't using utf-8.
     print(formatter(results, failed=lint.failed).encode(
         sys.stdout.encoding or 'ascii', 'replace'))
     return 1 if results or lint.failed else 0
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -4,26 +4,22 @@
 
 from __future__ import unicode_literals
 
 import os
 import signal
 import sys
 import traceback
 from collections import defaultdict
+from multiprocessing import Manager, Pool, cpu_count
 from Queue import Empty
-from multiprocessing import (
-    Manager,
-    Pool,
-    cpu_count,
-)
 
 from .errors import LintersNotConfigured
+from .parser import Parser
 from .types import supported_types
-from .parser import Parser
 from .vcs import VCSFiles
 
 
 def _run_linters(queue, paths, **lintargs):
     parse = Parser()
     results = defaultdict(list)
     failed = []
 
@@ -93,21 +89,22 @@ class LintRoller(object):
         :param paths: A path or iterable of paths to linter definitions.
         """
         if isinstance(paths, basestring):
             paths = (paths,)
 
         for path in paths:
             self.linters.append(self.parse(path))
 
-    def roll(self, paths=None, rev=None, workdir=None, num_procs=None):
+    def roll(self, paths=None, rev=None, outgoing=None, workdir=None, num_procs=None):
         """Run all of the registered linters against the specified file paths.
 
         :param paths: An iterable of files and/or directories to lint.
         :param rev: Lint all files touched by the specified revision.
+        :param outgoing: Lint files touched by commits that are not on the remote repository.
         :param workdir: Lint all files touched in the working directory.
         :param num_procs: The number of processes to use. Default: cpu count
         :return: A dictionary with file names as the key, and a list of
                  :class:`~result.ResultContainer`s as the value.
         """
         paths = paths or []
         if isinstance(paths, basestring):
             paths = [paths]
@@ -115,16 +112,19 @@ class LintRoller(object):
         if not self.linters:
             raise LintersNotConfigured
 
         # Calculate files from VCS
         if rev:
             paths.extend(self.vcs.by_rev(rev))
         if workdir:
             paths.extend(self.vcs.by_workdir())
+        if outgoing:
+            paths.extend(self.vcs.outgoing(outgoing))
+
         paths = paths or ['.']
         paths = map(os.path.abspath, paths)
 
         # Set up multiprocessing
         m = Manager()
         queue = m.Queue()
 
         for linter in self.linters:
--- a/python/mozlint/mozlint/vcs.py
+++ b/python/mozlint/mozlint/vcs.py
@@ -49,14 +49,27 @@ class VCSFiles(object):
 
     def by_rev(self, rev):
         if self.is_hg:
             return self._run(['hg', 'log', '--template', '{files % "\\n{file}"}', '-r', rev])
         elif self.is_git:
             return self._run(['git', 'diff', '--name-only', rev])
         return []
 
+    def outgoing(self, destination='default'):
+        if self.is_hg:
+            return self._run(['hg', 'outgoing', '--quiet', '-r .',
+                              destination, '--template',
+                              '{files % "\n{file}"}'])
+        elif self.is_git:
+            if destination == 'default':
+                comparing = 'origin/master..HEAD'
+            else:
+                comparing = '{}..HEAD'.format(destination)
+            return self._run(['git', 'log', '--name-only', comparing])
+        return []
+
     def by_workdir(self):
         if self.is_hg:
             return self._run(['hg', 'status', '-amn'])
         elif self.is_git:
             return self._run(['git', 'diff', '--name-only'])
         return []