diff --git a/.gitignore b/.gitignore index a26db3ad1..b4e2ca70b 100644 --- a/.gitignore +++ b/.gitignore @@ -84,4 +84,7 @@ testing_support/google_appengine /metrics.cfg # Ignore the ninjalog upload config. -/ninjalog.cfg \ No newline at end of file +/ninjalog.cfg + +# Ignore git traces produced by git push on git-cl upload. +/traces diff --git a/git_cl.py b/git_cl.py index 2d3b54c72..662c28e79 100755 --- a/git_cl.py +++ b/git_cl.py @@ -68,6 +68,24 @@ import watchlists __version__ = '2.0' +# Traces for git push will be stored in a traces directory inside the +# depot_tools checkout. +DEPOT_TOOLS = os.path.dirname(os.path.abspath(__file__)) +TRACES_DIR = os.path.join(DEPOT_TOOLS, 'traces') + +# When collecting traces, Git hashes will be reduced to 6 characters to reduce +# the size after compression. +GIT_HASH_RE = re.compile(r'\b([a-f0-9]{6})[a-f0-9]{34}\b', flags=re.I) +# Used to redact the cookies from the gitcookies file. +GITCOOKIES_REDACT_RE = re.compile(r'1/.*') + +TRACES_MESSAGE = ( +'When filing a bug, be sure to include the traces found at:\n' +' %s.zip\n' +'Consider including the git config and gitcookies,\n' +'which we have packed for you at:\n' +' %s.zip\n') + COMMIT_BOT_EMAIL = 'commit-bot@chromium.org' POSTUPSTREAM_HOOK = '.git/hooks/post-cl-land' DESCRIPTION_BACKUP_FILE = '~/.git_cl_description_backup' @@ -2478,6 +2496,83 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): else: print('OK, will keep Gerrit commit-msg hook in place.') + def _RunGitPushWithTraces(self, change_desc, refspec, refspec_opts): + gclient_utils.safe_makedirs(TRACES_DIR) + + # Create a temporary directory to store traces in. Traces will be compressed + # and stored in a 'traces' dir inside depot_tools. + traces_dir = tempfile.mkdtemp() + trace_name = os.path.basename(traces_dir) + traces_zip = os.path.join(TRACES_DIR, trace_name + '-traces') + # Create a temporary dir to store git config and gitcookies in. It will be + # compressed and stored next to the traces. + git_info_dir = tempfile.mkdtemp() + git_info_zip = os.path.join(TRACES_DIR, trace_name + '-git-info') + + env = os.environ.copy() + env['GIT_REDACT_COOKIES'] = 'o,SSO,GSSO_Uberproxy' + env['GIT_TR2_EVENT'] = os.path.join(traces_dir, 'tr2-event') + env['GIT_TRACE_CURL'] = os.path.join(traces_dir, 'trace-curl') + env['GIT_TRACE_CURL_NO_DATA'] = '1' + env['GIT_TRACE_PACKET'] = os.path.join(traces_dir, 'trace-packet') + + try: + push_returncode = 0 + before_push = time_time() + push_stdout = gclient_utils.CheckCallAndFilter( + ['git', 'push', self.GetRemoteUrl(), refspec], + env=env, + print_stdout=True, + # Flush after every line: useful for seeing progress when running as + # recipe. + filter_fn=lambda _: sys.stdout.flush()) + except subprocess2.CalledProcessError as e: + push_returncode = e.returncode + DieWithError('Failed to create a change. Please examine output above ' + 'for the reason of the failure.\n' + 'Hint: run command below to diagnose common Git/Gerrit ' + 'credential problems:\n' + ' git cl creds-check\n' + + TRACES_MESSAGE % (traces_zip, git_info_zip), + change_desc) + finally: + execution_time = time_time() - before_push + metrics.collector.add_repeated('sub_commands', { + 'command': 'git push', + 'execution_time': execution_time, + 'exit_code': push_returncode, + 'arguments': metrics_utils.extract_known_subcommand_args(refspec_opts), + }) + + if push_returncode != 0: + # Keep only the first 6 characters of the git hashes on the packet + # trace. This greatly decreases size after compression. + packet_traces = os.path.join(traces_dir, 'trace-packet') + contents = gclient_utils.FileRead(packet_traces) + gclient_utils.FileWrite( + packet_traces, GIT_HASH_RE.sub(r'\1', contents)) + shutil.make_archive(traces_zip, 'zip', traces_dir) + + # Collect and compress the git config and gitcookies. + git_config = RunGit(['config', '-l']) + gclient_utils.FileWrite( + os.path.join(git_info_dir, 'git-config'), + git_config) + + cookie_auth = gerrit_util.Authenticator.get() + if isinstance(cookie_auth, gerrit_util.CookiesAuthenticator): + gitcookies_path = cookie_auth.get_gitcookies_path() + gitcookies = gclient_utils.FileRead(gitcookies_path) + gclient_utils.FileWrite( + os.path.join(git_info_dir, 'gitcookies'), + GITCOOKIES_REDACT_RE.sub('REDACTED', gitcookies)) + shutil.make_archive(git_info_zip, 'zip', git_info_dir) + + gclient_utils.rmtree(git_info_dir) + gclient_utils.rmtree(traces_dir) + + return push_stdout + def CMDUploadChange(self, options, git_diff_args, custom_cl_base, change): """Upload the current branch to Gerrit.""" if options.squash and options.no_squash: @@ -2727,30 +2822,7 @@ class _GerritChangelistImpl(_ChangelistCodereviewBase): 'spaces not allowed in refspec: "%s"' % refspec_suffix) refspec = '%s:refs/for/%s%s' % (ref_to_push, branch, refspec_suffix) - try: - push_returncode = 0 - before_push = time_time() - push_stdout = gclient_utils.CheckCallAndFilter( - ['git', 'push', self.GetRemoteUrl(), refspec], - print_stdout=True, - # Flush after every line: useful for seeing progress when running as - # recipe. - filter_fn=lambda _: sys.stdout.flush()) - except subprocess2.CalledProcessError as e: - push_returncode = e.returncode - DieWithError('Failed to create a change. Please examine output above ' - 'for the reason of the failure.\n' - 'Hint: run command below to diagnose common Git/Gerrit ' - 'credential problems:\n' - ' git cl creds-check\n', - change_desc) - finally: - metrics.collector.add_repeated('sub_commands', { - 'command': 'git push', - 'execution_time': time_time() - before_push, - 'exit_code': push_returncode, - 'arguments': metrics_utils.extract_known_subcommand_args(refspec_opts), - }) + push_stdout = self._RunGitPushWithTraces(change_desc, refspec, refspec_opts) if options.squash: regex = re.compile(r'remote:\s+https?://[\w\-\.\+\/#]*/(\d+)\s.*')