|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | 3 | require_relative "command_execution" |
| 4 | +require_relative "path" |
4 | 5 |
|
5 | 6 | module Spec |
6 | 7 | module Subprocess |
@@ -31,9 +32,29 @@ def exitstatus |
31 | 32 | end |
32 | 33 |
|
33 | 34 | def git(cmd, path = Dir.pwd, options = {}) |
| 35 | + reject_git_config_pollution!(cmd, path) |
34 | 36 | sh("git #{cmd}", options.merge(dir: path)) |
35 | 37 | end |
36 | 38 |
|
| 39 | + # A local `git config` write in a directory without a `.git` makes git |
| 40 | + # discover an enclosing repository, which can be the rubygems checkout |
| 41 | + # itself, polluting its (possibly worktree-shared) `.git/config` with |
| 42 | + # fixture identities. Only allow local config writes inside tmp/. |
| 43 | + def reject_git_config_pollution!(cmd, path) |
| 44 | + require "shellwords" |
| 45 | + args = cmd.to_s.shellsplit |
| 46 | + return unless args.first == "config" |
| 47 | + return if args.any? {|a| ["--global", "--system", "-f", "--file"].include?(a) || a.start_with?("--file=") } |
| 48 | + return if args.any? {|a| ["--get", "--get-all", "--get-regexp", "--get-urlmatch", "--list", "-l"].include?(a) } |
| 49 | + |
| 50 | + dir = File.expand_path(path.to_s) |
| 51 | + tmp_root = Spec::Path.tmp_root.to_s |
| 52 | + return if dir == tmp_root || dir.start_with?(tmp_root + File::SEPARATOR) |
| 53 | + |
| 54 | + raise "Refusing to run `git #{cmd}` in #{dir}: " \ |
| 55 | + "a local git config write outside tmp/ could end up in the checkout's own .git/config" |
| 56 | + end |
| 57 | + |
37 | 58 | def sh(cmd, options = {}) |
38 | 59 | dir = options[:dir] |
39 | 60 | env = options[:env] || {} |
|
0 commit comments