autotest: Skip updating the repo when it is already up-to-date

Currently, the server deployment will fail when either autotest or
chromite repo is already up-to-date. Add a check before updating the
repo to see whether it is already up-to-date. If yes, skip it.

BUG=chromium:715306
TEST=unittest

Change-Id: I68ca235cabb961663d6fce95dd10c43e23520f8b
Reviewed-on: https://chromium-review.googlesource.com/497593
Commit-Ready: Shuqian Zhao <shuqianz@chromium.org>
Tested-by: Shuqian Zhao <shuqianz@chromium.org>
Reviewed-by: Aviv Keshet <akeshet@chromium.org>
diff --git a/site_utils/automated_deploy.py b/site_utils/automated_deploy.py
index ce080d0..770d9a1 100755
--- a/site_utils/automated_deploy.py
+++ b/site_utils/automated_deploy.py
@@ -98,25 +98,38 @@
     @param hash_to_rebase: Hash to rebase the prod branch to. If it is None,
                            prod branch will rebase to prod-next branch.
 
-    @returns the range of the pushed commits as a string. E.g 123...345
+    @returns the range of the pushed commits as a string. E.g 123...345. If the
+        prod branch is already up-to-date, return None.
     @raises subprocess.CalledProcessError on a command failure.
     """
     with infra.chdir(repo_dir):
         print 'Updating %s prod branch.' % repo
         rebase_to = hash_to_rebase if hash_to_rebase else 'origin/prod-next'
-        infra.local_runner('git rebase %s prod' % rebase_to, stream_output=True)
-        result = infra.local_runner('git push origin prod', stream_output=True)
-        print 'Successfully pushed %s prod branch!\n' % repo
+        # Check whether prod branch is already up-to-date, which means there is
+        # no changes since last push.
+        print 'Detecting new changes since last push...'
+        diff = infra.local_runner('git log prod..%s --oneline' % rebase_to,
+                                  stream_output=True)
+        if diff:
+            print 'Find new changes, will update prod branch...'
+            infra.local_runner('git rebase %s prod' % rebase_to,
+                               stream_output=True)
+            result = infra.local_runner('git push origin prod',
+                                        stream_output=True)
+            print 'Successfully pushed %s prod branch!\n' % repo
 
-        # Get the pushed commit range, which is used to get the pushed commits
-        # using git log E.g. 123..456, then run git log --oneline 123..456.
-        grep = re.search('(\w)*\.\.(\w)*', result)
+            # Get the pushed commit range, which is used to get pushed commits
+            # using git log E.g. 123..456, then run git log --oneline 123..456.
+            grep = re.search('(\w)*\.\.(\w)*', result)
 
-    if not grep:
-        raise AutoDeployException(
-            'Fail to get pushed commits for repo %s from git push log: %s' %
-            (repo, result))
-    return grep.group(0)
+            if not grep:
+                raise AutoDeployException(
+                    'Fail to get pushed commits for repo %s from git log: %s' %
+                    (repo, result))
+            return grep.group(0)
+        else:
+            print 'No new %s changes found since last push.' % repo
+            return None
 
 
 def get_pushed_commits(repo, repo_dir, pushed_commits_range):
@@ -132,6 +145,9 @@
     @raises subprocess.CalledProcessError on a command failure.
     """
     print 'Getting pushed CLs for %s repo.' % repo
+    if not pushed_commits_range:
+        return '\n%s:\nNo new changes since last push.' % repo
+
     with infra.chdir(repo_dir):
         get_commits_cmd = 'git log --oneline %s' % pushed_commits_range
         if repo == 'autotest':
diff --git a/site_utils/automated_deploy_unittest.py b/site_utils/automated_deploy_unittest.py
index 144c63c..6e73d39 100644
--- a/site_utils/automated_deploy_unittest.py
+++ b/site_utils/automated_deploy_unittest.py
@@ -18,6 +18,9 @@
 class AutomatedDeployTest(unittest.TestCase):
     """Test automated_deploy with commands mocked out."""
 
+    GIT_LOG_FOR_COMMITS = '''123 foo
+456 bar'''
+
     PUSH_LOG = '''Total 0 (delta 0), reused 0 (delta 0)
 remote: Processing changes: done
 To https:TEST_URL
@@ -28,33 +31,69 @@
 
 
     @mock.patch.object(infra, 'local_runner')
-    def testUpdateProdBranch(self, run_cmd):
-        """Test automated_deploy.update_prod_branch.
+    def testUpdateProdBranchWithNoNewChanges(self, run_cmd):
+        """Test update_prod_branch when there exist no new changes.
 
         @param run_cmd: Mock of infra.local_runner call used.
         """
-        # Test whether rebase to the given hash when the hash is given.
-        run_cmd.return_value = self.PUSH_LOG
+        run_cmd.return_value = None
+        self.assertEqual(ad.update_prod_branch('test', 'test_dir', '123'), None)
+        expect_cmds = [
+            mock.call('git log prod..123 --oneline', stream_output=True)]
+        run_cmd.assert_has_calls(expect_cmds)
+
+
+    @mock.patch.object(infra, 'local_runner')
+    def testUpdateProdBranchRebaseToCorrectHash(self, run_cmd):
+        """Test whether update_prod_branch can rebase to the correct hash.
+
+        @param run_cmd: Mock of infra.local_runner call used.
+        """
+        run_cmd.side_effect = [self.GIT_LOG_FOR_COMMITS, None, self.PUSH_LOG]
         ad.update_prod_branch('test', 'test_dir', '123')
-        expect_cmds = [mock.call('git rebase 123 prod', stream_output=True),
-                       mock.call('git push origin prod', stream_output=True)]
+        expect_cmds = [
+            mock.call('git log prod..123 --oneline', stream_output=True),
+            mock.call('git rebase 123 prod', stream_output=True),
+            mock.call('git push origin prod', stream_output=True)]
         run_cmd.assert_has_calls(expect_cmds)
 
-        # Test whether rebase to prod-next branch when the hash is not given.
-        run_cmd.return_value = self.PUSH_LOG
+
+    @mock.patch.object(infra, 'local_runner')
+    def testUpdateProdBranchRebaseToProdNext(self, run_cmd):
+        """Test whether rebase to prod-next branch when the hash is not given.
+
+        @param run_cmd: Mock of infra.local_runner call used.
+        """
+        run_cmd.side_effect = [self.GIT_LOG_FOR_COMMITS, None, self.PUSH_LOG]
         ad.update_prod_branch('test', 'test_dir', None)
-        expect_cmds = [mock.call('git rebase origin/prod-next prod',
-                                 stream_output=True),
-                       mock.call('git push origin prod', stream_output=True)]
+        expect_cmds = [
+            mock.call('git log prod..origin/prod-next --oneline',
+                      stream_output=True),
+            mock.call('git rebase origin/prod-next prod',
+                      stream_output=True),
+            mock.call('git push origin prod', stream_output=True)]
         run_cmd.assert_has_calls(expect_cmds)
 
-        # Test to grep the pushed commit range from the normal push log.
-        run_cmd.return_value = self.PUSH_LOG
+
+    @mock.patch.object(infra, 'local_runner')
+    def testUpdateProdBranchParseCommitRange(self, run_cmd):
+        """Test to grep the pushed commit range from the normal push log.
+
+        @param run_cmd: Mock of infra.local_runner call used.
+        """
+        run_cmd.side_effect = [self.GIT_LOG_FOR_COMMITS, None, self.PUSH_LOG]
         self.assertEqual(ad.update_prod_branch('test', 'test_dir', None),
                          '123..456')
 
-        # Test to grep the pushed commit range from the failed push log.
-        run_cmd.return_value = 'Fail to push prod branch'
+
+    @mock.patch.object(infra, 'local_runner')
+    def testUpdateProdBranchFailToParseCommitRange(self, run_cmd):
+        """Test to grep the pushed commit range from the failed push log.
+
+        @param run_cmd: Mock of infra.local_runner call used.
+        """
+        run_cmd.side_effect = [self.GIT_LOG_FOR_COMMITS, None,
+                               'Fail to push prod branch']
         with self.assertRaises(ad.AutoDeployException):
              ad.update_prod_branch('test', 'test_dir', None)