Module: Datadog::CI::Git::CLI

Defined in:
lib/datadog/ci/git/cli.rb

Defined Under Namespace

Classes: GitCommandExecutionError

Constant Summary collapse

UNSHALLOW_TIMEOUT =

Timeout constants for git commands (in seconds) These values were set based on internal telemetry

500
LONG_TIMEOUT =
30
SHORT_TIMEOUT =
3

Class Method Summary collapse

Class Method Details

.exec_git_command(cmd, stdin: nil, timeout: SHORT_TIMEOUT) ⇒ String?

Execute a git command with optional stdin input and timeout

All git commands are executed with the ‘-c safe.directory` option to handle cases where the repository is owned by a different user (common in CI environments with containerized builds).

Parameters:

  • cmd (Array<String>)

    The git command as an array of strings

  • stdin (String, nil) (defaults to: nil)

    Optional stdin data to pass to the command

  • timeout (Integer) (defaults to: SHORT_TIMEOUT)

    Timeout in seconds for the command execution

Returns:

  • (String, nil)

    The command output, or nil if the output is empty

Raises:



37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
# File 'lib/datadog/ci/git/cli.rb', line 37

def self.exec_git_command(cmd, stdin: nil, timeout: SHORT_TIMEOUT)
  # @type var out: String
  # @type var status: Process::Status?
  out, status = Utils::Command.exec_command(
    ["git", "-c", "safe.directory=#{safe_directory}"] + cmd,
    stdin_data: stdin,
    timeout: timeout
  )

  if status.nil? || !status.success?
    # Convert command to string representation for error message
    cmd_str = cmd.join(" ")
    raise GitCommandExecutionError.new(
      "Failed to run git command [#{cmd_str}] with input [#{stdin}] and output [#{out}]. Status: #{status}",
      output: out,
      command: cmd_str,
      status: status
    )
  end

  return nil if out.empty?

  out
end

.find_git_directory(start_dir) ⇒ String

Traverses up from the given directory to find the nearest .git folder or file. Returns the repository root (parent of .git) if found, otherwise the original directory.

Note: .git can be either a directory (regular repos) or a file (worktrees/submodules). In worktrees and submodules, .git is a file containing a pointer to the actual git directory.

Parameters:

  • start_dir (String)

    The directory to start searching from

Returns:

  • (String)

    The repository root path or the start directory if not found



86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
# File 'lib/datadog/ci/git/cli.rb', line 86

def self.find_git_directory(start_dir)
  Datadog.logger.debug { "Searching for .git starting from: #{start_dir}" }
  current_dir = File.expand_path(start_dir)

  loop do
    git_path = File.join(current_dir, ".git")

    # Check for both directory (.git in regular repos) and file (.git in worktrees/submodules)
    if File.exist?(git_path)
      Datadog.logger.debug { "Found .git at: #{git_path} (#{File.directory?(git_path) ? "directory" : "file"})" }
      return current_dir
    end

    parent_dir = File.dirname(current_dir)

    # Reached the root directory
    break if parent_dir == current_dir

    current_dir = parent_dir
  end

  # Fallback to original directory if no .git found
  Datadog.logger.debug { "No .git found, using fallback: #{start_dir}" }
  start_dir
end

.safe_directoryString

Returns the directory to use for git’s safe.directory config. This is cached to avoid repeated filesystem lookups.

Traverses up from current directory to find the nearest .git folder and returns its parent (the repository root). Falls back to current working directory if no .git folder is found.

Returns:

  • (String)

    The safe directory path



70
71
72
73
74
75
76
# File 'lib/datadog/ci/git/cli.rb', line 70

def self.safe_directory
  return @safe_directory if defined?(@safe_directory)

  @safe_directory = find_git_directory(Dir.pwd)
  Datadog.logger.debug { "Git safe.directory configured to: #{@safe_directory}" }
  @safe_directory
end