git-cl: Keep git push traces

Keep the last N git push traces.
Name them after the time when they were collected, and add a
README file to each one to provide some context to developers.

Bug: 955206
Change-Id: Ib5fcf2f78fb65f6ddd80a93619c14e1ef70c5564
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/1595108
Commit-Queue: Edward Lesmes <ehmaldonado@chromium.org>
Reviewed-by: Dirk Pranke <dpranke@chromium.org>
diff --git a/tests/git_cl_test.py b/tests/git_cl_test.py
index 36c0997..386ca8e 100755
--- a/tests/git_cl_test.py
+++ b/tests/git_cl_test.py
@@ -909,7 +909,8 @@
                            post_amend_description=None, issue=None, cc=None,
                            custom_cl_base=None, tbr=None,
                            short_hostname='chromium',
-                           labels=None):
+                           labels=None, change_id=None, original_title=None,
+                           final_description=None):
     if post_amend_description is None:
       post_amend_description = description
     cc = cc or []
@@ -1107,6 +1108,67 @@
         None,),
     ]
 
+    final_description = final_description or post_amend_description.strip()
+    original_title = original_title or title or '<untitled>'
+    # Trace-related calls
+    calls += [
+        # Write a description with context for the current trace.
+        ((['FileWrite', 'TRACES_DIR/20170316T200041.000000-README',
+           'Date: Thu Mar 16 20:00:41 2017\n\n'
+           'Change: https://%(short_hostname)s-review.googlesource.com/'
+           'q/%(change_id)s\n'
+           'Title: %(title)s\n\n'
+           '%(description)s\n\n'
+           'Execution time: 1000\n'
+           'Exit code: 0\n\n'
+           'When filing a bug for this push, be sure to include the traces '
+           'found at:\n'
+           '  TRACES_DIR/20170316T200041.000000-traces.zip\n'
+           'Consider including the git config and gitcookies, which we have '
+           'packed for \nyou at:\n'
+           '  TRACES_DIR/20170316T200041.000000-git-info.zip\n' % {
+             'short_hostname': short_hostname,
+             'change_id': change_id,
+             'description': final_description,
+             'title': original_title,
+           }],),
+         None,
+        ),
+        # Read traces and shorten git hashes.
+        ((['FileRead', 'TEMP_DIR/trace-packet'],),
+         ('git-hash: 0123456789012345678901234567890123456789\n'
+          'git-hash: abcdeabcdeabcdeabcdeabcdeabcdeabcdeabcde\n'),
+        ),
+        ((['FileWrite', 'TEMP_DIR/trace-packet',
+           'git-hash: 012345\n'
+           'git-hash: abcdea\n'],),
+         None,
+        ),
+        # Make zip file for the git traces.
+        ((['make_archive', 'TRACES_DIR/20170316T200041.000000-traces', 'zip',
+           'TEMP_DIR'],),
+         None,
+        ),
+        # Collect git config and gitcookies.
+        ((['git', 'config', '-l'],),
+         'git-config-output',
+        ),
+        ((['FileWrite', 'TEMP_DIR/git-config', 'git-config-output'],),
+         None,
+        ),
+        ((['FileRead', '~/.gitcookies'],),
+         'gitcookies 1/SECRET',
+        ),
+        ((['FileWrite', 'TEMP_DIR/gitcookies', 'gitcookies REDACTED'],),
+         None,
+        ),
+        # Make zip file for the git config and gitcookies.
+        ((['make_archive', 'TRACES_DIR/20170316T200041.000000-git-info', 'zip',
+           'TEMP_DIR'],),
+         None,
+        ),
+    ]
+
     if squash:
       calls += [
           ((['git', 'config', 'branch.master.gerritissue', '123456'],),
@@ -1147,7 +1209,10 @@
       custom_cl_base=None,
       tbr=None,
       short_hostname='chromium',
-      labels=None):
+      labels=None,
+      change_id=None,
+      original_title=None,
+      final_description=None):
     """Generic gerrit upload test framework."""
     if squash_mode is None:
       if '--no-squash' in upload_args:
@@ -1169,6 +1234,17 @@
               lambda *_, **__: self._mocked_call(['RunEditor']))
     self.mock(git_cl, 'DownloadGerritHook', lambda force: self._mocked_call(
       'DownloadGerritHook', force))
+    self.mock(git_cl.gclient_utils, 'FileRead',
+              lambda path: self._mocked_call(['FileRead', path]))
+    self.mock(git_cl.gclient_utils, 'FileWrite',
+              lambda path, contents: self._mocked_call(
+                  ['FileWrite', path, contents]))
+    self.mock(git_cl, 'datetime_now',
+             lambda: datetime.datetime(2017, 3, 16, 20, 0, 41, 0))
+    self.mock(git_cl.tempfile, 'mkdtemp', lambda: 'TEMP_DIR')
+    self.mock(git_cl, 'TRACES_DIR', 'TRACES_DIR')
+    self.mock(git_cl.shutil, 'make_archive',
+              lambda *args: self._mocked_call(['make_archive'] + list(args)))
 
     self.calls = self._gerrit_base_calls(
         issue=issue,
@@ -1190,7 +1266,10 @@
           issue=issue, cc=cc,
           custom_cl_base=custom_cl_base, tbr=tbr,
           short_hostname=short_hostname,
-          labels=labels)
+          labels=labels,
+          change_id=change_id,
+          original_title=original_title,
+          final_description=final_description)
     # Uncomment when debugging.
     # print('\n'.join(map(lambda x: '%2i: %s' % x, enumerate(self.calls))))
     git_cl.main(['upload'] + upload_args)
@@ -1201,7 +1280,8 @@
         'desc\n\nBUG=\n',
         [],
         squash=False,
-        post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx')
+        post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx',
+        change_id='Ixxx')
 
   def test_gerrit_upload_without_change_id_override_nosquash(self):
     self._run_gerrit_upload_test(
@@ -1210,7 +1290,8 @@
         [],
         squash=False,
         squash_mode='override_nosquash',
-        post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx')
+        post_amend_description='desc\n\nBUG=\n\nChange-Id: Ixxx',
+        change_id='Ixxx')
 
   def test_gerrit_no_reviewer(self):
     self._run_gerrit_upload_test(
@@ -1218,7 +1299,8 @@
         'desc\n\nBUG=\n\nChange-Id: I123456789\n',
         [],
         squash=False,
-        squash_mode='override_nosquash')
+        squash_mode='override_nosquash',
+        change_id='I123456789')
 
   def test_gerrit_no_reviewer_non_chromium_host(self):
     # TODO(crbug/877717): remove this test case.
@@ -1228,7 +1310,8 @@
         [],
         squash=False,
         squash_mode='override_nosquash',
-        short_hostname='other')
+        short_hostname='other',
+        change_id='I123456789')
 
   def test_gerrit_patchset_title_special_chars(self):
     self.mock(git_cl.sys, 'stdout', StringIO.StringIO())
@@ -1237,7 +1320,9 @@
         'desc\n\nBUG=\n\nChange-Id: I123456789',
         squash=False,
         squash_mode='override_nosquash',
-        title='We%27ll_escape_%5E%5F_%5E_special_chars%2E%2E%2E%40%7Bu%7D')
+        title='We%27ll_escape_%5E%5F_%5E_special_chars%2E%2E%2E%40%7Bu%7D',
+        change_id='I123456789',
+        original_title='We\'ll escape ^_ ^ special chars...@{u}')
 
   def test_gerrit_reviewers_cmd_line(self):
     self._run_gerrit_upload_test(
@@ -1246,7 +1331,10 @@
         ['foo@example.com'],
         squash=False,
         squash_mode='override_nosquash',
-        notify=True)
+        notify=True,
+        change_id='I123456789',
+        final_description=
+            'desc\n\nBUG=\nR=foo@example.com\n\nChange-Id: I123456789')
 
   def test_gerrit_reviewer_multiple(self):
     self.mock(git_cl.gerrit_util, 'GetCodeReviewTbrScore',
@@ -1260,14 +1348,18 @@
         expected_upstream_ref='origin/master',
         cc=['more@example.com', 'people@example.com'],
         tbr='reviewer@example.com',
-        labels={'Code-Review': 2})
+        labels={'Code-Review': 2},
+        change_id='123456789',
+        original_title='Initial upload')
 
   def test_gerrit_upload_squash_first_is_default(self):
     self._run_gerrit_upload_test(
         [],
         'desc\nBUG=\n\nChange-Id: 123456789',
         [],
-        expected_upstream_ref='origin/master')
+        expected_upstream_ref='origin/master',
+        change_id='123456789',
+        original_title='Initial upload')
 
   def test_gerrit_upload_squash_first(self):
     self._run_gerrit_upload_test(
@@ -1275,7 +1367,9 @@
         'desc\nBUG=\n\nChange-Id: 123456789',
         [],
         squash=True,
-        expected_upstream_ref='origin/master')
+        expected_upstream_ref='origin/master',
+        change_id='123456789',
+        original_title='Initial upload')
 
   def test_gerrit_upload_squash_first_with_labels(self):
     self._run_gerrit_upload_test(
@@ -1284,7 +1378,9 @@
         [],
         squash=True,
         expected_upstream_ref='origin/master',
-        labels={'Commit-Queue': 1, 'Auto-Submit': 1})
+        labels={'Commit-Queue': 1, 'Auto-Submit': 1},
+        change_id='123456789',
+        original_title='Initial upload')
 
   def test_gerrit_upload_squash_first_against_rev(self):
     custom_cl_base = 'custom_cl_base_rev_or_branch'
@@ -1294,7 +1390,9 @@
         [],
         squash=True,
         expected_upstream_ref='origin/master',
-        custom_cl_base=custom_cl_base)
+        custom_cl_base=custom_cl_base,
+        change_id='123456789',
+        original_title='Initial upload')
     self.assertIn(
         'If you proceed with upload, more than 1 CL may be created by Gerrit',
         sys.stdout.getvalue())
@@ -1307,7 +1405,9 @@
         [],
         squash=True,
         expected_upstream_ref='origin/master',
-        issue=123456)
+        issue=123456,
+        change_id='123456789',
+        original_title='User input')
 
   def test_gerrit_upload_squash_reupload_to_abandoned(self):
     self.mock(git_cl, 'DieWithError',
@@ -1321,7 +1421,8 @@
           squash=True,
           expected_upstream_ref='origin/master',
           issue=123456,
-          fetched_status='ABANDONED')
+          fetched_status='ABANDONED',
+          change_id='123456789')
 
   def test_gerrit_upload_squash_reupload_to_not_owned(self):
     self.mock(git_cl.gerrit_util, 'GetAccountDetails',
@@ -1334,7 +1435,9 @@
           squash=True,
           expected_upstream_ref='origin/master',
           issue=123456,
-          other_cl_owner='other@example.com')
+          other_cl_owner='other@example.com',
+          change_id='123456789',
+          original_title='User input')
     self.assertIn(
         'WARNING: Change 123456 is owned by other@example.com, but you '
         'authenticate to Gerrit as yet-another@example.com.\n'