summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch2500
-rw-r--r--meta/recipes-devtools/git/git_2.35.7.bb1
2 files changed, 2501 insertions, 0 deletions
diff --git a/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch b/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch
new file mode 100644
index 0000000000..e08bf41b3c
--- /dev/null
+++ b/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch
@@ -0,0 +1,2500 @@
1From: d61cfed2c23705fbeb9c0d08f59e75ee08738950 Merge: 664d4fa692 311d9ada3a
2Author: Taylor Blau <me@ttaylorr.com>
3Date: Fri May 23 17:17:06 2025 -0400
4
5 Merge branch 'js/gitk-git-gui-harden-exec-open' into maint-2.43
6
7 This merges in fixes for CVE-2025-27614, CVE-2025-27613, CVE-2025-46334,
8 and CVE-2025-46835 targeting Gitk and Git GUI.
9
10 * js/gitk-git-gui-harden-exec-open: (41 commits)
11 git-gui: sanitize 'exec' arguments: convert new 'cygpath' calls
12 git-gui: do not mistake command arguments as redirection operators
13 git-gui: introduce function git_redir for git calls with redirections
14 git-gui: pass redirections as separate argument to git_read
15 git-gui: pass redirections as separate argument to _open_stdout_stderr
16 git-gui: convert git_read*, git_write to be non-variadic
17 git-gui: override exec and open only on Windows
18 gitk: sanitize 'open' arguments: revisit recently updated 'open' calls
19 git-gui: use git_read in githook_read
20 git-gui: sanitize $PATH on all platforms
21 git-gui: break out a separate function git_read_nice
22 git-gui: assure PATH has only absolute elements.
23 git-gui: remove option --stderr from git_read
24 git-gui: cleanup git-bash menu item
25 git-gui: sanitize 'exec' arguments: background
26 git-gui: avoid auto_execok in do_windows_shortcut
27 git-gui: sanitize 'exec' arguments: simple cases
28 git-gui: avoid auto_execok for git-bash menu item
29 git-gui: treat file names beginning with "|" as relative paths
30 git-gui: remove unused proc is_shellscript
31 git-gui: remove git config --list handling for git < 1.5.3
32 git-gui: remove special treatment of Windows from open_cmd_pipe
33 git-gui: remove HEAD detachment implementation for git < 1.5.3
34 git-gui: use only the configured shell
35 git-gui: remove Tcl 8.4 workaround on 2>@1 redirection
36 git-gui: make _shellpath usable on startup
37 git-gui: use [is_Windows], not bad _shellpath
38 git-gui: _which, only add .exe suffix if not present
39 gitk: encode arguments correctly with "open"
40 gitk: sanitize 'open' arguments: command pipeline
41 gitk: collect construction of blameargs into a single conditional
42 gitk: sanitize 'open' arguments: simple commands, readable and writable
43 gitk: sanitize 'open' arguments: simple commands with redirections
44 gitk: sanitize 'open' arguments: simple commands
45 gitk: sanitize 'exec' arguments: redirect to process
46 gitk: sanitize 'exec' arguments: redirections and background
47 gitk: sanitize 'exec' arguments: redirections
48 gitk: sanitize 'exec' arguments: 'eval exec'
49 gitk: sanitize 'exec' arguments: simple cases
50 gitk: have callers of diffcmd supply pipe symbol when necessary
51 gitk: treat file names beginning with "|" as relative paths
52 ...
53
54 Signed-off-by: Taylor Blau <me@ttaylorr.com>
55
56Upstream-Status: Backport from [https://github.com/git/git/commit/d61cfed2c23705fbeb9c0d08f59e75ee08738950]
57CVE: CVE-2025-27614, CVE-2025-27613, CVE-2025-46334, CVE-2025-46835
58Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
59---
60 git-gui/git-gui.sh | 622 +++++++++++++++++----------
61 git-gui/lib/blame.tcl | 12 +-
62 git-gui/lib/branch.tcl | 6 +-
63 git-gui/lib/browser.tcl | 2 +-
64 git-gui/lib/checkout_op.tcl | 25 +-
65 git-gui/lib/choose_repository.tcl | 23 +-
66 git-gui/lib/choose_rev.tcl | 8 +-
67 git-gui/lib/commit.tcl | 14 +-
68 git-gui/lib/console.tcl | 5 +-
69 git-gui/lib/database.tcl | 2 +-
70 git-gui/lib/diff.tcl | 12 +-
71 git-gui/lib/index.tcl | 8 +-
72 git-gui/lib/merge.tcl | 6 +-
73 git-gui/lib/mergetool.tcl | 8 +-
74 git-gui/lib/remote.tcl | 8 +-
75 git-gui/lib/remote_branch_delete.tcl | 2 +-
76 git-gui/lib/shortcut.tcl | 16 +-
77 git-gui/lib/sshkey.tcl | 7 +-
78 git-gui/lib/tools.tcl | 7 +-
79 git-gui/lib/win32.tcl | 9 +-
80 gitk-git/gitk | 298 ++++++++-----
81 21 files changed, 667 insertions(+), 433 deletions(-)
82
83diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
84index 201524c..2f38291 100755
85--- a/git-gui/git-gui.sh
86+++ b/git-gui/git-gui.sh
87@@ -24,7 +24,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
88 GNU General Public License for more details.
89
90 You should have received a copy of the GNU General Public License
91-along with this program; if not, see <http://www.gnu.org/licenses/>.}]
92+along with this program; if not, see <https://www.gnu.org/licenses/>.}]
93
94 ######################################################################
95 ##
96@@ -44,6 +44,211 @@ if {[catch {package require Tcl 8.5} err]
97
98 catch {rename send {}} ; # What an evil concept...
99
100+######################################################################
101+##
102+## Enabling platform-specific code paths
103+
104+proc is_MacOSX {} {
105+ if {[tk windowingsystem] eq {aqua}} {
106+ return 1
107+ }
108+ return 0
109+}
110+
111+proc is_Windows {} {
112+ if {$::tcl_platform(platform) eq {windows}} {
113+ return 1
114+ }
115+ return 0
116+}
117+
118+set _iscygwin {}
119+proc is_Cygwin {} {
120+ global _iscygwin
121+ if {$_iscygwin eq {}} {
122+ if {[string match "CYGWIN_*" $::tcl_platform(os)]} {
123+ set _iscygwin 1
124+ } else {
125+ set _iscygwin 0
126+ }
127+ }
128+ return $_iscygwin
129+}
130+
131+######################################################################
132+##
133+## PATH lookup. Sanitize $PATH, assure exec/open use only that
134+
135+if {[is_Windows]} {
136+ set _path_sep {;}
137+ set _search_exe .exe
138+} else {
139+ set _path_sep {:}
140+ set _search_exe {}
141+}
142+
143+if {[is_Windows]} {
144+ set gitguidir [file dirname [info script]]
145+ regsub -all ";" $gitguidir "\\;" gitguidir
146+ set env(PATH) "$gitguidir;$env(PATH)"
147+}
148+
149+set _search_path {}
150+set _path_seen [dict create]
151+foreach p [split $env(PATH) $_path_sep] {
152+ # Keep only absolute paths, getting rid of ., empty, etc.
153+ if {[file pathtype $p] ne {absolute}} {
154+ continue
155+ }
156+ # Keep only the first occurence of any duplicates.
157+ set norm_p [file normalize $p]
158+ if {[dict exists $_path_seen $norm_p]} {
159+ continue
160+ }
161+ dict set _path_seen $norm_p 1
162+ lappend _search_path $norm_p
163+}
164+unset _path_seen
165+
166+set env(PATH) [join $_search_path $_path_sep]
167+
168+if {[is_Windows]} {
169+ proc _which {what args} {
170+ global _search_exe _search_path
171+
172+ if {[lsearch -exact $args -script] >= 0} {
173+ set suffix {}
174+ } elseif {[string match *$_search_exe [string tolower $what]]} {
175+ # The search string already has the file extension
176+ set suffix {}
177+ } else {
178+ set suffix $_search_exe
179+ }
180+
181+ foreach p $_search_path {
182+ set p [file join $p $what$suffix]
183+ if {[file exists $p]} {
184+ return [file normalize $p]
185+ }
186+ }
187+ return {}
188+ }
189+
190+ proc sanitize_command_line {command_line from_index} {
191+ set i $from_index
192+ while {$i < [llength $command_line]} {
193+ set cmd [lindex $command_line $i]
194+ if {[llength [file split $cmd]] < 2} {
195+ set fullpath [_which $cmd]
196+ if {$fullpath eq ""} {
197+ throw {NOT-FOUND} "$cmd not found in PATH"
198+ }
199+ lset command_line $i $fullpath
200+ }
201+
202+ # handle piped commands, e.g. `exec A | B`
203+ for {incr i} {$i < [llength $command_line]} {incr i} {
204+ if {[lindex $command_line $i] eq "|"} {
205+ incr i
206+ break
207+ }
208+ }
209+ }
210+ return $command_line
211+ }
212+
213+ # Override `exec` to avoid unsafe PATH lookup
214+
215+ rename exec real_exec
216+
217+ proc exec {args} {
218+ # skip options
219+ for {set i 0} {$i < [llength $args]} {incr i} {
220+ set arg [lindex $args $i]
221+ if {$arg eq "--"} {
222+ incr i
223+ break
224+ }
225+ if {[string range $arg 0 0] ne "-"} {
226+ break
227+ }
228+ }
229+ set args [sanitize_command_line $args $i]
230+ uplevel 1 real_exec $args
231+ }
232+
233+ # Override `open` to avoid unsafe PATH lookup
234+
235+ rename open real_open
236+
237+ proc open {args} {
238+ set arg0 [lindex $args 0]
239+ if {[string range $arg0 0 0] eq "|"} {
240+ set command_line [string trim [string range $arg0 1 end]]
241+ lset args 0 "| [sanitize_command_line $command_line 0]"
242+ }
243+ uplevel 1 real_open $args
244+ }
245+
246+} else {
247+ # On non-Windows platforms, auto_execok, exec, and open are safe, and will
248+ # use the sanitized search path. But, we need _which for these.
249+
250+ proc _which {what args} {
251+ return [lindex [auto_execok $what] 0]
252+ }
253+}
254+
255+# Wrap exec/open to sanitize arguments
256+
257+# unsafe arguments begin with redirections or the pipe or background operators
258+proc is_arg_unsafe {arg} {
259+ regexp {^([<|>&]|2>)} $arg
260+}
261+
262+proc make_arg_safe {arg} {
263+ if {[is_arg_unsafe $arg]} {
264+ set arg [file join . $arg]
265+ }
266+ return $arg
267+}
268+
269+proc make_arglist_safe {arglist} {
270+ set res {}
271+ foreach arg $arglist {
272+ lappend res [make_arg_safe $arg]
273+ }
274+ return $res
275+}
276+
277+# executes one command
278+# no redirections or pipelines are possible
279+# cmd is a list that specifies the command and its arguments
280+# calls `exec` and returns its value
281+proc safe_exec {cmd} {
282+ eval exec [make_arglist_safe $cmd]
283+}
284+
285+# executes one command in the background
286+# no redirections or pipelines are possible
287+# cmd is a list that specifies the command and its arguments
288+# calls `exec` and returns its value
289+proc safe_exec_bg {cmd} {
290+ eval exec [make_arglist_safe $cmd] &
291+}
292+
293+proc safe_open_file {filename flags} {
294+ # a file name starting with "|" would attempt to run a process
295+ # but such a file name must be treated as a relative path
296+ # hide the "|" behind "./"
297+ if {[string index $filename 0] eq "|"} {
298+ set filename [file join . $filename]
299+ }
300+ open $filename $flags
301+}
302+
303+# End exec/open wrappers
304+
305 ######################################################################
306 ##
307 ## locate our library
308@@ -144,14 +349,64 @@ unset oguimsg
309
310 if {[tk windowingsystem] eq "aqua"} {
311 catch {
312- exec osascript -e [format {
313+ safe_exec [list osascript -e [format {
314 tell application "System Events"
315 set frontmost of processes whose unix id is %d to true
316 end tell
317- } [pid]]
318+ } [pid]]]
319 }
320 }
321
322+# Wrap exec/open to sanitize arguments
323+
324+# unsafe arguments begin with redirections or the pipe or background operators
325+proc is_arg_unsafe {arg} {
326+ regexp {^([<|>&]|2>)} $arg
327+}
328+
329+proc make_arg_safe {arg} {
330+ if {[is_arg_unsafe $arg]} {
331+ set arg [file join . $arg]
332+ }
333+ return $arg
334+}
335+
336+proc make_arglist_safe {arglist} {
337+ set res {}
338+ foreach arg $arglist {
339+ lappend res [make_arg_safe $arg]
340+ }
341+ return $res
342+}
343+
344+# executes one command
345+# no redirections or pipelines are possible
346+# cmd is a list that specifies the command and its arguments
347+# calls `exec` and returns its value
348+proc safe_exec {cmd} {
349+ eval exec [make_arglist_safe $cmd]
350+}
351+
352+# executes one command in the background
353+# no redirections or pipelines are possible
354+# cmd is a list that specifies the command and its arguments
355+# calls `exec` and returns its value
356+proc safe_exec_bg {cmd} {
357+ eval exec [make_arglist_safe $cmd] &
358+}
359+
360+proc safe_open_file {filename flags} {
361+ # a file name starting with "|" would attempt to run a process
362+ # but such a file name must be treated as a relative path
363+ # hide the "|" behind "./"
364+ if {[string index $filename 0] eq "|"} {
365+ set filename [file join . $filename]
366+ }
367+ open $filename $flags
368+}
369+
370+# End exec/open wrappers
371+
372 ######################################################################
373 ##
374 ## read only globals
375@@ -180,15 +435,37 @@ if {$_trace >= 0} {
376 # branches).
377 set _last_merged_branch {}
378
379-proc shellpath {} {
380- global _shellpath env
381- if {[string match @@* $_shellpath]} {
382- if {[info exists env(SHELL)]} {
383- return $env(SHELL)
384- } else {
385- return /bin/sh
386- }
387+# for testing, allow unconfigured _shellpath
388+if {[string match @@* $_shellpath]} {
389+ if {[info exists env(SHELL)]} {
390+ set _shellpath $env(SHELL)
391+ } else {
392+ set _shellpath /bin/sh
393 }
394+}
395+
396+if {[is_Windows]} {
397+ set _shellpath [safe_exec [list cygpath -m $_shellpath]]
398+}
399+
400+if {![file executable $_shellpath] || \
401+ !([file pathtype $_shellpath] eq {absolute})} {
402+ set errmsg "The defined shell ('$_shellpath') is not usable, \
403+ it must be an absolute path to an executable."
404+ puts stderr $errmsg
405+
406+ catch {wm withdraw .}
407+ tk_messageBox \
408+ -icon error \
409+ -type ok \
410+ -title "git-gui: configuration error" \
411+ -message $errmsg
412+ exit 1
413+}
414+
415+
416+proc shellpath {} {
417+ global _shellpath
418 return $_shellpath
419 }
420
421@@ -252,40 +529,6 @@ proc reponame {} {
422 return $::_reponame
423 }
424
425-proc is_MacOSX {} {
426- if {[tk windowingsystem] eq {aqua}} {
427- return 1
428- }
429- return 0
430-}
431-
432-proc is_Windows {} {
433- if {$::tcl_platform(platform) eq {windows}} {
434- return 1
435- }
436- return 0
437-}
438-
439-proc is_Cygwin {} {
440- global _iscygwin
441- if {$_iscygwin eq {}} {
442- if {$::tcl_platform(platform) eq {windows}} {
443- if {[catch {set p [exec cygpath --windir]} err]} {
444- set _iscygwin 0
445- } else {
446- set _iscygwin 1
447- # Handle MSys2 which is only cygwin when MSYSTEM is MSYS.
448- if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} {
449- set _iscygwin 0
450- }
451- }
452- } else {
453- set _iscygwin 0
454- }
455- }
456- return $_iscygwin
457-}
458-
459 proc is_enabled {option} {
460 global enabled_options
461 if {[catch {set on $enabled_options($option)}]} {return 0}
462@@ -418,7 +661,7 @@ proc _git_cmd {name} {
463 # Tcl on Windows doesn't know it.
464 #
465 set p [gitexec git-$name]
466- set f [open $p r]
467+ set f [safe_open_file $p r]
468 set s [gets $f]
469 close $f
470
471@@ -473,6 +716,9 @@ proc _which {what args} {
472
473 if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
474 set suffix {}
475+ } elseif {[is_Windows] && [string match *$_search_exe [string tolower $what]]} {
476+ # The search string already has the file extension
477+ set suffix {}
478 } else {
479 set suffix $_search_exe
480 }
481@@ -486,32 +732,14 @@ proc _which {what args} {
482 return {}
483 }
484
485-# Test a file for a hashbang to identify executable scripts on Windows.
486-proc is_shellscript {filename} {
487- if {![file exists $filename]} {return 0}
488- set f [open $filename r]
489- fconfigure $f -encoding binary
490- set magic [read $f 2]
491- close $f
492- return [expr {$magic eq "#!"}]
493-}
494-
495-# Run a command connected via pipes on stdout.
496+# Run a shell command connected via pipes on stdout.
497 # This is for use with textconv filters and uses sh -c "..." to allow it to
498-# contain a command with arguments. On windows we must check for shell
499-# scripts specifically otherwise just call the filter command.
500+# contain a command with arguments. We presume this
501+# to be a shellscript that the configured shell (/bin/sh by default) knows
502+# how to run.
503 proc open_cmd_pipe {cmd path} {
504- global env
505- if {![file executable [shellpath]]} {
506- set exe [auto_execok [lindex $cmd 0]]
507- if {[is_shellscript [lindex $exe 0]]} {
508- set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
509- } else {
510- set run [concat $exe [lrange $cmd 1 end] $path]
511- }
512- } else {
513- set run [list [shellpath] -c "$cmd \"\$0\"" $path]
514- }
515+ set run [list [shellpath] -c "$cmd \"\$0\"" $path]
516+ set run [make_arglist_safe $run]
517 return [open |$run r]
518 }
519
520@@ -521,7 +749,7 @@ proc _lappend_nice {cmd_var} {
521
522 if {![info exists _nice]} {
523 set _nice [_which nice]
524- if {[catch {exec $_nice git version}]} {
525+ if {[catch {safe_exec [list $_nice git version]}]} {
526 set _nice {}
527 } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
528 set _nice {}
529@@ -533,7 +761,11 @@ proc _lappend_nice {cmd_var} {
530 }
531
532 proc git {args} {
533- set fd [eval [list git_read] $args]
534+ git_redir $args {}
535+}
536+
537+proc git_redir {cmd redir} {
538+ set fd [git_read $cmd $redir]
539 fconfigure $fd -translation binary -encoding utf-8
540 set result [string trimright [read $fd] "\n"]
541 close $fd
542@@ -543,111 +775,47 @@ proc git {args} {
543 return $result
544 }
545
546-proc _open_stdout_stderr {cmd} {
547- _trace_exec $cmd
548+proc safe_open_command {cmd {redir {}}} {
549+ set cmd [make_arglist_safe $cmd]
550+ _trace_exec [concat $cmd $redir]
551 if {[catch {
552- set fd [open [concat [list | ] $cmd] r]
553- } err]} {
554- if { [lindex $cmd end] eq {2>@1}
555- && $err eq {can not find channel named "1"}
556- } {
557- # Older versions of Tcl 8.4 don't have this 2>@1 IO
558- # redirect operator. Fallback to |& cat for those.
559- # The command was not actually started, so its safe
560- # to try to start it a second time.
561- #
562- set fd [open [concat \
563- [list | ] \
564- [lrange $cmd 0 end-1] \
565- [list |& cat] \
566- ] r]
567- } else {
568- error $err
569- }
570+ set fd [open [concat [list | ] $cmd $redir] r]
571+ } err]} {
572+ error $err
573 }
574 fconfigure $fd -eofchar {}
575 return $fd
576 }
577
578-proc git_read {args} {
579- set opt [list]
580-
581- while {1} {
582- switch -- [lindex $args 0] {
583- --nice {
584- _lappend_nice opt
585- }
586-
587- --stderr {
588- lappend args 2>@1
589- }
590+proc git_read {cmd {redir {}}} {
591+ set cmdp [_git_cmd [lindex $cmd 0]]
592+ set cmd [lrange $cmd 1 end]
593
594- default {
595- break
596- }
597-
598- }
599-
600- set args [lrange $args 1 end]
601- }
602-
603- set cmdp [_git_cmd [lindex $args 0]]
604- set args [lrange $args 1 end]
605-
606- return [_open_stdout_stderr [concat $opt $cmdp $args]]
607+ return [safe_open_command [concat $cmdp $cmd] $redir]
608 }
609
610-proc git_write {args} {
611+proc git_read_nice {cmd} {
612 set opt [list]
613
614- while {1} {
615- switch -- [lindex $args 0] {
616- --nice {
617- _lappend_nice opt
618- }
619+ _lappend_nice opt
620
621- default {
622- break
623- }
624+ set cmdp [_git_cmd [lindex $cmd 0]]
625+ set cmd [lrange $cmd 1 end]
626
627- }
628-
629- set args [lrange $args 1 end]
630- }
631+ return [safe_open_command [concat $opt $cmdp $cmd]]
632+}
633
634- set cmdp [_git_cmd [lindex $args 0]]
635- set args [lrange $args 1 end]
636+proc git_write {cmd} {
637+ set cmd [make_arglist_safe $cmd]
638+ set cmdp [_git_cmd [lindex $cmd 0]]
639+ set cmd [lrange $cmd 1 end]
640
641- _trace_exec [concat $opt $cmdp $args]
642- return [open [concat [list | ] $opt $cmdp $args] w]
643+ _trace_exec [concat $cmdp $cmd]
644+ return [open [concat [list | ] $cmdp $cmd] w]
645 }
646
647 proc githook_read {hook_name args} {
648- set pchook [gitdir hooks $hook_name]
649- lappend args 2>@1
650-
651- # On Windows [file executable] might lie so we need to ask
652- # the shell if the hook is executable. Yes that's annoying.
653- #
654- if {[is_Windows]} {
655- upvar #0 _sh interp
656- if {![info exists interp]} {
657- set interp [_which sh]
658- }
659- if {$interp eq {}} {
660- error "hook execution requires sh (not in PATH)"
661- }
662-
663- set scr {if test -x "$1";then exec "$@";fi}
664- set sh_c [list $interp -c $scr $interp $pchook]
665- return [_open_stdout_stderr [concat $sh_c $args]]
666- }
667-
668- if {[file executable $pchook]} {
669- return [_open_stdout_stderr [concat [list $pchook] $args]]
670- }
671-
672- return {}
673+ git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1]
674 }
675
676 proc kill_file_process {fd} {
677@@ -655,9 +823,9 @@ proc kill_file_process {fd} {
678
679 catch {
680 if {[is_Windows]} {
681- exec taskkill /pid $process
682+ safe_exec [list taskkill /pid $process]
683 } else {
684- exec kill $process
685+ safe_exec [list kill $process]
686 }
687 }
688 }
689@@ -683,7 +851,7 @@ proc sq {value} {
690 proc load_current_branch {} {
691 global current_branch is_detached
692
693- set fd [open [gitdir HEAD] r]
694+ set fd [safe_open_file [gitdir HEAD] r]
695 fconfigure $fd -translation binary -encoding utf-8
696 if {[gets $fd ref] < 1} {
697 set ref {}
698@@ -1045,7 +1213,7 @@ You are using [git-version]:
699 ## configure our library
700
701 set idx [file join $oguilib tclIndex]
702-if {[catch {set fd [open $idx r]} err]} {
703+if {[catch {set fd [safe_open_file $idx r]} err]} {
704 catch {wm withdraw .}
705 tk_messageBox \
706 -icon error \
707@@ -1083,53 +1251,30 @@ unset -nocomplain idx fd
708 ##
709 ## config file parsing
710
711-git-version proc _parse_config {arr_name args} {
712- >= 1.5.3 {
713- upvar $arr_name arr
714- array unset arr
715- set buf {}
716- catch {
717- set fd_rc [eval \
718- [list git_read config] \
719- $args \
720- [list --null --list]]
721- fconfigure $fd_rc -translation binary -encoding utf-8
722- set buf [read $fd_rc]
723- close $fd_rc
724- }
725- foreach line [split $buf "\0"] {
726- if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
727- if {[is_many_config $name]} {
728- lappend arr($name) $value
729- } else {
730- set arr($name) $value
731- }
732- } elseif {[regexp {^([^\n]+)$} $line line name]} {
733- # no value given, but interpreting them as
734- # boolean will be handled as true
735- set arr($name) {}
736- }
737- }
738- }
739- default {
740- upvar $arr_name arr
741- array unset arr
742- catch {
743- set fd_rc [eval [list git_read config --list] $args]
744- while {[gets $fd_rc line] >= 0} {
745- if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
746- if {[is_many_config $name]} {
747- lappend arr($name) $value
748- } else {
749- set arr($name) $value
750- }
751- } elseif {[regexp {^([^=]+)$} $line line name]} {
752- # no value given, but interpreting them as
753- # boolean will be handled as true
754- set arr($name) {}
755- }
756+proc _parse_config {arr_name args} {
757+ upvar $arr_name arr
758+ array unset arr
759+ set buf {}
760+ catch {
761+ set fd_rc [git_read \
762+ [concat config \
763+ $args \
764+ --null --list]]
765+ fconfigure $fd_rc -translation binary -encoding utf-8
766+ set buf [read $fd_rc]
767+ close $fd_rc
768+ }
769+ foreach line [split $buf "\0"] {
770+ if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
771+ if {[is_many_config $name]} {
772+ lappend arr($name) $value
773+ } else {
774+ set arr($name) $value
775 }
776- close $fd_rc
777+ } elseif {[regexp {^([^\n]+)$} $line line name]} {
778+ # no value given, but interpreting them as
779+ # boolean will be handled as true
780+ set arr($name) {}
781 }
782 }
783 }
784@@ -1412,7 +1557,7 @@ proc repository_state {ctvar hdvar mhvar} {
785 set merge_head [gitdir MERGE_HEAD]
786 if {[file exists $merge_head]} {
787 set ct merge
788- set fd_mh [open $merge_head r]
789+ set fd_mh [safe_open_file $merge_head r]
790 while {[gets $fd_mh line] >= 0} {
791 lappend mh $line
792 }
793@@ -1431,7 +1576,7 @@ proc PARENT {} {
794 return $p
795 }
796 if {$empty_tree eq {}} {
797- set empty_tree [git mktree << {}]
798+ set empty_tree [git_redir [list mktree] [list << {}]]
799 }
800 return $empty_tree
801 }
802@@ -1490,12 +1635,12 @@ proc rescan {after {honor_trustmtime 1}} {
803 } else {
804 set rescan_active 1
805 ui_status [mc "Refreshing file status..."]
806- set fd_rf [git_read update-index \
807+ set fd_rf [git_read [list update-index \
808 -q \
809 --unmerged \
810 --ignore-missing \
811 --refresh \
812- ]
813+ ]]
814 fconfigure $fd_rf -blocking 0 -translation binary
815 fileevent $fd_rf readable \
816 [list rescan_stage2 $fd_rf $after]
817@@ -1551,11 +1696,11 @@ proc rescan_stage2 {fd after} {
818 set rescan_active 2
819 ui_status [mc "Scanning for modified files ..."]
820 if {[git-version >= "1.7.2"]} {
821- set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]]
822+ set fd_di [git_read [list diff-index --cached --ignore-submodules=dirty -z [PARENT]]]
823 } else {
824- set fd_di [git_read diff-index --cached -z [PARENT]]
825+ set fd_di [git_read [list diff-index --cached -z [PARENT]]]
826 }
827- set fd_df [git_read diff-files -z]
828+ set fd_df [git_read [list diff-files -z]]
829
830 fconfigure $fd_di -blocking 0 -translation binary -encoding binary
831 fconfigure $fd_df -blocking 0 -translation binary -encoding binary
832@@ -1564,7 +1709,7 @@ proc rescan_stage2 {fd after} {
833 fileevent $fd_df readable [list read_diff_files $fd_df $after]
834
835 if {[is_config_true gui.displayuntracked]} {
836- set fd_lo [eval git_read ls-files --others -z $ls_others]
837+ set fd_lo [git_read [concat ls-files --others -z $ls_others]]
838 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
839 fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
840 incr rescan_active
841@@ -1576,7 +1721,7 @@ proc load_message {file {encoding {}}} {
842
843 set f [gitdir $file]
844 if {[file isfile $f]} {
845- if {[catch {set fd [open $f r]}]} {
846+ if {[catch {set fd [safe_open_file $f r]}]} {
847 return 0
848 }
849 fconfigure $fd -eofchar {}
850@@ -1600,23 +1745,23 @@ proc run_prepare_commit_msg_hook {} {
851 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
852 # empty file but existent file.
853
854- set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
855+ set fd_pcm [safe_open_file [gitdir PREPARE_COMMIT_MSG] a]
856
857 if {[file isfile [gitdir MERGE_MSG]]} {
858 set pcm_source "merge"
859- set fd_mm [open [gitdir MERGE_MSG] r]
860+ set fd_mm [safe_open_file [gitdir MERGE_MSG] r]
861 fconfigure $fd_mm -encoding utf-8
862 puts -nonewline $fd_pcm [read $fd_mm]
863 close $fd_mm
864 } elseif {[file isfile [gitdir SQUASH_MSG]]} {
865 set pcm_source "squash"
866- set fd_sm [open [gitdir SQUASH_MSG] r]
867+ set fd_sm [safe_open_file [gitdir SQUASH_MSG] r]
868 fconfigure $fd_sm -encoding utf-8
869 puts -nonewline $fd_pcm [read $fd_sm]
870 close $fd_sm
871 } elseif {[file isfile [get_config commit.template]]} {
872 set pcm_source "template"
873- set fd_sm [open [get_config commit.template] r]
874+ set fd_sm [safe_open_file [get_config commit.template] r]
875 fconfigure $fd_sm -encoding utf-8
876 puts -nonewline $fd_pcm [read $fd_sm]
877 close $fd_sm
878@@ -2206,7 +2351,7 @@ proc do_gitk {revs {is_submodule false}} {
879 unset env(GIT_DIR)
880 unset env(GIT_WORK_TREE)
881 }
882- eval exec $cmd $revs "--" "--" &
883+ safe_exec_bg [concat $cmd $revs "--" "--"]
884
885 set env(GIT_DIR) $_gitdir
886 set env(GIT_WORK_TREE) $_gitworktree
887@@ -2243,7 +2388,7 @@ proc do_git_gui {} {
888 set pwd [pwd]
889 cd $current_diff_path
890
891- eval exec $exe gui &
892+ safe_exec_bg [concat $exe gui]
893
894 set env(GIT_DIR) $_gitdir
895 set env(GIT_WORK_TREE) $_gitworktree
896@@ -2272,16 +2417,18 @@ proc get_explorer {} {
897
898 proc do_explore {} {
899 global _gitworktree
900- set explorer [get_explorer]
901- eval exec $explorer [list [file nativename $_gitworktree]] &
902+ set cmd [get_explorer]
903+ lappend cmd [file nativename $_gitworktree]
904+ safe_exec_bg $cmd
905 }
906
907 # Open file relative to the working tree by the default associated app.
908 proc do_file_open {file} {
909 global _gitworktree
910- set explorer [get_explorer]
911+ set cmd [get_explorer]
912 set full_file_path [file join $_gitworktree $file]
913- exec $explorer [file nativename $full_file_path] &
914+ lappend cmd [file nativename $full_file_path]
915+ safe_exec_bg $cmd
916 }
917
918 set is_quitting 0
919@@ -2315,7 +2462,7 @@ proc do_quit {{rc {1}}} {
920 if {![string match amend* $commit_type]
921 && $msg ne {}} {
922 catch {
923- set fd [open $save w]
924+ set fd [safe_open_file $save w]
925 fconfigure $fd -encoding utf-8
926 puts -nonewline $fd $msg
927 close $fd
928@@ -2373,7 +2520,7 @@ proc do_quit {{rc {1}}} {
929 set ret_code $rc
930
931 # Briefly enable send again, working around Tk bug
932- # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
933+ # https://sourceforge.net/p/tktoolkit/bugs/2343/
934 tk appname [appname]
935
936 destroy .
937@@ -2759,17 +2906,16 @@ if {![is_bare]} {
938
939 if {[is_Windows]} {
940 # Use /git-bash.exe if available
941- set normalized [file normalize $::argv0]
942- regsub "/mingw../libexec/git-core/git-gui$" \
943- $normalized "/git-bash.exe" cmdLine
944- if {$cmdLine != $normalized && [file exists $cmdLine]} {
945- set cmdLine [list "Git Bash" $cmdLine &]
946+ set _git_bash [safe_exec [list cygpath -m /git-bash.exe]]
947+ if {[file executable $_git_bash]} {
948+ set _bash_cmdline [list "Git Bash" $_git_bash]
949 } else {
950- set cmdLine [list "Git Bash" bash --login -l &]
951+ set _bash_cmdline [list "Git Bash" bash --login -l]
952 }
953 .mbar.repository add command \
954 -label [mc "Git Bash"] \
955- -command {eval exec [auto_execok start] $cmdLine}
956+ -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]}
957+ unset _git_bash
958 }
959
960 if {[is_Windows] || ![is_bare]} {
961@@ -4134,7 +4280,7 @@ if {[winfo exists $ui_comm]} {
962 }
963 } elseif {$m} {
964 catch {
965- set fd [open [gitdir GITGUI_BCK] w]
966+ set fd [safe_open_file [gitdir GITGUI_BCK] w]
967 fconfigure $fd -encoding utf-8
968 puts -nonewline $fd $msg
969 close $fd
970diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
971index 8441e10..d6fd8be 100644
972--- a/git-gui/lib/blame.tcl
973+++ b/git-gui/lib/blame.tcl
974@@ -481,14 +481,14 @@ method _load {jump} {
975 if {$do_textconv ne 0} {
976 set fd [open_cmd_pipe $textconv $path]
977 } else {
978- set fd [open $path r]
979+ set fd [safe_open_file $path r]
980 }
981 fconfigure $fd -eofchar {}
982 } else {
983 if {$do_textconv ne 0} {
984- set fd [git_read cat-file --textconv "$commit:$path"]
985+ set fd [git_read [list cat-file --textconv "$commit:$path"]]
986 } else {
987- set fd [git_read cat-file blob "$commit:$path"]
988+ set fd [git_read [list cat-file blob "$commit:$path"]]
989 }
990 }
991 fconfigure $fd \
992@@ -617,7 +617,7 @@ method _exec_blame {cur_w cur_d options cur_s} {
993 }
994
995 lappend options -- $path
996- set fd [eval git_read --nice blame $options]
997+ set fd [git_read_nice [concat blame $options]]
998 fconfigure $fd -blocking 0 -translation lf -encoding utf-8
999 fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
1000 set current_fd $fd
1001@@ -986,7 +986,7 @@ method _showcommit {cur_w lno} {
1002 if {[catch {set msg $header($cmit,message)}]} {
1003 set msg {}
1004 catch {
1005- set fd [git_read cat-file commit $cmit]
1006+ set fd [git_read [list cat-file commit $cmit]]
1007 fconfigure $fd -encoding binary -translation lf
1008 # By default commits are assumed to be in utf-8
1009 set enc utf-8
1010@@ -1134,7 +1134,7 @@ method _blameparent {} {
1011 } else {
1012 set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path]
1013 }
1014- if {[catch {set fd [eval git_read $diffcmd]} err]} {
1015+ if {[catch {set fd [git_read $diffcmd]} err]} {
1016 $status_operation stop [mc "Unable to display parent"]
1017 error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
1018 return
1019diff --git a/git-gui/lib/branch.tcl b/git-gui/lib/branch.tcl
1020index 8b0c485..39e0f2d 100644
1021--- a/git-gui/lib/branch.tcl
1022+++ b/git-gui/lib/branch.tcl
1023@@ -7,7 +7,7 @@ proc load_all_heads {} {
1024 set rh refs/heads
1025 set rh_len [expr {[string length $rh] + 1}]
1026 set all_heads [list]
1027- set fd [git_read for-each-ref --format=%(refname) $rh]
1028+ set fd [git_read [list for-each-ref --format=%(refname) $rh]]
1029 fconfigure $fd -translation binary -encoding utf-8
1030 while {[gets $fd line] > 0} {
1031 if {!$some_heads_tracking || ![is_tracking_branch $line]} {
1032@@ -21,10 +21,10 @@ proc load_all_heads {} {
1033
1034 proc load_all_tags {} {
1035 set all_tags [list]
1036- set fd [git_read for-each-ref \
1037+ set fd [git_read [list for-each-ref \
1038 --sort=-taggerdate \
1039 --format=%(refname) \
1040- refs/tags]
1041+ refs/tags]]
1042 fconfigure $fd -translation binary -encoding utf-8
1043 while {[gets $fd line] > 0} {
1044 if {![regsub ^refs/tags/ $line {} name]} continue
1045diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
1046index a982983..6fc8d4d 100644
1047--- a/git-gui/lib/browser.tcl
1048+++ b/git-gui/lib/browser.tcl
1049@@ -196,7 +196,7 @@ method _ls {tree_id {name {}}} {
1050 lappend browser_stack [list $tree_id $name]
1051 $w conf -state disabled
1052
1053- set fd [git_read ls-tree -z $tree_id]
1054+ set fd [git_read [list ls-tree -z $tree_id]]
1055 fconfigure $fd -blocking 0 -translation binary -encoding utf-8
1056 fileevent $fd readable [cb _read $fd]
1057 }
1058diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl
1059index 21ea768..87ed0b4 100644
1060--- a/git-gui/lib/checkout_op.tcl
1061+++ b/git-gui/lib/checkout_op.tcl
1062@@ -304,12 +304,12 @@ The rescan will be automatically started now.
1063 _readtree $this
1064 } else {
1065 ui_status [mc "Refreshing file status..."]
1066- set fd [git_read update-index \
1067+ set fd [git_read [list update-index \
1068 -q \
1069 --unmerged \
1070 --ignore-missing \
1071 --refresh \
1072- ]
1073+ ]]
1074 fconfigure $fd -blocking 0 -translation binary
1075 fileevent $fd readable [cb _refresh_wait $fd]
1076 }
1077@@ -345,14 +345,15 @@ method _readtree {} {
1078 [mc "Updating working directory to '%s'..." [_name $this]] \
1079 [mc "files checked out"]]
1080
1081- set fd [git_read --stderr read-tree \
1082+ set fd [git_read [list read-tree \
1083 -m \
1084 -u \
1085 -v \
1086 --exclude-per-directory=.gitignore \
1087 $HEAD \
1088 $new_hash \
1089- ]
1090+ ] \
1091+ [list 2>@1]]
1092 fconfigure $fd -blocking 0 -translation binary
1093 fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation]
1094 }
1095@@ -510,18 +511,8 @@ method _update_repo_state {} {
1096 delete_this
1097 }
1098
1099-git-version proc _detach_HEAD {log new} {
1100- >= 1.5.3 {
1101- git update-ref --no-deref -m $log HEAD $new
1102- }
1103- default {
1104- set p [gitdir HEAD]
1105- file delete $p
1106- set fd [open $p w]
1107- fconfigure $fd -translation lf -encoding utf-8
1108- puts $fd $new
1109- close $fd
1110- }
1111+proc _detach_HEAD {log new} {
1112+ git update-ref --no-deref -m $log HEAD $new
1113 }
1114
1115 method _confirm_reset {cur} {
1116@@ -582,7 +573,7 @@ method _confirm_reset {cur} {
1117 pack $w.buttons.cancel -side right -padx 5
1118 pack $w.buttons -side bottom -fill x -pady 10 -padx 10
1119
1120- set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
1121+ set fd [git_read [list rev-list --pretty=oneline $cur ^$new_hash]]
1122 while {[gets $fd line] > 0} {
1123 set abbr [string range $line 0 7]
1124 set subj [string range $line 41 end]
1125diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
1126index af1fee7..76224d9 100644
1127--- a/git-gui/lib/choose_repository.tcl
1128+++ b/git-gui/lib/choose_repository.tcl
1129@@ -662,8 +662,8 @@ method _do_clone2 {} {
1130 set pwd [pwd]
1131 if {[catch {
1132 file mkdir [gitdir objects info]
1133- set f_in [open [file join $objdir info alternates] r]
1134- set f_cp [open [gitdir objects info alternates] w]
1135+ set f_in [safe_open_file [file join $objdir info alternates] r]
1136+ set f_cp [safe_open_file [gitdir objects info alternates] w]
1137 fconfigure $f_in -translation binary -encoding binary
1138 fconfigure $f_cp -translation binary -encoding binary
1139 cd $objdir
1140@@ -752,7 +752,7 @@ method _do_clone2 {} {
1141 [cb _do_clone_tags]
1142 }
1143 shared {
1144- set fd [open [gitdir objects info alternates] w]
1145+ set fd [safe_open_file [gitdir objects info alternates] w]
1146 fconfigure $fd -translation binary
1147 puts $fd $objdir
1148 close $fd
1149@@ -785,8 +785,8 @@ method _copy_files {objdir tocopy} {
1150 }
1151 foreach p $tocopy {
1152 if {[catch {
1153- set f_in [open [file join $objdir $p] r]
1154- set f_cp [open [file join .git objects $p] w]
1155+ set f_in [safe_open_file [file join $objdir $p] r]
1156+ set f_cp [safe_open_file [file join .git objects $p] w]
1157 fconfigure $f_in -translation binary -encoding binary
1158 fconfigure $f_cp -translation binary -encoding binary
1159
1160@@ -843,12 +843,12 @@ method _clone_refs {} {
1161 error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
1162 return 0
1163 }
1164- set fd_in [git_read for-each-ref \
1165+ set fd_in [git_read [list for-each-ref \
1166 --tcl \
1167- {--format=list %(refname) %(objectname) %(*objectname)}]
1168+ {--format=list %(refname) %(objectname) %(*objectname)}]]
1169 cd $pwd
1170
1171- set fd [open [gitdir packed-refs] w]
1172+ set fd [safe_open_file [gitdir packed-refs] w]
1173 fconfigure $fd -translation binary
1174 puts $fd "# pack-refs with: peeled"
1175 while {[gets $fd_in line] >= 0} {
1176@@ -902,7 +902,7 @@ method _do_clone_full_end {ok} {
1177
1178 set HEAD {}
1179 if {[file exists [gitdir FETCH_HEAD]]} {
1180- set fd [open [gitdir FETCH_HEAD] r]
1181+ set fd [safe_open_file [gitdir FETCH_HEAD] r]
1182 while {[gets $fd line] >= 0} {
1183 if {[regexp "^(.{40})\t\t" $line line HEAD]} {
1184 break
1185@@ -978,13 +978,14 @@ method _do_clone_checkout {HEAD} {
1186 [mc "files"]]
1187
1188 set readtree_err {}
1189- set fd [git_read --stderr read-tree \
1190+ set fd [git_read [list read-tree \
1191 -m \
1192 -u \
1193 -v \
1194 HEAD \
1195 HEAD \
1196- ]
1197+ ] \
1198+ [list 2>@1]]
1199 fconfigure $fd -blocking 0 -translation binary
1200 fileevent $fd readable [cb _readtree_wait $fd]
1201 }
1202diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl
1203index 6dae793..8ae7e8a 100644
1204--- a/git-gui/lib/choose_rev.tcl
1205+++ b/git-gui/lib/choose_rev.tcl
1206@@ -146,14 +146,14 @@ constructor _new {path unmerged_only title} {
1207 append fmt { %(*subject)}
1208 append fmt {]}
1209 set all_refn [list]
1210- set fr_fd [git_read for-each-ref \
1211+ set fr_fd [git_read [list for-each-ref \
1212 --tcl \
1213 --sort=-taggerdate \
1214 --format=$fmt \
1215 refs/heads \
1216 refs/remotes \
1217 refs/tags \
1218- ]
1219+ ]]
1220 fconfigure $fr_fd -translation lf -encoding utf-8
1221 while {[gets $fr_fd line] > 0} {
1222 set line [eval $line]
1223@@ -176,7 +176,7 @@ constructor _new {path unmerged_only title} {
1224 close $fr_fd
1225
1226 if {$unmerged_only} {
1227- set fr_fd [git_read rev-list --all ^$::HEAD]
1228+ set fr_fd [git_read [list rev-list --all ^$::HEAD]]
1229 while {[gets $fr_fd sha1] > 0} {
1230 if {[catch {set rlst $cmt_refn($sha1)}]} continue
1231 foreach refn $rlst {
1232@@ -579,7 +579,7 @@ method _reflog_last {name} {
1233
1234 set last {}
1235 if {[catch {set last [file mtime [gitdir $name]]}]
1236- && ![catch {set g [open [gitdir logs $name] r]}]} {
1237+ && ![catch {set g [safe_open_file [gitdir logs $name] r]}]} {
1238 fconfigure $g -translation binary
1239 while {[gets $g line] >= 0} {
1240 if {[regexp {> ([1-9][0-9]*) } $line line when]} {
1241diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
1242index 11379f8..bb6056d 100644
1243--- a/git-gui/lib/commit.tcl
1244+++ b/git-gui/lib/commit.tcl
1245@@ -27,7 +27,7 @@ You are currently in the middle of a merge that has not been fully completed. Y
1246 if {[catch {
1247 set name ""
1248 set email ""
1249- set fd [git_read cat-file commit $curHEAD]
1250+ set fd [git_read [list cat-file commit $curHEAD]]
1251 fconfigure $fd -encoding binary -translation lf
1252 # By default commits are assumed to be in utf-8
1253 set enc utf-8
1254@@ -225,7 +225,7 @@ A good commit message has the following format:
1255 # -- Build the message file.
1256 #
1257 set msg_p [gitdir GITGUI_EDITMSG]
1258- set msg_wt [open $msg_p w]
1259+ set msg_wt [safe_open_file $msg_p w]
1260 fconfigure $msg_wt -translation lf
1261 setup_commit_encoding $msg_wt
1262 puts $msg_wt $msg
1263@@ -325,7 +325,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
1264
1265 proc commit_writetree {curHEAD msg_p} {
1266 ui_status [mc "Committing changes..."]
1267- set fd_wt [git_read write-tree]
1268+ set fd_wt [git_read [list write-tree]]
1269 fileevent $fd_wt readable \
1270 [list commit_committree $fd_wt $curHEAD $msg_p]
1271 }
1272@@ -350,7 +350,7 @@ proc commit_committree {fd_wt curHEAD msg_p} {
1273 # -- Verify this wasn't an empty change.
1274 #
1275 if {$commit_type eq {normal}} {
1276- set fd_ot [git_read cat-file commit $PARENT]
1277+ set fd_ot [git_read [list cat-file commit $PARENT]]
1278 fconfigure $fd_ot -encoding binary -translation lf
1279 set old_tree [gets $fd_ot]
1280 close $fd_ot
1281@@ -388,8 +388,8 @@ A rescan will be automatically started now.
1282 foreach p [concat $PARENT $MERGE_HEAD] {
1283 lappend cmd -p $p
1284 }
1285- lappend cmd <$msg_p
1286- if {[catch {set cmt_id [eval git $cmd]} err]} {
1287+ set msgtxt [list <$msg_p]
1288+ if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} {
1289 catch {file delete $msg_p}
1290 error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
1291 ui_status [mc "Commit failed."]
1292@@ -409,7 +409,7 @@ A rescan will be automatically started now.
1293 if {$commit_type ne {normal}} {
1294 append reflogm " ($commit_type)"
1295 }
1296- set msg_fd [open $msg_p r]
1297+ set msg_fd [safe_open_file $msg_p r]
1298 setup_commit_encoding $msg_fd 1
1299 gets $msg_fd subject
1300 close $msg_fd
1301diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl
1302index bb6b9c8..4715ce9 100644
1303--- a/git-gui/lib/console.tcl
1304+++ b/git-gui/lib/console.tcl
1305@@ -92,10 +92,9 @@ method _init {} {
1306
1307 method exec {cmd {after {}}} {
1308 if {[lindex $cmd 0] eq {git}} {
1309- set fd_f [eval git_read --stderr [lrange $cmd 1 end]]
1310+ set fd_f [git_read [lrange $cmd 1 end] [list 2>@1]]
1311 } else {
1312- lappend cmd 2>@1
1313- set fd_f [_open_stdout_stderr $cmd]
1314+ set fd_f [safe_open_command $cmd [list 2>@1]]
1315 }
1316 fconfigure $fd_f -blocking 0 -translation binary
1317 fileevent $fd_f readable [cb _read $fd_f $after]
1318diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl
1319index 8578308..1fc0ea0 100644
1320--- a/git-gui/lib/database.tcl
1321+++ b/git-gui/lib/database.tcl
1322@@ -3,7 +3,7 @@
1323
1324 proc do_stats {} {
1325 global use_ttk NS
1326- set fd [git_read count-objects -v]
1327+ set fd [git_read [list count-objects -v]]
1328 while {[gets $fd line] > 0} {
1329 if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
1330 set stats($name) $value
1331diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
1332index 871ad48..8ec740e 100644
1333--- a/git-gui/lib/diff.tcl
1334+++ b/git-gui/lib/diff.tcl
1335@@ -202,7 +202,7 @@ proc show_other_diff {path w m cont_info} {
1336 set sz [string length $content]
1337 }
1338 file {
1339- set fd [open $path r]
1340+ set fd [safe_open_file $path r]
1341 fconfigure $fd \
1342 -eofchar {} \
1343 -encoding [get_path_encoding $path]
1344@@ -226,7 +226,7 @@ proc show_other_diff {path w m cont_info} {
1345 $ui_diff insert end \
1346 "* [mc "Git Repository (subproject)"]\n" \
1347 d_info
1348- } elseif {![catch {set type [exec file $path]}]} {
1349+ } elseif {![catch {set type [safe_exec [list file $path]]}]} {
1350 set n [string length $path]
1351 if {[string equal -length $n $path $type]} {
1352 set type [string range $type $n end]
1353@@ -338,7 +338,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
1354 }
1355 }
1356
1357- if {[catch {set fd [eval git_read --nice $cmd]} err]} {
1358+ if {[catch {set fd [git_read_nice $cmd]} err]} {
1359 set diff_active 0
1360 unlock_index
1361 ui_status [mc "Unable to display %s" [escape_path $path]]
1362@@ -617,7 +617,7 @@ proc apply_or_revert_hunk {x y revert} {
1363
1364 if {[catch {
1365 set enc [get_path_encoding $current_diff_path]
1366- set p [eval git_write $apply_cmd]
1367+ set p [git_write $apply_cmd]
1368 fconfigure $p -translation binary -encoding $enc
1369 puts -nonewline $p $wholepatch
1370 close $p} err]} {
1371@@ -853,7 +853,7 @@ proc apply_or_revert_range_or_line {x y revert} {
1372
1373 if {[catch {
1374 set enc [get_path_encoding $current_diff_path]
1375- set p [eval git_write $apply_cmd]
1376+ set p [git_write $apply_cmd]
1377 fconfigure $p -translation binary -encoding $enc
1378 puts -nonewline $p $current_diff_header
1379 puts -nonewline $p $wholepatch
1380@@ -890,7 +890,7 @@ proc undo_last_revert {} {
1381
1382 if {[catch {
1383 set enc $last_revert_enc
1384- set p [eval git_write $apply_cmd]
1385+ set p [git_write $apply_cmd]
1386 fconfigure $p -translation binary -encoding $enc
1387 puts -nonewline $p $last_revert
1388 close $p} err]} {
1389diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
1390index d2ec24b..857864f 100644
1391--- a/git-gui/lib/index.tcl
1392+++ b/git-gui/lib/index.tcl
1393@@ -75,7 +75,7 @@ proc update_indexinfo {msg path_list after} {
1394 if {$batch > 25} {set batch 25}
1395
1396 set status_bar_operation [$::main_status start $msg [mc "files"]]
1397- set fd [git_write update-index -z --index-info]
1398+ set fd [git_write [list update-index -z --index-info]]
1399 fconfigure $fd \
1400 -blocking 0 \
1401 -buffering full \
1402@@ -144,7 +144,7 @@ proc update_index {msg path_list after} {
1403 if {$batch > 25} {set batch 25}
1404
1405 set status_bar_operation [$::main_status start $msg [mc "files"]]
1406- set fd [git_write update-index --add --remove -z --stdin]
1407+ set fd [git_write [list update-index --add --remove -z --stdin]]
1408 fconfigure $fd \
1409 -blocking 0 \
1410 -buffering full \
1411@@ -218,13 +218,13 @@ proc checkout_index {msg path_list after capture_error} {
1412 if {$batch > 25} {set batch 25}
1413
1414 set status_bar_operation [$::main_status start $msg [mc "files"]]
1415- set fd [git_write checkout-index \
1416+ set fd [git_write [list checkout-index \
1417 --index \
1418 --quiet \
1419 --force \
1420 -z \
1421 --stdin \
1422- ]
1423+ ]]
1424 fconfigure $fd \
1425 -blocking 0 \
1426 -buffering full \
1427diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
1428index 664803c..44c3f93 100644
1429--- a/git-gui/lib/merge.tcl
1430+++ b/git-gui/lib/merge.tcl
1431@@ -93,7 +93,7 @@ method _start {} {
1432 set spec [$w_rev get_tracking_branch]
1433 set cmit [$w_rev get_commit]
1434
1435- set fh [open [gitdir FETCH_HEAD] w]
1436+ set fh [safe_open_file [gitdir FETCH_HEAD] w]
1437 fconfigure $fh -translation lf
1438 if {$spec eq {}} {
1439 set remote .
1440@@ -118,7 +118,7 @@ method _start {} {
1441 set cmd [list git]
1442 lappend cmd merge
1443 lappend cmd --strategy=recursive
1444- lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
1445+ lappend cmd [git_redir [list fmt-merge-msg] [list <[gitdir FETCH_HEAD]]]
1446 lappend cmd HEAD
1447 lappend cmd $name
1448 }
1449@@ -239,7 +239,7 @@ Continue with resetting the current changes?"]
1450 }
1451
1452 if {[ask_popup $op_question] eq {yes}} {
1453- set fd [git_read --stderr read-tree --reset -u -v HEAD]
1454+ set fd [git_read [list read-tree --reset -u -v HEAD] [list 2>@1]]
1455 fconfigure $fd -blocking 0 -translation binary
1456 set status_bar_operation [$::main_status \
1457 start \
1458diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
1459index e688b01..6b26726 100644
1460--- a/git-gui/lib/mergetool.tcl
1461+++ b/git-gui/lib/mergetool.tcl
1462@@ -88,7 +88,7 @@ proc merge_load_stages {path cont} {
1463 set merge_stages(3) {}
1464 set merge_stages_buf {}
1465
1466- set merge_stages_fd [eval git_read ls-files -u -z -- {$path}]
1467+ set merge_stages_fd [git_read [list ls-files -u -z -- $path]]
1468
1469 fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
1470 fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
1471@@ -293,7 +293,7 @@ proc merge_tool_get_stages {target stages} {
1472 foreach fname $stages {
1473 if {$merge_stages($i) eq {}} {
1474 file delete $fname
1475- catch { close [open $fname w] }
1476+ catch { close [safe_open_file $fname w] }
1477 } else {
1478 # A hack to support autocrlf properly
1479 git checkout-index -f --stage=$i -- $target
1480@@ -343,9 +343,9 @@ proc merge_tool_start {cmdline target backup stages} {
1481
1482 # Force redirection to avoid interpreting output on stderr
1483 # as an error, and launch the tool
1484- lappend cmdline {2>@1}
1485+ set redir [list {2>@1}]
1486
1487- if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
1488+ if {[catch { set mtool_fd [safe_open_command $cmdline $redir] } err]} {
1489 delete_temp_files $mtool_tmpfiles
1490 error_popup [mc "Could not start the merge tool:\n\n%s" $err]
1491 return
1492diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
1493index ef77ed7..cf796d1 100644
1494--- a/git-gui/lib/remote.tcl
1495+++ b/git-gui/lib/remote.tcl
1496@@ -32,7 +32,7 @@ proc all_tracking_branches {} {
1497 }
1498
1499 if {$pat ne {}} {
1500- set fd [eval git_read for-each-ref --format=%(refname) $cmd]
1501+ set fd [git_read [concat for-each-ref --format=%(refname) $cmd]]
1502 while {[gets $fd n] > 0} {
1503 foreach spec $pat {
1504 set dst [string range [lindex $spec 0] 0 end-2]
1505@@ -75,7 +75,7 @@ proc load_all_remotes {} {
1506
1507 foreach name $all_remotes {
1508 catch {
1509- set fd [open [file join $rm_dir $name] r]
1510+ set fd [safe_open_file [file join $rm_dir $name] r]
1511 while {[gets $fd line] >= 0} {
1512 if {[regexp {^URL:[ ]*(.+)$} $line line url]} {
1513 set remote_url($name) $url
1514@@ -145,7 +145,7 @@ proc add_fetch_entry {r} {
1515 }
1516 } else {
1517 catch {
1518- set fd [open [gitdir remotes $r] r]
1519+ set fd [safe_open_file [gitdir remotes $r] r]
1520 while {[gets $fd n] >= 0} {
1521 if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
1522 set enable 1
1523@@ -182,7 +182,7 @@ proc add_push_entry {r} {
1524 }
1525 } else {
1526 catch {
1527- set fd [open [gitdir remotes $r] r]
1528+ set fd [safe_open_file [gitdir remotes $r] r]
1529 while {[gets $fd n] >= 0} {
1530 if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
1531 set enable 1
1532diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl
1533index 5ba9fca..c8c99b1 100644
1534--- a/git-gui/lib/remote_branch_delete.tcl
1535+++ b/git-gui/lib/remote_branch_delete.tcl
1536@@ -308,7 +308,7 @@ method _load {cache uri} {
1537 set full_list [list]
1538 set head_cache($cache) [list]
1539 set full_cache($cache) [list]
1540- set active_ls [git_read ls-remote $uri]
1541+ set active_ls [git_read [list ls-remote $uri]]
1542 fconfigure $active_ls \
1543 -blocking 0 \
1544 -translation lf \
1545diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
1546index 97d1d7a..d97be99 100644
1547--- a/git-gui/lib/shortcut.tcl
1548+++ b/git-gui/lib/shortcut.tcl
1549@@ -12,7 +12,7 @@ proc do_windows_shortcut {} {
1550 set fn ${fn}.lnk
1551 }
1552 # Use git-gui.exe if available (ie: git-for-windows)
1553- set cmdLine [auto_execok git-gui.exe]
1554+ set cmdLine [list [_which git-gui]]
1555 if {$cmdLine eq {}} {
1556 set cmdLine [list [info nameofexecutable] \
1557 [file normalize $::argv0]]
1558@@ -30,7 +30,7 @@ proc do_cygwin_shortcut {} {
1559 global argv0 _gitworktree
1560
1561 if {[catch {
1562- set desktop [exec cygpath \
1563+ set desktop [safe_exec [list cygpath \
1564 --windows \
1565 --absolute \
1566 --long-name \
1567@@ -48,14 +48,14 @@ proc do_cygwin_shortcut {} {
1568 set fn ${fn}.lnk
1569 }
1570 if {[catch {
1571- set sh [exec cygpath \
1572+ set sh [safe_exec [list cygpath \
1573 --windows \
1574 --absolute \
1575- /bin/sh.exe]
1576- set me [exec cygpath \
1577+ /bin/sh.exe]]
1578+ set me [safe_exec [list cygpath \
1579 --unix \
1580 --absolute \
1581- $argv0]
1582+ $argv0]]
1583 win32_create_lnk $fn [list \
1584 $sh -c \
1585 "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
1586@@ -86,7 +86,7 @@ proc do_macosx_app {} {
1587
1588 file mkdir $MacOS
1589
1590- set fd [open [file join $Contents Info.plist] w]
1591+ set fd [safe_open_file [file join $Contents Info.plist] w]
1592 puts $fd {<?xml version="1.0" encoding="UTF-8"?>
1593 <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
1594 <plist version="1.0">
1595@@ -111,7 +111,7 @@ proc do_macosx_app {} {
1596 </plist>}
1597 close $fd
1598
1599- set fd [open $exe w]
1600+ set fd [safe_open_file $exe w]
1601 puts $fd "#!/bin/sh"
1602 foreach name [lsort [array names env]] {
1603 set value $env($name)
1604diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl
1605index 589ff8f..c3e681b 100644
1606--- a/git-gui/lib/sshkey.tcl
1607+++ b/git-gui/lib/sshkey.tcl
1608@@ -7,7 +7,7 @@ proc find_ssh_key {} {
1609 ~/.ssh/id_rsa.pub ~/.ssh/identity.pub
1610 } {
1611 if {[file exists $name]} {
1612- set fh [open $name r]
1613+ set fh [safe_open_file $name r]
1614 set cont [read $fh]
1615 close $fh
1616 return [list $name $cont]
1617@@ -83,9 +83,10 @@ proc make_ssh_key {w} {
1618 set sshkey_title [mc "Generating..."]
1619 $w.header.gen configure -state disabled
1620
1621- set cmdline [list sh -c {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
1622+ set cmdline [list [shellpath] -c \
1623+ {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
1624
1625- if {[catch { set sshkey_fd [_open_stdout_stderr $cmdline] } err]} {
1626+ if {[catch { set sshkey_fd [safe_open_command $cmdline] } err]} {
1627 error_popup [mc "Could not start ssh-keygen:\n\n%s" $err]
1628 return
1629 }
1630diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl
1631index 413f1a1..48fddfd 100644
1632--- a/git-gui/lib/tools.tcl
1633+++ b/git-gui/lib/tools.tcl
1634@@ -110,14 +110,14 @@ proc tools_exec {fullname} {
1635
1636 set cmdline $repo_config(guitool.$fullname.cmd)
1637 if {[is_config_true "guitool.$fullname.noconsole"]} {
1638- tools_run_silent [list sh -c $cmdline] \
1639+ tools_run_silent [list [shellpath] -c $cmdline] \
1640 [list tools_complete $fullname {}]
1641 } else {
1642 regsub {/} $fullname { / } title
1643 set w [console::new \
1644 [mc "Tool: %s" $title] \
1645 [mc "Running: %s" $cmdline]]
1646- console::exec $w [list sh -c $cmdline] \
1647+ console::exec $w [list [shellpath] -c $cmdline] \
1648 [list tools_complete $fullname $w]
1649 }
1650
1651@@ -130,8 +130,7 @@ proc tools_exec {fullname} {
1652 }
1653
1654 proc tools_run_silent {cmd after} {
1655- lappend cmd 2>@1
1656- set fd [_open_stdout_stderr $cmd]
1657+ set fd [safe_open_command $cmd [list 2>@1]]
1658
1659 fconfigure $fd -blocking 0 -translation binary
1660 fileevent $fd readable [list tools_consume_input $fd $after]
1661diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl
1662index db91ab8..3aedae2 100644
1663--- a/git-gui/lib/win32.tcl
1664+++ b/git-gui/lib/win32.tcl
1665@@ -2,11 +2,11 @@
1666 # Copyright (C) 2007 Shawn Pearce
1667
1668 proc win32_read_lnk {lnk_path} {
1669- return [exec cscript.exe \
1670+ return [safe_exec [list cscript.exe \
1671 /E:jscript \
1672 /nologo \
1673 [file join $::oguilib win32_shortcut.js] \
1674- $lnk_path]
1675+ $lnk_path]]
1676 }
1677
1678 proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
1679@@ -15,12 +15,13 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
1680 set lnk_args [lrange $lnk_exec 1 end]
1681 set lnk_exec [lindex $lnk_exec 0]
1682
1683- eval [list exec wscript.exe \
1684+ set cmd [list wscript.exe \
1685 /E:jscript \
1686 /nologo \
1687 [file nativename [file join $oguilib win32_shortcut.js]] \
1688 $lnk_path \
1689 [file nativename [file join $oguilib git-gui.ico]] \
1690 $lnk_dir \
1691- $lnk_exec] $lnk_args
1692+ $lnk_exec]
1693+ safe_exec [concat $cmd $lnk_args]
1694 }
1695diff --git a/gitk-git/gitk b/gitk-git/gitk
1696index 23d9dd1..1c8c9c0 100755
1697--- a/gitk-git/gitk
1698+++ b/gitk-git/gitk
1699@@ -9,6 +9,92 @@ exec wish "$0" -- "$@"
1700
1701 package require Tk
1702
1703+
1704+# Wrap exec/open to sanitize arguments
1705+
1706+# unsafe arguments begin with redirections or the pipe or background operators
1707+proc is_arg_unsafe {arg} {
1708+ regexp {^([<|>&]|2>)} $arg
1709+}
1710+
1711+proc make_arg_safe {arg} {
1712+ if {[is_arg_unsafe $arg]} {
1713+ set arg [file join . $arg]
1714+ }
1715+ return $arg
1716+}
1717+
1718+proc make_arglist_safe {arglist} {
1719+ set res {}
1720+ foreach arg $arglist {
1721+ lappend res [make_arg_safe $arg]
1722+ }
1723+ return $res
1724+}
1725+
1726+# executes one command
1727+# no redirections or pipelines are possible
1728+# cmd is a list that specifies the command and its arguments
1729+# calls `exec` and returns its value
1730+proc safe_exec {cmd} {
1731+ eval exec [make_arglist_safe $cmd]
1732+}
1733+
1734+# executes one command with redirections
1735+# no pipelines are possible
1736+# cmd is a list that specifies the command and its arguments
1737+# redir is a list that specifies redirections (output, background, constant(!) commands)
1738+# calls `exec` and returns its value
1739+proc safe_exec_redirect {cmd redir} {
1740+ eval exec [make_arglist_safe $cmd] $redir
1741+}
1742+
1743+proc safe_open_file {filename flags} {
1744+ # a file name starting with "|" would attempt to run a process
1745+ # but such a file name must be treated as a relative path
1746+ # hide the "|" behind "./"
1747+ if {[string index $filename 0] eq "|"} {
1748+ set filename [file join . $filename]
1749+ }
1750+ open $filename $flags
1751+}
1752+
1753+# opens a command pipeline for reading
1754+# cmd is a list that specifies the command and its arguments
1755+# calls `open` and returns the file id
1756+proc safe_open_command {cmd} {
1757+ open |[make_arglist_safe $cmd] r
1758+}
1759+
1760+# opens a command pipeline for reading and writing
1761+# cmd is a list that specifies the command and its arguments
1762+# calls `open` and returns the file id
1763+proc safe_open_command_rw {cmd} {
1764+ open |[make_arglist_safe $cmd] r+
1765+}
1766+
1767+# opens a command pipeline for reading with redirections
1768+# cmd is a list that specifies the command and its arguments
1769+# redir is a list that specifies redirections
1770+# calls `open` and returns the file id
1771+proc safe_open_command_redirect {cmd redir} {
1772+ set cmd [make_arglist_safe $cmd]
1773+ open |[concat $cmd $redir] r
1774+}
1775+
1776+# opens a pipeline with several commands for reading
1777+# cmds is a list of lists, each of which specifies a command and its arguments
1778+# calls `open` and returns the file id
1779+proc safe_open_pipeline {cmds} {
1780+ set cmd {}
1781+ foreach subcmd $cmds {
1782+ set cmd [concat $cmd | [make_arglist_safe $subcmd]]
1783+ }
1784+ open $cmd r
1785+}
1786+
1787+# End exec/open wrappers
1788+
1789 proc hasworktree {} {
1790 return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
1791 [exec git rev-parse --is-inside-git-dir] == "false"}]
1792@@ -134,7 +220,7 @@ proc unmerged_files {files} {
1793 set mlist {}
1794 set nr_unmerged 0
1795 if {[catch {
1796- set fd [open "| git ls-files -u" r]
1797+ set fd [safe_open_command {git ls-files -u}]
1798 } err]} {
1799 show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
1800 exit 1
1801@@ -296,7 +382,7 @@ proc parseviewrevs {view revs} {
1802 } elseif {[lsearch -exact $revs --all] >= 0} {
1803 lappend revs HEAD
1804 }
1805- if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
1806+ if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} {
1807 # we get stdout followed by stderr in $err
1808 # for an unknown rev, git rev-parse echoes it and then errors out
1809 set errlines [split $err "\n"]
1810@@ -374,7 +460,7 @@ proc start_rev_list {view} {
1811 set args $viewargs($view)
1812 if {$viewargscmd($view) ne {}} {
1813 if {[catch {
1814- set str [exec sh -c $viewargscmd($view)]
1815+ set str [safe_exec [list sh -c $viewargscmd($view)]]
1816 } err]} {
1817 error_popup "[mc "Error executing --argscmd command:"] $err"
1818 return 0
1819@@ -405,14 +491,16 @@ proc start_rev_list {view} {
1820 if {$revs eq {}} {
1821 return 0
1822 }
1823- set args [concat $vflags($view) $revs]
1824+ set args $vflags($view)
1825 } else {
1826+ set revs {}
1827 set args $vorigargs($view)
1828 }
1829
1830 if {[catch {
1831- set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
1832- --parents --boundary $args "--" $files] r]
1833+ set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
1834+ --parents --boundary $args --stdin] \
1835+ [list "<<[join [concat $revs "--" $files] "\n"]"]]
1836 } err]} {
1837 error_popup "[mc "Error executing git log:"] $err"
1838 return 0
1839@@ -446,9 +534,9 @@ proc stop_instance {inst} {
1840 set pid [pid $fd]
1841
1842 if {$::tcl_platform(platform) eq {windows}} {
1843- exec taskkill /pid $pid
1844+ safe_exec [list taskkill /pid $pid]
1845 } else {
1846- exec kill $pid
1847+ safe_exec [list kill $pid]
1848 }
1849 }
1850 catch {close $fd}
1851@@ -554,13 +642,18 @@ proc updatecommits {} {
1852 set revs $newrevs
1853 set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
1854 }
1855- set args [concat $vflags($view) $revs --not $oldpos]
1856+ set args $vflags($view)
1857+ foreach r $oldpos {
1858+ lappend revs "^$r"
1859+ }
1860 } else {
1861+ set revs {}
1862 set args $vorigargs($view)
1863 }
1864 if {[catch {
1865- set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
1866- --parents --boundary $args "--" $vfilelimit($view)] r]
1867+ set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
1868+ --parents --boundary $args --stdin] \
1869+ [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]]
1870 } err]} {
1871 error_popup "[mc "Error executing git log:"] $err"
1872 return
1873@@ -1527,8 +1620,8 @@ proc getcommitlines {fd inst view updating} {
1874 # and if we already know about it, using the rewritten
1875 # parent as a substitute parent for $id's children.
1876 if {![catch {
1877- set rwid [exec git rev-list --first-parent --max-count=1 \
1878- $id -- $vfilelimit($view)]
1879+ set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \
1880+ $id -- $vfilelimit($view)]]
1881 }]} {
1882 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
1883 # use $rwid in place of $id
1884@@ -1648,7 +1741,7 @@ proc do_readcommit {id} {
1885 global tclencoding
1886
1887 # Invoke git-log to handle automatic encoding conversion
1888- set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
1889+ set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]]
1890 # Read the results using i18n.logoutputencoding
1891 fconfigure $fd -translation lf -eofchar {}
1892 if {$tclencoding != {}} {
1893@@ -1784,7 +1877,7 @@ proc readrefs {} {
1894 foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
1895 unset -nocomplain $v
1896 }
1897- set refd [open [list | git show-ref -d] r]
1898+ set refd [safe_open_command [list git show-ref -d]]
1899 if {$tclencoding != {}} {
1900 fconfigure $refd -encoding $tclencoding
1901 }
1902@@ -1832,7 +1925,7 @@ proc readrefs {} {
1903 set selectheadid {}
1904 if {$selecthead ne {}} {
1905 catch {
1906- set selectheadid [exec git rev-parse --verify $selecthead]
1907+ set selectheadid [safe_exec [list git rev-parse --verify $selecthead]]
1908 }
1909 }
1910 }
1911@@ -2092,7 +2185,7 @@ proc makewindow {} {
1912 {mc "Reread re&ferences" command rereadrefs}
1913 {mc "&List references" command showrefs -accelerator F2}
1914 {xx "" separator}
1915- {mc "Start git &gui" command {exec git gui &}}
1916+ {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}}
1917 {xx "" separator}
1918 {mc "&Quit" command doquit -accelerator Meta1-Q}
1919 }}
1920@@ -2874,7 +2967,7 @@ proc savestuff {w} {
1921 set remove_tmp 0
1922 if {[catch {
1923 set try_count 0
1924- while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} {
1925+ while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} {
1926 if {[incr try_count] > 50} {
1927 error "Unable to write config file: $config_file_tmp exists"
1928 }
1929@@ -2955,9 +3048,9 @@ proc savestuff {w} {
1930 proc resizeclistpanes {win w} {
1931 global oldwidth oldsash use_ttk
1932 if {[info exists oldwidth($win)]} {
1933- if {[info exists oldsash($win)]} {
1934- set s0 [lindex $oldsash($win) 0]
1935- set s1 [lindex $oldsash($win) 1]
1936+ if {[info exists oldsash($win)]} {
1937+ set s0 [lindex $oldsash($win) 0]
1938+ set s1 [lindex $oldsash($win) 1]
1939 } elseif {$use_ttk} {
1940 set s0 [$win sashpos 0]
1941 set s1 [$win sashpos 1]
1942@@ -2991,8 +3084,10 @@ proc resizeclistpanes {win w} {
1943 } else {
1944 $win sash place 0 $sash0 [lindex $s0 1]
1945 $win sash place 1 $sash1 [lindex $s1 1]
1946+ set sash0 [list $sash0 [lindex $s0 1]]
1947+ set sash1 [list $sash1 [lindex $s1 1]]
1948 }
1949- set oldsash($win) [list $sash0 $sash1]
1950+ set oldsash($win) [list $sash0 $sash1]
1951 }
1952 set oldwidth($win) $w
1953 }
1954@@ -3000,8 +3095,8 @@ proc resizeclistpanes {win w} {
1955 proc resizecdetpanes {win w} {
1956 global oldwidth oldsash use_ttk
1957 if {[info exists oldwidth($win)]} {
1958- if {[info exists oldsash($win)]} {
1959- set s0 $oldsash($win)
1960+ if {[info exists oldsash($win)]} {
1961+ set s0 $oldsash($win)
1962 } elseif {$use_ttk} {
1963 set s0 [$win sashpos 0]
1964 } else {
1965@@ -3023,8 +3118,9 @@ proc resizecdetpanes {win w} {
1966 $win sashpos 0 $sash0
1967 } else {
1968 $win sash place 0 $sash0 [lindex $s0 1]
1969+ set sash0 [list $sash0 [lindex $s0 1]]
1970 }
1971- set oldsash($win) $sash0
1972+ set oldsash($win) $sash0
1973 }
1974 set oldwidth($win) $w
1975 }
1976@@ -3587,7 +3683,7 @@ proc gitknewtmpdir {} {
1977 set tmpdir $gitdir
1978 }
1979 set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"]
1980- if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} {
1981+ if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} {
1982 set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
1983 }
1984 if {[catch {file mkdir $gitktmpdir} err]} {
1985@@ -3609,7 +3705,7 @@ proc gitknewtmpdir {} {
1986 proc save_file_from_commit {filename output what} {
1987 global nullfile
1988
1989- if {[catch {exec git show $filename -- > $output} err]} {
1990+ if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} {
1991 if {[string match "fatal: bad revision *" $err]} {
1992 return $nullfile
1993 }
1994@@ -3674,7 +3770,7 @@ proc external_diff {} {
1995
1996 if {$difffromfile ne {} && $difftofile ne {}} {
1997 set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
1998- if {[catch {set fl [open |$cmd r]} err]} {
1999+ if {[catch {set fl [safe_open_command $cmd]} err]} {
2000 file delete -force $diffdir
2001 error_popup "$extdifftool: [mc "command failed:"] $err"
2002 } else {
2003@@ -3778,7 +3874,7 @@ proc external_blame_diff {} {
2004 # Find the SHA1 ID of the blob for file $fname in the index
2005 # at stage 0 or 2
2006 proc index_sha1 {fname} {
2007- set f [open [list | git ls-files -s $fname] r]
2008+ set f [safe_open_command [list git ls-files -s $fname]]
2009 while {[gets $f line] >= 0} {
2010 set info [lindex [split $line "\t"] 0]
2011 set stage [lindex $info 2]
2012@@ -3838,7 +3934,7 @@ proc external_blame {parent_idx {line {}}} {
2013 # being given an absolute path...
2014 set f [make_relative $f]
2015 lappend cmdline $base_commit $f
2016- if {[catch {eval exec $cmdline &} err]} {
2017+ if {[catch {safe_exec_redirect $cmdline [list &]} err]} {
2018 error_popup "[mc "git gui blame: command failed:"] $err"
2019 }
2020 }
2021@@ -3866,7 +3962,7 @@ proc show_line_source {} {
2022 # must be a merge in progress...
2023 if {[catch {
2024 # get the last line from .git/MERGE_HEAD
2025- set f [open [file join $gitdir MERGE_HEAD] r]
2026+ set f [safe_open_file [file join $gitdir MERGE_HEAD] r]
2027 set id [lindex [split [read $f] "\n"] end-1]
2028 close $f
2029 } err]} {
2030@@ -3889,19 +3985,17 @@ proc show_line_source {} {
2031 }
2032 set line [lindex $h 1]
2033 }
2034- set blameargs {}
2035+ set blamefile [file join $cdup $flist_menu_file]
2036 if {$from_index ne {}} {
2037- lappend blameargs | git cat-file blob $from_index
2038- }
2039- lappend blameargs | git blame -p -L$line,+1
2040- if {$from_index ne {}} {
2041- lappend blameargs --contents -
2042+ set blameargs [list \
2043+ [list git cat-file blob $from_index] \
2044+ [list git blame -p -L$line,+1 --contents - -- $blamefile]]
2045 } else {
2046- lappend blameargs $id
2047+ set blameargs [list \
2048+ [list git blame -p -L$line,+1 $id -- $blamefile]]
2049 }
2050- lappend blameargs -- [file join $cdup $flist_menu_file]
2051 if {[catch {
2052- set f [open $blameargs r]
2053+ set f [safe_open_pipeline $blameargs]
2054 } err]} {
2055 error_popup [mc "Couldn't start git blame: %s" $err]
2056 return
2057@@ -4826,8 +4920,8 @@ proc do_file_hl {serial} {
2058 # must be "containing:", i.e. we're searching commit info
2059 return
2060 }
2061- set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
2062- set filehighlight [open $cmd r+]
2063+ set cmd [concat git diff-tree -r -s --stdin $gdtargs]
2064+ set filehighlight [safe_open_command_rw $cmd]
2065 fconfigure $filehighlight -blocking 0
2066 filerun $filehighlight readfhighlight
2067 set fhl_list {}
2068@@ -5256,8 +5350,8 @@ proc get_viewmainhead {view} {
2069 global viewmainheadid vfilelimit viewinstances mainheadid
2070
2071 catch {
2072- set rfd [open [concat | git rev-list -1 $mainheadid \
2073- -- $vfilelimit($view)] r]
2074+ set rfd [safe_open_command [concat git rev-list -1 $mainheadid \
2075+ -- $vfilelimit($view)]]
2076 set j [reg_instance $rfd]
2077 lappend viewinstances($view) $j
2078 fconfigure $rfd -blocking 0
2079@@ -5322,14 +5416,14 @@ proc dodiffindex {} {
2080 if {!$showlocalchanges || !$hasworktree} return
2081 incr lserial
2082 if {[package vcompare $git_version "1.7.2"] >= 0} {
2083- set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD"
2084+ set cmd "git diff-index --cached --ignore-submodules=dirty HEAD"
2085 } else {
2086- set cmd "|git diff-index --cached HEAD"
2087+ set cmd "git diff-index --cached HEAD"
2088 }
2089 if {$vfilelimit($curview) ne {}} {
2090 set cmd [concat $cmd -- $vfilelimit($curview)]
2091 }
2092- set fd [open $cmd r]
2093+ set fd [safe_open_command $cmd]
2094 fconfigure $fd -blocking 0
2095 set i [reg_instance $fd]
2096 filerun $fd [list readdiffindex $fd $lserial $i]
2097@@ -5354,11 +5448,11 @@ proc readdiffindex {fd serial inst} {
2098 }
2099
2100 # now see if there are any local changes not checked in to the index
2101- set cmd "|git diff-files"
2102+ set cmd "git diff-files"
2103 if {$vfilelimit($curview) ne {}} {
2104 set cmd [concat $cmd -- $vfilelimit($curview)]
2105 }
2106- set fd [open $cmd r]
2107+ set fd [safe_open_command $cmd]
2108 fconfigure $fd -blocking 0
2109 set i [reg_instance $fd]
2110 filerun $fd [list readdifffiles $fd $serial $i]
2111@@ -7147,8 +7241,8 @@ proc browseweb {url} {
2112 global web_browser
2113
2114 if {$web_browser eq {}} return
2115- # Use eval here in case $web_browser is a command plus some arguments
2116- if {[catch {eval exec $web_browser [list $url] &} err]} {
2117+ # Use concat here in case $web_browser is a command plus some arguments
2118+ if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} {
2119 error_popup "[mc "Error starting web browser:"] $err"
2120 }
2121 }
2122@@ -7650,13 +7744,13 @@ proc gettree {id} {
2123 if {![info exists treefilelist($id)]} {
2124 if {![info exists treepending]} {
2125 if {$id eq $nullid} {
2126- set cmd [list | git ls-files]
2127+ set cmd [list git ls-files]
2128 } elseif {$id eq $nullid2} {
2129- set cmd [list | git ls-files --stage -t]
2130+ set cmd [list git ls-files --stage -t]
2131 } else {
2132- set cmd [list | git ls-tree -r $id]
2133+ set cmd [list git ls-tree -r $id]
2134 }
2135- if {[catch {set gtf [open $cmd r]}]} {
2136+ if {[catch {set gtf [safe_open_command $cmd]}]} {
2137 return
2138 }
2139 set treepending $id
2140@@ -7720,13 +7814,13 @@ proc showfile {f} {
2141 return
2142 }
2143 if {$diffids eq $nullid} {
2144- if {[catch {set bf [open $f r]} err]} {
2145+ if {[catch {set bf [safe_open_file $f r]} err]} {
2146 puts "oops, can't read $f: $err"
2147 return
2148 }
2149 } else {
2150 set blob [lindex $treeidlist($diffids) $i]
2151- if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
2152+ if {[catch {set bf [safe_open_command [concat git cat-file blob $blob]]} err]} {
2153 puts "oops, error reading blob $blob: $err"
2154 return
2155 }
2156@@ -7876,7 +7970,7 @@ proc diffcmd {ids flags} {
2157 if {$i >= 0} {
2158 if {[llength $ids] > 1 && $j < 0} {
2159 # comparing working directory with some specific revision
2160- set cmd [concat | git diff-index $flags]
2161+ set cmd [concat git diff-index $flags]
2162 if {$i == 0} {
2163 lappend cmd -R [lindex $ids 1]
2164 } else {
2165@@ -7884,7 +7978,7 @@ proc diffcmd {ids flags} {
2166 }
2167 } else {
2168 # comparing working directory with index
2169- set cmd [concat | git diff-files $flags]
2170+ set cmd [concat git diff-files $flags]
2171 if {$j == 1} {
2172 lappend cmd -R
2173 }
2174@@ -7893,7 +7987,7 @@ proc diffcmd {ids flags} {
2175 if {[package vcompare $git_version "1.7.2"] >= 0} {
2176 set flags "$flags --ignore-submodules=dirty"
2177 }
2178- set cmd [concat | git diff-index --cached $flags]
2179+ set cmd [concat git diff-index --cached $flags]
2180 if {[llength $ids] > 1} {
2181 # comparing index with specific revision
2182 if {$j == 0} {
2183@@ -7909,7 +8003,7 @@ proc diffcmd {ids flags} {
2184 if {$log_showroot} {
2185 lappend flags --root
2186 }
2187- set cmd [concat | git diff-tree -r $flags $ids]
2188+ set cmd [concat git diff-tree -r $flags $ids]
2189 }
2190 return $cmd
2191 }
2192@@ -7921,7 +8015,7 @@ proc gettreediffs {ids} {
2193 if {$limitdiffs && $vfilelimit($curview) ne {}} {
2194 set cmd [concat $cmd -- $vfilelimit($curview)]
2195 }
2196- if {[catch {set gdtf [open $cmd r]}]} return
2197+ if {[catch {set gdtf [safe_open_command $cmd]}]} return
2198
2199 set treepending $ids
2200 set treediff {}
2201@@ -8041,7 +8135,7 @@ proc getblobdiffs {ids} {
2202 if {$limitdiffs && $vfilelimit($curview) ne {}} {
2203 set cmd [concat $cmd -- $vfilelimit($curview)]
2204 }
2205- if {[catch {set bdf [open $cmd r]} err]} {
2206+ if {[catch {set bdf [safe_open_command $cmd]} err]} {
2207 error_popup [mc "Error getting diffs: %s" $err]
2208 return
2209 }
2210@@ -8758,7 +8852,7 @@ proc gotocommit {} {
2211 set id [lindex $matches 0]
2212 }
2213 } else {
2214- if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
2215+ if {[catch {set id [safe_exec [list git rev-parse --verify $sha1string]]}]} {
2216 error_popup [mc "Revision %s is not known" $sha1string]
2217 return
2218 }
2219@@ -9064,10 +9158,8 @@ proc getpatchid {id} {
2220
2221 if {![info exists patchids($id)]} {
2222 set cmd [diffcmd [list $id] {-p --root}]
2223- # trim off the initial "|"
2224- set cmd [lrange $cmd 1 end]
2225 if {[catch {
2226- set x [eval exec $cmd | git patch-id]
2227+ set x [safe_exec_redirect $cmd [list | git patch-id]]
2228 set patchids($id) [lindex $x 0]
2229 }]} {
2230 set patchids($id) "error"
2231@@ -9163,14 +9255,14 @@ proc diffcommits {a b} {
2232 set fna [file join $tmpdir "commit-[string range $a 0 7]"]
2233 set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
2234 if {[catch {
2235- exec git diff-tree -p --pretty $a >$fna
2236- exec git diff-tree -p --pretty $b >$fnb
2237+ safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna]
2238+ safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb]
2239 } err]} {
2240 error_popup [mc "Error writing commit to file: %s" $err]
2241 return
2242 }
2243 if {[catch {
2244- set fd [open "| diff -U$diffcontext $fna $fnb" r]
2245+ set fd [safe_open_command "diff -U$diffcontext $fna $fnb"]
2246 } err]} {
2247 error_popup [mc "Error diffing commits: %s" $err]
2248 return
2249@@ -9310,10 +9402,7 @@ proc mkpatchgo {} {
2250 set newid [$patchtop.tosha1 get]
2251 set fname [$patchtop.fname get]
2252 set cmd [diffcmd [list $oldid $newid] -p]
2253- # trim off the initial "|"
2254- set cmd [lrange $cmd 1 end]
2255- lappend cmd >$fname &
2256- if {[catch {eval exec $cmd} err]} {
2257+ if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} {
2258 error_popup "[mc "Error creating patch:"] $err" $patchtop
2259 }
2260 catch {destroy $patchtop}
2261@@ -9382,9 +9471,9 @@ proc domktag {} {
2262 }
2263 if {[catch {
2264 if {$msg != {}} {
2265- exec git tag -a -m $msg $tag $id
2266+ safe_exec [list git tag -a -m $msg $tag $id]
2267 } else {
2268- exec git tag $tag $id
2269+ safe_exec [list git tag $tag $id]
2270 }
2271 } err]} {
2272 error_popup "[mc "Error creating tag:"] $err" $mktagtop
2273@@ -9452,7 +9541,7 @@ proc copyreference {} {
2274 if {$autosellen < 40} {
2275 lappend cmd --abbrev=$autosellen
2276 }
2277- set reference [eval exec $cmd $rowmenuid]
2278+ set reference [safe_exec [concat $cmd $rowmenuid]]
2279
2280 clipboard clear
2281 clipboard append $reference
2282@@ -9502,7 +9591,7 @@ proc wrcomgo {} {
2283 set id [$wrcomtop.sha1 get]
2284 set cmd "echo $id | [$wrcomtop.cmd get]"
2285 set fname [$wrcomtop.fname get]
2286- if {[catch {exec sh -c $cmd >$fname &} err]} {
2287+ if {[catch {safe_exec_redirect [list sh -c $cmd] [list >$fname &]} err]} {
2288 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
2289 }
2290 catch {destroy $wrcomtop}
2291@@ -9606,7 +9695,7 @@ proc mkbrgo {top} {
2292 nowbusy newbranch
2293 update
2294 if {[catch {
2295- eval exec git branch $cmdargs
2296+ safe_exec [concat git branch $cmdargs]
2297 } err]} {
2298 notbusy newbranch
2299 error_popup $err
2300@@ -9647,7 +9736,7 @@ proc mvbrgo {top prevname} {
2301 nowbusy renamebranch
2302 update
2303 if {[catch {
2304- eval exec git branch $cmdargs
2305+ safe_exec [concat git branch $cmdargs]
2306 } err]} {
2307 notbusy renamebranch
2308 error_popup $err
2309@@ -9688,7 +9777,7 @@ proc exec_citool {tool_args {baseid {}}} {
2310 }
2311 }
2312
2313- eval exec git citool $tool_args &
2314+ safe_exec_redirect [concat git citool $tool_args] [list &]
2315
2316 array unset env GIT_AUTHOR_*
2317 array set env $save_env
2318@@ -9711,7 +9800,7 @@ proc cherrypick {} {
2319 update
2320 # Unfortunately git-cherry-pick writes stuff to stderr even when
2321 # no error occurs, and exec takes that as an indication of error...
2322- if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
2323+ if {[catch {safe_exec [list sh -c "git cherry-pick -r $rowmenuid 2>&1"]} err]} {
2324 notbusy cherrypick
2325 if {[regexp -line \
2326 {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
2327@@ -9773,7 +9862,7 @@ proc revert {} {
2328 nowbusy revert [mc "Reverting"]
2329 update
2330
2331- if [catch {exec git revert --no-edit $rowmenuid} err] {
2332+ if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] {
2333 notbusy revert
2334 if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\
2335 $err match files] {
2336@@ -9849,8 +9938,8 @@ proc resethead {} {
2337 bind $w <Visibility> "grab $w; focus $w"
2338 tkwait window $w
2339 if {!$confirm_ok} return
2340- if {[catch {set fd [open \
2341- [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
2342+ if {[catch {set fd [safe_open_command_redirect \
2343+ [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} {
2344 error_popup $err
2345 } else {
2346 dohidelocalchanges
2347@@ -9921,7 +10010,7 @@ proc cobranch {} {
2348
2349 # check the tree is clean first??
2350 set newhead $headmenuhead
2351- set command [list | git checkout]
2352+ set command [list git checkout]
2353 if {[string match "remotes/*" $newhead]} {
2354 set remote $newhead
2355 set newhead [string range $newhead [expr [string last / $newhead] + 1] end]
2356@@ -9935,12 +10024,11 @@ proc cobranch {} {
2357 } else {
2358 lappend command $newhead
2359 }
2360- lappend command 2>@1
2361 nowbusy checkout [mc "Checking out"]
2362 update
2363 dohidelocalchanges
2364 if {[catch {
2365- set fd [open $command r]
2366+ set fd [safe_open_command_redirect $command [list 2>@1]]
2367 } err]} {
2368 notbusy checkout
2369 error_popup $err
2370@@ -10006,7 +10094,7 @@ proc rmbranch {} {
2371 }
2372 nowbusy rmbranch
2373 update
2374- if {[catch {exec git branch -D $head} err]} {
2375+ if {[catch {safe_exec [list git branch -D $head]} err]} {
2376 notbusy rmbranch
2377 error_popup $err
2378 return
2379@@ -10197,7 +10285,7 @@ proc getallcommits {} {
2380 set cachedarcs 0
2381 set allccache [file join $gitdir "gitk.cache"]
2382 if {![catch {
2383- set f [open $allccache r]
2384+ set f [safe_open_file $allccache r]
2385 set allcwait 1
2386 getcache $f
2387 }]} return
2388@@ -10206,7 +10294,7 @@ proc getallcommits {} {
2389 if {$allcwait} {
2390 return
2391 }
2392- set cmd [list | git rev-list --parents]
2393+ set cmd [list git rev-list --parents]
2394 set allcupdate [expr {$seeds ne {}}]
2395 if {!$allcupdate} {
2396 set ids "--all"
2397@@ -10228,10 +10316,17 @@ proc getallcommits {} {
2398 foreach id $seeds {
2399 lappend ids "^$id"
2400 }
2401+ lappend ids "--"
2402 }
2403 }
2404 if {$ids ne {}} {
2405- set fd [open [concat $cmd $ids] r]
2406+ if {$ids eq "--all"} {
2407+ set cmd [concat $cmd "--all"]
2408+ set fd [safe_open_command $cmd]
2409+ } else {
2410+ set cmd [concat $cmd --stdin]
2411+ set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]]
2412+ }
2413 fconfigure $fd -blocking 0
2414 incr allcommits
2415 nowbusy allcommits
2416@@ -10621,7 +10716,7 @@ proc savecache {} {
2417 set cachearc 0
2418 set cachedarcs $nextarc
2419 catch {
2420- set f [open $allccache w]
2421+ set f [safe_open_file $allccache w]
2422 puts $f [list 1 $cachedarcs]
2423 run writecache $f
2424 }
2425@@ -11324,7 +11419,7 @@ proc add_tag_ctext {tag} {
2426
2427 if {![info exists cached_tagcontent($tag)]} {
2428 catch {
2429- set cached_tagcontent($tag) [exec git cat-file -p $tag]
2430+ set cached_tagcontent($tag) [safe_exec [list git cat-file -p $tag]]
2431 }
2432 }
2433 $ctext insert end "[mc "Tag"]: $tag\n" bold
2434@@ -11927,7 +12022,7 @@ proc formatdate {d} {
2435 }
2436
2437 # This list of encoding names and aliases is distilled from
2438-# http://www.iana.org/assignments/character-sets.
2439+# https://www.iana.org/assignments/character-sets.
2440 # Not all of them are supported by Tcl.
2441 set encoding_aliases {
2442 { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
2443@@ -12210,7 +12305,7 @@ proc gitattr {path attr default} {
2444 set r $path_attr_cache($attr,$path)
2445 } else {
2446 set r "unspecified"
2447- if {![catch {set line [exec git check-attr $attr -- $path]}]} {
2448+ if {![catch {set line [safe_exec [list git check-attr $attr -- $path]]}]} {
2449 regexp "(.*): $attr: (.*)" $line m f r
2450 }
2451 set path_attr_cache($attr,$path) $r
2452@@ -12237,7 +12332,7 @@ proc cache_gitattr {attr pathlist} {
2453 while {$newlist ne {}} {
2454 set head [lrange $newlist 0 [expr {$lim - 1}]]
2455 set newlist [lrange $newlist $lim end]
2456- if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
2457+ if {![catch {set rlist [safe_exec [concat git check-attr $attr -- $head]]}]} {
2458 foreach row [split $rlist "\n"] {
2459 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
2460 if {[string index $path 0] eq "\""} {
2461@@ -12290,11 +12385,11 @@ if {[catch {package require Tk 8.4} err]} {
2462
2463 # on OSX bring the current Wish process window to front
2464 if {[tk windowingsystem] eq "aqua"} {
2465- exec osascript -e [format {
2466+ safe_exec [list osascript -e [format {
2467 tell application "System Events"
2468 set frontmost of processes whose unix id is %d to true
2469 end tell
2470- } [pid] ]
2471+ } [pid] ]]
2472 }
2473
2474 # Unset GIT_TRACE var if set
2475@@ -12443,7 +12538,7 @@ if {[tk windowingsystem] eq "aqua"} {
2476
2477 catch {
2478 # follow the XDG base directory specification by default. See
2479- # http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
2480+ # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
2481 if {[info exists env(XDG_CONFIG_HOME)] && $env(XDG_CONFIG_HOME) ne ""} {
2482 # XDG_CONFIG_HOME environment variable is set
2483 set config_file [file join $env(XDG_CONFIG_HOME) git gitk]
2484@@ -12539,7 +12634,7 @@ if {$selecthead eq "HEAD"} {
2485 if {$i >= [llength $argv] && $revtreeargs ne {}} {
2486 # no -- on command line, but some arguments (other than --argscmd)
2487 if {[catch {
2488- set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
2489+ set f [safe_exec [concat git rev-parse --no-revs --no-flags $revtreeargs]]
2490 set cmdline_files [split $f "\n"]
2491 set n [llength $cmdline_files]
2492 set revtreeargs [lrange $revtreeargs 0 end-$n]
2493@@ -12705,3 +12800,4 @@ getcommits {}
2494 # indent-tabs-mode: t
2495 # tab-width: 8
2496 # End:
2497+
2498--
24992.50.1
2500
diff --git a/meta/recipes-devtools/git/git_2.35.7.bb b/meta/recipes-devtools/git/git_2.35.7.bb
index 765180a38d..3520b4db90 100644
--- a/meta/recipes-devtools/git/git_2.35.7.bb
+++ b/meta/recipes-devtools/git/git_2.35.7.bb
@@ -26,6 +26,7 @@ SRC_URI = "${KERNELORG_MIRROR}/software/scm/git/git-${PV}.tar.gz;name=tarball \
26 file://CVE-2024-50349-0001.patch \ 26 file://CVE-2024-50349-0001.patch \
27 file://CVE-2024-50349-0002.patch \ 27 file://CVE-2024-50349-0002.patch \
28 file://CVE-2024-52006.patch \ 28 file://CVE-2024-52006.patch \
29 file://CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch \
29 " 30 "
30 31
31S = "${WORKDIR}/git-${PV}" 32S = "${WORKDIR}/git-${PV}"