Git fork

Sync with 2.49.1

+749 -449
+73
Documentation/RelNotes/2.43.7.adoc
··· 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.adoc
··· 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.adoc
··· 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.
+7
Documentation/RelNotes/2.46.4.adoc
··· 1 + Git v2.46.4 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appears in v2.43.7, v2.44.4, and 5 + v2.45.4 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.
+8
Documentation/RelNotes/2.47.3.adoc
··· 1 + Git v2.47.3 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appears in v2.43.7, v2.44.4, 5 + v2.45.4, and v2.46.4 to address the following CVEs: CVE-2025-27613, 6 + CVE-2025-27614, CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, 7 + CVE-2025-48385, and CVE-2025-48386. See the release notes for v2.43.7 8 + for details.
+8
Documentation/RelNotes/2.48.2.adoc
··· 1 + Git v2.48.2 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appears in v2.43.7, v2.44.4, 5 + v2.45.4, v2.46.4, and v2.47.3 to address the following CVEs: 6 + CVE-2025-27613, CVE-2025-27614, CVE-2025-46334, CVE-2025-46835, 7 + CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386. See the release 8 + notes for v2.43.7 for details.
+12
Documentation/RelNotes/2.49.1.adoc
··· 1 + Git v2.49.1 Release Notes 2 + ========================= 3 + 4 + This release merges up the fixes that appear in v2.43.7, v2.44.4, 5 + v2.45.4, v2.46.4, v2.47.3, and v2.48.2 to address the following CVEs: 6 + CVE-2025-27613, CVE-2025-27614, CVE-2025-46334, CVE-2025-46835, 7 + CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386. See the release 8 + notes for v2.43.7 for details. 9 + 10 + It also contains some updates to various CI bits to work around 11 + and/or to adjust to the deprecation of use of Ubuntu 20.04 GitHub 12 + Actions CI, updates to to Fedora base image.
+22
bundle-uri.c
··· 297 297 struct strbuf line = STRBUF_INIT; 298 298 int found_get = 0; 299 299 300 + /* 301 + * The protocol we speak with git-remote-https(1) uses a space to 302 + * separate between URI and file, so the URI itself must not contain a 303 + * space. If it did, an adversary could change the location where the 304 + * downloaded file is being written to. 305 + * 306 + * Similarly, we use newlines to separate commands from one another. 307 + * Consequently, neither the URI nor the file must contain a newline or 308 + * otherwise an adversary could inject arbitrary commands. 309 + * 310 + * TODO: Restricting newlines in the target paths may break valid 311 + * usecases, even if those are a bit more on the esoteric side. 312 + * If this ever becomes a problem we should probably think about 313 + * alternatives. One alternative could be to use NUL-delimited 314 + * requests in git-remote-http(1). Another alternative could be 315 + * to use URL quoting. 316 + */ 317 + if (strpbrk(uri, " \n")) 318 + return error("bundle-uri: URI is malformed: '%s'", file); 319 + if (strchr(file, '\n')) 320 + return error("bundle-uri: filename is malformed: '%s'", file); 321 + 300 322 strvec_pushl(&cp.args, "git-remote-https", uri, NULL); 301 323 cp.err = -1; 302 324 cp.in = -1;
+1 -1
config.c
··· 2940 2940 if (value[0] == ' ') 2941 2941 quote = "\""; 2942 2942 for (i = 0; value[i]; i++) 2943 - if (value[i] == ';' || value[i] == '#') 2943 + if (value[i] == ';' || value[i] == '#' || value[i] == '\r') 2944 2944 quote = "\""; 2945 2945 if (i && value[i - 1] == ' ') 2946 2946 quote = "\"";
+15 -7
contrib/credential/wincred/git-credential-wincred.c
··· 39 39 static WCHAR *wusername, *password, *protocol, *host, *path, target[1024], 40 40 *password_expiry_utc, *oauth_refresh_token; 41 41 42 + static void target_append(const WCHAR *src) 43 + { 44 + size_t avail = ARRAY_SIZE(target) - wcslen(target) - 1; /* -1 for NUL */ 45 + if (avail < wcslen(src)) 46 + die("target buffer overflow"); 47 + wcsncat(target, src, avail); 48 + } 49 + 42 50 static void write_item(const char *what, LPCWSTR wbuf, int wlen) 43 51 { 44 52 char *buf; ··· 330 338 331 339 /* prepare 'target', the unique key for the credential */ 332 340 wcscpy(target, L"git:"); 333 - wcsncat(target, protocol, ARRAY_SIZE(target)); 334 - wcsncat(target, L"://", ARRAY_SIZE(target)); 341 + target_append(protocol); 342 + target_append(L"://"); 335 343 if (wusername) { 336 - wcsncat(target, wusername, ARRAY_SIZE(target)); 337 - wcsncat(target, L"@", ARRAY_SIZE(target)); 344 + target_append(wusername); 345 + target_append(L"@"); 338 346 } 339 347 if (host) 340 - wcsncat(target, host, ARRAY_SIZE(target)); 348 + target_append(host); 341 349 if (path) { 342 - wcsncat(target, L"/", ARRAY_SIZE(target)); 343 - wcsncat(target, path, ARRAY_SIZE(target)); 350 + target_append(L"/"); 351 + target_append(path); 344 352 } 345 353 346 354 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 {} ··· 1068 1114 ## configure our library 1069 1115 1070 1116 set idx [file join $oguilib tclIndex] 1071 - if {[catch {set fd [open $idx r]} err]} { 1117 + if {[catch {set fd [safe_open_file $idx r]} err]} { 1072 1118 catch {wm withdraw .} 1073 1119 tk_messageBox \ 1074 1120 -icon error \ ··· 1106 1152 ## 1107 1153 ## config file parsing 1108 1154 1109 - git-version proc _parse_config {arr_name args} { 1110 - >= 1.5.3 { 1111 - upvar $arr_name arr 1112 - array unset arr 1113 - set buf {} 1114 - catch { 1115 - set fd_rc [eval \ 1116 - [list git_read config] \ 1117 - $args \ 1118 - [list --null --list]] 1119 - fconfigure $fd_rc -translation binary -encoding utf-8 1120 - set buf [read $fd_rc] 1121 - close $fd_rc 1122 - } 1123 - foreach line [split $buf "\0"] { 1124 - if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { 1125 - if {[is_many_config $name]} { 1126 - lappend arr($name) $value 1127 - } else { 1128 - set arr($name) $value 1129 - } 1130 - } elseif {[regexp {^([^\n]+)$} $line line name]} { 1131 - # no value given, but interpreting them as 1132 - # boolean will be handled as true 1133 - set arr($name) {} 1134 - } 1135 - } 1155 + proc _parse_config {arr_name args} { 1156 + upvar $arr_name arr 1157 + array unset arr 1158 + set buf {} 1159 + catch { 1160 + set fd_rc [git_read \ 1161 + [concat config \ 1162 + $args \ 1163 + --null --list]] 1164 + fconfigure $fd_rc -translation binary -encoding utf-8 1165 + set buf [read $fd_rc] 1166 + close $fd_rc 1136 1167 } 1137 - default { 1138 - upvar $arr_name arr 1139 - array unset arr 1140 - catch { 1141 - set fd_rc [eval [list git_read config --list] $args] 1142 - while {[gets $fd_rc line] >= 0} { 1143 - if {[regexp {^([^=]+)=(.*)$} $line line name value]} { 1144 - if {[is_many_config $name]} { 1145 - lappend arr($name) $value 1146 - } else { 1147 - set arr($name) $value 1148 - } 1149 - } elseif {[regexp {^([^=]+)$} $line line name]} { 1150 - # no value given, but interpreting them as 1151 - # boolean will be handled as true 1152 - set arr($name) {} 1153 - } 1168 + foreach line [split $buf "\0"] { 1169 + if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} { 1170 + if {[is_many_config $name]} { 1171 + lappend arr($name) $value 1172 + } else { 1173 + set arr($name) $value 1154 1174 } 1155 - close $fd_rc 1175 + } elseif {[regexp {^([^\n]+)$} $line line name]} { 1176 + # no value given, but interpreting them as 1177 + # boolean will be handled as true 1178 + set arr($name) {} 1156 1179 } 1157 1180 } 1158 1181 } ··· 1427 1450 set merge_head [gitdir MERGE_HEAD] 1428 1451 if {[file exists $merge_head]} { 1429 1452 set ct merge 1430 - set fd_mh [open $merge_head r] 1453 + set fd_mh [safe_open_file $merge_head r] 1431 1454 while {[gets $fd_mh line] >= 0} { 1432 1455 lappend mh $line 1433 1456 } ··· 1446 1469 return $p 1447 1470 } 1448 1471 if {$empty_tree eq {}} { 1449 - set empty_tree [git mktree << {}] 1472 + set empty_tree [git_redir [list mktree] [list << {}]] 1450 1473 } 1451 1474 return $empty_tree 1452 1475 } ··· 1505 1528 } else { 1506 1529 set rescan_active 1 1507 1530 ui_status [mc "Refreshing file status..."] 1508 - set fd_rf [git_read update-index \ 1531 + set fd_rf [git_read [list update-index \ 1509 1532 -q \ 1510 1533 --unmerged \ 1511 1534 --ignore-missing \ 1512 1535 --refresh \ 1513 - ] 1536 + ]] 1514 1537 fconfigure $fd_rf -blocking 0 -translation binary 1515 1538 fileevent $fd_rf readable \ 1516 1539 [list rescan_stage2 $fd_rf $after] ··· 1550 1573 set rescan_active 2 1551 1574 ui_status [mc "Scanning for modified files ..."] 1552 1575 if {[git-version >= "1.7.2"]} { 1553 - set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]] 1576 + set fd_di [git_read [list diff-index --cached --ignore-submodules=dirty -z [PARENT]]] 1554 1577 } else { 1555 - set fd_di [git_read diff-index --cached -z [PARENT]] 1578 + set fd_di [git_read [list diff-index --cached -z [PARENT]]] 1556 1579 } 1557 - set fd_df [git_read diff-files -z] 1580 + set fd_df [git_read [list diff-files -z]] 1558 1581 1559 1582 fconfigure $fd_di -blocking 0 -translation binary -encoding binary 1560 1583 fconfigure $fd_df -blocking 0 -translation binary -encoding binary ··· 1563 1586 fileevent $fd_df readable [list read_diff_files $fd_df $after] 1564 1587 1565 1588 if {[is_config_true gui.displayuntracked]} { 1566 - set fd_lo [eval git_read ls-files --others -z $ls_others] 1589 + set fd_lo [git_read [concat ls-files --others -z $ls_others]] 1567 1590 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary 1568 1591 fileevent $fd_lo readable [list read_ls_others $fd_lo $after] 1569 1592 incr rescan_active ··· 1575 1598 1576 1599 set f [gitdir $file] 1577 1600 if {[file isfile $f]} { 1578 - if {[catch {set fd [open $f r]}]} { 1601 + if {[catch {set fd [safe_open_file $f r]}]} { 1579 1602 return 0 1580 1603 } 1581 1604 fconfigure $fd -eofchar {} ··· 1599 1622 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an 1600 1623 # empty file but existent file. 1601 1624 1602 - set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a] 1625 + set fd_pcm [safe_open_file [gitdir PREPARE_COMMIT_MSG] a] 1603 1626 1604 1627 if {[file isfile [gitdir MERGE_MSG]]} { 1605 1628 set pcm_source "merge" 1606 - set fd_mm [open [gitdir MERGE_MSG] r] 1629 + set fd_mm [safe_open_file [gitdir MERGE_MSG] r] 1607 1630 fconfigure $fd_mm -encoding utf-8 1608 1631 puts -nonewline $fd_pcm [read $fd_mm] 1609 1632 close $fd_mm 1610 1633 } elseif {[file isfile [gitdir SQUASH_MSG]]} { 1611 1634 set pcm_source "squash" 1612 - set fd_sm [open [gitdir SQUASH_MSG] r] 1635 + set fd_sm [safe_open_file [gitdir SQUASH_MSG] r] 1613 1636 fconfigure $fd_sm -encoding utf-8 1614 1637 puts -nonewline $fd_pcm [read $fd_sm] 1615 1638 close $fd_sm 1616 1639 } elseif {[file isfile [get_config commit.template]]} { 1617 1640 set pcm_source "template" 1618 - set fd_sm [open [get_config commit.template] r] 1641 + set fd_sm [safe_open_file [get_config commit.template] r] 1619 1642 fconfigure $fd_sm -encoding utf-8 1620 1643 puts -nonewline $fd_pcm [read $fd_sm] 1621 1644 close $fd_sm ··· 2205 2228 unset env(GIT_DIR) 2206 2229 unset env(GIT_WORK_TREE) 2207 2230 } 2208 - eval exec $cmd $revs "--" "--" & 2231 + safe_exec_bg [concat $cmd $revs "--" "--"] 2209 2232 2210 2233 set env(GIT_DIR) $_gitdir 2211 2234 set env(GIT_WORK_TREE) $_gitworktree ··· 2242 2265 set pwd [pwd] 2243 2266 cd $current_diff_path 2244 2267 2245 - eval exec $exe gui & 2268 + safe_exec_bg [concat $exe gui] 2246 2269 2247 2270 set env(GIT_DIR) $_gitdir 2248 2271 set env(GIT_WORK_TREE) $_gitworktree ··· 2273 2296 2274 2297 proc do_explore {} { 2275 2298 global _gitworktree 2276 - set explorer [get_explorer] 2277 - eval exec $explorer [list [file nativename $_gitworktree]] & 2299 + set cmd [get_explorer] 2300 + lappend cmd [file nativename $_gitworktree] 2301 + safe_exec_bg $cmd 2278 2302 } 2279 2303 2280 2304 # Open file relative to the working tree by the default associated app. 2281 2305 proc do_file_open {file} { 2282 2306 global _gitworktree 2283 - set explorer [get_explorer] 2307 + set cmd [get_explorer] 2284 2308 set full_file_path [file join $_gitworktree $file] 2285 - exec $explorer [file nativename $full_file_path] & 2309 + lappend cmd [file nativename $full_file_path] 2310 + safe_exec_bg $cmd 2286 2311 } 2287 2312 2288 2313 set is_quitting 0 ··· 2316 2341 if {![string match amend* $commit_type] 2317 2342 && $msg ne {}} { 2318 2343 catch { 2319 - set fd [open $save w] 2344 + set fd [safe_open_file $save w] 2320 2345 fconfigure $fd -encoding utf-8 2321 2346 puts -nonewline $fd $msg 2322 2347 close $fd ··· 2760 2785 2761 2786 if {[is_Windows]} { 2762 2787 # Use /git-bash.exe if available 2763 - set normalized [file normalize $::argv0] 2764 - regsub "/mingw../libexec/git-core/git-gui$" \ 2765 - $normalized "/git-bash.exe" cmdLine 2766 - if {$cmdLine != $normalized && [file exists $cmdLine]} { 2767 - set cmdLine [list "Git Bash" $cmdLine &] 2788 + set _git_bash [safe_exec [list cygpath -m /git-bash.exe]] 2789 + if {[file executable $_git_bash]} { 2790 + set _bash_cmdline [list "Git Bash" $_git_bash] 2768 2791 } else { 2769 - set cmdLine [list "Git Bash" bash --login -l &] 2792 + set _bash_cmdline [list "Git Bash" bash --login -l] 2770 2793 } 2771 2794 .mbar.repository add command \ 2772 2795 -label [mc "Git Bash"] \ 2773 - -command {eval exec [auto_execok start] $cmdLine} 2796 + -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]} 2797 + unset _git_bash 2774 2798 } 2775 2799 2776 2800 if {[is_Windows] || ![is_bare]} { ··· 4079 4103 } 4080 4104 } elseif {$m} { 4081 4105 catch { 4082 - set fd [open [gitdir GITGUI_BCK] w] 4106 + set fd [safe_open_file [gitdir GITGUI_BCK] w] 4083 4107 fconfigure $fd -encoding utf-8 4084 4108 puts -nonewline $fd $msg 4085 4109 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 ··· 236 236 # -- Build the message file. 237 237 # 238 238 set msg_p [gitdir GITGUI_EDITMSG] 239 - set msg_wt [open $msg_p w] 239 + set msg_wt [safe_open_file $msg_p w] 240 240 fconfigure $msg_wt -translation lf 241 241 setup_commit_encoding $msg_wt 242 242 puts $msg_wt $msg ··· 336 336 337 337 proc commit_writetree {curHEAD msg_p} { 338 338 ui_status [mc "Committing changes..."] 339 - set fd_wt [git_read write-tree] 339 + set fd_wt [git_read [list write-tree]] 340 340 fileevent $fd_wt readable \ 341 341 [list commit_committree $fd_wt $curHEAD $msg_p] 342 342 } ··· 361 361 # -- Verify this wasn't an empty change. 362 362 # 363 363 if {$commit_type eq {normal}} { 364 - set fd_ot [git_read cat-file commit $PARENT] 364 + set fd_ot [git_read [list cat-file commit $PARENT]] 365 365 fconfigure $fd_ot -encoding binary -translation lf 366 366 set old_tree [gets $fd_ot] 367 367 close $fd_ot ··· 399 399 foreach p [concat $PARENT $MERGE_HEAD] { 400 400 lappend cmd -p $p 401 401 } 402 - lappend cmd <$msg_p 403 - if {[catch {set cmt_id [eval git $cmd]} err]} { 402 + set msgtxt [list <$msg_p] 403 + if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} { 404 404 catch {file delete $msg_p} 405 405 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] 406 406 ui_status [mc "Commit failed."] ··· 420 420 if {$commit_type ne {normal}} { 421 421 append reflogm " ($commit_type)" 422 422 } 423 - set msg_fd [open $msg_p r] 423 + set msg_fd [safe_open_file $msg_p r] 424 424 setup_commit_encoding $msg_fd 1 425 425 gets $msg_fd subject 426 426 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 -encoding [encoding system] 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
··· 191 191 set sz [string length $content] 192 192 } 193 193 file { 194 - set fd [open $path r] 194 + set fd [safe_open_file $path r] 195 195 fconfigure $fd \ 196 196 -eofchar {} \ 197 197 -encoding [get_path_encoding $path] ··· 215 215 $ui_diff insert end \ 216 216 "* [mc "Git Repository (subproject)"]\n" \ 217 217 d_info 218 - } elseif {![catch {set type [exec file $path]}]} { 218 + } elseif {![catch {set type [safe_exec [list file $path]]}]} { 219 219 set n [string length $path] 220 220 if {[string equal -length $n $path $type]} { 221 221 set type [string range $type $n end] ··· 327 327 } 328 328 } 329 329 330 - if {[catch {set fd [eval git_read --nice $cmd]} err]} { 330 + if {[catch {set fd [git_read_nice $cmd]} err]} { 331 331 set diff_active 0 332 332 unlock_index 333 333 ui_status [mc "Unable to display %s" [escape_path $path]] ··· 603 603 604 604 if {[catch { 605 605 set enc [get_path_encoding $current_diff_path] 606 - set p [eval git_write $apply_cmd] 606 + set p [git_write $apply_cmd] 607 607 fconfigure $p -translation binary -encoding $enc 608 608 puts -nonewline $p $wholepatch 609 609 close $p} err]} { ··· 839 839 840 840 if {[catch { 841 841 set enc [get_path_encoding $current_diff_path] 842 - set p [eval git_write $apply_cmd] 842 + set p [git_write $apply_cmd] 843 843 fconfigure $p -translation binary -encoding $enc 844 844 puts -nonewline $p $current_diff_header 845 845 puts -nonewline $p $wholepatch ··· 876 876 877 877 if {[catch { 878 878 set enc $last_revert_enc 879 - set p [eval git_write $apply_cmd] 879 + set p [git_write $apply_cmd] 880 880 fconfigure $p -translation binary -encoding $enc 881 881 puts -nonewline $p $last_revert 882 882 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] ··· 310 310 foreach fname $stages { 311 311 if {$merge_stages($i) eq {}} { 312 312 file delete $fname 313 - catch { close [open $fname w] } 313 + catch { close [safe_open_file $fname w] } 314 314 } else { 315 315 # A hack to support autocrlf properly 316 316 git checkout-index -f --stage=$i -- $target ··· 360 360 361 361 # Force redirection to avoid interpreting output on stderr 362 362 # as an error, and launch the tool 363 - lappend cmdline {2>@1} 363 + set redir [list {2>@1}] 364 364 365 - if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} { 365 + if {[catch { set mtool_fd [safe_open_command $cmdline $redir] } err]} { 366 366 delete_temp_files $mtool_tmpfiles 367 367 error_popup [mc "Could not start the merge tool:\n\n%s" $err] 368 368 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 }
+171 -106
gitk-git/gitk
··· 113 113 114 114 # End of safe PATH lookup stuff 115 115 116 + # Wrap exec/open to sanitize arguments 117 + 118 + # unsafe arguments begin with redirections or the pipe or background operators 119 + proc is_arg_unsafe {arg} { 120 + regexp {^([<|>&]|2>)} $arg 121 + } 122 + 123 + proc make_arg_safe {arg} { 124 + if {[is_arg_unsafe $arg]} { 125 + set arg [file join . $arg] 126 + } 127 + return $arg 128 + } 129 + 130 + proc make_arglist_safe {arglist} { 131 + set res {} 132 + foreach arg $arglist { 133 + lappend res [make_arg_safe $arg] 134 + } 135 + return $res 136 + } 137 + 138 + # executes one command 139 + # no redirections or pipelines are possible 140 + # cmd is a list that specifies the command and its arguments 141 + # calls `exec` and returns its value 142 + proc safe_exec {cmd} { 143 + eval exec [make_arglist_safe $cmd] 144 + } 145 + 146 + # executes one command with redirections 147 + # no pipelines are possible 148 + # cmd is a list that specifies the command and its arguments 149 + # redir is a list that specifies redirections (output, background, constant(!) commands) 150 + # calls `exec` and returns its value 151 + proc safe_exec_redirect {cmd redir} { 152 + eval exec [make_arglist_safe $cmd] $redir 153 + } 154 + 155 + proc safe_open_file {filename flags} { 156 + # a file name starting with "|" would attempt to run a process 157 + # but such a file name must be treated as a relative path 158 + # hide the "|" behind "./" 159 + if {[string index $filename 0] eq "|"} { 160 + set filename [file join . $filename] 161 + } 162 + open $filename $flags 163 + } 164 + 165 + # opens a command pipeline for reading 166 + # cmd is a list that specifies the command and its arguments 167 + # calls `open` and returns the file id 168 + proc safe_open_command {cmd} { 169 + open |[make_arglist_safe $cmd] r 170 + } 171 + 172 + # opens a command pipeline for reading and writing 173 + # cmd is a list that specifies the command and its arguments 174 + # calls `open` and returns the file id 175 + proc safe_open_command_rw {cmd} { 176 + open |[make_arglist_safe $cmd] r+ 177 + } 178 + 179 + # opens a command pipeline for reading with redirections 180 + # cmd is a list that specifies the command and its arguments 181 + # redir is a list that specifies redirections 182 + # calls `open` and returns the file id 183 + proc safe_open_command_redirect {cmd redir} { 184 + set cmd [make_arglist_safe $cmd] 185 + open |[concat $cmd $redir] r 186 + } 187 + 188 + # opens a pipeline with several commands for reading 189 + # cmds is a list of lists, each of which specifies a command and its arguments 190 + # calls `open` and returns the file id 191 + proc safe_open_pipeline {cmds} { 192 + set cmd {} 193 + foreach subcmd $cmds { 194 + set cmd [concat $cmd | [make_arglist_safe $subcmd]] 195 + } 196 + open $cmd r 197 + } 198 + 199 + # End exec/open wrappers 200 + 116 201 proc hasworktree {} { 117 202 return [expr {[exec git rev-parse --is-bare-repository] == "false" && 118 203 [exec git rev-parse --is-inside-git-dir] == "false"}] ··· 238 323 set mlist {} 239 324 set nr_unmerged 0 240 325 if {[catch { 241 - set fd [open "| git ls-files -u" r] 326 + set fd [safe_open_command {git ls-files -u}] 242 327 } err]} { 243 328 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" 244 329 exit 1 ··· 400 485 } elseif {[lsearch -exact $revs --all] >= 0} { 401 486 lappend revs HEAD 402 487 } 403 - if {[catch {set ids [eval exec git rev-parse $revs]} err]} { 488 + if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} { 404 489 # we get stdout followed by stderr in $err 405 490 # for an unknown rev, git rev-parse echoes it and then errors out 406 491 set errlines [split $err "\n"] ··· 457 542 return $ret 458 543 } 459 544 460 - # Escapes a list of filter paths to be passed to git log via stdin. Note that 461 - # paths must not be quoted. 462 - proc escape_filter_paths {paths} { 463 - set escaped [list] 464 - foreach path $paths { 465 - lappend escaped [string map {\\ \\\\ "\ " "\\\ "} $path] 466 - } 467 - return $escaped 468 - } 469 - 470 545 # Start off a git log process and arrange to read its output 471 546 proc start_rev_list {view} { 472 547 global startmsecs commitidx viewcomplete curview ··· 488 563 set args $viewargs($view) 489 564 if {$viewargscmd($view) ne {}} { 490 565 if {[catch { 491 - set str [exec sh -c $viewargscmd($view)] 566 + set str [safe_exec [list sh -c $viewargscmd($view)]] 492 567 } err]} { 493 568 error_popup "[mc "Error executing --argscmd command:"] $err" 494 569 return 0 ··· 526 601 } 527 602 528 603 if {[catch { 529 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 530 - --parents --boundary $args --stdin \ 531 - "<<[join [concat $revs "--" \ 532 - [escape_filter_paths $files]] "\\n"]"] r] 604 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 605 + --parents --boundary $args --stdin] \ 606 + [list "<<[join [concat $revs "--" $files] "\n"]"]] 533 607 } err]} { 534 608 error_popup "[mc "Error executing git log:"] $err" 535 609 return 0 ··· 563 637 set pid [pid $fd] 564 638 565 639 if {$::tcl_platform(platform) eq {windows}} { 566 - exec taskkill /pid $pid 640 + safe_exec [list taskkill /pid $pid] 567 641 } else { 568 - exec kill $pid 642 + safe_exec [list kill $pid] 569 643 } 570 644 } 571 645 catch {close $fd} ··· 680 754 set args $vorigargs($view) 681 755 } 682 756 if {[catch { 683 - set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ 684 - --parents --boundary $args --stdin \ 685 - "<<[join [concat $revs "--" \ 686 - [escape_filter_paths \ 687 - $vfilelimit($view)]] "\\n"]"] r] 757 + set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \ 758 + --parents --boundary $args --stdin] \ 759 + [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]] 688 760 } err]} { 689 761 error_popup "[mc "Error executing git log:"] $err" 690 762 return ··· 1651 1723 # and if we already know about it, using the rewritten 1652 1724 # parent as a substitute parent for $id's children. 1653 1725 if {![catch { 1654 - set rwid [exec git rev-list --first-parent --max-count=1 \ 1655 - $id -- $vfilelimit($view)] 1726 + set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \ 1727 + $id -- $vfilelimit($view)]] 1656 1728 }]} { 1657 1729 if {$rwid ne {} && [info exists varcid($view,$rwid)]} { 1658 1730 # use $rwid in place of $id ··· 1772 1844 global tclencoding 1773 1845 1774 1846 # Invoke git-log to handle automatic encoding conversion 1775 - set fd [open [concat | git log --no-color --pretty=raw -1 $id] r] 1847 + set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]] 1776 1848 # Read the results using i18n.logoutputencoding 1777 1849 fconfigure $fd -translation lf -eofchar {} 1778 1850 if {$tclencoding != {}} { ··· 1908 1980 foreach v {tagids idtags headids idheads otherrefids idotherrefs} { 1909 1981 unset -nocomplain $v 1910 1982 } 1911 - set refd [open [list | git show-ref -d] r] 1983 + set refd [safe_open_command [list git show-ref -d]] 1912 1984 if {$tclencoding != {}} { 1913 1985 fconfigure $refd -encoding $tclencoding 1914 1986 } ··· 1956 2028 set selectheadid {} 1957 2029 if {$selecthead ne {}} { 1958 2030 catch { 1959 - set selectheadid [exec git rev-parse --verify $selecthead] 2031 + set selectheadid [safe_exec [list git rev-parse --verify $selecthead]] 1960 2032 } 1961 2033 } 1962 2034 } ··· 2220 2292 {mc "Reread re&ferences" command rereadrefs} 2221 2293 {mc "&List references" command showrefs -accelerator F2} 2222 2294 {xx "" separator} 2223 - {mc "Start git &gui" command {exec git gui &}} 2295 + {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}} 2224 2296 {xx "" separator} 2225 2297 {mc "&Quit" command doquit -accelerator Meta1-Q} 2226 2298 }} ··· 3007 3079 set remove_tmp 0 3008 3080 if {[catch { 3009 3081 set try_count 0 3010 - while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} { 3082 + while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} { 3011 3083 if {[incr try_count] > 50} { 3012 3084 error "Unable to write config file: $config_file_tmp exists" 3013 3085 } ··· 3723 3795 set tmpdir $gitdir 3724 3796 } 3725 3797 set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"] 3726 - if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} { 3798 + if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} { 3727 3799 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]] 3728 3800 } 3729 3801 if {[catch {file mkdir $gitktmpdir} err]} { ··· 3745 3817 proc save_file_from_commit {filename output what} { 3746 3818 global nullfile 3747 3819 3748 - if {[catch {exec git show $filename -- > $output} err]} { 3820 + if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} { 3749 3821 if {[string match "fatal: bad revision *" $err]} { 3750 3822 return $nullfile 3751 3823 } ··· 3810 3882 3811 3883 if {$difffromfile ne {} && $difftofile ne {}} { 3812 3884 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] 3813 - if {[catch {set fl [open |$cmd r]} err]} { 3885 + if {[catch {set fl [safe_open_command $cmd]} err]} { 3814 3886 file delete -force $diffdir 3815 3887 error_popup "$extdifftool: [mc "command failed:"] $err" 3816 3888 } else { ··· 3914 3986 # Find the SHA1 ID of the blob for file $fname in the index 3915 3987 # at stage 0 or 2 3916 3988 proc index_sha1 {fname} { 3917 - set f [open [list | git ls-files -s $fname] r] 3989 + set f [safe_open_command [list git ls-files -s $fname]] 3918 3990 while {[gets $f line] >= 0} { 3919 3991 set info [lindex [split $line "\t"] 0] 3920 3992 set stage [lindex $info 2] ··· 3974 4046 # being given an absolute path... 3975 4047 set f [make_relative $f] 3976 4048 lappend cmdline $base_commit $f 3977 - if {[catch {eval exec $cmdline &} err]} { 4049 + if {[catch {safe_exec_redirect $cmdline [list &]} err]} { 3978 4050 error_popup "[mc "git gui blame: command failed:"] $err" 3979 4051 } 3980 4052 } ··· 4002 4074 # must be a merge in progress... 4003 4075 if {[catch { 4004 4076 # get the last line from .git/MERGE_HEAD 4005 - set f [open [file join $gitdir MERGE_HEAD] r] 4077 + set f [safe_open_file [file join $gitdir MERGE_HEAD] r] 4006 4078 set id [lindex [split [read $f] "\n"] end-1] 4007 4079 close $f 4008 4080 } err]} { ··· 4025 4097 } 4026 4098 set line [lindex $h 1] 4027 4099 } 4028 - set blameargs {} 4100 + set blamefile [file join $cdup $flist_menu_file] 4029 4101 if {$from_index ne {}} { 4030 - lappend blameargs | git cat-file blob $from_index 4031 - } 4032 - lappend blameargs | git blame -p -L$line,+1 4033 - if {$from_index ne {}} { 4034 - lappend blameargs --contents - 4102 + set blameargs [list \ 4103 + [list git cat-file blob $from_index] \ 4104 + [list git blame -p -L$line,+1 --contents - -- $blamefile]] 4035 4105 } else { 4036 - lappend blameargs $id 4106 + set blameargs [list \ 4107 + [list git blame -p -L$line,+1 $id -- $blamefile]] 4037 4108 } 4038 - lappend blameargs -- [file join $cdup $flist_menu_file] 4039 4109 if {[catch { 4040 - set f [open $blameargs r] 4110 + set f [safe_open_pipeline $blameargs] 4041 4111 } err]} { 4042 4112 error_popup [mc "Couldn't start git blame: %s" $err] 4043 4113 return ··· 4962 5032 # must be "containing:", i.e. we're searching commit info 4963 5033 return 4964 5034 } 4965 - set cmd [concat | git diff-tree -r -s --stdin $gdtargs] 4966 - set filehighlight [open $cmd r+] 5035 + set cmd [concat git diff-tree -r -s --stdin $gdtargs] 5036 + set filehighlight [safe_open_command_rw $cmd] 4967 5037 fconfigure $filehighlight -blocking 0 4968 5038 filerun $filehighlight readfhighlight 4969 5039 set fhl_list {} ··· 5392 5462 global viewmainheadid vfilelimit viewinstances mainheadid 5393 5463 5394 5464 catch { 5395 - set rfd [open [concat | git rev-list -1 $mainheadid \ 5396 - -- $vfilelimit($view)] r] 5465 + set rfd [safe_open_command [concat git rev-list -1 $mainheadid \ 5466 + -- $vfilelimit($view)]] 5397 5467 set j [reg_instance $rfd] 5398 5468 lappend viewinstances($view) $j 5399 5469 fconfigure $rfd -blocking 0 ··· 5458 5528 if {!$showlocalchanges || !$hasworktree} return 5459 5529 incr lserial 5460 5530 if {[package vcompare $git_version "1.7.2"] >= 0} { 5461 - set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD" 5531 + set cmd "git diff-index --cached --ignore-submodules=dirty HEAD" 5462 5532 } else { 5463 - set cmd "|git diff-index --cached HEAD" 5533 + set cmd "git diff-index --cached HEAD" 5464 5534 } 5465 5535 if {$vfilelimit($curview) ne {}} { 5466 5536 set cmd [concat $cmd -- $vfilelimit($curview)] 5467 5537 } 5468 - set fd [open $cmd r] 5538 + set fd [safe_open_command $cmd] 5469 5539 fconfigure $fd -blocking 0 5470 5540 set i [reg_instance $fd] 5471 5541 filerun $fd [list readdiffindex $fd $lserial $i] ··· 5490 5560 } 5491 5561 5492 5562 # now see if there are any local changes not checked in to the index 5493 - set cmd "|git diff-files" 5563 + set cmd "git diff-files" 5494 5564 if {$vfilelimit($curview) ne {}} { 5495 5565 set cmd [concat $cmd -- $vfilelimit($curview)] 5496 5566 } 5497 - set fd [open $cmd r] 5567 + set fd [safe_open_command $cmd] 5498 5568 fconfigure $fd -blocking 0 5499 5569 set i [reg_instance $fd] 5500 5570 filerun $fd [list readdifffiles $fd $serial $i] ··· 7283 7353 global web_browser 7284 7354 7285 7355 if {$web_browser eq {}} return 7286 - # Use eval here in case $web_browser is a command plus some arguments 7287 - if {[catch {eval exec $web_browser [list $url] &} err]} { 7356 + # Use concat here in case $web_browser is a command plus some arguments 7357 + if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} { 7288 7358 error_popup "[mc "Error starting web browser:"] $err" 7289 7359 } 7290 7360 } ··· 7790 7860 if {![info exists treefilelist($id)]} { 7791 7861 if {![info exists treepending]} { 7792 7862 if {$id eq $nullid} { 7793 - set cmd [list | git ls-files] 7863 + set cmd [list git ls-files] 7794 7864 } elseif {$id eq $nullid2} { 7795 - set cmd [list | git ls-files --stage -t] 7865 + set cmd [list git ls-files --stage -t] 7796 7866 } else { 7797 - set cmd [list | git ls-tree -r $id] 7867 + set cmd [list git ls-tree -r $id] 7798 7868 } 7799 - if {[catch {set gtf [open $cmd r]}]} { 7869 + if {[catch {set gtf [safe_open_command $cmd]}]} { 7800 7870 return 7801 7871 } 7802 7872 set treepending $id ··· 7860 7930 return 7861 7931 } 7862 7932 if {$diffids eq $nullid} { 7863 - if {[catch {set bf [open $f r]} err]} { 7933 + if {[catch {set bf [safe_open_file $f r]} err]} { 7864 7934 puts "oops, can't read $f: $err" 7865 7935 return 7866 7936 } 7867 7937 } else { 7868 7938 set blob [lindex $treeidlist($diffids) $i] 7869 - if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} { 7939 + if {[catch {set bf [safe_open_command [concat git cat-file blob $blob]]} err]} { 7870 7940 puts "oops, error reading blob $blob: $err" 7871 7941 return 7872 7942 } ··· 8016 8086 if {$i >= 0} { 8017 8087 if {[llength $ids] > 1 && $j < 0} { 8018 8088 # comparing working directory with some specific revision 8019 - set cmd [concat | git diff-index $flags] 8089 + set cmd [concat git diff-index $flags] 8020 8090 if {$i == 0} { 8021 8091 lappend cmd -R [lindex $ids 1] 8022 8092 } else { ··· 8024 8094 } 8025 8095 } else { 8026 8096 # comparing working directory with index 8027 - set cmd [concat | git diff-files $flags] 8097 + set cmd [concat git diff-files $flags] 8028 8098 if {$j == 1} { 8029 8099 lappend cmd -R 8030 8100 } ··· 8033 8103 if {[package vcompare $git_version "1.7.2"] >= 0} { 8034 8104 set flags "$flags --ignore-submodules=dirty" 8035 8105 } 8036 - set cmd [concat | git diff-index --cached $flags] 8106 + set cmd [concat git diff-index --cached $flags] 8037 8107 if {[llength $ids] > 1} { 8038 8108 # comparing index with specific revision 8039 8109 if {$j == 0} { ··· 8049 8119 if {$log_showroot} { 8050 8120 lappend flags --root 8051 8121 } 8052 - set cmd [concat | git diff-tree -r $flags $ids] 8122 + set cmd [concat git diff-tree -r $flags $ids] 8053 8123 } 8054 8124 return $cmd 8055 8125 } ··· 8061 8131 if {$limitdiffs && $vfilelimit($curview) ne {}} { 8062 8132 set cmd [concat $cmd -- $vfilelimit($curview)] 8063 8133 } 8064 - if {[catch {set gdtf [open $cmd r]}]} return 8134 + if {[catch {set gdtf [safe_open_command $cmd]}]} return 8065 8135 8066 8136 set treepending $ids 8067 8137 set treediff {} ··· 8181 8251 if {$limitdiffs && $vfilelimit($curview) ne {}} { 8182 8252 set cmd [concat $cmd -- $vfilelimit($curview)] 8183 8253 } 8184 - if {[catch {set bdf [open $cmd r]} err]} { 8254 + if {[catch {set bdf [safe_open_command $cmd]} err]} { 8185 8255 error_popup [mc "Error getting diffs: %s" $err] 8186 8256 return 8187 8257 } ··· 8899 8969 set id [lindex $matches 0] 8900 8970 } 8901 8971 } else { 8902 - if {[catch {set id [exec git rev-parse --verify $sha1string]}]} { 8972 + if {[catch {set id [safe_exec [list git rev-parse --verify $sha1string]]}]} { 8903 8973 error_popup [mc "Revision %s is not known" $sha1string] 8904 8974 return 8905 8975 } ··· 9205 9275 9206 9276 if {![info exists patchids($id)]} { 9207 9277 set cmd [diffcmd [list $id] {-p --root}] 9208 - # trim off the initial "|" 9209 - set cmd [lrange $cmd 1 end] 9210 9278 if {[catch { 9211 - set x [eval exec $cmd | git patch-id] 9279 + set x [safe_exec_redirect $cmd [list | git patch-id]] 9212 9280 set patchids($id) [lindex $x 0] 9213 9281 }]} { 9214 9282 set patchids($id) "error" ··· 9304 9372 set fna [file join $tmpdir "commit-[string range $a 0 7]"] 9305 9373 set fnb [file join $tmpdir "commit-[string range $b 0 7]"] 9306 9374 if {[catch { 9307 - exec git diff-tree -p --pretty $a >$fna 9308 - exec git diff-tree -p --pretty $b >$fnb 9375 + safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna] 9376 + safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb] 9309 9377 } err]} { 9310 9378 error_popup [mc "Error writing commit to file: %s" $err] 9311 9379 return 9312 9380 } 9313 9381 if {[catch { 9314 - set fd [open "| diff -U$diffcontext $fna $fnb" r] 9382 + set fd [safe_open_command "diff -U$diffcontext $fna $fnb"] 9315 9383 } err]} { 9316 9384 error_popup [mc "Error diffing commits: %s" $err] 9317 9385 return ··· 9451 9519 set newid [$patchtop.tosha1 get] 9452 9520 set fname [$patchtop.fname get] 9453 9521 set cmd [diffcmd [list $oldid $newid] -p] 9454 - # trim off the initial "|" 9455 - set cmd [lrange $cmd 1 end] 9456 - lappend cmd >$fname & 9457 - if {[catch {eval exec $cmd} err]} { 9522 + if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} { 9458 9523 error_popup "[mc "Error creating patch:"] $err" $patchtop 9459 9524 } 9460 9525 catch {destroy $patchtop} ··· 9523 9588 } 9524 9589 if {[catch { 9525 9590 if {$msg != {}} { 9526 - exec git tag -a -m $msg $tag $id 9591 + safe_exec [list git tag -a -m $msg $tag $id] 9527 9592 } else { 9528 - exec git tag $tag $id 9593 + safe_exec [list git tag $tag $id] 9529 9594 } 9530 9595 } err]} { 9531 9596 error_popup "[mc "Error creating tag:"] $err" $mktagtop ··· 9593 9658 if {$autosellen < 40} { 9594 9659 lappend cmd --abbrev=$autosellen 9595 9660 } 9596 - set reference [eval exec $cmd $rowmenuid] 9661 + set reference [safe_exec [concat $cmd $rowmenuid]] 9597 9662 9598 9663 clipboard clear 9599 9664 clipboard append $reference ··· 9643 9708 set id [$wrcomtop.sha1 get] 9644 9709 set cmd "echo $id | [$wrcomtop.cmd get]" 9645 9710 set fname [$wrcomtop.fname get] 9646 - if {[catch {exec sh -c $cmd >$fname &} err]} { 9711 + if {[catch {safe_exec_redirect [list sh -c $cmd] [list >$fname &]} err]} { 9647 9712 error_popup "[mc "Error writing commit:"] $err" $wrcomtop 9648 9713 } 9649 9714 catch {destroy $wrcomtop} ··· 9747 9812 nowbusy newbranch 9748 9813 update 9749 9814 if {[catch { 9750 - eval exec git branch $cmdargs 9815 + safe_exec [concat git branch $cmdargs] 9751 9816 } err]} { 9752 9817 notbusy newbranch 9753 9818 error_popup $err ··· 9788 9853 nowbusy renamebranch 9789 9854 update 9790 9855 if {[catch { 9791 - eval exec git branch $cmdargs 9856 + safe_exec [concat git branch $cmdargs] 9792 9857 } err]} { 9793 9858 notbusy renamebranch 9794 9859 error_popup $err ··· 9829 9894 } 9830 9895 } 9831 9896 9832 - eval exec git citool $tool_args & 9897 + safe_exec_redirect [concat git citool $tool_args] [list &] 9833 9898 9834 9899 array unset env GIT_AUTHOR_* 9835 9900 array set env $save_env ··· 9852 9917 update 9853 9918 # Unfortunately git-cherry-pick writes stuff to stderr even when 9854 9919 # no error occurs, and exec takes that as an indication of error... 9855 - if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} { 9920 + if {[catch {safe_exec [list sh -c "git cherry-pick -r $rowmenuid 2>&1"]} err]} { 9856 9921 notbusy cherrypick 9857 9922 if {[regexp -line \ 9858 9923 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \ ··· 9914 9979 nowbusy revert [mc "Reverting"] 9915 9980 update 9916 9981 9917 - if [catch {exec git revert --no-edit $rowmenuid} err] { 9982 + if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] { 9918 9983 notbusy revert 9919 9984 if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\ 9920 9985 $err match files] { ··· 9990 10055 bind $w <Visibility> "grab $w; focus $w" 9991 10056 tkwait window $w 9992 10057 if {!$confirm_ok} return 9993 - if {[catch {set fd [open \ 9994 - [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} { 10058 + if {[catch {set fd [safe_open_command_redirect \ 10059 + [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} { 9995 10060 error_popup $err 9996 10061 } else { 9997 10062 dohidelocalchanges ··· 10062 10127 10063 10128 # check the tree is clean first?? 10064 10129 set newhead $headmenuhead 10065 - set command [list | git checkout] 10130 + set command [list git checkout] 10066 10131 if {[string match "remotes/*" $newhead]} { 10067 10132 set remote $newhead 10068 10133 set newhead [string range $newhead [expr [string last / $newhead] + 1] end] ··· 10076 10141 } else { 10077 10142 lappend command $newhead 10078 10143 } 10079 - lappend command 2>@1 10080 10144 nowbusy checkout [mc "Checking out"] 10081 10145 update 10082 10146 dohidelocalchanges 10083 10147 if {[catch { 10084 - set fd [open $command r] 10148 + set fd [safe_open_command_redirect $command [list 2>@1]] 10085 10149 } err]} { 10086 10150 notbusy checkout 10087 10151 error_popup $err ··· 10147 10211 } 10148 10212 nowbusy rmbranch 10149 10213 update 10150 - if {[catch {exec git branch -D $head} err]} { 10214 + if {[catch {safe_exec [list git branch -D $head]} err]} { 10151 10215 notbusy rmbranch 10152 10216 error_popup $err 10153 10217 return ··· 10338 10402 set cachedarcs 0 10339 10403 set allccache [file join $gitdir "gitk.cache"] 10340 10404 if {![catch { 10341 - set f [open $allccache r] 10405 + set f [safe_open_file $allccache r] 10342 10406 set allcwait 1 10343 10407 getcache $f 10344 10408 }]} return ··· 10347 10411 if {$allcwait} { 10348 10412 return 10349 10413 } 10350 - set cmd [list | git rev-list --parents] 10414 + set cmd [list git rev-list --parents] 10351 10415 set allcupdate [expr {$seeds ne {}}] 10352 10416 if {!$allcupdate} { 10353 10417 set ids "--all" ··· 10375 10439 if {$ids ne {}} { 10376 10440 if {$ids eq "--all"} { 10377 10441 set cmd [concat $cmd "--all"] 10442 + set fd [safe_open_command $cmd] 10378 10443 } else { 10379 - set cmd [concat $cmd --stdin "<<[join $ids "\\n"]"] 10444 + set cmd [concat $cmd --stdin] 10445 + set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]] 10380 10446 } 10381 - set fd [open $cmd r] 10382 10447 fconfigure $fd -blocking 0 10383 10448 incr allcommits 10384 10449 nowbusy allcommits ··· 10768 10833 set cachearc 0 10769 10834 set cachedarcs $nextarc 10770 10835 catch { 10771 - set f [open $allccache w] 10836 + set f [safe_open_file $allccache w] 10772 10837 puts $f [list 1 $cachedarcs] 10773 10838 run writecache $f 10774 10839 } ··· 11471 11536 11472 11537 if {![info exists cached_tagcontent($tag)]} { 11473 11538 catch { 11474 - set cached_tagcontent($tag) [exec git cat-file -p $tag] 11539 + set cached_tagcontent($tag) [safe_exec [list git cat-file -p $tag]] 11475 11540 } 11476 11541 } 11477 11542 $ctext insert end "[mc "Tag"]: $tag\n" bold ··· 12382 12447 set r $path_attr_cache($attr,$path) 12383 12448 } else { 12384 12449 set r "unspecified" 12385 - if {![catch {set line [exec git check-attr $attr -- $path]}]} { 12450 + if {![catch {set line [safe_exec [list git check-attr $attr -- $path]]}]} { 12386 12451 regexp "(.*): $attr: (.*)" $line m f r 12387 12452 } 12388 12453 set path_attr_cache($attr,$path) $r ··· 12409 12474 while {$newlist ne {}} { 12410 12475 set head [lrange $newlist 0 [expr {$lim - 1}]] 12411 12476 set newlist [lrange $newlist $lim end] 12412 - if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} { 12477 + if {![catch {set rlist [safe_exec [concat git check-attr $attr -- $head]]}]} { 12413 12478 foreach row [split $rlist "\n"] { 12414 12479 if {[regexp "(.*): $attr: (.*)" $row m path value]} { 12415 12480 if {[string index $path 0] eq "\""} { ··· 12461 12526 12462 12527 # on OSX bring the current Wish process window to front 12463 12528 if {[tk windowingsystem] eq "aqua"} { 12464 - exec osascript -e [format { 12529 + safe_exec [list osascript -e [format { 12465 12530 tell application "System Events" 12466 12531 set frontmost of processes whose unix id is %d to true 12467 12532 end tell 12468 - } [pid] ] 12533 + } [pid] ]] 12469 12534 } 12470 12535 12471 12536 # Unset GIT_TRACE var if set ··· 12713 12778 if {$i >= [llength $argv] && $revtreeargs ne {}} { 12714 12779 # no -- on command line, but some arguments (other than --argscmd) 12715 12780 if {[catch { 12716 - set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs] 12781 + set f [safe_exec [concat git rev-parse --no-revs --no-flags $revtreeargs]] 12717 12782 set cmdline_files [split $f "\n"] 12718 12783 set n [llength $cmdline_files] 12719 12784 set revtreeargs [lrange $revtreeargs 0 end-$n]
+11
t/t1300-config.sh
··· 2851 2851 2852 2852 done 2853 2853 2854 + test_expect_success 'writing value with trailing CR not stripped on read' ' 2855 + test_when_finished "rm -rf cr-test" && 2856 + 2857 + printf "bar\r\n" >expect && 2858 + git init cr-test && 2859 + git -C cr-test config set core.foo $(printf "bar\r") && 2860 + git -C cr-test config get core.foo >actual && 2861 + 2862 + test_cmp expect actual 2863 + ' 2864 + 2854 2865 test_done
+23
t/t5558-clone-bundle-uri.sh
··· 1279 1279 trace-mult.txt >bundle-fetches && 1280 1280 test_line_count = 1 bundle-fetches 1281 1281 ' 1282 + 1283 + test_expect_success 'bundles with space in URI are rejected' ' 1284 + test_when_finished "rm -rf busted repo" && 1285 + mkdir -p "$HOME/busted/ /$HOME/repo/.git/objects/bundles" && 1286 + git clone --bundle-uri="$HTTPD_URL/bogus $HOME/busted/" "$HTTPD_URL/smart/fetch.git" repo 2>err && 1287 + test_grep "error: bundle-uri: URI is malformed: " err && 1288 + find busted -type f >files && 1289 + test_must_be_empty files 1290 + ' 1291 + 1292 + test_expect_success 'bundles with newline in URI are rejected' ' 1293 + test_when_finished "rm -rf busted repo" && 1294 + git clone --bundle-uri="$HTTPD_URL/bogus\nget $HTTPD_URL/bogus $HOME/busted" "$HTTPD_URL/smart/fetch.git" repo 2>err && 1295 + test_grep "error: bundle-uri: URI is malformed: " err && 1296 + test_path_is_missing "$HOME/busted" 1297 + ' 1298 + 1299 + test_expect_success 'bundles with newline in target path are rejected' ' 1300 + git clone --bundle-uri="$HTTPD_URL/bogus" "$HTTPD_URL/smart/fetch.git" "$(printf "escape\nget $HTTPD_URL/bogus .")" 2>err && 1301 + test_grep "error: bundle-uri: filename is malformed: " err && 1302 + test_path_is_missing escape 1303 + ' 1304 + 1282 1305 # Do not add tests here unless they use the HTTP server, as they will 1283 1306 # not run unless the HTTP dependencies exist. 1284 1307
+33
t/t7450-bad-git-dotfiles.sh
··· 372 372 test_path_is_missing nested_checkout/thing2/.git 373 373 ' 374 374 375 + test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' ' 376 + test_when_finished "rm -rf sub repo bad-clone" && 377 + 378 + git init sub && 379 + write_script sub/post-checkout <<-\EOF && 380 + touch "$PWD/foo" 381 + EOF 382 + git -C sub add post-checkout && 383 + git -C sub commit -m hook && 384 + 385 + git init repo && 386 + git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub && 387 + git -C repo mv sub $(printf "sub\r") && 388 + 389 + # Ensure config values containing CR are wrapped in quotes. 390 + git config unset -f repo/.gitmodules submodule.sub.path && 391 + printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules && 392 + 393 + git config unset -f repo/.git/modules/sub/config core.worktree && 394 + { 395 + printf "[core]\n" && 396 + printf "\tworktree = \"../../../sub\r\"\n" 397 + } >>repo/.git/modules/sub/config && 398 + 399 + ln -s .git/modules/sub/hooks repo/sub && 400 + git -C repo add -A && 401 + git -C repo commit -m submodule && 402 + 403 + git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone && 404 + ! test -f "$PWD/foo" && 405 + test -f $(printf "bad-clone/sub\r/post-checkout") 406 + ' 407 + 375 408 test_done