Bug 1397406 - Add Repository method to determine if working directory clean; r=dustin
authorGregory Szorc <gps@mozilla.com>
Wed, 06 Sep 2017 12:15:12 -0700
changeset 660397 da68a5ff28d0cd132e031b1a67577a19274c01df
parent 660396 caccc7951f5bd8f0d4b840da0a7b9ea4a5d4df84
child 660398 e9416a307987f144f994dfeb6ed00b3277068279
push id78390
push userbmo:emilio@crisal.io
push dateWed, 06 Sep 2017 23:04:15 +0000
reviewersdustin
bugs1397406
milestone57.0a1
Bug 1397406 - Add Repository method to determine if working directory clean; r=dustin This is generally useful functionality to have. A consume will be introduced in an upcoming commit. MozReview-Commit-ID: 4arTMfJSiEC
python/mozversioncontrol/mozversioncontrol/__init__.py
--- a/python/mozversioncontrol/mozversioncontrol/__init__.py
+++ b/python/mozversioncontrol/mozversioncontrol/__init__.py
@@ -116,16 +116,28 @@ class Repository(object):
     def forget_add_remove_files(self, path):
         '''Undo the effects of a previous add_remove_files call for `path`.
         '''
 
     @abc.abstractmethod
     def get_files_in_working_directory(self):
         """Obtain a list of managed files in the working directory."""
 
+    @abc.abstractmethod
+    def working_directory_clean(self, untracked=False, ignored=False):
+        """Determine if the working directory is free of modifications.
+
+        Returns True if the working directory does not have any file
+        modifications. False otherwise.
+
+        By default, untracked and ignored files are not considered. If
+        ``untracked`` or ``ignored`` are set, they influence the clean check
+        to factor these file classes into consideration.
+        """
+
 
 class HgRepository(Repository):
     '''An implementation of `Repository` for Mercurial repositories.'''
     def __init__(self, path, hg='hg'):
         import hglib.client
 
         super(HgRepository, self).__init__(path, tool=hg)
         self._env[b'HGPLAIN'] = b'1'
@@ -202,16 +214,28 @@ class HgRepository(Repository):
         self._run('forget', path)
 
     def get_files_in_working_directory(self):
         # Can return backslashes on Windows. Normalize to forward slashes.
         return list(p.replace('\\', '/') for p in
                     self._run_in_client([b'files', b'-0']).split(b'\0')
                     if p)
 
+    def working_directory_clean(self, untracked=False, ignored=False):
+        args = [b'status', b'\0', b'--modified', b'--added', b'--removed',
+                b'--deleted']
+        if untracked:
+            args.append(b'--unknown')
+        if ignored:
+            args.append(b'--ignored')
+
+        # If output is empty, there are no entries of requested status, which
+        # means we are clean.
+        return not len(self._run_in_client(args).strip())
+
 
 class GitRepository(Repository):
     '''An implementation of `Repository` for Git repositories.'''
     def __init__(self, path, git='git'):
         super(GitRepository, self).__init__(path, tool=git)
 
     @property
     def name(self):
@@ -231,16 +255,25 @@ class GitRepository(Repository):
         self._run('add', path)
 
     def forget_add_remove_files(self, path):
         self._run('reset', path)
 
     def get_files_in_working_directory(self):
         return self._run('ls-files', '-z').split(b'\0')
 
+    def working_directory_clean(self, untracked=False, ignored=False):
+        args = ['status', '--porcelain']
+        if untracked:
+            args.append('--untracked-files')
+        if ignored:
+            args.append('--ignored')
+
+        return not len(self._run(*args).strip())
+
 
 class InvalidRepoPath(Exception):
     """Represents a failure to find a VCS repo at a specified path."""
 
 
 def get_repository_object(path, hg='hg', git='git'):
     '''Get a repository object for the repository at `path`.
     If `path` is not a known VCS repository, raise an exception.