deploy_production_local: Add better change reporting.
This will now report all of the changes added to a repo after an update.
BUG=chromium:425636
TEST=Unittests.
Change-Id: I999d03cdbd63f22d97fda47c8fb7eba31c5a62a1
Reviewed-on: https://chromium-review.googlesource.com/234828
Tested-by: Don Garrett <dgarrett@chromium.org>
Reviewed-by: Richard Barnette <jrbarnette@chromium.org>
Commit-Queue: Don Garrett <dgarrett@chromium.org>
diff --git a/contrib/deploy_production_local.py b/contrib/deploy_production_local.py
index 0d58ea8..a723fd3 100755
--- a/contrib/deploy_production_local.py
+++ b/contrib/deploy_production_local.py
@@ -65,11 +65,40 @@
def repo_versions():
"""This function collects the versions of all git repos in the general repo.
- @returns A string the describes HEAD of all git repos.
+ @returns A dictionary mapping project names to git hashes for HEAD.
@raises subprocess.CalledProcessError on a repo command failure.
"""
- cmd = ['repo', 'forall', '-p', '-c', 'git', 'log', '-1', '--oneline']
- return subprocess.check_output(cmd)
+ cmd = ['repo', 'forall', '-p', '-c', 'pwd && git log -1 --format=%h']
+ output = subprocess.check_output(cmd)
+
+ # The expected output format is:
+
+ # project chrome_build/
+ # /dir/holding/chrome_build
+ # 73dee9d
+ #
+ # project chrome_release/
+ # /dir/holding/chrome_release
+ # 9f3a5d8
+
+ lines = output.splitlines()
+
+ PROJECT_PREFIX = 'project '
+
+ project_heads = {}
+ for n in range(0, len(lines), 4):
+ project_line = lines[n]
+ project_dir = lines[n+1]
+ project_hash = lines[n+2]
+ # lines[n+3] is a blank line, but doesn't exist for the final block.
+
+ # Convert 'project chrome_build/' -> 'chrome_build'
+ assert project_line.startswith(PROJECT_PREFIX)
+ name = project_line[len(PROJECT_PREFIX):].rstrip('/')
+
+ project_heads[name] = (project_dir, project_hash)
+
+ return project_heads
def repo_sync():
@@ -213,7 +242,7 @@
def run_deploy_actions(dryrun=False):
- """
+ """Run arbitrary update commands specified in global.ini.
@param dryrun: Don't really restart the service, just print out the command.
@@ -232,6 +261,43 @@
restart_services(services, dryrun=dryrun)
+def report_changes(versions_before, versions_after):
+ """Produce a report describing what changed in all repos.
+
+ @param versions_before: Results of repo_versions() from before the update.
+ @param versions_after: Results of repo_versions() from after the update.
+
+ @returns string containing a human friendly changes report.
+ """
+ result = []
+
+ for project in sorted(set(versions_before.keys() + versions_after.keys())):
+ result.append('%s:' % project)
+
+ _, before_hash = versions_before.get(project, (None, None))
+ after_dir, after_hash = versions_after.get(project, (None, None))
+
+ if project not in versions_before:
+ result.append('Added.')
+
+ elif project not in versions_after:
+ result.append('Removed.')
+
+ elif before_hash == after_hash:
+ result.append('No Change.')
+
+ else:
+ hashes = '%s..%s' % (before_hash, after_hash)
+ cmd = ['git', 'log', hashes, '--oneline']
+ out = subprocess.check_output(cmd, cwd=after_dir,
+ stderr=subprocess.STDOUT)
+ result.append(out.strip())
+
+ result.append('')
+
+ return '\n'.join(result)
+
+
def parse_arguments(args):
"""Parse command line arguments.
@@ -292,6 +358,8 @@
print(e.args[0])
return 1
+ versions_before = versions_after = {}
+
if behaviors.update:
print('Checking repository versions.')
versions_before = repo_versions()
@@ -300,7 +368,8 @@
repo_sync()
print('Checking repository versions after update.')
- if versions_before == repo_versions():
+ versions_after = repo_versions()
+ if versions_before == versions_after:
print('No change found.')
return
@@ -313,9 +382,9 @@
print(e.args[0])
return 1
- if behaviors.report:
- print('Current production versions:')
- print(repo_versions())
+ if behaviors.report and versions_before and versions_after:
+ print('Changes:')
+ print(report_changes(versions_before, versions_after))
if __name__ == '__main__':
diff --git a/contrib/deploy_production_local_unittest.py b/contrib/deploy_production_local_unittest.py
index 67f3f39..017af2d 100755
--- a/contrib/deploy_production_local_unittest.py
+++ b/contrib/deploy_production_local_unittest.py
@@ -8,6 +8,7 @@
from __future__ import print_function
import mock
+import subprocess
import unittest
import deploy_production_local as dpl
@@ -60,14 +61,35 @@
@param run_cmd: Mock of subprocess call used.
"""
- expected = 'expected return'
+ output = """project autotest/
+/usr/local/autotest
+5897108
- run_cmd.return_value = expected
+project autotest/site_utils/autotest_private/
+/usr/local/autotest/site_utils/autotest_private
+78b9626
+
+project autotest/site_utils/autotest_tools/
+/usr/local/autotest/site_utils/autotest_tools
+a1598f7
+"""
+
+ expected = {
+ 'autotest':
+ ('/usr/local/autotest', '5897108'),
+ 'autotest/site_utils/autotest_private':
+ ('/usr/local/autotest/site_utils/autotest_private', '78b9626'),
+ 'autotest/site_utils/autotest_tools':
+ ('/usr/local/autotest/site_utils/autotest_tools', 'a1598f7'),
+ }
+
+ run_cmd.return_value = output
result = dpl.repo_versions()
self.assertEquals(result, expected)
run_cmd.assert_called_with(
- ['repo', 'forall', '-p', '-c', 'git', 'log', '-1', '--oneline'])
+ ['repo', 'forall', '-p', '-c',
+ 'pwd && git log -1 --format=%h'])
@mock.patch('subprocess.check_output', autospec=True)
def test_repo_sync(self, run_cmd):
@@ -165,6 +187,47 @@
self._test_restart_services(triple_unstable)
self.assertEqual(unstable.exception.args[0], ['bar', 'joe'])
+ @mock.patch('subprocess.check_output', autospec=True)
+ def test_report_changes(self, run_cmd):
+ """Test deploy_production_local.report_changes.
+
+ @param run_cmd: Mock of subprocess call used.
+ """
+
+ before = {
+ 'autotest': ('/usr/local/autotest', 'auto_before'),
+ 'autotest_private': ('/dir/autotest_private', '78b9626'),
+ 'other': ('/fake/unchanged', 'constant_hash'),
+ }
+
+ after = {
+ 'autotest': ('/usr/local/autotest', 'auto_after'),
+ 'autotest_tools': ('/dir/autotest_tools', 'a1598f7'),
+ 'other': ('/fake/unchanged', 'constant_hash'),
+ }
+
+ run_cmd.return_value = 'hash1 Fix change.\nhash2 Bad change.\n'
+
+ result = dpl.report_changes(before, after)
+
+ self.assertEqual(result, """autotest:
+hash1 Fix change.
+hash2 Bad change.
+
+autotest_private:
+Removed.
+
+autotest_tools:
+Added.
+
+other:
+No Change.
+""")
+
+ run_cmd.assert_called_with(
+ ['git', 'log', 'auto_before..auto_after', '--oneline'],
+ cwd='/usr/local/autotest', stderr=subprocess.STDOUT)
+
def test_parse_arguments(self):
"""Test deploy_production_local.parse_arguments."""
# No arguments.