Git fork

Sync with 2.45.4

* maint-2.45:
Git 2.45.4
Git 2.44.4
Git 2.43.7
wincred: avoid buffer overflow in wcsncat()
bundle-uri: fix arbitrary file writes via parameter injection
config: quote values containing CR character
git-gui: sanitize 'exec' arguments: convert new 'cygpath' calls
git-gui: do not mistake command arguments as redirection operators
git-gui: introduce function git_redir for git calls with redirections
git-gui: pass redirections as separate argument to git_read
git-gui: pass redirections as separate argument to _open_stdout_stderr
git-gui: convert git_read*, git_write to be non-variadic
git-gui: override exec and open only on Windows
gitk: sanitize 'open' arguments: revisit recently updated 'open' calls
git-gui: use git_read in githook_read
git-gui: sanitize $PATH on all platforms
git-gui: break out a separate function git_read_nice
git-gui: assure PATH has only absolute elements.
git-gui: remove option --stderr from git_read
git-gui: cleanup git-bash menu item
git-gui: sanitize 'exec' arguments: background
git-gui: avoid auto_execok in do_windows_shortcut
git-gui: sanitize 'exec' arguments: simple cases
git-gui: avoid auto_execok for git-bash menu item
git-gui: treat file names beginning with "|" as relative paths
git-gui: remove unused proc is_shellscript
git-gui: remove git config --list handling for git < 1.5.3
git-gui: remove special treatment of Windows from open_cmd_pipe
git-gui: remove HEAD detachment implementation for git < 1.5.3
git-gui: use only the configured shell
git-gui: remove Tcl 8.4 workaround on 2>@1 redirection
git-gui: make _shellpath usable on startup
git-gui: use [is_Windows], not bad _shellpath
git-gui: _which, only add .exe suffix if not present
gitk: encode arguments correctly with "open"
gitk: sanitize 'open' arguments: command pipeline
gitk: collect construction of blameargs into a single conditional
gitk: sanitize 'open' arguments: simple commands, readable and writable
gitk: sanitize 'open' arguments: simple commands with redirections
gitk: sanitize 'open' arguments: simple commands
gitk: sanitize 'exec' arguments: redirect to process
gitk: sanitize 'exec' arguments: redirections and background
gitk: sanitize 'exec' arguments: redirections
gitk: sanitize 'exec' arguments: 'eval exec'
gitk: sanitize 'exec' arguments: simple cases
gitk: have callers of diffcmd supply pipe symbol when necessary
gitk: treat file names beginning with "|" as relative paths

Signed-off-by: Taylor Blau <me@ttaylorr.com>

+715 -449
+73
Documentation/RelNotes/2.43.7.txt
··· 1 + Git v2.43.7 Release Notes 2 + ========================= 3 + 4 + This release includes fixes for CVE-2025-27613, CVE-2025-27614, 5 + CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and 6 + CVE-2025-48386. 7 + 8 + Fixes since v2.43.6 9 + ------------------- 10 + 11 + * CVE-2025-27613, Gitk: 12 + 13 + When a user clones an untrusted repository and runs Gitk without 14 + additional command arguments, any writable file can be created and 15 + truncated. The option "Support per-file encoding" must have been 16 + enabled. The operation "Show origin of this line" is affected as 17 + well, regardless of the option being enabled or not. 18 + 19 + * CVE-2025-27614, Gitk: 20 + 21 + A Git repository can be crafted in such a way that a user who has 22 + cloned the repository can be tricked into running any script 23 + supplied by the attacker by invoking `gitk filename`, where 24 + `filename` has a particular structure. 25 + 26 + * CVE-2025-46334, Git GUI (Windows only): 27 + 28 + A malicious repository can ship versions of sh.exe or typical 29 + textconv filter programs such as astextplain. On Windows, path 30 + lookup can find such executables in the worktree. These programs 31 + are invoked when the user selects "Git Bash" or "Browse Files" from 32 + the menu. 33 + 34 + * CVE-2025-46835, Git GUI: 35 + 36 + When a user clones an untrusted repository and is tricked into 37 + editing a file located in a maliciously named directory in the 38 + repository, then Git GUI can create and overwrite any writable 39 + file. 40 + 41 + * CVE-2025-48384, Git: 42 + 43 + When reading a config value, Git strips any trailing carriage 44 + return and line feed (CRLF). When writing a config entry, values 45 + with a trailing CR are not quoted, causing the CR to be lost when 46 + the config is later read. When initializing a submodule, if the 47 + submodule path contains a trailing CR, the altered path is read 48 + resulting in the submodule being checked out to an incorrect 49 + location. If a symlink exists that points the altered path to the 50 + submodule hooks directory, and the submodule contains an executable 51 + post-checkout hook, the script may be unintentionally executed 52 + after checkout. 53 + 54 + * CVE-2025-48385, Git: 55 + 56 + When cloning a repository Git knows to optionally fetch a bundle 57 + advertised by the remote server, which allows the server-side to 58 + offload parts of the clone to a CDN. The Git client does not 59 + perform sufficient validation of the advertised bundles, which 60 + allows the remote side to perform protocol injection. 61 + 62 + This protocol injection can cause the client to write the fetched 63 + bundle to a location controlled by the adversary. The fetched 64 + content is fully controlled by the server, which can in the worst 65 + case lead to arbitrary code execution. 66 + 67 + * CVE-2025-48386, Git: 68 + 69 + The wincred credential helper uses a static buffer (`target`) as a 70 + unique key for storing and comparing against internal storage. This 71 + credential helper does not properly bounds check the available 72 + space remaining in the buffer before appending to it with 73 + `wcsncat()`, leading to potential buffer overflows.
+7
Documentation/RelNotes/2.44.4.txt
··· 1 + Git v2.44.4 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appears in v2.43.7 to address 5 + the following CVEs: CVE-2025-27613, CVE-2025-27614, CVE-2025-46334, 6 + CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386. 7 + See the release notes for v2.43.7 for details.
+7
Documentation/RelNotes/2.45.4.txt
··· 1 + Git v2.45.4 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appears in v2.43.7, and v2.44.4 5 + to address the following CVEs: CVE-2025-27613, CVE-2025-27614, 6 + CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and 7 + CVE-2025-48386. See the release notes for v2.43.7 for details.
+22
bundle-uri.c
··· 295 295 struct strbuf line = STRBUF_INIT; 296 296 int found_get = 0; 297 297 298 + /* 299 + * The protocol we speak with git-remote-https(1) uses a space to 300 + * separate between URI and file, so the URI itself must not contain a 301 + * space. If it did, an adversary could change the location where the 302 + * downloaded file is being written to. 303 + * 304 + * Similarly, we use newlines to separate commands from one another. 305 + * Consequently, neither the URI nor the file must contain a newline or 306 + * otherwise an adversary could inject arbitrary commands. 307 + * 308 + * TODO: Restricting newlines in the target paths may break valid 309 + * usecases, even if those are a bit more on the esoteric side. 310 + * If this ever becomes a problem we should probably think about 311 + * alternatives. One alternative could be to use NUL-delimited 312 + * requests in git-remote-http(1). Another alternative could be 313 + * to use URL quoting. 314 + */ 315 + if (strpbrk(uri, " \n")) 316 + return error("bundle-uri: URI is malformed: '%s'", file); 317 + if (strchr(file, '\n')) 318 + return error("bundle-uri: filename is malformed: '%s'", file); 319 + 298 320 strvec_pushl(&cp.args, "git-remote-https", uri, NULL); 299 321 cp.err = -1; 300 322 cp.in = -1;
+1 -1
config.c
··· 3063 3063 if (value[0] == ' ') 3064 3064 quote = "\""; 3065 3065 for (i = 0; value[i]; i++) 3066 - if (value[i] == ';' || value[i] == '#') 3066 + if (value[i] == ';' || value[i] == '#' || value[i] == '\r') 3067 3067 quote = "\""; 3068 3068 if (i && value[i - 1] == ' ') 3069 3069 quote = "\"";
+15 -7
contrib/credential/wincred/git-credential-wincred.c
··· 37 37 static WCHAR *wusername, *password, *protocol, *host, *path, target[1024], 38 38 *password_expiry_utc, *oauth_refresh_token; 39 39 40 + static void target_append(const WCHAR *src) 41 + { 42 + size_t avail = ARRAY_SIZE(target) - wcslen(target) - 1; /* -1 for NUL */ 43 + if (avail < wcslen(src)) 44 + die("target buffer overflow"); 45 + wcsncat(target, src, avail); 46 + } 47 + 40 48 static void write_item(const char *what, LPCWSTR wbuf, int wlen) 41 49 { 42 50 char *buf; ··· 328 336 329 337 /* prepare 'target', the unique key for the credential */ 330 338 wcscpy(target, L"git:"); 331 - wcsncat(target, protocol, ARRAY_SIZE(target)); 332 - wcsncat(target, L"://", ARRAY_SIZE(target)); 339 + target_append(protocol); 340 + target_append(L"://"); 333 341 if (wusername) { 334 - wcsncat(target, wusername, ARRAY_SIZE(target)); 335 - wcsncat(target, L"@", ARRAY_SIZE(target)); 342 + target_append(wusername); 343 + target_append(L"@"); 336 344 } 337 345 if (host) 338 - wcsncat(target, host, ARRAY_SIZE(target)); 346 + target_append(host); 339 347 if (path) { 340 - wcsncat(target, L"/", ARRAY_SIZE(target)); 341 - wcsncat(target, path, ARRAY_SIZE(target)); 348 + target_append(L"/"); 349 + target_append(path); 342 350 } 343 351 344 352 if (!strcmp(argv[1], "get"))
+266 -242
git-gui/git-gui.sh
··· 77 77 78 78 ###################################################################### 79 79 ## 80 - ## PATH lookup 80 + ## PATH lookup. Sanitize $PATH, assure exec/open use only that 81 + 82 + if {[is_Windows]} { 83 + set _path_sep {;} 84 + set _search_exe .exe 85 + } else { 86 + set _path_sep {:} 87 + set _search_exe {} 88 + } 89 + 90 + if {[is_Windows]} { 91 + set gitguidir [file dirname [info script]] 92 + regsub -all ";" $gitguidir "\\;" gitguidir 93 + set env(PATH) "$gitguidir;$env(PATH)" 94 + } 81 95 82 96 set _search_path {} 83 - proc _which {what args} { 84 - global env _search_exe _search_path 97 + set _path_seen [dict create] 98 + foreach p [split $env(PATH) $_path_sep] { 99 + # Keep only absolute paths, getting rid of ., empty, etc. 100 + if {[file pathtype $p] ne {absolute}} { 101 + continue 102 + } 103 + # Keep only the first occurence of any duplicates. 104 + set norm_p [file normalize $p] 105 + if {[dict exists $_path_seen $norm_p]} { 106 + continue 107 + } 108 + dict set _path_seen $norm_p 1 109 + lappend _search_path $norm_p 110 + } 111 + unset _path_seen 85 112 86 - if {$_search_path eq {}} { 87 - if {[is_Windows]} { 88 - set gitguidir [file dirname [info script]] 89 - regsub -all ";" $gitguidir "\\;" gitguidir 90 - set env(PATH) "$gitguidir;$env(PATH)" 91 - set _search_path [split $env(PATH) {;}] 92 - # Skip empty `PATH` elements 93 - set _search_path [lsearch -all -inline -not -exact \ 94 - $_search_path ""] 95 - set _search_exe .exe 113 + set env(PATH) [join $_search_path $_path_sep] 114 + 115 + if {[is_Windows]} { 116 + proc _which {what args} { 117 + global _search_exe _search_path 118 + 119 + if {[lsearch -exact $args -script] >= 0} { 120 + set suffix {} 121 + } elseif {[string match *$_search_exe [string tolower $what]]} { 122 + # The search string already has the file extension 123 + set suffix {} 96 124 } else { 97 - set _search_path [split $env(PATH) :] 98 - set _search_exe {} 125 + set suffix $_search_exe 126 + } 127 + 128 + foreach p $_search_path { 129 + set p [file join $p $what$suffix] 130 + if {[file exists $p]} { 131 + return [file normalize $p] 132 + } 99 133 } 134 + return {} 100 135 } 101 136 102 - if {[is_Windows] && [lsearch -exact $args -script] >= 0} { 103 - set suffix {} 104 - } else { 105 - set suffix $_search_exe 106 - } 137 + proc sanitize_command_line {command_line from_index} { 138 + set i $from_index 139 + while {$i < [llength $command_line]} { 140 + set cmd [lindex $command_line $i] 141 + if {[llength [file split $cmd]] < 2} { 142 + set fullpath [_which $cmd] 143 + if {$fullpath eq ""} { 144 + throw {NOT-FOUND} "$cmd not found in PATH" 145 + } 146 + lset command_line $i $fullpath 147 + } 107 148 108 - foreach p $_search_path { 109 - set p [file join $p $what$suffix] 110 - if {[file exists $p]} { 111 - return [file normalize $p] 149 + # handle piped commands, e.g. `exec A | B` 150 + for {incr i} {$i < [llength $command_line]} {incr i} { 151 + if {[lindex $command_line $i] eq "|"} { 152 + incr i 153 + break 154 + } 155 + } 112 156 } 157 + return $command_line 113 158 } 114 - return {} 115 - } 159 + 160 + # Override `exec` to avoid unsafe PATH lookup 116 161 117 - proc sanitize_command_line {command_line from_index} { 118 - set i $from_index 119 - while {$i < [llength $command_line]} { 120 - set cmd [lindex $command_line $i] 121 - if {[llength [file split $cmd]] < 2} { 122 - set fullpath [_which $cmd] 123 - if {$fullpath eq ""} { 124 - throw {NOT-FOUND} "$cmd not found in PATH" 125 - } 126 - lset command_line $i $fullpath 127 - } 162 + rename exec real_exec 128 163 129 - # handle piped commands, e.g. `exec A | B` 130 - for {incr i} {$i < [llength $command_line]} {incr i} { 131 - if {[lindex $command_line $i] eq "|"} { 164 + proc exec {args} { 165 + # skip options 166 + for {set i 0} {$i < [llength $args]} {incr i} { 167 + set arg [lindex $args $i] 168 + if {$arg eq "--"} { 132 169 incr i 170 + break 171 + } 172 + if {[string range $arg 0 0] ne "-"} { 133 173 break 134 174 } 135 175 } 176 + set args [sanitize_command_line $args $i] 177 + uplevel 1 real_exec $args 136 178 } 137 - return $command_line 138 - } 139 179 140 - # Override `exec` to avoid unsafe PATH lookup 180 + # Override `open` to avoid unsafe PATH lookup 141 181 142 - rename exec real_exec 182 + rename open real_open 143 183 144 - proc exec {args} { 145 - # skip options 146 - for {set i 0} {$i < [llength $args]} {incr i} { 147 - set arg [lindex $args $i] 148 - if {$arg eq "--"} { 149 - incr i 150 - break 151 - } 152 - if {[string range $arg 0 0] ne "-"} { 153 - break 184 + proc open {args} { 185 + set arg0 [lindex $args 0] 186 + if {[string range $arg0 0 0] eq "|"} { 187 + set command_line [string trim [string range $arg0 1 end]] 188 + lset args 0 "| [sanitize_command_line $command_line 0]" 154 189 } 190 + uplevel 1 real_open $args 155 191 } 156 - set args [sanitize_command_line $args $i] 157 - uplevel 1 real_exec $args 192 + 193 + } else { 194 + # On non-Windows platforms, auto_execok, exec, and open are safe, and will 195 + # use the sanitized search path. But, we need _which for these. 196 + 197 + proc _which {what args} { 198 + return [lindex [auto_execok $what] 0] 199 + } 158 200 } 159 201 160 - # Override `open` to avoid unsafe PATH lookup 202 + # Wrap exec/open to sanitize arguments 161 203 162 - rename open real_open 204 + # unsafe arguments begin with redirections or the pipe or background operators 205 + proc is_arg_unsafe {arg} { 206 + regexp {^([<|>&]|2>)} $arg 207 + } 208 + 209 + proc make_arg_safe {arg} { 210 + if {[is_arg_unsafe $arg]} { 211 + set arg [file join . $arg] 212 + } 213 + return $arg 214 + } 215 + 216 + proc make_arglist_safe {arglist} { 217 + set res {} 218 + foreach arg $arglist { 219 + lappend res [make_arg_safe $arg] 220 + } 221 + return $res 222 + } 223 + 224 + # executes one command 225 + # no redirections or pipelines are possible 226 + # cmd is a list that specifies the command and its arguments 227 + # calls `exec` and returns its value 228 + proc safe_exec {cmd} { 229 + eval exec [make_arglist_safe $cmd] 230 + } 231 + 232 + # executes one command in the background 233 + # no redirections or pipelines are possible 234 + # cmd is a list that specifies the command and its arguments 235 + # calls `exec` and returns its value 236 + proc safe_exec_bg {cmd} { 237 + eval exec [make_arglist_safe $cmd] & 238 + } 163 239 164 - proc open {args} { 165 - set arg0 [lindex $args 0] 166 - if {[string range $arg0 0 0] eq "|"} { 167 - set command_line [string trim [string range $arg0 1 end]] 168 - lset args 0 "| [sanitize_command_line $command_line 0]" 240 + proc safe_open_file {filename flags} { 241 + # a file name starting with "|" would attempt to run a process 242 + # but such a file name must be treated as a relative path 243 + # hide the "|" behind "./" 244 + if {[string index $filename 0] eq "|"} { 245 + set filename [file join . $filename] 169 246 } 170 - uplevel 1 real_open $args 247 + open $filename $flags 171 248 } 249 + 250 + # End exec/open wrappers 172 251 173 252 ###################################################################### 174 253 ## ··· 270 349 271 350 if {[tk windowingsystem] eq "aqua"} { 272 351 catch { 273 - exec osascript -e [format { 352 + safe_exec [list osascript -e [format { 274 353 tell application "System Events" 275 354 set frontmost of processes whose unix id is %d to true 276 355 end tell 277 - } [pid]] 356 + } [pid]]] 278 357 } 279 358 } 280 359 ··· 304 383 # branches). 305 384 set _last_merged_branch {} 306 385 386 + # for testing, allow unconfigured _shellpath 387 + if {[string match @@* $_shellpath]} { 388 + if {[info exists env(SHELL)]} { 389 + set _shellpath $env(SHELL) 390 + } else { 391 + set _shellpath /bin/sh 392 + } 393 + } 394 + 395 + if {[is_Windows]} { 396 + set _shellpath [safe_exec [list cygpath -m $_shellpath]] 397 + } 398 + 399 + if {![file executable $_shellpath] || \ 400 + !([file pathtype $_shellpath] eq {absolute})} { 401 + set errmsg "The defined shell ('$_shellpath') is not usable, \ 402 + it must be an absolute path to an executable." 403 + puts stderr $errmsg 404 + 405 + catch {wm withdraw .} 406 + tk_messageBox \ 407 + -icon error \ 408 + -type ok \ 409 + -title "git-gui: configuration error" \ 410 + -message $errmsg 411 + exit 1 412 + } 413 + 414 + 307 415 proc shellpath {} { 308 - global _shellpath env 309 - if {[string match @@* $_shellpath]} { 310 - if {[info exists env(SHELL)]} { 311 - return $env(SHELL) 312 - } else { 313 - return /bin/sh 314 - } 315 - } 416 + global _shellpath 316 417 return $_shellpath 317 418 } 318 419 ··· 494 595 # Tcl on Windows doesn't know it. 495 596 # 496 597 set p [gitexec git-$name] 497 - set f [open $p r] 598 + set f [safe_open_file $p r] 498 599 set s [gets $f] 499 600 close $f 500 601 ··· 524 625 return $v 525 626 } 526 627 527 - # Test a file for a hashbang to identify executable scripts on Windows. 528 - proc is_shellscript {filename} { 529 - if {![file exists $filename]} {return 0} 530 - set f [open $filename r] 531 - fconfigure $f -encoding binary 532 - set magic [read $f 2] 533 - close $f 534 - return [expr {$magic eq "#!"}] 535 - } 536 - 537 - # Run a command connected via pipes on stdout. 628 + # Run a shell command connected via pipes on stdout. 538 629 # This is for use with textconv filters and uses sh -c "..." to allow it to 539 - # contain a command with arguments. On windows we must check for shell 540 - # scripts specifically otherwise just call the filter command. 630 + # contain a command with arguments. We presume this 631 + # to be a shellscript that the configured shell (/bin/sh by default) knows 632 + # how to run. 541 633 proc open_cmd_pipe {cmd path} { 542 - global env 543 - if {![file executable [shellpath]]} { 544 - set exe [auto_execok [lindex $cmd 0]] 545 - if {[is_shellscript [lindex $exe 0]]} { 546 - set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path] 547 - } else { 548 - set run [concat $exe [lrange $cmd 1 end] $path] 549 - } 550 - } else { 551 - set run [list [shellpath] -c "$cmd \"\$0\"" $path] 552 - } 634 + set run [list [shellpath] -c "$cmd \"\$0\"" $path] 635 + set run [make_arglist_safe $run] 553 636 return [open |$run r] 554 637 } 555 638 ··· 559 642 560 643 if {![info exists _nice]} { 561 644 set _nice [_which nice] 562 - if {[catch {exec $_nice git version}]} { 645 + if {[catch {safe_exec [list $_nice git version]}]} { 563 646 set _nice {} 564 647 } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} { 565 648 set _nice {} ··· 571 654 } 572 655 573 656 proc git {args} { 574 - set fd [eval [list git_read] $args] 657 + git_redir $args {} 658 + } 659 + 660 + proc git_redir {cmd redir} { 661 + set fd [git_read $cmd $redir] 575 662 fconfigure $fd -translation binary -encoding utf-8 576 663 set result [string trimright [read $fd] "\n"] 577 664 close $fd ··· 581 668 return $result 582 669 } 583 670 584 - proc _open_stdout_stderr {cmd} { 585 - _trace_exec $cmd 671 + proc safe_open_command {cmd {redir {}}} { 672 + set cmd [make_arglist_safe $cmd] 673 + _trace_exec [concat $cmd $redir] 586 674 if {[catch { 587 - set fd [open [concat [list | ] $cmd] r] 588 - } err]} { 589 - if { [lindex $cmd end] eq {2>@1} 590 - && $err eq {can not find channel named "1"} 591 - } { 592 - # Older versions of Tcl 8.4 don't have this 2>@1 IO 593 - # redirect operator. Fallback to |& cat for those. 594 - # The command was not actually started, so its safe 595 - # to try to start it a second time. 596 - # 597 - set fd [open [concat \ 598 - [list | ] \ 599 - [lrange $cmd 0 end-1] \ 600 - [list |& cat] \ 601 - ] r] 602 - } else { 603 - error $err 604 - } 675 + set fd [open [concat [list | ] $cmd $redir] r] 676 + } err]} { 677 + error $err 605 678 } 606 679 fconfigure $fd -eofchar {} 607 680 return $fd 608 681 } 609 682 610 - proc git_read {args} { 611 - set opt [list] 612 - 613 - while {1} { 614 - switch -- [lindex $args 0] { 615 - --nice { 616 - _lappend_nice opt 617 - } 618 - 619 - --stderr { 620 - lappend args 2>@1 621 - } 622 - 623 - default { 624 - break 625 - } 683 + proc git_read {cmd {redir {}}} { 684 + set cmdp [_git_cmd [lindex $cmd 0]] 685 + set cmd [lrange $cmd 1 end] 626 686 627 - } 628 - 629 - set args [lrange $args 1 end] 630 - } 631 - 632 - set cmdp [_git_cmd [lindex $args 0]] 633 - set args [lrange $args 1 end] 634 - 635 - return [_open_stdout_stderr [concat $opt $cmdp $args]] 687 + return [safe_open_command [concat $cmdp $cmd] $redir] 636 688 } 637 689 638 - proc git_write {args} { 690 + proc git_read_nice {cmd} { 639 691 set opt [list] 640 692 641 - while {1} { 642 - switch -- [lindex $args 0] { 643 - --nice { 644 - _lappend_nice opt 645 - } 693 + _lappend_nice opt 646 694 647 - default { 648 - break 649 - } 695 + set cmdp [_git_cmd [lindex $cmd 0]] 696 + set cmd [lrange $cmd 1 end] 650 697 651 - } 698 + return [safe_open_command [concat $opt $cmdp $cmd]] 699 + } 652 700 653 - set args [lrange $args 1 end] 654 - } 701 + proc git_write {cmd} { 702 + set cmd [make_arglist_safe $cmd] 703 + set cmdp [_git_cmd [lindex $cmd 0]] 704 + set cmd [lrange $cmd 1 end] 655 705 656 - set cmdp [_git_cmd [lindex $args 0]] 657 - set args [lrange $args 1 end] 658 - 659 - _trace_exec [concat $opt $cmdp $args] 660 - return [open [concat [list | ] $opt $cmdp $args] w] 706 + _trace_exec [concat $cmdp $cmd] 707 + return [open [concat [list | ] $cmdp $cmd] w] 661 708 } 662 709 663 710 proc githook_read {hook_name args} { 664 - set cmd [concat git hook run --ignore-missing $hook_name -- $args 2>@1] 665 - return [_open_stdout_stderr $cmd] 711 + git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1] 666 712 } 667 713 668 714 proc kill_file_process {fd} { ··· 670 716 671 717 catch { 672 718 if {[is_Windows]} { 673 - exec taskkill /pid $process 719 + safe_exec [list taskkill /pid $process] 674 720 } else { 675 - exec kill $process 721 + safe_exec [list kill $process] 676 722 } 677 723 } 678 724 } ··· 698 744 proc load_current_branch {} { 699 745 global current_branch is_detached 700 746 701 - set fd [open [gitdir HEAD] r] 747 + set fd [safe_open_file [gitdir HEAD] r] 702 748 fconfigure $fd -translation binary -encoding utf-8 703 749 if {[gets $fd ref] < 1} { 704 750 set ref {} ··· 1060 1106 ## configure our library 1061 1107 1062 1108 set idx [file join $oguilib tclIndex] 1063 - if {[catch {set fd [open $idx r]} err]} { 1109 + if {[catch {set fd [safe_open_file $idx r]} err]} { 1064 1110 catch {wm withdraw .} 1065 1111 tk_messageBox \ 1066 1112 -icon error \ ··· 1098 1144 ## 1099 1145 ## config file parsing 1100 1146 1101 - git-version proc _parse_config {arr_name args} { 1102 - >= 1.5.3 { 1103 - upvar $arr_name arr 1104 - array unset arr 1105 - set buf {} 1106 - catch { 1107 - set fd_rc [eval \ 1108 - [list git_read config] \ 1109 - $args \ 1110 - [list --null --list]] 1111 - fconfigure $fd_rc -translation binary -encoding utf-8 1112 - set buf [read $fd_rc] 1113 - close $fd_rc 1114 - } 1115 - foreach line [split $buf "\0"] { 1116 - if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { 1117 - if {[is_many_config $name]} { 1118 - lappend arr($name) $value 1119 - } else { 1120 - set arr($name) $value 1121 - } 1122 - } elseif {[regexp {^([^\n]+)$} $line line name]} { 1123 - # no value given, but interpreting them as 1124 - # boolean will be handled as true 1125 - set arr($name) {} 1126 - } 1127 - } 1147 + proc _parse_config {arr_name args} { 1148 + upvar $arr_name arr 1149 + array unset arr 1150 + set buf {} 1151 + catch { 1152 + set fd_rc [git_read \ 1153 + [concat config \ 1154 + $args \ 1155 + --null --list]] 1156 + fconfigure $fd_rc -translation binary -encoding utf-8 1157 + set buf [read $fd_rc] 1158 + close $fd_rc 1128 1159 } 1129 - default { 1130 - upvar $arr_name arr 1131 - array unset arr 1132 - catch { 1133 - set fd_rc [eval [list git_read config --list] $args] 1134 - while {[gets $fd_rc line] >= 0} { 1135 - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { 1136 - if {[is_many_config $name]} { 1137 - lappend arr($name) $value 1138 - } else { 1139 - set arr($name) $value 1140 - } 1141 - } elseif {[regexp {^([^=]+)$} $line line name]} { 1142 - # no value given, but interpreting them as 1143 - # boolean will be handled as true 1144 - set arr($name) {} 1145 - } 1160 + foreach line [split $buf "\0"] { 1161 + if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { 1162 + if {[is_many_config $name]} { 1163 + lappend arr($name) $value 1164 + } else { 1165 + set arr($name) $value 1146 1166 } 1147 - close $fd_rc 1167 + } elseif {[regexp {^([^\n]+)$} $line line name]} { 1168 + # no value given, but interpreting them as 1169 + # boolean will be handled as true 1170 + set arr($name) {} 1148 1171 } 1149 1172 } 1150 1173 } ··· 1420 1443 set merge_head [gitdir MERGE_HEAD] 1421 1444 if {[file exists $merge_head]} { 1422 1445 set ct merge 1423 - set fd_mh [open $merge_head r] 1446 + set fd_mh [safe_open_file $merge_head r] 1424 1447 while {[gets $fd_mh line] >= 0} { 1425 1448 lappend mh $line 1426 1449 } ··· 1439 1462 return $p 1440 1463 } 1441 1464 if {$empty_tree eq {}} { 1442 - set empty_tree [git mktree << {}] 1465 + set empty_tree [git_redir [list mktree] [list << {}]] 1443 1466 } 1444 1467 return $empty_tree 1445 1468 } ··· 1498 1521 } else { 1499 1522 set rescan_active 1 1500 1523 ui_status [mc "Refreshing file status..."] 1501 - set fd_rf [git_read update-index \ 1524 + set fd_rf [git_read [list update-index \ 1502 1525 -q \ 1503 1526 --unmerged \ 1504 1527 --ignore-missing \ 1505 1528 --refresh \ 1506 - ] 1529 + ]] 1507 1530 fconfigure $fd_rf -blocking 0 -translation binary 1508 1531 fileevent $fd_rf readable \ 1509 1532 [list rescan_stage2 $fd_rf $after] ··· 1543 1566 set rescan_active 2 1544 1567 ui_status [mc "Scanning for modified files ..."] 1545 1568 if {[git-version >= "1.7.2"]} { 1546 - set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]] 1569 + set fd_di [git_read [list diff-index --cached --ignore-submodules=dirty -z [PARENT]]] 1547 1570 } else { 1548 - set fd_di [git_read diff-index --cached -z [PARENT]] 1571 + set fd_di [git_read [list diff-index --cached -z [PARENT]]] 1549 1572 } 1550 - set fd_df [git_read diff-files -z] 1573 + set fd_df [git_read [list diff-files -z]] 1551 1574 1552 1575 fconfigure $fd_di -blocking 0 -translation binary -encoding binary 1553 1576 fconfigure $fd_df -blocking 0 -translation binary -encoding binary ··· 1556 1579 fileevent $fd_df readable [list read_diff_files $fd_df $after] 1557 1580 1558 1581 if {[is_config_true gui.displayuntracked]} { 1559 - set fd_lo [eval git_read ls-files --others -z $ls_others] 1582 + set fd_lo [git_read [concat ls-files --others -z $ls_others]] 1560 1583 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary 1561 1584 fileevent $fd_lo readable [list read_ls_others $fd_lo $after] 1562 1585 incr rescan_active ··· 1568 1591 1569 1592 set f [gitdir $file] 1570 1593 if {[file isfile $f]} { 1571 - if {[catch {set fd [open $f r]}]} { 1594 + if {[catch {set fd [safe_open_file $f r]}]} { 1572 1595 return 0 1573 1596 } 1574 1597 fconfigure $fd -eofchar {} ··· 1592 1615 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an 1593 1616 # empty file but existent file. 1594 1617 1595 - set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a] 1618 + set fd_pcm [safe_open_file [gitdir PREPARE_COMMIT_MSG] a] 1596 1619 1597 1620 if {[file isfile [gitdir MERGE_MSG]]} { 1598 1621 set pcm_source "merge" 1599 - set fd_mm [open [gitdir MERGE_MSG] r] 1622 + set fd_mm [safe_open_file [gitdir MERGE_MSG] r] 1600 1623 fconfigure $fd_mm -encoding utf-8 1601 1624 puts -nonewline $fd_pcm [read $fd_mm] 1602 1625 close $fd_mm 1603 1626 } elseif {[file isfile [gitdir SQUASH_MSG]]} { 1604 1627 set pcm_source "squash" 1605 - set fd_sm [open [gitdir SQUASH_MSG] r] 1628 + set fd_sm [safe_open_file [gitdir SQUASH_MSG] r] 1606 1629 fconfigure $fd_sm -encoding utf-8 1607 1630 puts -nonewline $fd_pcm [read $fd_sm] 1608 1631 close $fd_sm 1609 1632 } elseif {[file isfile [get_config commit.template]]} { 1610 1633 set pcm_source "template" 1611 - set fd_sm [open [get_config commit.template] r] 1634 + set fd_sm [safe_open_file [get_config commit.template] r] 1612 1635 fconfigure $fd_sm -encoding utf-8 1613 1636 puts -nonewline $fd_pcm [read $fd_sm] 1614 1637 close $fd_sm ··· 2198 2221 unset env(GIT_DIR) 2199 2222 unset env(GIT_WORK_TREE) 2200 2223 } 2201 - eval exec $cmd $revs "--" "--" & 2224 + safe_exec_bg [concat $cmd $revs "--" "--"] 2202 2225 2203 2226 set env(GIT_DIR) $_gitdir 2204 2227 set env(GIT_WORK_TREE) $_gitworktree ··· 2235 2258 set pwd [pwd] 2236 2259 cd $current_diff_path 2237 2260 2238 - eval exec $exe gui & 2261 + safe_exec_bg [concat $exe gui] 2239 2262 2240 2263 set env(GIT_DIR) $_gitdir 2241 2264 set env(GIT_WORK_TREE) $_gitworktree ··· 2266 2289 2267 2290 proc do_explore {} { 2268 2291 global _gitworktree 2269 - set explorer [get_explorer] 2270 - eval exec $explorer [list [file nativename $_gitworktree]] & 2292 + set cmd [get_explorer] 2293 + lappend cmd [file nativename $_gitworktree] 2294 + safe_exec_bg $cmd 2271 2295 } 2272 2296 2273 2297 # Open file relative to the working tree by the default associated app. 2274 2298 proc do_file_open {file} { 2275 2299 global _gitworktree 2276 - set explorer [get_explorer] 2300 + set cmd [get_explorer] 2277 2301 set full_file_path [file join $_gitworktree $file] 2278 - exec $explorer [file nativename $full_file_path] & 2302 + lappend cmd [file nativename $full_file_path] 2303 + safe_exec_bg $cmd 2279 2304 } 2280 2305 2281 2306 set is_quitting 0 ··· 2309 2334 if {![string match amend* $commit_type] 2310 2335 && $msg ne {}} { 2311 2336 catch { 2312 - set fd [open $save w] 2337 + set fd [safe_open_file $save w] 2313 2338 fconfigure $fd -encoding utf-8 2314 2339 puts -nonewline $fd $msg 2315 2340 close $fd ··· 2753 2778 2754 2779 if {[is_Windows]} { 2755 2780 # Use /git-bash.exe if available 2756 - set normalized [file normalize $::argv0] 2757 - regsub "/mingw../libexec/git-core/git-gui$" \ 2758 - $normalized "/git-bash.exe" cmdLine 2759 - if {$cmdLine != $normalized && [file exists $cmdLine]} { 2760 - set cmdLine [list "Git Bash" $cmdLine &] 2781 + set _git_bash [safe_exec [list cygpath -m /git-bash.exe]] 2782 + if {[file executable $_git_bash]} { 2783 + set _bash_cmdline [list "Git Bash" $_git_bash] 2761 2784 } else { 2762 - set cmdLine [list "Git Bash" bash --login -l &] 2785 + set _bash_cmdline [list "Git Bash" bash --login -l] 2763 2786 } 2764 2787 .mbar.repository add command \ 2765 2788 -label [mc "Git Bash"] \ 2766 - -command {eval exec [auto_execok start] $cmdLine} 2789 + -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]} 2790 + unset _git_bash 2767 2791 } 2768 2792 2769 2793 if {[is_Windows] || ![is_bare]} { ··· 4070 4094 } 4071 4095 } elseif {$m} { 4072 4096 catch { 4073 - set fd [open [gitdir GITGUI_BCK] w] 4097 + set fd [safe_open_file [gitdir GITGUI_BCK] w] 4074 4098 fconfigure $fd -encoding utf-8 4075 4099 puts -nonewline $fd $msg 4076 4100 close $fd
+6 -6
git-gui/lib/blame.tcl
··· 481 481 if {$do_textconv ne 0} { 482 482 set fd [open_cmd_pipe $textconv $path] 483 483 } else { 484 - set fd [open $path r] 484 + set fd [safe_open_file $path r] 485 485 } 486 486 fconfigure $fd -eofchar {} 487 487 } else { 488 488 if {$do_textconv ne 0} { 489 - set fd [git_read cat-file --textconv "$commit:$path"] 489 + set fd [git_read [list cat-file --textconv "$commit:$path"]] 490 490 } else { 491 - set fd [git_read cat-file blob "$commit:$path"] 491 + set fd [git_read [list cat-file blob "$commit:$path"]] 492 492 } 493 493 } 494 494 fconfigure $fd \ ··· 617 617 } 618 618 619 619 lappend options -- $path 620 - set fd [eval git_read --nice blame $options] 620 + set fd [git_read_nice [concat blame $options]] 621 621 fconfigure $fd -blocking 0 -translation lf -encoding utf-8 622 622 fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d] 623 623 set current_fd $fd ··· 986 986 if {[catch {set msg $header($cmit,message)}]} { 987 987 set msg {} 988 988 catch { 989 - set fd [git_read cat-file commit $cmit] 989 + set fd [git_read [list cat-file commit $cmit]] 990 990 fconfigure $fd -encoding binary -translation lf 991 991 # By default commits are assumed to be in utf-8 992 992 set enc utf-8 ··· 1134 1134 } else { 1135 1135 set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path] 1136 1136 } 1137 - if {[catch {set fd [eval git_read $diffcmd]} err]} { 1137 + if {[catch {set fd [git_read $diffcmd]} err]} { 1138 1138 $status_operation stop [mc "Unable to display parent"] 1139 1139 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] 1140 1140 return
+3 -3
git-gui/lib/branch.tcl
··· 7 7 set rh refs/heads 8 8 set rh_len [expr {[string length $rh] + 1}] 9 9 set all_heads [list] 10 - set fd [git_read for-each-ref --format=%(refname) $rh] 10 + set fd [git_read [list for-each-ref --format=%(refname) $rh]] 11 11 fconfigure $fd -translation binary -encoding utf-8 12 12 while {[gets $fd line] > 0} { 13 13 if {!$some_heads_tracking || ![is_tracking_branch $line]} { ··· 21 21 22 22 proc load_all_tags {} { 23 23 set all_tags [list] 24 - set fd [git_read for-each-ref \ 24 + set fd [git_read [list for-each-ref \ 25 25 --sort=-taggerdate \ 26 26 --format=%(refname) \ 27 - refs/tags] 27 + refs/tags]] 28 28 fconfigure $fd -translation binary -encoding utf-8 29 29 while {[gets $fd line] > 0} { 30 30 if {![regsub ^refs/tags/ $line {} name]} continue
+1 -1
git-gui/lib/browser.tcl
··· 196 196 lappend browser_stack [list $tree_id $name] 197 197 $w conf -state disabled 198 198 199 - set fd [git_read ls-tree -z $tree_id] 199 + set fd [git_read [list ls-tree -z $tree_id]] 200 200 fconfigure $fd -blocking 0 -translation binary -encoding utf-8 201 201 fileevent $fd readable [cb _read $fd] 202 202 }
+8 -17
git-gui/lib/checkout_op.tcl
··· 304 304 _readtree $this 305 305 } else { 306 306 ui_status [mc "Refreshing file status..."] 307 - set fd [git_read update-index \ 307 + set fd [git_read [list update-index \ 308 308 -q \ 309 309 --unmerged \ 310 310 --ignore-missing \ 311 311 --refresh \ 312 - ] 312 + ]] 313 313 fconfigure $fd -blocking 0 -translation binary 314 314 fileevent $fd readable [cb _refresh_wait $fd] 315 315 } ··· 345 345 [mc "Updating working directory to '%s'..." [_name $this]] \ 346 346 [mc "files checked out"]] 347 347 348 - set fd [git_read --stderr read-tree \ 348 + set fd [git_read [list read-tree \ 349 349 -m \ 350 350 -u \ 351 351 -v \ 352 352 --exclude-per-directory=.gitignore \ 353 353 $HEAD \ 354 354 $new_hash \ 355 - ] 355 + ] \ 356 + [list 2>@1]] 356 357 fconfigure $fd -blocking 0 -translation binary 357 358 fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation] 358 359 } ··· 510 511 delete_this 511 512 } 512 513 513 - git-version proc _detach_HEAD {log new} { 514 - >= 1.5.3 { 515 - git update-ref --no-deref -m $log HEAD $new 516 - } 517 - default { 518 - set p [gitdir HEAD] 519 - file delete $p 520 - set fd [open $p w] 521 - fconfigure $fd -translation lf -encoding utf-8 522 - puts $fd $new 523 - close $fd 524 - } 514 + proc _detach_HEAD {log new} { 515 + git update-ref --no-deref -m $log HEAD $new 525 516 } 526 517 527 518 method _confirm_reset {cur} { ··· 582 573 pack $w.buttons.cancel -side right -padx 5 583 574 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 584 575 585 - set fd [git_read rev-list --pretty=oneline $cur ^$new_hash] 576 + set fd [git_read [list rev-list --pretty=oneline $cur ^$new_hash]] 586 577 while {[gets $fd line] > 0} { 587 578 set abbr [string range $line 0 7] 588 579 set subj [string range $line 41 end]
+12 -11
git-gui/lib/choose_repository.tcl
··· 641 641 set pwd [pwd] 642 642 if {[catch { 643 643 file mkdir [gitdir objects info] 644 - set f_in [open [file join $objdir info alternates] r] 645 - set f_cp [open [gitdir objects info alternates] w] 644 + set f_in [safe_open_file [file join $objdir info alternates] r] 645 + set f_cp [safe_open_file [gitdir objects info alternates] w] 646 646 fconfigure $f_in -translation binary -encoding binary 647 647 fconfigure $f_cp -translation binary -encoding binary 648 648 cd $objdir ··· 727 727 [cb _do_clone_tags] 728 728 } 729 729 shared { 730 - set fd [open [gitdir objects info alternates] w] 730 + set fd [safe_open_file [gitdir objects info alternates] w] 731 731 fconfigure $fd -translation binary 732 732 puts $fd $objdir 733 733 close $fd ··· 760 760 } 761 761 foreach p $tocopy { 762 762 if {[catch { 763 - set f_in [open [file join $objdir $p] r] 764 - set f_cp [open [file join .git objects $p] w] 763 + set f_in [safe_open_file [file join $objdir $p] r] 764 + set f_cp [safe_open_file [file join .git objects $p] w] 765 765 fconfigure $f_in -translation binary -encoding binary 766 766 fconfigure $f_cp -translation binary -encoding binary 767 767 ··· 818 818 error_popup [mc "Not a Git repository: %s" [file tail $origin_url]] 819 819 return 0 820 820 } 821 - set fd_in [git_read for-each-ref \ 821 + set fd_in [git_read [list for-each-ref \ 822 822 --tcl \ 823 - {--format=list %(refname) %(objectname) %(*objectname)}] 823 + {--format=list %(refname) %(objectname) %(*objectname)}]] 824 824 cd $pwd 825 825 826 - set fd [open [gitdir packed-refs] w] 826 + set fd [safe_open_file [gitdir packed-refs] w] 827 827 fconfigure $fd -translation binary 828 828 puts $fd "# pack-refs with: peeled" 829 829 while {[gets $fd_in line] >= 0} { ··· 877 877 878 878 set HEAD {} 879 879 if {[file exists [gitdir FETCH_HEAD]]} { 880 - set fd [open [gitdir FETCH_HEAD] r] 880 + set fd [safe_open_file [gitdir FETCH_HEAD] r] 881 881 while {[gets $fd line] >= 0} { 882 882 if {[regexp "^(.{40})\t\t" $line line HEAD]} { 883 883 break ··· 953 953 [mc "files"]] 954 954 955 955 set readtree_err {} 956 - set fd [git_read --stderr read-tree \ 956 + set fd [git_read [list read-tree \ 957 957 -m \ 958 958 -u \ 959 959 -v \ 960 960 HEAD \ 961 961 HEAD \ 962 - ] 962 + ] \ 963 + [list 2>@1]] 963 964 fconfigure $fd -blocking 0 -translation binary 964 965 fileevent $fd readable [cb _readtree_wait $fd] 965 966 }
+4 -4
git-gui/lib/choose_rev.tcl
··· 146 146 append fmt { %(*subject)} 147 147 append fmt {]} 148 148 set all_refn [list] 149 - set fr_fd [git_read for-each-ref \ 149 + set fr_fd [git_read [list for-each-ref \ 150 150 --tcl \ 151 151 --sort=-taggerdate \ 152 152 --format=$fmt \ 153 153 refs/heads \ 154 154 refs/remotes \ 155 155 refs/tags \ 156 - ] 156 + ]] 157 157 fconfigure $fr_fd -translation lf -encoding utf-8 158 158 while {[gets $fr_fd line] > 0} { 159 159 set line [eval $line] ··· 176 176 close $fr_fd 177 177 178 178 if {$unmerged_only} { 179 - set fr_fd [git_read rev-list --all ^$::HEAD] 179 + set fr_fd [git_read [list rev-list --all ^$::HEAD]] 180 180 while {[gets $fr_fd sha1] > 0} { 181 181 if {[catch {set rlst $cmt_refn($sha1)}]} continue 182 182 foreach refn $rlst { ··· 579 579 580 580 set last {} 581 581 if {[catch {set last [file mtime [gitdir $name]]}] 582 - && ![catch {set g [open [gitdir logs $name] r]}]} { 582 + && ![catch {set g [safe_open_file [gitdir logs $name] r]}]} { 583 583 fconfigure $g -translation binary 584 584 while {[gets $g line] >= 0} { 585 585 if {[regexp {> ([1-9][0-9]*) } $line line when]} {
+7 -7
git-gui/lib/commit.tcl
··· 27 27 if {[catch { 28 28 set name "" 29 29 set email "" 30 - set fd [git_read cat-file commit $curHEAD] 30 + set fd [git_read [list cat-file commit $curHEAD]] 31 31 fconfigure $fd -encoding binary -translation lf 32 32 # By default commits are assumed to be in utf-8 33 33 set enc utf-8 ··· 225 225 # -- Build the message file. 226 226 # 227 227 set msg_p [gitdir GITGUI_EDITMSG] 228 - set msg_wt [open $msg_p w] 228 + set msg_wt [safe_open_file $msg_p w] 229 229 fconfigure $msg_wt -translation lf 230 230 setup_commit_encoding $msg_wt 231 231 puts $msg_wt $msg ··· 325 325 326 326 proc commit_writetree {curHEAD msg_p} { 327 327 ui_status [mc "Committing changes..."] 328 - set fd_wt [git_read write-tree] 328 + set fd_wt [git_read [list write-tree]] 329 329 fileevent $fd_wt readable \ 330 330 [list commit_committree $fd_wt $curHEAD $msg_p] 331 331 } ··· 350 350 # -- Verify this wasn't an empty change. 351 351 # 352 352 if {$commit_type eq {normal}} { 353 - set fd_ot [git_read cat-file commit $PARENT] 353 + set fd_ot [git_read [list cat-file commit $PARENT]] 354 354 fconfigure $fd_ot -encoding binary -translation lf 355 355 set old_tree [gets $fd_ot] 356 356 close $fd_ot ··· 388 388 foreach p [concat $PARENT $MERGE_HEAD] { 389 389 lappend cmd -p $p 390 390 } 391 - lappend cmd <$msg_p 392 - if {[catch {set cmt_id [eval git $cmd]} err]} { 391 + set msgtxt [list <$msg_p] 392 + if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} { 393 393 catch {file delete $msg_p} 394 394 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] 395 395 ui_status [mc "Commit failed."] ··· 409 409 if {$commit_type ne {normal}} { 410 410 append reflogm " ($commit_type)" 411 411 } 412 - set msg_fd [open $msg_p r] 412 + set msg_fd [safe_open_file $msg_p r] 413 413 setup_commit_encoding $msg_fd 1 414 414 gets $msg_fd subject 415 415 close $msg_fd
+2 -3
git-gui/lib/console.tcl
··· 92 92 93 93 method exec {cmd {after {}}} { 94 94 if {[lindex $cmd 0] eq {git}} { 95 - set fd_f [eval git_read --stderr [lrange $cmd 1 end]] 95 + set fd_f [git_read [lrange $cmd 1 end] [list 2>@1]] 96 96 } else { 97 - lappend cmd 2>@1 98 - set fd_f [_open_stdout_stderr $cmd] 97 + set fd_f [safe_open_command $cmd [list 2>@1]] 99 98 } 100 99 fconfigure $fd_f -blocking 0 -translation binary 101 100 fileevent $fd_f readable [cb _read $fd_f $after]
+1 -1
git-gui/lib/database.tcl
··· 3 3 4 4 proc do_stats {} { 5 5 global use_ttk NS 6 - set fd [git_read count-objects -v] 6 + set fd [git_read [list count-objects -v]] 7 7 while {[gets $fd line] > 0} { 8 8 if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { 9 9 set stats($name) $value
+6 -6
git-gui/lib/diff.tcl
··· 202 202 set sz [string length $content] 203 203 } 204 204 file { 205 - set fd [open $path r] 205 + set fd [safe_open_file $path r] 206 206 fconfigure $fd \ 207 207 -eofchar {} \ 208 208 -encoding [get_path_encoding $path] ··· 226 226 $ui_diff insert end \ 227 227 "* [mc "Git Repository (subproject)"]\n" \ 228 228 d_info 229 - } elseif {![catch {set type [exec file $path]}]} { 229 + } elseif {![catch {set type [safe_exec [list file $path]]}]} { 230 230 set n [string length $path] 231 231 if {[string equal -length $n $path $type]} { 232 232 set type [string range $type $n end] ··· 338 338 } 339 339 } 340 340 341 - if {[catch {set fd [eval git_read --nice $cmd]} err]} { 341 + if {[catch {set fd [git_read_nice $cmd]} err]} { 342 342 set diff_active 0 343 343 unlock_index 344 344 ui_status [mc "Unable to display %s" [escape_path $path]] ··· 617 617 618 618 if {[catch { 619 619 set enc [get_path_encoding $current_diff_path] 620 - set p [eval git_write $apply_cmd] 620 + set p [git_write $apply_cmd] 621 621 fconfigure $p -translation binary -encoding $enc 622 622 puts -nonewline $p $wholepatch 623 623 close $p} err]} { ··· 853 853 854 854 if {[catch { 855 855 set enc [get_path_encoding $current_diff_path] 856 - set p [eval git_write $apply_cmd] 856 + set p [git_write $apply_cmd] 857 857 fconfigure $p -translation binary -encoding $enc 858 858 puts -nonewline $p $current_diff_header 859 859 puts -nonewline $p $wholepatch ··· 890 890 891 891 if {[catch { 892 892 set enc $last_revert_enc 893 - set p [eval git_write $apply_cmd] 893 + set p [git_write $apply_cmd] 894 894 fconfigure $p -translation binary -encoding $enc 895 895 puts -nonewline $p $last_revert 896 896 close $p} err]} {
+4 -4
git-gui/lib/index.tcl
··· 75 75 if {$batch > 25} {set batch 25} 76 76 77 77 set status_bar_operation [$::main_status start $msg [mc "files"]] 78 - set fd [git_write update-index -z --index-info] 78 + set fd [git_write [list update-index -z --index-info]] 79 79 fconfigure $fd \ 80 80 -blocking 0 \ 81 81 -buffering full \ ··· 144 144 if {$batch > 25} {set batch 25} 145 145 146 146 set status_bar_operation [$::main_status start $msg [mc "files"]] 147 - set fd [git_write update-index --add --remove -z --stdin] 147 + set fd [git_write [list update-index --add --remove -z --stdin]] 148 148 fconfigure $fd \ 149 149 -blocking 0 \ 150 150 -buffering full \ ··· 218 218 if {$batch > 25} {set batch 25} 219 219 220 220 set status_bar_operation [$::main_status start $msg [mc "files"]] 221 - set fd [git_write checkout-index \ 221 + set fd [git_write [list checkout-index \ 222 222 --index \ 223 223 --quiet \ 224 224 --force \ 225 225 -z \ 226 226 --stdin \ 227 - ] 227 + ]] 228 228 fconfigure $fd \ 229 229 -blocking 0 \ 230 230 -buffering full \
+3 -3
git-gui/lib/merge.tcl
··· 93 93 set spec [$w_rev get_tracking_branch] 94 94 set cmit [$w_rev get_commit] 95 95 96 - set fh [open [gitdir FETCH_HEAD] w] 96 + set fh [safe_open_file [gitdir FETCH_HEAD] w] 97 97 fconfigure $fh -translation lf 98 98 if {$spec eq {}} { 99 99 set remote . ··· 118 118 set cmd [list git] 119 119 lappend cmd merge 120 120 lappend cmd --strategy=recursive 121 - lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]] 121 + lappend cmd [git_redir [list fmt-merge-msg] [list <[gitdir FETCH_HEAD]]] 122 122 lappend cmd HEAD 123 123 lappend cmd $name 124 124 } ··· 239 239 } 240 240 241 241 if {[ask_popup $op_question] eq {yes}} { 242 - set fd [git_read --stderr read-tree --reset -u -v HEAD] 242 + set fd [git_read [list read-tree --reset -u -v HEAD] [list 2>@1]] 243 243 fconfigure $fd -blocking 0 -translation binary 244 244 set status_bar_operation [$::main_status \ 245 245 start \
+4 -4
git-gui/lib/mergetool.tcl
··· 88 88 set merge_stages(3) {} 89 89 set merge_stages_buf {} 90 90 91 - set merge_stages_fd [eval git_read ls-files -u -z -- {$path}] 91 + set merge_stages_fd [git_read [list ls-files -u -z -- $path]] 92 92 93 93 fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary 94 94 fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont] ··· 293 293 foreach fname $stages { 294 294 if {$merge_stages($i) eq {}} { 295 295 file delete $fname 296 - catch { close [open $fname w] } 296 + catch { close [safe_open_file $fname w] } 297 297 } else { 298 298 # A hack to support autocrlf properly 299 299 git checkout-index -f --stage=$i -- $target ··· 343 343 344 344 # Force redirection to avoid interpreting output on stderr 345 345 # as an error, and launch the tool 346 - lappend cmdline {2>@1} 346 + set redir [list {2>@1}] 347 347 348 - if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} { 348 + if {[catch { set mtool_fd [safe_open_command $cmdline $redir] } err]} { 349 349 delete_temp_files $mtool_tmpfiles 350 350 error_popup [mc "Could not start the merge tool:\n\n%s" $err] 351 351 return
+4 -4
git-gui/lib/remote.tcl
··· 32 32 } 33 33 34 34 if {$pat ne {}} { 35 - set fd [eval git_read for-each-ref --format=%(refname) $cmd] 35 + set fd [git_read [concat for-each-ref --format=%(refname) $cmd]] 36 36 while {[gets $fd n] > 0} { 37 37 foreach spec $pat { 38 38 set dst [string range [lindex $spec 0] 0 end-2] ··· 75 75 76 76 foreach name $all_remotes { 77 77 catch { 78 - set fd [open [file join $rm_dir $name] r] 78 + set fd [safe_open_file [file join $rm_dir $name] r] 79 79 while {[gets $fd line] >= 0} { 80 80 if {[regexp {^URL:[ ]*(.+)$} $line line url]} { 81 81 set remote_url($name) $url ··· 145 145 } 146 146 } else { 147 147 catch { 148 - set fd [open [gitdir remotes $r] r] 148 + set fd [safe_open_file [gitdir remotes $r] r] 149 149 while {[gets $fd n] >= 0} { 150 150 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { 151 151 set enable 1 ··· 182 182 } 183 183 } else { 184 184 catch { 185 - set fd [open [gitdir remotes $r] r] 185 + set fd [safe_open_file [gitdir remotes $r] r] 186 186 while {[gets $fd n] >= 0} { 187 187 if {[regexp {^Push:[ \t]*([^:]+):} $n]} { 188 188 set enable 1
+1 -1
git-gui/lib/remote_branch_delete.tcl
··· 308 308 set full_list [list] 309 309 set head_cache($cache) [list] 310 310 set full_cache($cache) [list] 311 - set active_ls [git_read ls-remote $uri] 311 + set active_ls [git_read [list ls-remote $uri]] 312 312 fconfigure $active_ls \ 313 313 -blocking 0 \ 314 314 -translation lf \
+7 -7
git-gui/lib/shortcut.tcl
··· 12 12 set fn ${fn}.lnk 13 13 } 14 14 # Use git-gui.exe if available (ie: git-for-windows) 15 - set cmdLine [auto_execok git-gui.exe] 15 + set cmdLine [list [_which git-gui]] 16 16 if {$cmdLine eq {}} { 17 17 set cmdLine [list [info nameofexecutable] \ 18 18 [file normalize $::argv0]] ··· 30 30 global argv0 _gitworktree oguilib 31 31 32 32 if {[catch { 33 - set desktop [exec cygpath \ 34 - --desktop] 33 + set desktop [safe_exec [list cygpath \ 34 + --desktop]] 35 35 }]} { 36 36 set desktop . 37 37 } ··· 50 50 "CHERE_INVOKING=1 \ 51 51 source /etc/profile; \ 52 52 git gui"} 53 - exec /bin/mkshortcut.exe \ 53 + safe_exec [list /bin/mkshortcut.exe \ 54 54 --arguments $shargs \ 55 55 --desc "git-gui on $repodir" \ 56 56 --icon $oguilib/git-gui.ico \ 57 57 --name $fn \ 58 58 --show min \ 59 59 --workingdir $repodir \ 60 - /bin/sh.exe 60 + /bin/sh.exe] 61 61 } err]} { 62 62 error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"] 63 63 } ··· 83 83 84 84 file mkdir $MacOS 85 85 86 - set fd [open [file join $Contents Info.plist] w] 86 + set fd [safe_open_file [file join $Contents Info.plist] w] 87 87 puts $fd {<?xml version="1.0" encoding="UTF-8"?> 88 88 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> 89 89 <plist version="1.0"> ··· 108 108 </plist>} 109 109 close $fd 110 110 111 - set fd [open $exe w] 111 + set fd [safe_open_file $exe w] 112 112 puts $fd "#!/bin/sh" 113 113 foreach name [lsort [array names env]] { 114 114 set value $env($name)
+4 -3
git-gui/lib/sshkey.tcl
··· 7 7 ~/.ssh/id_rsa.pub ~/.ssh/identity.pub 8 8 } { 9 9 if {[file exists $name]} { 10 - set fh [open $name r] 10 + set fh [safe_open_file $name r] 11 11 set cont [read $fh] 12 12 close $fh 13 13 return [list $name $cont] ··· 83 83 set sshkey_title [mc "Generating..."] 84 84 $w.header.gen configure -state disabled 85 85 86 - set cmdline [list sh -c {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}] 86 + set cmdline [list [shellpath] -c \ 87 + {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}] 87 88 88 - if {[catch { set sshkey_fd [_open_stdout_stderr $cmdline] } err]} { 89 + if {[catch { set sshkey_fd [safe_open_command $cmdline] } err]} { 89 90 error_popup [mc "Could not start ssh-keygen:\n\n%s" $err] 90 91 return 91 92 }
+3 -4
git-gui/lib/tools.tcl
··· 110 110 111 111 set cmdline $repo_config(guitool.$fullname.cmd) 112 112 if {[is_config_true "guitool.$fullname.noconsole"]} { 113 - tools_run_silent [list sh -c $cmdline] \ 113 + tools_run_silent [list [shellpath] -c $cmdline] \ 114 114 [list tools_complete $fullname {}] 115 115 } else { 116 116 regsub {/} $fullname { / } title 117 117 set w [console::new \ 118 118 [mc "Tool: %s" $title] \ 119 119 [mc "Running: %s" $cmdline]] 120 - console::exec $w [list sh -c $cmdline] \ 120 + console::exec $w [list [shellpath] -c $cmdline] \ 121 121 [list tools_complete $fullname $w] 122 122 } 123 123 ··· 130 130 } 131 131 132 132 proc tools_run_silent {cmd after} { 133 - lappend cmd 2>@1 134 - set fd [_open_stdout_stderr $cmd] 133 + set fd [safe_open_command $cmd [list 2>@1]] 135 134 136 135 fconfigure $fd -blocking 0 -translation binary 137 136 fileevent $fd readable [list tools_consume_input $fd $after]
+5 -4
git-gui/lib/win32.tcl
··· 2 2 # Copyright (C) 2007 Shawn Pearce 3 3 4 4 proc win32_read_lnk {lnk_path} { 5 - return [exec cscript.exe \ 5 + return [safe_exec [list cscript.exe \ 6 6 /E:jscript \ 7 7 /nologo \ 8 8 [file join $::oguilib win32_shortcut.js] \ 9 - $lnk_path] 9 + $lnk_path]] 10 10 } 11 11 12 12 proc win32_create_lnk {lnk_path lnk_exec lnk_dir} { ··· 15 15 set lnk_args [lrange $lnk_exec 1 end] 16 16 set lnk_exec [lindex $lnk_exec 0] 17 17 18 - eval [list exec wscript.exe \ 18 + set cmd [list wscript.exe \ 19 19 /E:jscript \ 20 20 /nologo \ 21 21 [file nativename [file join $oguilib win32_shortcut.js]] \ 22 22 $lnk_path \ 23 23 [file nativename [file join $oguilib git-gui.ico]] \ 24 24 $lnk_dir \ 25 - $lnk_exec] $lnk_args 25 + $lnk_exec] 26 + safe_exec [concat $cmd $lnk_args] 26 27 }
+172 -106
gitk-git/gitk
··· 9 9 10 10 package require Tk 11 11 12 + 13 + # Wrap exec/open to sanitize arguments 14 + 15 + # unsafe arguments begin with redirections or the pipe or background operators 16 + proc is_arg_unsafe {arg} { 17 + regexp {^([<|>&]|2>)} $arg 18 + } 19 + 20 + proc make_arg_safe {arg} { 21 + if {[is_arg_unsafe $arg]} { 22 + set arg [file join . $arg] 23 + } 24 + return $arg 25 + } 26 + 27 + proc make_arglist_safe {arglist} { 28 + set res {} 29 + foreach arg $arglist { 30 + lappend res [make_arg_safe $arg] 31 + } 32 + return $res 33 + } 34 + 35 + # executes one command 36 + # no redirections or pipelines are possible 37 + # cmd is a list that specifies the command and its arguments 38 + # calls `exec` and returns its value 39 + proc safe_exec {cmd} { 40 + eval exec [make_arglist_safe $cmd] 41 + } 42 + 43 + # executes one command with redirections 44 + # no pipelines are possible 45 + # cmd is a list that specifies the command and its arguments 46 + # redir is a list that specifies redirections (output, background, constant(!) commands) 47 + # calls `exec` and returns its value 48 + proc safe_exec_redirect {cmd redir} { 49 + eval exec [make_arglist_safe $cmd] $redir 50 + } 51 + 52 + proc safe_open_file {filename flags} { 53 + # a file name starting with "|" would attempt to run a process 54 + # but such a file name must be treated as a relative path 55 + # hide the "|" behind "./" 56 + if {[string index $filename 0] eq "|"} { 57 + set filename [file join . $filename] 58 + } 59 + open $filename $flags 60 + } 61 + 62 + # opens a command pipeline for reading 63 + # cmd is a list that specifies the command and its arguments 64 + # calls `open` and returns the file id 65 + proc safe_open_command {cmd} { 66 + open |[make_arglist_safe $cmd] r 67 + } 68 + 69 + # opens a command pipeline for reading and writing 70 + # cmd is a list that specifies the command and its arguments 71 + # calls `open` and returns the file id 72 + proc safe_open_command_rw {cmd} { 73 + open |[make_arglist_safe $cmd] r+ 74 + } 75 + 76 + # opens a command pipeline for reading with redirections 77 + # cmd is a list that specifies the command and its arguments 78 + # redir is a list that specifies redirections 79 + # calls `open` and returns the file id 80 + proc safe_open_command_redirect {cmd redir} { 81 + set cmd [make_arglist_safe $cmd] 82 + open |[concat $cmd $redir] r 83 + } 84 + 85 + # opens a pipeline with several commands for reading 86 + # cmds is a list of lists, each of which specifies a command and its arguments 87 + # calls `open` and returns the file id 88 + proc safe_open_pipeline {cmds} { 89 + set cmd {} 90 + foreach subcmd $cmds { 91 + set cmd [concat $cmd | [make_arglist_safe $subcmd]] 92 + } 93 + open $cmd r 94 + } 95 + 96 + # End exec/open wrappers 97 + 12 98 proc hasworktree {} { 13 99 return [expr {[exec git rev-parse --is-bare-repository] == "false" && 14 100 [exec git rev-parse --is-inside-git-dir] == "false"}] ··· 134 220 set mlist {} 135 221 set nr_unmerged 0 136 222 if {[catch { 137 - set fd [open "| git ls-files -u" r] 223 + set fd [safe_open_command {git ls-files -u}] 138 224 } err]} { 139 225 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" 140 226 exit 1 ··· 296 382 } elseif {[lsearch -exact $revs --all] >= 0} { 297 383 lappend revs HEAD 298 384 } 299 - if {[catch {set ids [eval exec git rev-parse $revs]} err]} { 385 + if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} { 300 386 # we get stdout followed by stderr in $err 301 387 # for an unknown rev, git rev-parse echoes it and then errors out 302 388 set errlines [split $err "\n"] ··· 353 439 return $ret 354 440 } 355 441 356 - # Escapes a list of filter paths to be passed to git log via stdin. Note that 357 - # paths must not be quoted. 358 - proc escape_filter_paths {paths} { 359 - set escaped [list] 360 - foreach path $paths { 361 - lappend escaped [string map {\\ \\\\ "\ " "\\\ "} $path] 362 - } 363 - return $escaped 364 - } 365 - 366 442 # Start off a git log process and arrange to read its output 367 443 proc start_rev_list {view} { 368 444 global startmsecs commitidx viewcomplete curview ··· 384 460 set args $viewargs($view) 385 461 if {$viewargscmd($view) ne {}} { 386 462 if {[catch { 387 - set str [exec sh -c $viewargscmd($view)] 463 + set str [safe_exec [list sh -c $viewargscmd($view)]] 388 464 } err]} { 389 465 error_popup "[mc "Error executing --argscmd command:"] $err" 390 466 return 0 ··· 422 498 } 423 499 424 500 if {[catch { 425 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 426 - --parents --boundary $args --stdin \ 427 - "<<[join [concat $revs "--" \ 428 - [escape_filter_paths $files]] "\\n"]"] r] 501 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 502 + --parents --boundary $args --stdin] \ 503 + [list "<<[join [concat $revs "--" $files] "\n"]"]] 429 504 } err]} { 430 505 error_popup "[mc "Error executing git log:"] $err" 431 506 return 0 ··· 459 534 set pid [pid $fd] 460 535 461 536 if {$::tcl_platform(platform) eq {windows}} { 462 - exec taskkill /pid $pid 537 + safe_exec [list taskkill /pid $pid] 463 538 } else { 464 - exec kill $pid 539 + safe_exec [list kill $pid] 465 540 } 466 541 } 467 542 catch {close $fd} ··· 576 651 set args $vorigargs($view) 577 652 } 578 653 if {[catch { 579 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 580 - --parents --boundary $args --stdin \ 581 - "<<[join [concat $revs "--" \ 582 - [escape_filter_paths \ 583 - $vfilelimit($view)]] "\\n"]"] r] 654 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 655 + --parents --boundary $args --stdin] \ 656 + [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]] 584 657 } err]} { 585 658 error_popup "[mc "Error executing git log:"] $err" 586 659 return ··· 1547 1620 # and if we already know about it, using the rewritten 1548 1621 # parent as a substitute parent for $id's children. 1549 1622 if {![catch { 1550 - set rwid [exec git rev-list --first-parent --max-count=1 \ 1551 - $id -- $vfilelimit($view)] 1623 + set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \ 1624 + $id -- $vfilelimit($view)]] 1552 1625 }]} { 1553 1626 if {$rwid ne {} && [info exists varcid($view,$rwid)]} { 1554 1627 # use $rwid in place of $id ··· 1668 1741 global tclencoding 1669 1742 1670 1743 # Invoke git-log to handle automatic encoding conversion 1671 - set fd [open [concat | git log --no-color --pretty=raw -1 $id] r] 1744 + set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]] 1672 1745 # Read the results using i18n.logoutputencoding 1673 1746 fconfigure $fd -translation lf -eofchar {} 1674 1747 if {$tclencoding != {}} { ··· 1804 1877 foreach v {tagids idtags headids idheads otherrefids idotherrefs} { 1805 1878 unset -nocomplain $v 1806 1879 } 1807 - set refd [open [list | git show-ref -d] r] 1880 + set refd [safe_open_command [list git show-ref -d]] 1808 1881 if {$tclencoding != {}} { 1809 1882 fconfigure $refd -encoding $tclencoding 1810 1883 } ··· 1852 1925 set selectheadid {} 1853 1926 if {$selecthead ne {}} { 1854 1927 catch { 1855 - set selectheadid [exec git rev-parse --verify $selecthead] 1928 + set selectheadid [safe_exec [list git rev-parse --verify $selecthead]] 1856 1929 } 1857 1930 } 1858 1931 } ··· 2112 2185 {mc "Reread re&ferences" command rereadrefs} 2113 2186 {mc "&List references" command showrefs -accelerator F2} 2114 2187 {xx "" separator} 2115 - {mc "Start git &gui" command {exec git gui &}} 2188 + {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}} 2116 2189 {xx "" separator} 2117 2190 {mc "&Quit" command doquit -accelerator Meta1-Q} 2118 2191 }} ··· 2894 2967 set remove_tmp 0 2895 2968 if {[catch { 2896 2969 set try_count 0 2897 - while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} { 2970 + while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} { 2898 2971 if {[incr try_count] > 50} { 2899 2972 error "Unable to write config file: $config_file_tmp exists" 2900 2973 } ··· 3610 3683 set tmpdir $gitdir 3611 3684 } 3612 3685 set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"] 3613 - if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} { 3686 + if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} { 3614 3687 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] 3615 3688 } 3616 3689 if {[catch {file mkdir $gitktmpdir} err]} { ··· 3632 3705 proc save_file_from_commit {filename output what} { 3633 3706 global nullfile 3634 3707 3635 - if {[catch {exec git show $filename -- > $output} err]} { 3708 + if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} { 3636 3709 if {[string match "fatal: bad revision *" $err]} { 3637 3710 return $nullfile 3638 3711 } ··· 3697 3770 3698 3771 if {$difffromfile ne {} && $difftofile ne {}} { 3699 3772 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] 3700 - if {[catch {set fl [open |$cmd r]} err]} { 3773 + if {[catch {set fl [safe_open_command $cmd]} err]} { 3701 3774 file delete -force $diffdir 3702 3775 error_popup "$extdifftool: [mc "command failed:"] $err" 3703 3776 } else { ··· 3801 3874 # Find the SHA1 ID of the blob for file $fname in the index 3802 3875 # at stage 0 or 2 3803 3876 proc index_sha1 {fname} { 3804 - set f [open [list | git ls-files -s $fname] r] 3877 + set f [safe_open_command [list git ls-files -s $fname]] 3805 3878 while {[gets $f line] >= 0} { 3806 3879 set info [lindex [split $line "\t"] 0] 3807 3880 set stage [lindex $info 2] ··· 3861 3934 # being given an absolute path... 3862 3935 set f [make_relative $f] 3863 3936 lappend cmdline $base_commit $f 3864 - if {[catch {eval exec $cmdline &} err]} { 3937 + if {[catch {safe_exec_redirect $cmdline [list &]} err]} { 3865 3938 error_popup "[mc "git gui blame: command failed:"] $err" 3866 3939 } 3867 3940 } ··· 3889 3962 # must be a merge in progress... 3890 3963 if {[catch { 3891 3964 # get the last line from .git/MERGE_HEAD 3892 - set f [open [file join $gitdir MERGE_HEAD] r] 3965 + set f [safe_open_file [file join $gitdir MERGE_HEAD] r] 3893 3966 set id [lindex [split [read $f] "\n"] end-1] 3894 3967 close $f 3895 3968 } err]} { ··· 3912 3985 } 3913 3986 set line [lindex $h 1] 3914 3987 } 3915 - set blameargs {} 3916 - if {$from_index ne {}} { 3917 - lappend blameargs | git cat-file blob $from_index 3918 - } 3919 - lappend blameargs | git blame -p -L$line,+1 3988 + set blamefile [file join $cdup $flist_menu_file] 3920 3989 if {$from_index ne {}} { 3921 - lappend blameargs --contents - 3990 + set blameargs [list \ 3991 + [list git cat-file blob $from_index] \ 3992 + [list git blame -p -L$line,+1 --contents - -- $blamefile]] 3922 3993 } else { 3923 - lappend blameargs $id 3994 + set blameargs [list \ 3995 + [list git blame -p -L$line,+1 $id -- $blamefile]] 3924 3996 } 3925 - lappend blameargs -- [file join $cdup $flist_menu_file] 3926 3997 if {[catch { 3927 - set f [open $blameargs r] 3998 + set f [safe_open_pipeline $blameargs] 3928 3999 } err]} { 3929 4000 error_popup [mc "Couldn't start git blame: %s" $err] 3930 4001 return ··· 4849 4920 # must be "containing:", i.e. we're searching commit info 4850 4921 return 4851 4922 } 4852 - set cmd [concat | git diff-tree -r -s --stdin $gdtargs] 4853 - set filehighlight [open $cmd r+] 4923 + set cmd [concat git diff-tree -r -s --stdin $gdtargs] 4924 + set filehighlight [safe_open_command_rw $cmd] 4854 4925 fconfigure $filehighlight -blocking 0 4855 4926 filerun $filehighlight readfhighlight 4856 4927 set fhl_list {} ··· 5279 5350 global viewmainheadid vfilelimit viewinstances mainheadid 5280 5351 5281 5352 catch { 5282 - set rfd [open [concat | git rev-list -1 $mainheadid \ 5283 - -- $vfilelimit($view)] r] 5353 + set rfd [safe_open_command [concat git rev-list -1 $mainheadid \ 5354 + -- $vfilelimit($view)]] 5284 5355 set j [reg_instance $rfd] 5285 5356 lappend viewinstances($view) $j 5286 5357 fconfigure $rfd -blocking 0 ··· 5345 5416 if {!$showlocalchanges || !$hasworktree} return 5346 5417 incr lserial 5347 5418 if {[package vcompare $git_version "1.7.2"] >= 0} { 5348 - set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD" 5419 + set cmd "git diff-index --cached --ignore-submodules=dirty HEAD" 5349 5420 } else { 5350 - set cmd "|git diff-index --cached HEAD" 5421 + set cmd "git diff-index --cached HEAD" 5351 5422 } 5352 5423 if {$vfilelimit($curview) ne {}} { 5353 5424 set cmd [concat $cmd -- $vfilelimit($curview)] 5354 5425 } 5355 - set fd [open $cmd r] 5426 + set fd [safe_open_command $cmd] 5356 5427 fconfigure $fd -blocking 0 5357 5428 set i [reg_instance $fd] 5358 5429 filerun $fd [list readdiffindex $fd $lserial $i] ··· 5377 5448 } 5378 5449 5379 5450 # now see if there are any local changes not checked in to the index 5380 - set cmd "|git diff-files" 5451 + set cmd "git diff-files" 5381 5452 if {$vfilelimit($curview) ne {}} { 5382 5453 set cmd [concat $cmd -- $vfilelimit($curview)] 5383 5454 } 5384 - set fd [open $cmd r] 5455 + set fd [safe_open_command $cmd] 5385 5456 fconfigure $fd -blocking 0 5386 5457 set i [reg_instance $fd] 5387 5458 filerun $fd [list readdifffiles $fd $serial $i] ··· 7170 7241 global web_browser 7171 7242 7172 7243 if {$web_browser eq {}} return 7173 - # Use eval here in case $web_browser is a command plus some arguments 7174 - if {[catch {eval exec $web_browser [list $url] &} err]} { 7244 + # Use concat here in case $web_browser is a command plus some arguments 7245 + if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} { 7175 7246 error_popup "[mc "Error starting web browser:"] $err" 7176 7247 } 7177 7248 } ··· 7673 7744 if {![info exists treefilelist($id)]} { 7674 7745 if {![info exists treepending]} { 7675 7746 if {$id eq $nullid} { 7676 - set cmd [list | git ls-files] 7747 + set cmd [list git ls-files] 7677 7748 } elseif {$id eq $nullid2} { 7678 - set cmd [list | git ls-files --stage -t] 7749 + set cmd [list git ls-files --stage -t] 7679 7750 } else { 7680 - set cmd [list | git ls-tree -r $id] 7751 + set cmd [list git ls-tree -r $id] 7681 7752 } 7682 - if {[catch {set gtf [open $cmd r]}]} { 7753 + if {[catch {set gtf [safe_open_command $cmd]}]} { 7683 7754 return 7684 7755 } 7685 7756 set treepending $id ··· 7743 7814 return 7744 7815 } 7745 7816 if {$diffids eq $nullid} { 7746 - if {[catch {set bf [open $f r]} err]} { 7817 + if {[catch {set bf [safe_open_file $f r]} err]} { 7747 7818 puts "oops, can't read $f: $err" 7748 7819 return 7749 7820 } 7750 7821 } else { 7751 7822 set blob [lindex $treeidlist($diffids) $i] 7752 - if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} { 7823 + if {[catch {set bf [safe_open_command [concat git cat-file blob $blob]]} err]} { 7753 7824 puts "oops, error reading blob $blob: $err" 7754 7825 return 7755 7826 } ··· 7899 7970 if {$i >= 0} { 7900 7971 if {[llength $ids] > 1 && $j < 0} { 7901 7972 # comparing working directory with some specific revision 7902 - set cmd [concat | git diff-index $flags] 7973 + set cmd [concat git diff-index $flags] 7903 7974 if {$i == 0} { 7904 7975 lappend cmd -R [lindex $ids 1] 7905 7976 } else { ··· 7907 7978 } 7908 7979 } else { 7909 7980 # comparing working directory with index 7910 - set cmd [concat | git diff-files $flags] 7981 + set cmd [concat git diff-files $flags] 7911 7982 if {$j == 1} { 7912 7983 lappend cmd -R 7913 7984 } ··· 7916 7987 if {[package vcompare $git_version "1.7.2"] >= 0} { 7917 7988 set flags "$flags --ignore-submodules=dirty" 7918 7989 } 7919 - set cmd [concat | git diff-index --cached $flags] 7990 + set cmd [concat git diff-index --cached $flags] 7920 7991 if {[llength $ids] > 1} { 7921 7992 # comparing index with specific revision 7922 7993 if {$j == 0} { ··· 7932 8003 if {$log_showroot} { 7933 8004 lappend flags --root 7934 8005 } 7935 - set cmd [concat | git diff-tree -r $flags $ids] 8006 + set cmd [concat git diff-tree -r $flags $ids] 7936 8007 } 7937 8008 return $cmd 7938 8009 } ··· 7944 8015 if {$limitdiffs && $vfilelimit($curview) ne {}} { 7945 8016 set cmd [concat $cmd -- $vfilelimit($curview)] 7946 8017 } 7947 - if {[catch {set gdtf [open $cmd r]}]} return 8018 + if {[catch {set gdtf [safe_open_command $cmd]}]} return 7948 8019 7949 8020 set treepending $ids 7950 8021 set treediff {} ··· 8064 8135 if {$limitdiffs && $vfilelimit($curview) ne {}} { 8065 8136 set cmd [concat $cmd -- $vfilelimit($curview)] 8066 8137 } 8067 - if {[catch {set bdf [open $cmd r]} err]} { 8138 + if {[catch {set bdf [safe_open_command $cmd]} err]} { 8068 8139 error_popup [mc "Error getting diffs: %s" $err] 8069 8140 return 8070 8141 } ··· 8781 8852 set id [lindex $matches 0] 8782 8853 } 8783 8854 } else { 8784 - if {[catch {set id [exec git rev-parse --verify $sha1string]}]} { 8855 + if {[catch {set id [safe_exec [list git rev-parse --verify $sha1string]]}]} { 8785 8856 error_popup [mc "Revision %s is not known" $sha1string] 8786 8857 return 8787 8858 } ··· 9087 9158 9088 9159 if {![info exists patchids($id)]} { 9089 9160 set cmd [diffcmd [list $id] {-p --root}] 9090 - # trim off the initial "|" 9091 - set cmd [lrange $cmd 1 end] 9092 9161 if {[catch { 9093 - set x [eval exec $cmd | git patch-id] 9162 + set x [safe_exec_redirect $cmd [list | git patch-id]] 9094 9163 set patchids($id) [lindex $x 0] 9095 9164 }]} { 9096 9165 set patchids($id) "error" ··· 9186 9255 set fna [file join $tmpdir "commit-[string range $a 0 7]"] 9187 9256 set fnb [file join $tmpdir "commit-[string range $b 0 7]"] 9188 9257 if {[catch { 9189 - exec git diff-tree -p --pretty $a >$fna 9190 - exec git diff-tree -p --pretty $b >$fnb 9258 + safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna] 9259 + safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb] 9191 9260 } err]} { 9192 9261 error_popup [mc "Error writing commit to file: %s" $err] 9193 9262 return 9194 9263 } 9195 9264 if {[catch { 9196 - set fd [open "| diff -U$diffcontext $fna $fnb" r] 9265 + set fd [safe_open_command "diff -U$diffcontext $fna $fnb"] 9197 9266 } err]} { 9198 9267 error_popup [mc "Error diffing commits: %s" $err] 9199 9268 return ··· 9333 9402 set newid [$patchtop.tosha1 get] 9334 9403 set fname [$patchtop.fname get] 9335 9404 set cmd [diffcmd [list $oldid $newid] -p] 9336 - # trim off the initial "|" 9337 - set cmd [lrange $cmd 1 end] 9338 - lappend cmd >$fname & 9339 - if {[catch {eval exec $cmd} err]} { 9405 + if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} { 9340 9406 error_popup "[mc "Error creating patch:"] $err" $patchtop 9341 9407 } 9342 9408 catch {destroy $patchtop} ··· 9405 9471 } 9406 9472 if {[catch { 9407 9473 if {$msg != {}} { 9408 - exec git tag -a -m $msg $tag $id 9474 + safe_exec [list git tag -a -m $msg $tag $id] 9409 9475 } else { 9410 - exec git tag $tag $id 9476 + safe_exec [list git tag $tag $id] 9411 9477 } 9412 9478 } err]} { 9413 9479 error_popup "[mc "Error creating tag:"] $err" $mktagtop ··· 9475 9541 if {$autosellen < 40} { 9476 9542 lappend cmd --abbrev=$autosellen 9477 9543 } 9478 - set reference [eval exec $cmd $rowmenuid] 9544 + set reference [safe_exec [concat $cmd $rowmenuid]] 9479 9545 9480 9546 clipboard clear 9481 9547 clipboard append $reference ··· 9525 9591 set id [$wrcomtop.sha1 get] 9526 9592 set cmd "echo $id | [$wrcomtop.cmd get]" 9527 9593 set fname [$wrcomtop.fname get] 9528 - if {[catch {exec sh -c $cmd >$fname &} err]} { 9594 + if {[catch {safe_exec_redirect [list sh -c $cmd] [list >$fname &]} err]} { 9529 9595 error_popup "[mc "Error writing commit:"] $err" $wrcomtop 9530 9596 } 9531 9597 catch {destroy $wrcomtop} ··· 9629 9695 nowbusy newbranch 9630 9696 update 9631 9697 if {[catch { 9632 - eval exec git branch $cmdargs 9698 + safe_exec [concat git branch $cmdargs] 9633 9699 } err]} { 9634 9700 notbusy newbranch 9635 9701 error_popup $err ··· 9670 9736 nowbusy renamebranch 9671 9737 update 9672 9738 if {[catch { 9673 - eval exec git branch $cmdargs 9739 + safe_exec [concat git branch $cmdargs] 9674 9740 } err]} { 9675 9741 notbusy renamebranch 9676 9742 error_popup $err ··· 9711 9777 } 9712 9778 } 9713 9779 9714 - eval exec git citool $tool_args & 9780 + safe_exec_redirect [concat git citool $tool_args] [list &] 9715 9781 9716 9782 array unset env GIT_AUTHOR_* 9717 9783 array set env $save_env ··· 9734 9800 update 9735 9801 # Unfortunately git-cherry-pick writes stuff to stderr even when 9736 9802 # no error occurs, and exec takes that as an indication of error... 9737 - if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { 9803 + if {[catch {safe_exec [list sh -c "git cherry-pick -r $rowmenuid 2>&1"]} err]} { 9738 9804 notbusy cherrypick 9739 9805 if {[regexp -line \ 9740 9806 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \ ··· 9796 9862 nowbusy revert [mc "Reverting"] 9797 9863 update 9798 9864 9799 - if [catch {exec git revert --no-edit $rowmenuid} err] { 9865 + if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] { 9800 9866 notbusy revert 9801 9867 if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\ 9802 9868 $err match files] { ··· 9872 9938 bind $w <Visibility> "grab $w; focus $w" 9873 9939 tkwait window $w 9874 9940 if {!$confirm_ok} return 9875 - if {[catch {set fd [open \ 9876 - [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} { 9941 + if {[catch {set fd [safe_open_command_redirect \ 9942 + [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} { 9877 9943 error_popup $err 9878 9944 } else { 9879 9945 dohidelocalchanges ··· 9944 10010 9945 10011 # check the tree is clean first?? 9946 10012 set newhead $headmenuhead 9947 - set command [list | git checkout] 10013 + set command [list git checkout] 9948 10014 if {[string match "remotes/*" $newhead]} { 9949 10015 set remote $newhead 9950 10016 set newhead [string range $newhead [expr [string last / $newhead] + 1] end] ··· 9958 10024 } else { 9959 10025 lappend command $newhead 9960 10026 } 9961 - lappend command 2>@1 9962 10027 nowbusy checkout [mc "Checking out"] 9963 10028 update 9964 10029 dohidelocalchanges 9965 10030 if {[catch { 9966 - set fd [open $command r] 10031 + set fd [safe_open_command_redirect $command [list 2>@1]] 9967 10032 } err]} { 9968 10033 notbusy checkout 9969 10034 error_popup $err ··· 10029 10094 } 10030 10095 nowbusy rmbranch 10031 10096 update 10032 - if {[catch {exec git branch -D $head} err]} { 10097 + if {[catch {safe_exec [list git branch -D $head]} err]} { 10033 10098 notbusy rmbranch 10034 10099 error_popup $err 10035 10100 return ··· 10220 10285 set cachedarcs 0 10221 10286 set allccache [file join $gitdir "gitk.cache"] 10222 10287 if {![catch { 10223 - set f [open $allccache r] 10288 + set f [safe_open_file $allccache r] 10224 10289 set allcwait 1 10225 10290 getcache $f 10226 10291 }]} return ··· 10229 10294 if {$allcwait} { 10230 10295 return 10231 10296 } 10232 - set cmd [list | git rev-list --parents] 10297 + set cmd [list git rev-list --parents] 10233 10298 set allcupdate [expr {$seeds ne {}}] 10234 10299 if {!$allcupdate} { 10235 10300 set ids "--all" ··· 10257 10322 if {$ids ne {}} { 10258 10323 if {$ids eq "--all"} { 10259 10324 set cmd [concat $cmd "--all"] 10325 + set fd [safe_open_command $cmd] 10260 10326 } else { 10261 - set cmd [concat $cmd --stdin "<<[join $ids "\\n"]"] 10327 + set cmd [concat $cmd --stdin] 10328 + set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]] 10262 10329 } 10263 - set fd [open $cmd r] 10264 10330 fconfigure $fd -blocking 0 10265 10331 incr allcommits 10266 10332 nowbusy allcommits ··· 10650 10716 set cachearc 0 10651 10717 set cachedarcs $nextarc 10652 10718 catch { 10653 - set f [open $allccache w] 10719 + set f [safe_open_file $allccache w] 10654 10720 puts $f [list 1 $cachedarcs] 10655 10721 run writecache $f 10656 10722 } ··· 11353 11419 11354 11420 if {![info exists cached_tagcontent($tag)]} { 11355 11421 catch { 11356 - set cached_tagcontent($tag) [exec git cat-file -p $tag] 11422 + set cached_tagcontent($tag) [safe_exec [list git cat-file -p $tag]] 11357 11423 } 11358 11424 } 11359 11425 $ctext insert end "[mc "Tag"]: $tag\n" bold ··· 12239 12305 set r $path_attr_cache($attr,$path) 12240 12306 } else { 12241 12307 set r "unspecified" 12242 - if {![catch {set line [exec git check-attr $attr -- $path]}]} { 12308 + if {![catch {set line [safe_exec [list git check-attr $attr -- $path]]}]} { 12243 12309 regexp "(.*): $attr: (.*)" $line m f r 12244 12310 } 12245 12311 set path_attr_cache($attr,$path) $r ··· 12266 12332 while {$newlist ne {}} { 12267 12333 set head [lrange $newlist 0 [expr {$lim - 1}]] 12268 12334 set newlist [lrange $newlist $lim end] 12269 - if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} { 12335 + if {![catch {set rlist [safe_exec [concat git check-attr $attr -- $head]]}]} { 12270 12336 foreach row [split $rlist "\n"] { 12271 12337 if {[regexp "(.*): $attr: (.*)" $row m path value]} { 12272 12338 if {[string index $path 0] eq "\""} { ··· 12319 12385 12320 12386 # on OSX bring the current Wish process window to front 12321 12387 if {[tk windowingsystem] eq "aqua"} { 12322 - exec osascript -e [format { 12388 + safe_exec [list osascript -e [format { 12323 12389 tell application "System Events" 12324 12390 set frontmost of processes whose unix id is %d to true 12325 12391 end tell 12326 - } [pid] ] 12392 + } [pid] ]] 12327 12393 } 12328 12394 12329 12395 # Unset GIT_TRACE var if set ··· 12568 12634 if {$i >= [llength $argv] && $revtreeargs ne {}} { 12569 12635 # no -- on command line, but some arguments (other than --argscmd) 12570 12636 if {[catch { 12571 - set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs] 12637 + set f [safe_exec [concat git rev-parse --no-revs --no-flags $revtreeargs]] 12572 12638 set cmdline_files [split $f "\n"] 12573 12639 set n [llength $cmdline_files] 12574 12640 set revtreeargs [lrange $revtreeargs 0 end-$n]
+11
t/t1300-config.sh
··· 2852 2852 2853 2853 done 2854 2854 2855 + test_expect_success 'writing value with trailing CR not stripped on read' ' 2856 + test_when_finished "rm -rf cr-test" && 2857 + 2858 + printf "bar\r\n" >expect && 2859 + git init cr-test && 2860 + git -C cr-test config set core.foo $(printf "bar\r") && 2861 + git -C cr-test config get core.foo >actual && 2862 + 2863 + test_cmp expect actual 2864 + ' 2865 + 2855 2866 test_done
+23
t/t5558-clone-bundle-uri.sh
··· 1249 1249 trace-mult.txt >bundle-fetches && 1250 1250 test_line_count = 1 bundle-fetches 1251 1251 ' 1252 + 1253 + test_expect_success 'bundles with space in URI are rejected' ' 1254 + test_when_finished "rm -rf busted repo" && 1255 + mkdir -p "$HOME/busted/ /$HOME/repo/.git/objects/bundles" && 1256 + git clone --bundle-uri="$HTTPD_URL/bogus $HOME/busted/" "$HTTPD_URL/smart/fetch.git" repo 2>err && 1257 + test_grep "error: bundle-uri: URI is malformed: " err && 1258 + find busted -type f >files && 1259 + test_must_be_empty files 1260 + ' 1261 + 1262 + test_expect_success 'bundles with newline in URI are rejected' ' 1263 + test_when_finished "rm -rf busted repo" && 1264 + git clone --bundle-uri="$HTTPD_URL/bogus\nget $HTTPD_URL/bogus $HOME/busted" "$HTTPD_URL/smart/fetch.git" repo 2>err && 1265 + test_grep "error: bundle-uri: URI is malformed: " err && 1266 + test_path_is_missing "$HOME/busted" 1267 + ' 1268 + 1269 + test_expect_success 'bundles with newline in target path are rejected' ' 1270 + git clone --bundle-uri="$HTTPD_URL/bogus" "$HTTPD_URL/smart/fetch.git" "$(printf "escape\nget $HTTPD_URL/bogus .")" 2>err && 1271 + test_grep "error: bundle-uri: filename is malformed: " err && 1272 + test_path_is_missing escape 1273 + ' 1274 + 1252 1275 # Do not add tests here unless they use the HTTP server, as they will 1253 1276 # not run unless the HTTP dependencies exist. 1254 1277
+33
t/t7450-bad-git-dotfiles.sh
··· 373 373 test_path_is_missing nested_checkout/thing2/.git 374 374 ' 375 375 376 + test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' ' 377 + test_when_finished "rm -rf sub repo bad-clone" && 378 + 379 + git init sub && 380 + write_script sub/post-checkout <<-\EOF && 381 + touch "$PWD/foo" 382 + EOF 383 + git -C sub add post-checkout && 384 + git -C sub commit -m hook && 385 + 386 + git init repo && 387 + git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub && 388 + git -C repo mv sub $(printf "sub\r") && 389 + 390 + # Ensure config values containing CR are wrapped in quotes. 391 + git config unset -f repo/.gitmodules submodule.sub.path && 392 + printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules && 393 + 394 + git config unset -f repo/.git/modules/sub/config core.worktree && 395 + { 396 + printf "[core]\n" && 397 + printf "\tworktree = \"../../../sub\r\"\n" 398 + } >>repo/.git/modules/sub/config && 399 + 400 + ln -s .git/modules/sub/hooks repo/sub && 401 + git -C repo add -A && 402 + git -C repo commit -m submodule && 403 + 404 + git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone && 405 + ! test -f "$PWD/foo" && 406 + test -f $(printf "bad-clone/sub\r/post-checkout") 407 + ' 408 + 376 409 test_done