@@ -77,97 +77,126 @@ proc is_Cygwin {} {
7777
7878# #####################################################################
7979# #
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+ }
8195
8296set _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
85112
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 {}
96124 } else {
97- set _search_path [ split $env(PATH) :]
98- set _search_exe {}
125+ set suffix $_search_exe
99126 }
100- }
101-
102- if {[ is_Windows] && [ lsearch -exact $args -script] >= 0} {
103- set suffix {}
104- } else {
105- set suffix $_search_exe
106- }
107127
108- foreach p $_search_path {
109- set p [ file join $p $what$suffix ]
110- if {[ file exists $p ] } {
111- return [ file normalize $p ]
128+ foreach p $_search_path {
129+ set p [ file join $p $what$suffix ]
130+ if {[ file exists $p ] } {
131+ return [ file normalize $p ]
132+ }
112133 }
134+ return {}
113135 }
114- return {}
115- }
116136
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"
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+ }
148+
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+ }
125155 }
126- lset command_line $i $fullpath
127156 }
157+ return $command_line
158+ }
159+
160+ # Override `exec` to avoid unsafe PATH lookup
128161
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 " |" } {
162+ rename exec real_exec
163+
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 " --" } {
132169 incr i
133170 break
134171 }
172+ if {[ string range $arg 0 0] ne " -" } {
173+ break
174+ }
135175 }
176+ set args [ sanitize_command_line $args $i ]
177+ uplevel 1 real_exec $args
136178 }
137- return $command_line
138- }
139179
140- # Override `exec ` to avoid unsafe PATH lookup
180+ # Override `open ` to avoid unsafe PATH lookup
141181
142- rename exec real_exec
182+ rename open real_open
143183
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]"
154189 }
190+ uplevel 1 real_open $args
155191 }
156- set args [ sanitize_command_line $args $i ]
157- uplevel 1 real_exec $args
158- }
159192
160- # Override `open` to avoid unsafe PATH lookup
161-
162- rename open real_open
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.
163196
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]"
197+ proc _which {what args} {
198+ return [ lindex [auto_execok $what ] 0]
169199 }
170- uplevel 1 real_open $args
171200}
172201
173202######################################################################
@@ -304,15 +333,37 @@ if {$_trace >= 0} {
304333# branches).
305334set _last_merged_branch {}
306335
307- 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- }
336+ # for testing, allow unconfigured _shellpath
337+ if {[ string match @@* $_shellpath ] } {
338+ if {[ info exists env(SHELL)] } {
339+ set _shellpath $env(SHELL)
340+ } else {
341+ set _shellpath /bin/sh
315342 }
343+ }
344+
345+ if {[ is_Windows] } {
346+ set _shellpath [ exec cygpath -m $_shellpath ]
347+ }
348+
349+ if {![ file executable $_shellpath ] || \
350+ !([ file pathtype $_shellpath ] eq {absolute})} {
351+ set errmsg " The defined shell ('$_shellpath ') is not usable, \
352+ it must be an absolute path to an executable."
353+ puts stderr $errmsg
354+
355+ catch {wm withdraw .}
356+ tk_messageBox \
357+ -icon error \
358+ -type ok \
359+ -title " git-gui: configuration error" \
360+ -message $errmsg
361+ exit 1
362+ }
363+
364+
365+ proc shellpath {} {
366+ global _shellpath
316367 return $_shellpath
317368}
318369
@@ -524,32 +575,13 @@ proc _git_cmd {name} {
524575 return $v
525576}
526577
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.
578+ # Run a shell command connected via pipes on stdout.
538579# 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.
580+ # contain a command with arguments. We presume this
581+ # to be a shellscript that the configured shell (/bin/sh by default) knows
582+ # how to run.
541583proc 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- }
584+ set run [list [shellpath] -c " $cmd \"\$ 0\" " $path ]
553585 return [open |$run r]
554586}
555587
@@ -2760,17 +2792,16 @@ if {![is_bare]} {
27602792
27612793if {[is_Windows]} {
27622794 # 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 &]
2795+ set _git_bash [exec cygpath -m /git-bash.exe]
2796+ if {[file executable $_git_bash ]} {
2797+ set _bash_cmdline [list " Git Bash" $_git_bash &]
27682798 } else {
2769- set cmdLine [list " Git Bash" bash --login -l &]
2799+ set _bash_cmdline [list " Git Bash" bash --login -l &]
27702800 }
27712801 .mbar.repository add command \
27722802 -label [mc " Git Bash" ] \
2773- -command {eval exec [auto_execok start] $cmdLine }
2803+ -command {eval exec [list [_which cmd] /c start] $_bash_cmdline }
2804+ unset _git_bash
27742805}
27752806
27762807if {[is_Windows] || ![is_bare]} {
0 commit comments