Bug 1361972 - [mozlint] Add ability to only lint staged changes to --workdir with git draft
authorAndrew Halberstadt <ahalberstadt@mozilla.com>
Fri, 30 Jun 2017 18:29:31 -0700
changeset 604823 e429ad5080a7bf8fa592fd7506b9dcc6b3ca798e
parent 604727 018b3829d0a7f6944a5ae1489f3a64c9e2893909
child 604824 d1ca75e77a10e62bf21c8789db363c2f290d3f5a
push id67196
push userahalberstadt@mozilla.com
push dateThu, 06 Jul 2017 13:45:23 +0000
bugs1361972
milestone56.0a1
Bug 1361972 - [mozlint] Add ability to only lint staged changes to --workdir with git MozReview-Commit-ID: DUxCKN2fiag
python/mozlint/mozlint/cli.py
python/mozlint/mozlint/roller.py
python/mozlint/mozlint/vcs.py
python/mozlint/test/test_vcs.py
--- a/python/mozlint/mozlint/cli.py
+++ b/python/mozlint/mozlint/cli.py
@@ -44,34 +44,44 @@ class MozlintParser(ArgumentParser):
          {'const': 'default',
           'nargs': '?',
           'help': "Lint files touched by commits that are not on the remote repository. "
                   "Without arguments, finds the default remote that would be pushed to. "
                   "The remote branch can also be specified manually. Works with "
                   "mercurial or git."
           }],
         [['-w', '--workdir'],
-         {'default': False,
-          'action': 'store_true',
+         {'const': 'all',
+          'nargs': '?',
+          'choices': ['staged', 'all'],
           'help': "Lint files touched by changes in the working directory "
-                  "(i.e haven't been committed yet). Works with mercurial or git.",
+                  "(i.e haven't been committed yet). On git, --workdir=staged "
+                  "can be used to only consider staged files. Works with "
+                  "mercurial or git.",
           }],
         [['extra_args'],
          {'nargs': REMAINDER,
           'help': "Extra arguments that will be forwarded to the underlying linter.",
           }],
     ]
 
     def __init__(self, **kwargs):
         ArgumentParser.__init__(self, usage=self.__doc__, **kwargs)
 
         for cli, args in self.arguments:
             self.add_argument(*cli, **args)
 
     def parse_known_args(self, *args, **kwargs):
+        # Allow '-wo' or '-ow' as shorthand for both --workdir and --outgoing.
+        for token in ('-wo', '-ow'):
+            if token in args[0]:
+                i = args[0].index(token)
+                args[0].pop(i)
+                args[0][i:i] = [token[:2], '-' + token[2]]
+
         # This is here so the eslint mach command doesn't lose 'extra_args'
         # when using mach's dispatch functionality.
         args, extra = ArgumentParser.parse_known_args(self, *args, **kwargs)
         args.extra_args = extra
         return args, extra
 
 
 def find_linters(linters=None):
--- a/python/mozlint/mozlint/roller.py
+++ b/python/mozlint/mozlint/roller.py
@@ -106,17 +106,17 @@ class LintRoller(object):
         elif isinstance(paths, (list, tuple)):
             paths = set(paths)
 
         if not self.linters:
             raise LintersNotConfigured
 
         # Calculate files from VCS
         if workdir:
-            paths.update(self.vcs.by_workdir())
+            paths.update(self.vcs.by_workdir(workdir))
         if outgoing:
             paths.update(self.vcs.by_outgoing(outgoing))
 
         if not paths and (workdir or outgoing):
             print("warning: no files linted")
             return {}
 
         paths = paths or ['.']
--- a/python/mozlint/mozlint/vcs.py
+++ b/python/mozlint/mozlint/vcs.py
@@ -39,31 +39,31 @@ class VCSHelper(object):
             files = subprocess.check_output(cmd, stderr=subprocess.STDOUT).split()
         except subprocess.CalledProcessError as e:
             if e.output:
                 print(' '.join(cmd))
                 print(e.output)
             return []
         return [os.path.join(self.root, f) for f in files if f]
 
-    def by_workdir(self, workdir):
+    def by_workdir(self, mode):
         return []
 
     def by_outgoing(self, dest='default'):
         return []
 
 
 class HgHelper(VCSHelper):
     """A helper to find files to lint from Mercurial."""
 
     def by_outgoing(self, dest='default'):
         return self.run(['hg', 'outgoing', '--quiet', '--template',
                          "{file_mods % '\\n{file}'}{file_adds % '\\n{file}'}", '-r', '.', dest])
 
-    def by_workdir(self):
+    def by_workdir(self, mode):
         return self.run(['hg', 'status', '-amn'])
 
 
 class GitHelper(VCSHelper):
     """A helper to find files to lint from Git."""
     _default = None
 
     @property
@@ -90,17 +90,22 @@ class GitHelper(VCSHelper):
             if not self.default:
                 print("warning: could not find default push, specify a remote for --outgoing")
                 return []
             dest = self.default
         comparing = '{}..HEAD'.format(self.default)
         return self.run(['git', 'log', '--name-only', '--diff-filter=AM',
                          '--oneline', '--pretty=format:', comparing])
 
-    def by_workdir(self):
-        return self.run(['git', 'diff', '--name-only', '--diff-filter=AM', 'HEAD'])
+    def by_workdir(self, mode):
+        cmd = ['git', 'diff', '--name-only', '--diff-filter=AM']
+        if mode == 'staged':
+            cmd.append('--cached')
+        else:
+            cmd.append('HEAD')
+        return self.run(cmd)
 
 
 vcs_class = {
     'git': GitHelper,
     'hg': HgHelper,
     'none': VCSHelper,
 }
--- a/python/mozlint/test/test_vcs.py
+++ b/python/mozlint/test/test_vcs.py
@@ -97,21 +97,26 @@ def test_vcs_helper(repo):
     vcs = VCSHelper.create()
     assert vcs.__class__ == vcs_class[repo.vcs]
     assert vcs.root == repo.strpath
 
     remotepath = '../remoterepo' if repo.vcs == 'hg' else 'upstream/master'
 
     next(repo.setup)
 
-    assert_files(vcs.by_workdir(), ['bar', 'baz'])
+    assert_files(vcs.by_workdir('all'), ['bar', 'baz'])
+    if repo.vcs == 'git':
+        assert_files(vcs.by_workdir('staged'), ['baz'])
+    elif repo.vcs == 'hg':
+        assert_files(vcs.by_workdir('staged'), ['bar', 'baz'])
     assert_files(vcs.by_outgoing(), [])
     assert_files(vcs.by_outgoing(remotepath), [])
 
     next(repo.setup)
 
-    assert_files(vcs.by_workdir(), [])
+    assert_files(vcs.by_workdir('all'), [])
+    assert_files(vcs.by_workdir('staged'), [])
     assert_files(vcs.by_outgoing(), ['bar', 'baz'])
     assert_files(vcs.by_outgoing(remotepath), ['bar', 'baz'])
 
 
 if __name__ == '__main__':
     sys.exit(pytest.main(['--verbose', __file__]))