lib/datadog/ci/git/local_repository.rb in datadog-ci-1.2.0 vs lib/datadog/ci/git/local_repository.rb in datadog-ci-1.3.0

- old
+ new

@@ -1,16 +1,29 @@ # frozen_string_literal: true require "open3" require "pathname" +require_relative "../ext/telemetry" +require_relative "telemetry" require_relative "user" module Datadog module CI module Git module LocalRepository + class GitCommandExecutionError < StandardError + attr_reader :output, :command, :status + def initialize(message, output:, command:, status:) + super(message) + + @output = output + @command = command + @status = status + end + end + COMMAND_RETRY_COUNT = 3 def self.root return @root if defined?(@root) @@ -46,13 +59,22 @@ def self.current_folder_name File.basename(root) end def self.git_repository_url - exec_git_command("git ls-remote --get-url") + Telemetry.git_command(Ext::Telemetry::Command::GET_REPOSITORY) + res = nil + + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + res = exec_git_command("git ls-remote --get-url") + end + + Telemetry.git_command_ms(Ext::Telemetry::Command::GET_REPOSITORY, duration_ms) + res rescue => e log_failure(e, "git repository url") + telemetry_track_error(e, Ext::Telemetry::Command::GET_REPOSITORY) nil end def self.git_root exec_git_command("git rev-parse --show-toplevel") @@ -67,13 +89,22 @@ log_failure(e, "git commit sha") nil end def self.git_branch - exec_git_command("git rev-parse --abbrev-ref HEAD") + Telemetry.git_command(Ext::Telemetry::Command::GET_BRANCH) + res = nil + + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + res = exec_git_command("git rev-parse --abbrev-ref HEAD") + end + + Telemetry.git_command_ms(Ext::Telemetry::Command::GET_BRANCH, duration_ms) + res rescue => e log_failure(e, "git branch") + telemetry_track_error(e, Ext::Telemetry::Command::GET_BRANCH) nil end def self.git_tag exec_git_command("git tag --points-at HEAD") @@ -114,33 +145,53 @@ [nil_user, nil_user] end # returns maximum of 1000 latest commits in the last month def self.git_commits - output = exec_git_command("git log --format=%H -n 1000 --since=\"1 month ago\"") + Telemetry.git_command(Ext::Telemetry::Command::GET_LOCAL_COMMITS) + + output = nil + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + output = exec_git_command("git log --format=%H -n 1000 --since=\"1 month ago\"") + end + + Telemetry.git_command_ms(Ext::Telemetry::Command::GET_LOCAL_COMMITS, duration_ms) + return [] if output.nil? + # @type var output: String output.split("\n") rescue => e log_failure(e, "git commits") + telemetry_track_error(e, Ext::Telemetry::Command::GET_LOCAL_COMMITS) [] end def self.git_commits_rev_list(included_commits:, excluded_commits:) + Telemetry.git_command(Ext::Telemetry::Command::GET_OBJECTS) included_commits = filter_invalid_commits(included_commits).join(" ") excluded_commits = filter_invalid_commits(excluded_commits).map! { |sha| "^#{sha}" }.join(" ") - exec_git_command( - "git rev-list " \ - "--objects " \ - "--no-object-names " \ - "--filter=blob:none " \ - "--since=\"1 month ago\" " \ - "#{excluded_commits} #{included_commits}" - ) + res = nil + + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + res = exec_git_command( + "git rev-list " \ + "--objects " \ + "--no-object-names " \ + "--filter=blob:none " \ + "--since=\"1 month ago\" " \ + "#{excluded_commits} #{included_commits}" + ) + end + + Telemetry.git_command_ms(Ext::Telemetry::Command::GET_OBJECTS, duration_ms) + + res rescue => e log_failure(e, "git commits rev list") + telemetry_track_error(e, Ext::Telemetry::Command::GET_OBJECTS) nil end def self.git_generate_packfiles(included_commits:, excluded_commits:, path:) return nil unless File.exist?(path) @@ -148,39 +199,63 @@ commit_tree = git_commits_rev_list(included_commits: included_commits, excluded_commits: excluded_commits) return nil if commit_tree.nil? basename = SecureRandom.hex(4) - exec_git_command( - "git pack-objects --compression=9 --max-pack-size=3m #{path}/#{basename}", - stdin: commit_tree - ) + Telemetry.git_command(Ext::Telemetry::Command::PACK_OBJECTS) + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + exec_git_command( + "git pack-objects --compression=9 --max-pack-size=3m #{path}/#{basename}", + stdin: commit_tree + ) + end + Telemetry.git_command_ms(Ext::Telemetry::Command::PACK_OBJECTS, duration_ms) + basename rescue => e log_failure(e, "git generate packfiles") + telemetry_track_error(e, Ext::Telemetry::Command::PACK_OBJECTS) nil end def self.git_shallow_clone? - exec_git_command("git rev-parse --is-shallow-repository") == "true" + Telemetry.git_command(Ext::Telemetry::Command::CHECK_SHALLOW) + res = false + + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + res = exec_git_command("git rev-parse --is-shallow-repository") == "true" + end + Telemetry.git_command_ms(Ext::Telemetry::Command::CHECK_SHALLOW, duration_ms) + + res rescue => e log_failure(e, "git shallow clone") + telemetry_track_error(e, Ext::Telemetry::Command::CHECK_SHALLOW) false end def self.git_unshallow - exec_git_command( - "git fetch " \ - "--shallow-since=\"1 month ago\" " \ - "--update-shallow " \ - "--filter=\"blob:none\" " \ - "--recurse-submodules=no " \ - "$(git config --default origin --get clone.defaultRemoteName) $(git rev-parse HEAD)" - ) + Telemetry.git_command(Ext::Telemetry::Command::UNSHALLOW) + res = nil + + duration_ms = Core::Utils::Time.measure(:float_millisecond) do + res = exec_git_command( + "git fetch " \ + "--shallow-since=\"1 month ago\" " \ + "--update-shallow " \ + "--filter=\"blob:none\" " \ + "--recurse-submodules=no " \ + "$(git config --default origin --get clone.defaultRemoteName) $(git rev-parse HEAD)" + ) + end + Telemetry.git_command_ms(Ext::Telemetry::Command::UNSHALLOW, duration_ms) + + res rescue => e log_failure(e, "git unshallow") + telemetry_track_error(e, Ext::Telemetry::Command::UNSHALLOW) nil end # makes .exec_git_command private to make sure that this method # is not called from outside of this module with insecure parameters @@ -207,11 +282,16 @@ retry_count -= 1 end end if status.nil? || !status.success? - raise "Failed to run git command [#{cmd}] with input [#{stdin}] and output [#{out}]" + raise GitCommandExecutionError.new( + "Failed to run git command [#{cmd}] with input [#{stdin}] and output [#{out}]", + output: out, + command: cmd, + status: status + ) end # Sometimes Encoding.default_external is somehow set to US-ASCII which breaks # commit messages with UTF-8 characters like emojis # We force output's encoding to be UTF-8 in this case @@ -228,9 +308,20 @@ def log_failure(e, action) Datadog.logger.debug( "Unable to perform #{action}: #{e.class.name} #{e.message} at #{Array(e.backtrace).first}" ) + end + + def telemetry_track_error(e, command) + case e + when Errno::ENOENT + Telemetry.git_command_errors(command, executable_missing: true) + when GitCommandExecutionError + Telemetry.git_command_errors(command, exit_code: e.status&.to_i) + else + Telemetry.git_command_errors(command, exit_code: -9000) + end end end end end end