Unify CLI git operations into a single place

We have some places in our codebase that invokes git CLI, because some operations are not supported by rugged.

We should focus on moving to rugged whenever possible, and as a way to make that move easier, we should refactor our codebase and centralize in a single class every invocation to git CLI, so it becomes obvious where we should change code when that specific "feature" becomes part of rugged library.

Here is a simple list of places to change in gitlab-ce (excluding specs):

# app/models/repository.rb
141:      #{Gitlab.config.git.bin_path} log #{ref} --pretty=%H --skip #{offset}
249:    command = %W(#{Gitlab.config.git.bin_path} update-ref --stdin -z)
657:    args = %W(#{Gitlab.config.git.bin_path} rev-list --max-count=1 #{sha} -- #{path})
727:    args = %W(#{Gitlab.config.git.bin_path} for-each-ref --count=1 #{ref_path} --contains #{sha})
735:    args = %W(#{Gitlab.config.git.bin_path} #{ref_type} --contains #{sha})
1054:    args = %W(#{Gitlab.config.git.bin_path} grep -i -I -n --before-context #{offset} --after-context #{offset} -E -e #{Regexp.escape(query)} #{ref || root_ref})
1061:    args = %W(#{Gitlab.config.git.bin_path} ls-tree --full-tree -r #{ref || root_ref} --name-status | #{Regexp.escape(query)})
1066:    args = %W(#{Gitlab.config.git.bin_path} fetch --no-tags -f #{source_path} #{source_ref}:#{target_ref})

# doc/development/shell_commands.md
45:system(*%W(#{Gitlab.config.git.bin_path} branch -d -- #{branch_name}))
94:system(*%W(#{Gitlab.config.git.bin_path} branch -d #{branch_name}))
96:system(*%W(#{Gitlab.config.git.bin_path} branch -d -- #{branch_name}))
107:logs = `cd #{repo_dir} && #{Gitlab.config.git.bin_path} log`
109:logs, exit_status = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} log), repo_dir)
121:logs = IO.popen(%W(#{Gitlab.config.git.bin_path} log), chdir: repo_dir) { |p| p.read }

# lib/backup/repository.rb
33:          cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path_to_project_repo} bundle create #{path_to_project_bundle} --all)
55:            cmd = %W(#{Gitlab.config.git.bin_path} --git-dir=#{path_to_wiki_repo} bundle create #{path_to_wiki_bundle} --all)
89:          cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_project_bundle} #{path_to_project_repo})
91:          cmd = %W(#{Gitlab.config.git.bin_path} init --bare #{path_to_project_repo})
127:          cmd = %W(#{Gitlab.config.git.bin_path} clone --bare #{path_to_wiki_bundle} #{path_to_wiki_repo})

# lib/gitlab/checks/force_push.rb
11:          missed_ref, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --git-dir=#{project.repository.path_to_repo} rev-list --max-count=1 #{oldrev} ^#{newrev}))

# lib/gitlab/git.rb
53:        Gitlab::VersionInfo.parse(Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} --version)).first)

# lib/gitlab/git_ref_validator.rb
9:        %W(#{Gitlab.config.git.bin_path} check-ref-format refs/#{ref_name}))

# lib/gitlab/import_export/command_line_util.rb
48:        Gitlab.config.git.bin_path

# lib/gitlab/upgrader.rb
53:      remote_tags, _ = Gitlab::Popen.popen(%W(#{Gitlab.config.git.bin_path} ls-remote --tags https://gitlab.com/gitlab-org/gitlab-ce.git))
59:        "Stash changed files" => %W(#{Gitlab.config.git.bin_path} stash),
60:        "Get latest code" => %W(#{Gitlab.config.git.bin_path} fetch),
61:        "Switch to new version" => %W(#{Gitlab.config.git.bin_path} checkout v#{latest_version}),

# lib/tasks/gitlab/check.rake
49:        run_command(%W(#{Gitlab.config.git.bin_path} config --global --get #{name})).try(:squish) == value
62:            sudo_gitlab("\"#{Gitlab.config.git.bin_path}\" config --global core.autocrlf \"#{options["core.autocrlf"]}\"")
934:    current_version = Gitlab::VersionInfo.parse(run_command(%W(#{Gitlab.config.git.bin_path} --version)))
936:    puts "Your git bin path is \"#{Gitlab.config.git.bin_path}\""
984:    system(*%W(#{Gitlab.config.git.bin_path} fsck), chdir: repo_dir)

# lib/tasks/gitlab/info.rake
70:      puts "Git:\t\t#{Gitlab.config.git.bin_path}"

# lib/tasks/gitlab/task_helpers.rb
126:          system(*%W(#{Gitlab.config.git.bin_path} config --global #{name} #{value}))
164:      run_command!(%W[#{Gitlab.config.git.bin_path} clone -- #{repo} #{target_dir}])
168:      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch --tags --quiet])
169:      run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} checkout --quiet #{tag}])
177:        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- #{tag_wanted}])
179:        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} fetch origin])
180:        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} describe -- origin/#{tag_wanted}])
184:        run_command!(%W[#{Gitlab.config.git.bin_path} -C #{target_dir} reset --hard #{tag.strip}])

This change will also make simpler to evaluate git cli upgrades: as omnibus-gitlab#1749 (closed)

Related issues:

  • https://gitlab.com/gitlab-org/gitlab-ce/issues/23517
  • https://gitlab.com/gitlab-org/gitlab-ce/issues/24265
  • https://gitlab.com/gitlab-org/gitlab-ce/issues/21657

Question: Should we move all to gitlab_git or keep a different/simpler file in gitlab-rails just to get this done?

cc @DouweM @rspeicher @stanhu

Edited Jun 16, 2025 by 🤖 GitLab Bot 🤖
Assignee Loading
Time tracking Loading