]> git.ipfire.org Git - thirdparty/openembedded/openembedded-core.git/commitdiff
git: fix CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835
authorHitendra Prajapati <hprajapati@mvista.com>
Mon, 18 Aug 2025 12:31:25 +0000 (18:01 +0530)
committerSteve Sakoman <steve@sakoman.com>
Tue, 19 Aug 2025 17:14:42 +0000 (10:14 -0700)
Upstream-Status: Backport from from https://github.com/git/git/commit/d61cfed2c23705fbeb9c0d08f59e75ee08738950

Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch [new file with mode: 0644]
meta/recipes-devtools/git/git_2.35.7.bb

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 (file)
index 0000000..e08bf41
--- /dev/null
@@ -0,0 +1,2500 @@
+From: d61cfed2c23705fbeb9c0d08f59e75ee08738950 Merge: 664d4fa692 311d9ada3a
+Author: Taylor Blau <me@ttaylorr.com>
+Date:   Fri May 23 17:17:06 2025 -0400
+
+    Merge branch 'js/gitk-git-gui-harden-exec-open' into maint-2.43
+
+    This merges in fixes for CVE-2025-27614, CVE-2025-27613, CVE-2025-46334,
+    and CVE-2025-46835 targeting Gitk and Git GUI.
+
+    * js/gitk-git-gui-harden-exec-open: (41 commits)
+      git-gui: sanitize 'exec' arguments: convert new 'cygpath' calls
+      git-gui: do not mistake command arguments as redirection operators
+      git-gui: introduce function git_redir for git calls with redirections
+      git-gui: pass redirections as separate argument to git_read
+      git-gui: pass redirections as separate argument to _open_stdout_stderr
+      git-gui: convert git_read*, git_write to be non-variadic
+      git-gui: override exec and open only on Windows
+      gitk: sanitize 'open' arguments: revisit recently updated 'open' calls
+      git-gui: use git_read in githook_read
+      git-gui: sanitize $PATH on all platforms
+      git-gui: break out a separate function git_read_nice
+      git-gui: assure PATH has only absolute elements.
+      git-gui: remove option --stderr from git_read
+      git-gui: cleanup git-bash menu item
+      git-gui: sanitize 'exec' arguments: background
+      git-gui: avoid auto_execok in do_windows_shortcut
+      git-gui: sanitize 'exec' arguments: simple cases
+      git-gui: avoid auto_execok for git-bash menu item
+      git-gui: treat file names beginning with "|" as relative paths
+      git-gui: remove unused proc is_shellscript
+      git-gui: remove git config --list handling for git < 1.5.3
+      git-gui: remove special treatment of Windows from open_cmd_pipe
+      git-gui: remove HEAD detachment implementation for git < 1.5.3
+      git-gui: use only the configured shell
+      git-gui: remove Tcl 8.4 workaround on 2>@1 redirection
+      git-gui: make _shellpath usable on startup
+      git-gui: use [is_Windows], not bad _shellpath
+      git-gui: _which, only add .exe suffix if not present
+      gitk: encode arguments correctly with "open"
+      gitk: sanitize 'open' arguments: command pipeline
+      gitk: collect construction of blameargs into a single conditional
+      gitk: sanitize 'open' arguments: simple commands, readable and writable
+      gitk: sanitize 'open' arguments: simple commands with redirections
+      gitk: sanitize 'open' arguments: simple commands
+      gitk: sanitize 'exec' arguments: redirect to process
+      gitk: sanitize 'exec' arguments: redirections and background
+      gitk: sanitize 'exec' arguments: redirections
+      gitk: sanitize 'exec' arguments: 'eval exec'
+      gitk: sanitize 'exec' arguments: simple cases
+      gitk: have callers of diffcmd supply pipe symbol when necessary
+      gitk: treat file names beginning with "|" as relative paths
+      ...
+
+    Signed-off-by: Taylor Blau <me@ttaylorr.com>
+
+Upstream-Status: Backport from [https://github.com/git/git/commit/d61cfed2c23705fbeb9c0d08f59e75ee08738950]
+CVE: CVE-2025-27614, CVE-2025-27613, CVE-2025-46334, CVE-2025-46835
+Signed-off-by: Hitendra Prajapati <hprajapati@mvista.com>
+---
+ git-gui/git-gui.sh                   | 622 +++++++++++++++++----------
+ git-gui/lib/blame.tcl                |  12 +-
+ git-gui/lib/branch.tcl               |   6 +-
+ git-gui/lib/browser.tcl              |   2 +-
+ git-gui/lib/checkout_op.tcl          |  25 +-
+ git-gui/lib/choose_repository.tcl    |  23 +-
+ git-gui/lib/choose_rev.tcl           |   8 +-
+ git-gui/lib/commit.tcl               |  14 +-
+ git-gui/lib/console.tcl              |   5 +-
+ git-gui/lib/database.tcl             |   2 +-
+ git-gui/lib/diff.tcl                 |  12 +-
+ git-gui/lib/index.tcl                |   8 +-
+ git-gui/lib/merge.tcl                |   6 +-
+ git-gui/lib/mergetool.tcl            |   8 +-
+ git-gui/lib/remote.tcl               |   8 +-
+ git-gui/lib/remote_branch_delete.tcl |   2 +-
+ git-gui/lib/shortcut.tcl             |  16 +-
+ git-gui/lib/sshkey.tcl               |   7 +-
+ git-gui/lib/tools.tcl                |   7 +-
+ git-gui/lib/win32.tcl                |   9 +-
+ gitk-git/gitk                        | 298 ++++++++-----
+ 21 files changed, 667 insertions(+), 433 deletions(-)
+
+diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
+index 201524c..2f38291 100755
+--- a/git-gui/git-gui.sh
++++ b/git-gui/git-gui.sh
+@@ -24,7 +24,7 @@ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ GNU General Public License for more details.
+ You should have received a copy of the GNU General Public License
+-along with this program; if not, see <http://www.gnu.org/licenses/>.}]
++along with this program; if not, see <https://www.gnu.org/licenses/>.}]
+ ######################################################################
+ ##
+@@ -44,6 +44,211 @@ if {[catch {package require Tcl 8.5} err]
+ catch {rename send {}} ; # What an evil concept...
++######################################################################
++##
++## Enabling platform-specific code paths
++
++proc is_MacOSX {} {
++      if {[tk windowingsystem] eq {aqua}} {
++              return 1
++      }
++      return 0
++}
++
++proc is_Windows {} {
++      if {$::tcl_platform(platform) eq {windows}} {
++              return 1
++      }
++      return 0
++}
++
++set _iscygwin {}
++proc is_Cygwin {} {
++      global _iscygwin
++      if {$_iscygwin eq {}} {
++              if {[string match "CYGWIN_*" $::tcl_platform(os)]} {
++                      set _iscygwin 1
++              } else {
++                      set _iscygwin 0
++              }
++      }
++      return $_iscygwin
++}
++
++######################################################################
++##
++## PATH lookup. Sanitize $PATH, assure exec/open use only that
++
++if {[is_Windows]} {
++      set _path_sep {;}
++      set _search_exe .exe
++} else {
++      set _path_sep {:}
++      set _search_exe {}
++}
++
++if {[is_Windows]} {
++      set gitguidir [file dirname [info script]]
++      regsub -all ";" $gitguidir "\\;" gitguidir
++      set env(PATH) "$gitguidir;$env(PATH)"
++}
++
++set _search_path {}
++set _path_seen [dict create]
++foreach p [split $env(PATH) $_path_sep] {
++      # Keep only absolute paths, getting rid of ., empty, etc.
++      if {[file pathtype $p] ne {absolute}} {
++              continue
++      }
++      # Keep only the first occurence of any duplicates.
++      set norm_p [file normalize $p]
++      if {[dict exists $_path_seen $norm_p]} {
++              continue
++      }
++      dict set _path_seen $norm_p 1
++      lappend _search_path $norm_p
++}
++unset _path_seen
++
++set env(PATH) [join $_search_path $_path_sep]
++
++if {[is_Windows]} {
++      proc _which {what args} {
++              global _search_exe _search_path
++
++              if {[lsearch -exact $args -script] >= 0} {
++                      set suffix {}
++              } elseif {[string match *$_search_exe [string tolower $what]]} {
++                      # The search string already has the file extension
++                      set suffix {}
++              } else {
++                      set suffix $_search_exe
++              }
++
++              foreach p $_search_path {
++                      set p [file join $p $what$suffix]
++                      if {[file exists $p]} {
++                              return [file normalize $p]
++                      }
++              }
++              return {}
++      }
++
++      proc sanitize_command_line {command_line from_index} {
++              set i $from_index
++              while {$i < [llength $command_line]} {
++                      set cmd [lindex $command_line $i]
++                      if {[llength [file split $cmd]] < 2} {
++                              set fullpath [_which $cmd]
++                              if {$fullpath eq ""} {
++                                      throw {NOT-FOUND} "$cmd not found in PATH"
++                              }
++                              lset command_line $i $fullpath
++                      }
++
++                      # handle piped commands, e.g. `exec A | B`
++                      for {incr i} {$i < [llength $command_line]} {incr i} {
++                              if {[lindex $command_line $i] eq "|"} {
++                                      incr i
++                                      break
++                              }
++                      }
++              }
++              return $command_line
++      }
++
++      # Override `exec` to avoid unsafe PATH lookup
++
++      rename exec real_exec
++
++      proc exec {args} {
++              # skip options
++              for {set i 0} {$i < [llength $args]} {incr i} {
++                      set arg [lindex $args $i]
++                      if {$arg eq "--"} {
++                              incr i
++                              break
++                      }
++                      if {[string range $arg 0 0] ne "-"} {
++                              break
++                      }
++              }
++              set args [sanitize_command_line $args $i]
++              uplevel 1 real_exec $args
++      }
++
++      # Override `open` to avoid unsafe PATH lookup
++
++      rename open real_open
++
++      proc open {args} {
++              set arg0 [lindex $args 0]
++              if {[string range $arg0 0 0] eq "|"} {
++                      set command_line [string trim [string range $arg0 1 end]]
++                      lset args 0 "| [sanitize_command_line $command_line 0]"
++              }
++              uplevel 1 real_open $args
++      }
++
++} else {
++      # On non-Windows platforms, auto_execok, exec, and open are safe, and will
++      # use the sanitized search path. But, we need _which for these.
++
++      proc _which {what args} {
++              return [lindex [auto_execok $what] 0]
++      }
++}
++
++# Wrap exec/open to sanitize arguments
++
++# unsafe arguments begin with redirections or the pipe or background operators
++proc is_arg_unsafe {arg} {
++      regexp {^([<|>&]|2>)} $arg
++}
++
++proc make_arg_safe {arg} {
++      if {[is_arg_unsafe $arg]} {
++              set arg [file join . $arg]
++      }
++      return $arg
++}
++
++proc make_arglist_safe {arglist} {
++      set res {}
++      foreach arg $arglist {
++              lappend res [make_arg_safe $arg]
++      }
++      return $res
++}
++
++# executes one command
++# no redirections or pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# calls `exec` and returns its value
++proc safe_exec {cmd} {
++      eval exec [make_arglist_safe $cmd]
++}
++
++# executes one command in the background
++# no redirections or pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# calls `exec` and returns its value
++proc safe_exec_bg {cmd} {
++      eval exec [make_arglist_safe $cmd] &
++}
++
++proc safe_open_file {filename flags} {
++      # a file name starting with "|" would attempt to run a process
++      # but such a file name must be treated as a relative path
++      # hide the "|" behind "./"
++      if {[string index $filename 0] eq "|"} {
++              set filename [file join . $filename]
++      }
++      open $filename $flags
++}
++
++# End exec/open wrappers
++
+ ######################################################################
+ ##
+ ## locate our library
+@@ -144,14 +349,64 @@ unset oguimsg
+ if {[tk windowingsystem] eq "aqua"} {
+       catch {
+-              exec osascript -e [format {
++              safe_exec [list osascript -e [format {
+                       tell application "System Events"
+                               set frontmost of processes whose unix id is %d to true
+                       end tell
+-              } [pid]]
++              } [pid]]]
+       }
+ }
++# Wrap exec/open to sanitize arguments
++
++# unsafe arguments begin with redirections or the pipe or background operators
++proc is_arg_unsafe {arg} {
++      regexp {^([<|>&]|2>)} $arg
++}
++
++proc make_arg_safe {arg} {
++      if {[is_arg_unsafe $arg]} {
++              set arg [file join . $arg]
++      }
++      return $arg
++}
++
++proc make_arglist_safe {arglist} {
++      set res {}
++      foreach arg $arglist {
++              lappend res [make_arg_safe $arg]
++      }
++      return $res
++}
++
++# executes one command
++# no redirections or pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# calls `exec` and returns its value
++proc safe_exec {cmd} {
++      eval exec [make_arglist_safe $cmd]
++}
++
++# executes one command in the background
++# no redirections or pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# calls `exec` and returns its value
++proc safe_exec_bg {cmd} {
++      eval exec [make_arglist_safe $cmd] &
++}
++
++proc safe_open_file {filename flags} {
++      # a file name starting with "|" would attempt to run a process
++      # but such a file name must be treated as a relative path
++      # hide the "|" behind "./"
++      if {[string index $filename 0] eq "|"} {
++              set filename [file join . $filename]
++      }
++      open $filename $flags
++}
++
++# End exec/open wrappers
++
+ ######################################################################
+ ##
+ ## read only globals
+@@ -180,15 +435,37 @@ if {$_trace >= 0} {
+ # branches).
+ set _last_merged_branch {}
+-proc shellpath {} {
+-      global _shellpath env
+-      if {[string match @@* $_shellpath]} {
+-              if {[info exists env(SHELL)]} {
+-                      return $env(SHELL)
+-              } else {
+-                      return /bin/sh
+-              }
++# for testing, allow unconfigured _shellpath
++if {[string match @@* $_shellpath]} {
++      if {[info exists env(SHELL)]} {
++              set _shellpath $env(SHELL)
++      } else {
++              set _shellpath /bin/sh
+       }
++}
++
++if {[is_Windows]} {
++      set _shellpath [safe_exec [list cygpath -m $_shellpath]]
++}
++
++if {![file executable $_shellpath] || \
++      !([file pathtype $_shellpath] eq {absolute})} {
++      set errmsg "The defined shell ('$_shellpath') is not usable, \
++              it must be an absolute path to an executable."
++      puts stderr $errmsg
++
++      catch {wm withdraw .}
++      tk_messageBox \
++              -icon error \
++              -type ok \
++              -title "git-gui: configuration error" \
++              -message $errmsg
++      exit 1
++}
++
++
++proc shellpath {} {
++      global _shellpath
+       return $_shellpath
+ }
+@@ -252,40 +529,6 @@ proc reponame {} {
+       return $::_reponame
+ }
+-proc is_MacOSX {} {
+-      if {[tk windowingsystem] eq {aqua}} {
+-              return 1
+-      }
+-      return 0
+-}
+-
+-proc is_Windows {} {
+-      if {$::tcl_platform(platform) eq {windows}} {
+-              return 1
+-      }
+-      return 0
+-}
+-
+-proc is_Cygwin {} {
+-      global _iscygwin
+-      if {$_iscygwin eq {}} {
+-              if {$::tcl_platform(platform) eq {windows}} {
+-                      if {[catch {set p [exec cygpath --windir]} err]} {
+-                              set _iscygwin 0
+-                      } else {
+-                              set _iscygwin 1
+-                              # Handle MSys2 which is only cygwin when MSYSTEM is MSYS.
+-                              if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} {
+-                                      set _iscygwin 0
+-                              }
+-                      }
+-              } else {
+-                      set _iscygwin 0
+-              }
+-      }
+-      return $_iscygwin
+-}
+-
+ proc is_enabled {option} {
+       global enabled_options
+       if {[catch {set on $enabled_options($option)}]} {return 0}
+@@ -418,7 +661,7 @@ proc _git_cmd {name} {
+                       # Tcl on Windows doesn't know it.
+                       #
+                       set p [gitexec git-$name]
+-                      set f [open $p r]
++                      set f [safe_open_file $p r]
+                       set s [gets $f]
+                       close $f
+@@ -473,6 +716,9 @@ proc _which {what args} {
+       if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
+               set suffix {}
++      } elseif {[is_Windows] && [string match *$_search_exe [string tolower $what]]} {
++              # The search string already has the file extension
++              set suffix {}
+       } else {
+               set suffix $_search_exe
+       }
+@@ -486,32 +732,14 @@ proc _which {what args} {
+       return {}
+ }
+-# Test a file for a hashbang to identify executable scripts on Windows.
+-proc is_shellscript {filename} {
+-      if {![file exists $filename]} {return 0}
+-      set f [open $filename r]
+-      fconfigure $f -encoding binary
+-      set magic [read $f 2]
+-      close $f
+-      return [expr {$magic eq "#!"}]
+-}
+-
+-# Run a command connected via pipes on stdout.
++# Run a shell command connected via pipes on stdout.
+ # This is for use with textconv filters and uses sh -c "..." to allow it to
+-# contain a command with arguments. On windows we must check for shell
+-# scripts specifically otherwise just call the filter command.
++# contain a command with arguments. We presume this
++# to be a shellscript that the configured shell (/bin/sh by default) knows
++# how to run.
+ proc open_cmd_pipe {cmd path} {
+-      global env
+-      if {![file executable [shellpath]]} {
+-              set exe [auto_execok [lindex $cmd 0]]
+-              if {[is_shellscript [lindex $exe 0]]} {
+-                      set run [linsert [auto_execok sh] end -c "$cmd \"\$0\"" $path]
+-              } else {
+-                      set run [concat $exe [lrange $cmd 1 end] $path]
+-              }
+-      } else {
+-              set run [list [shellpath] -c "$cmd \"\$0\"" $path]
+-      }
++      set run [list [shellpath] -c "$cmd \"\$0\"" $path]
++      set run [make_arglist_safe $run]
+       return [open |$run r]
+ }
+@@ -521,7 +749,7 @@ proc _lappend_nice {cmd_var} {
+       if {![info exists _nice]} {
+               set _nice [_which nice]
+-              if {[catch {exec $_nice git version}]} {
++              if {[catch {safe_exec [list $_nice git version]}]} {
+                       set _nice {}
+               } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
+                       set _nice {}
+@@ -533,7 +761,11 @@ proc _lappend_nice {cmd_var} {
+ }
+ proc git {args} {
+-      set fd [eval [list git_read] $args]
++      git_redir $args {}
++}
++
++proc git_redir {cmd redir} {
++      set fd [git_read $cmd $redir]
+       fconfigure $fd -translation binary -encoding utf-8
+       set result [string trimright [read $fd] "\n"]
+       close $fd
+@@ -543,111 +775,47 @@ proc git {args} {
+       return $result
+ }
+-proc _open_stdout_stderr {cmd} {
+-      _trace_exec $cmd
++proc safe_open_command {cmd {redir {}}} {
++      set cmd [make_arglist_safe $cmd]
++      _trace_exec [concat $cmd $redir]
+       if {[catch {
+-                      set fd [open [concat [list | ] $cmd] r]
+-              } err]} {
+-              if {   [lindex $cmd end] eq {2>@1}
+-                  && $err eq {can not find channel named "1"}
+-                      } {
+-                      # Older versions of Tcl 8.4 don't have this 2>@1 IO
+-                      # redirect operator.  Fallback to |& cat for those.
+-                      # The command was not actually started, so its safe
+-                      # to try to start it a second time.
+-                      #
+-                      set fd [open [concat \
+-                              [list | ] \
+-                              [lrange $cmd 0 end-1] \
+-                              [list |& cat] \
+-                              ] r]
+-              } else {
+-                      error $err
+-              }
++              set fd [open [concat [list | ] $cmd $redir] r]
++      } err]} {
++              error $err
+       }
+       fconfigure $fd -eofchar {}
+       return $fd
+ }
+-proc git_read {args} {
+-      set opt [list]
+-
+-      while {1} {
+-              switch -- [lindex $args 0] {
+-              --nice {
+-                      _lappend_nice opt
+-              }
+-
+-              --stderr {
+-                      lappend args 2>@1
+-              }
++proc git_read {cmd {redir {}}} {
++      set cmdp [_git_cmd [lindex $cmd 0]]
++      set cmd [lrange $cmd 1 end]
+-              default {
+-                      break
+-              }
+-
+-              }
+-
+-              set args [lrange $args 1 end]
+-      }
+-
+-      set cmdp [_git_cmd [lindex $args 0]]
+-      set args [lrange $args 1 end]
+-
+-      return [_open_stdout_stderr [concat $opt $cmdp $args]]
++      return [safe_open_command [concat $cmdp $cmd] $redir]
+ }
+-proc git_write {args} {
++proc git_read_nice {cmd} {
+       set opt [list]
+-      while {1} {
+-              switch -- [lindex $args 0] {
+-              --nice {
+-                      _lappend_nice opt
+-              }
++      _lappend_nice opt
+-              default {
+-                      break
+-              }
++      set cmdp [_git_cmd [lindex $cmd 0]]
++      set cmd [lrange $cmd 1 end]
+-              }
+-
+-              set args [lrange $args 1 end]
+-      }
++      return [safe_open_command [concat $opt $cmdp $cmd]]
++}
+-      set cmdp [_git_cmd [lindex $args 0]]
+-      set args [lrange $args 1 end]
++proc git_write {cmd} {
++      set cmd [make_arglist_safe $cmd]
++      set cmdp [_git_cmd [lindex $cmd 0]]
++      set cmd [lrange $cmd 1 end]
+-      _trace_exec [concat $opt $cmdp $args]
+-      return [open [concat [list | ] $opt $cmdp $args] w]
++      _trace_exec [concat $cmdp $cmd]
++      return [open [concat [list | ] $cmdp $cmd] w]
+ }
+ proc githook_read {hook_name args} {
+-      set pchook [gitdir hooks $hook_name]
+-      lappend args 2>@1
+-
+-      # On Windows [file executable] might lie so we need to ask
+-      # the shell if the hook is executable.  Yes that's annoying.
+-      #
+-      if {[is_Windows]} {
+-              upvar #0 _sh interp
+-              if {![info exists interp]} {
+-                      set interp [_which sh]
+-              }
+-              if {$interp eq {}} {
+-                      error "hook execution requires sh (not in PATH)"
+-              }
+-
+-              set scr {if test -x "$1";then exec "$@";fi}
+-              set sh_c [list $interp -c $scr $interp $pchook]
+-              return [_open_stdout_stderr [concat $sh_c $args]]
+-      }
+-
+-      if {[file executable $pchook]} {
+-              return [_open_stdout_stderr [concat [list $pchook] $args]]
+-      }
+-
+-      return {}
++      git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1]
+ }
+ proc kill_file_process {fd} {
+@@ -655,9 +823,9 @@ proc kill_file_process {fd} {
+       catch {
+               if {[is_Windows]} {
+-                      exec taskkill /pid $process
++                      safe_exec [list taskkill /pid $process]
+               } else {
+-                      exec kill $process
++                      safe_exec [list kill $process]
+               }
+       }
+ }
+@@ -683,7 +851,7 @@ proc sq {value} {
+ proc load_current_branch {} {
+       global current_branch is_detached
+-      set fd [open [gitdir HEAD] r]
++      set fd [safe_open_file [gitdir HEAD] r]
+       fconfigure $fd -translation binary -encoding utf-8
+       if {[gets $fd ref] < 1} {
+               set ref {}
+@@ -1045,7 +1213,7 @@ You are using [git-version]:
+ ## configure our library
+ set idx [file join $oguilib tclIndex]
+-if {[catch {set fd [open $idx r]} err]} {
++if {[catch {set fd [safe_open_file $idx r]} err]} {
+       catch {wm withdraw .}
+       tk_messageBox \
+               -icon error \
+@@ -1083,53 +1251,30 @@ unset -nocomplain idx fd
+ ##
+ ## config file parsing
+-git-version proc _parse_config {arr_name args} {
+-      >= 1.5.3 {
+-              upvar $arr_name arr
+-              array unset arr
+-              set buf {}
+-              catch {
+-                      set fd_rc [eval \
+-                              [list git_read config] \
+-                              $args \
+-                              [list --null --list]]
+-                      fconfigure $fd_rc -translation binary -encoding utf-8
+-                      set buf [read $fd_rc]
+-                      close $fd_rc
+-              }
+-              foreach line [split $buf "\0"] {
+-                      if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
+-                              if {[is_many_config $name]} {
+-                                      lappend arr($name) $value
+-                              } else {
+-                                      set arr($name) $value
+-                              }
+-                      } elseif {[regexp {^([^\n]+)$} $line line name]} {
+-                              # no value given, but interpreting them as
+-                              # boolean will be handled as true
+-                              set arr($name) {}
+-                      }
+-              }
+-      }
+-      default {
+-              upvar $arr_name arr
+-              array unset arr
+-              catch {
+-                      set fd_rc [eval [list git_read config --list] $args]
+-                      while {[gets $fd_rc line] >= 0} {
+-                              if {[regexp {^([^=]+)=(.*)$} $line line name value]} {
+-                                      if {[is_many_config $name]} {
+-                                              lappend arr($name) $value
+-                                      } else {
+-                                              set arr($name) $value
+-                                      }
+-                              } elseif {[regexp {^([^=]+)$} $line line name]} {
+-                                      # no value given, but interpreting them as
+-                                      # boolean will be handled as true
+-                                      set arr($name) {}
+-                              }
++proc _parse_config {arr_name args} {
++      upvar $arr_name arr
++      array unset arr
++      set buf {}
++      catch {
++              set fd_rc [git_read \
++                      [concat config \
++                      $args \
++                      --null --list]]
++              fconfigure $fd_rc -translation binary -encoding utf-8
++              set buf [read $fd_rc]
++              close $fd_rc
++      }
++      foreach line [split $buf "\0"] {
++              if {[regexp {^([^\n]+)\n(.*)$} $line line name value]} {
++                      if {[is_many_config $name]} {
++                              lappend arr($name) $value
++                      } else {
++                              set arr($name) $value
+                       }
+-                      close $fd_rc
++              } elseif {[regexp {^([^\n]+)$} $line line name]} {
++                      # no value given, but interpreting them as
++                      # boolean will be handled as true
++                      set arr($name) {}
+               }
+       }
+ }
+@@ -1412,7 +1557,7 @@ proc repository_state {ctvar hdvar mhvar} {
+       set merge_head [gitdir MERGE_HEAD]
+       if {[file exists $merge_head]} {
+               set ct merge
+-              set fd_mh [open $merge_head r]
++              set fd_mh [safe_open_file $merge_head r]
+               while {[gets $fd_mh line] >= 0} {
+                       lappend mh $line
+               }
+@@ -1431,7 +1576,7 @@ proc PARENT {} {
+               return $p
+       }
+       if {$empty_tree eq {}} {
+-              set empty_tree [git mktree << {}]
++              set empty_tree [git_redir [list mktree] [list << {}]]
+       }
+       return $empty_tree
+ }
+@@ -1490,12 +1635,12 @@ proc rescan {after {honor_trustmtime 1}} {
+       } else {
+               set rescan_active 1
+               ui_status [mc "Refreshing file status..."]
+-              set fd_rf [git_read update-index \
++              set fd_rf [git_read [list update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+-                      ]
++                      ]]
+               fconfigure $fd_rf -blocking 0 -translation binary
+               fileevent $fd_rf readable \
+                       [list rescan_stage2 $fd_rf $after]
+@@ -1551,11 +1696,11 @@ proc rescan_stage2 {fd after} {
+       set rescan_active 2
+       ui_status [mc "Scanning for modified files ..."]
+       if {[git-version >= "1.7.2"]} {
+-              set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]]
++              set fd_di [git_read [list diff-index --cached --ignore-submodules=dirty -z [PARENT]]]
+       } else {
+-              set fd_di [git_read diff-index --cached -z [PARENT]]
++              set fd_di [git_read [list diff-index --cached -z [PARENT]]]
+       }
+-      set fd_df [git_read diff-files -z]
++      set fd_df [git_read [list diff-files -z]]
+       fconfigure $fd_di -blocking 0 -translation binary -encoding binary
+       fconfigure $fd_df -blocking 0 -translation binary -encoding binary
+@@ -1564,7 +1709,7 @@ proc rescan_stage2 {fd after} {
+       fileevent $fd_df readable [list read_diff_files $fd_df $after]
+       if {[is_config_true gui.displayuntracked]} {
+-              set fd_lo [eval git_read ls-files --others -z $ls_others]
++              set fd_lo [git_read [concat ls-files --others -z $ls_others]]
+               fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
+               fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
+               incr rescan_active
+@@ -1576,7 +1721,7 @@ proc load_message {file {encoding {}}} {
+       set f [gitdir $file]
+       if {[file isfile $f]} {
+-              if {[catch {set fd [open $f r]}]} {
++              if {[catch {set fd [safe_open_file $f r]}]} {
+                       return 0
+               }
+               fconfigure $fd -eofchar {}
+@@ -1600,23 +1745,23 @@ proc run_prepare_commit_msg_hook {} {
+       # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
+       # empty file but existent file.
+-      set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a]
++      set fd_pcm [safe_open_file [gitdir PREPARE_COMMIT_MSG] a]
+       if {[file isfile [gitdir MERGE_MSG]]} {
+               set pcm_source "merge"
+-              set fd_mm [open [gitdir MERGE_MSG] r]
++              set fd_mm [safe_open_file [gitdir MERGE_MSG] r]
+               fconfigure $fd_mm -encoding utf-8
+               puts -nonewline $fd_pcm [read $fd_mm]
+               close $fd_mm
+       } elseif {[file isfile [gitdir SQUASH_MSG]]} {
+               set pcm_source "squash"
+-              set fd_sm [open [gitdir SQUASH_MSG] r]
++              set fd_sm [safe_open_file [gitdir SQUASH_MSG] r]
+               fconfigure $fd_sm -encoding utf-8
+               puts -nonewline $fd_pcm [read $fd_sm]
+               close $fd_sm
+       } elseif {[file isfile [get_config commit.template]]} {
+               set pcm_source "template"
+-              set fd_sm [open [get_config commit.template] r]
++              set fd_sm [safe_open_file [get_config commit.template] r]
+               fconfigure $fd_sm -encoding utf-8
+               puts -nonewline $fd_pcm [read $fd_sm]
+               close $fd_sm
+@@ -2206,7 +2351,7 @@ proc do_gitk {revs {is_submodule false}} {
+                       unset env(GIT_DIR)
+                       unset env(GIT_WORK_TREE)
+               }
+-              eval exec $cmd $revs "--" "--" &
++              safe_exec_bg [concat $cmd $revs "--" "--"]
+               set env(GIT_DIR) $_gitdir
+               set env(GIT_WORK_TREE) $_gitworktree
+@@ -2243,7 +2388,7 @@ proc do_git_gui {} {
+               set pwd [pwd]
+               cd $current_diff_path
+-              eval exec $exe gui &
++              safe_exec_bg [concat $exe gui]
+               set env(GIT_DIR) $_gitdir
+               set env(GIT_WORK_TREE) $_gitworktree
+@@ -2272,16 +2417,18 @@ proc get_explorer {} {
+ proc do_explore {} {
+       global _gitworktree
+-      set explorer [get_explorer]
+-      eval exec $explorer [list [file nativename $_gitworktree]] &
++      set cmd [get_explorer]
++      lappend cmd [file nativename $_gitworktree]
++      safe_exec_bg $cmd
+ }
+ # Open file relative to the working tree by the default associated app.
+ proc do_file_open {file} {
+       global _gitworktree
+-      set explorer [get_explorer]
++      set cmd [get_explorer]
+       set full_file_path [file join $_gitworktree $file]
+-      exec $explorer [file nativename $full_file_path] &
++      lappend cmd [file nativename $full_file_path]
++      safe_exec_bg $cmd
+ }
+ set is_quitting 0
+@@ -2315,7 +2462,7 @@ proc do_quit {{rc {1}}} {
+                       if {![string match amend* $commit_type]
+                               && $msg ne {}} {
+                               catch {
+-                                      set fd [open $save w]
++                                      set fd [safe_open_file $save w]
+                                       fconfigure $fd -encoding utf-8
+                                       puts -nonewline $fd $msg
+                                       close $fd
+@@ -2373,7 +2520,7 @@ proc do_quit {{rc {1}}} {
+       set ret_code $rc
+       # Briefly enable send again, working around Tk bug
+-      # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997
++      # https://sourceforge.net/p/tktoolkit/bugs/2343/
+       tk appname [appname]
+       destroy .
+@@ -2759,17 +2906,16 @@ if {![is_bare]} {
+ if {[is_Windows]} {
+       # Use /git-bash.exe if available
+-      set normalized [file normalize $::argv0]
+-      regsub "/mingw../libexec/git-core/git-gui$" \
+-              $normalized "/git-bash.exe" cmdLine
+-      if {$cmdLine != $normalized && [file exists $cmdLine]} {
+-              set cmdLine [list "Git Bash" $cmdLine &]
++      set _git_bash [safe_exec [list cygpath -m /git-bash.exe]]
++      if {[file executable $_git_bash]} {
++              set _bash_cmdline [list "Git Bash" $_git_bash]
+       } else {
+-              set cmdLine [list "Git Bash" bash --login -l &]
++              set _bash_cmdline [list "Git Bash" bash --login -l]
+       }
+       .mbar.repository add command \
+               -label [mc "Git Bash"] \
+-              -command {eval exec [auto_execok start] $cmdLine}
++              -command {safe_exec_bg [concat [list [_which cmd] /c start] $_bash_cmdline]}
++      unset _git_bash
+ }
+ if {[is_Windows] || ![is_bare]} {
+@@ -4134,7 +4280,7 @@ if {[winfo exists $ui_comm]} {
+                               }
+                       } elseif {$m} {
+                               catch {
+-                                      set fd [open [gitdir GITGUI_BCK] w]
++                                      set fd [safe_open_file [gitdir GITGUI_BCK] w]
+                                       fconfigure $fd -encoding utf-8
+                                       puts -nonewline $fd $msg
+                                       close $fd
+diff --git a/git-gui/lib/blame.tcl b/git-gui/lib/blame.tcl
+index 8441e10..d6fd8be 100644
+--- a/git-gui/lib/blame.tcl
++++ b/git-gui/lib/blame.tcl
+@@ -481,14 +481,14 @@ method _load {jump} {
+               if {$do_textconv ne 0} {
+                       set fd [open_cmd_pipe $textconv $path]
+               } else {
+-                      set fd [open $path r]
++                      set fd [safe_open_file $path r]
+               }
+               fconfigure $fd -eofchar {}
+       } else {
+               if {$do_textconv ne 0} {
+-                      set fd [git_read cat-file --textconv "$commit:$path"]
++                      set fd [git_read [list cat-file --textconv "$commit:$path"]]
+               } else {
+-                      set fd [git_read cat-file blob "$commit:$path"]
++                      set fd [git_read [list cat-file blob "$commit:$path"]]
+               }
+       }
+       fconfigure $fd \
+@@ -617,7 +617,7 @@ method _exec_blame {cur_w cur_d options cur_s} {
+       }
+       lappend options -- $path
+-      set fd [eval git_read --nice blame $options]
++      set fd [git_read_nice [concat blame $options]]
+       fconfigure $fd -blocking 0 -translation lf -encoding utf-8
+       fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
+       set current_fd $fd
+@@ -986,7 +986,7 @@ method _showcommit {cur_w lno} {
+               if {[catch {set msg $header($cmit,message)}]} {
+                       set msg {}
+                       catch {
+-                              set fd [git_read cat-file commit $cmit]
++                              set fd [git_read [list cat-file commit $cmit]]
+                               fconfigure $fd -encoding binary -translation lf
+                               # By default commits are assumed to be in utf-8
+                               set enc utf-8
+@@ -1134,7 +1134,7 @@ method _blameparent {} {
+               } else {
+                       set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path]
+               }
+-              if {[catch {set fd [eval git_read $diffcmd]} err]} {
++              if {[catch {set fd [git_read $diffcmd]} err]} {
+                       $status_operation stop [mc "Unable to display parent"]
+                       error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
+                       return
+diff --git a/git-gui/lib/branch.tcl b/git-gui/lib/branch.tcl
+index 8b0c485..39e0f2d 100644
+--- a/git-gui/lib/branch.tcl
++++ b/git-gui/lib/branch.tcl
+@@ -7,7 +7,7 @@ proc load_all_heads {} {
+       set rh refs/heads
+       set rh_len [expr {[string length $rh] + 1}]
+       set all_heads [list]
+-      set fd [git_read for-each-ref --format=%(refname) $rh]
++      set fd [git_read [list for-each-ref --format=%(refname) $rh]]
+       fconfigure $fd -translation binary -encoding utf-8
+       while {[gets $fd line] > 0} {
+               if {!$some_heads_tracking || ![is_tracking_branch $line]} {
+@@ -21,10 +21,10 @@ proc load_all_heads {} {
+ proc load_all_tags {} {
+       set all_tags [list]
+-      set fd [git_read for-each-ref \
++      set fd [git_read [list for-each-ref \
+               --sort=-taggerdate \
+               --format=%(refname) \
+-              refs/tags]
++              refs/tags]]
+       fconfigure $fd -translation binary -encoding utf-8
+       while {[gets $fd line] > 0} {
+               if {![regsub ^refs/tags/ $line {} name]} continue
+diff --git a/git-gui/lib/browser.tcl b/git-gui/lib/browser.tcl
+index a982983..6fc8d4d 100644
+--- a/git-gui/lib/browser.tcl
++++ b/git-gui/lib/browser.tcl
+@@ -196,7 +196,7 @@ method _ls {tree_id {name {}}} {
+       lappend browser_stack [list $tree_id $name]
+       $w conf -state disabled
+-      set fd [git_read ls-tree -z $tree_id]
++      set fd [git_read [list ls-tree -z $tree_id]]
+       fconfigure $fd -blocking 0 -translation binary -encoding utf-8
+       fileevent $fd readable [cb _read $fd]
+ }
+diff --git a/git-gui/lib/checkout_op.tcl b/git-gui/lib/checkout_op.tcl
+index 21ea768..87ed0b4 100644
+--- a/git-gui/lib/checkout_op.tcl
++++ b/git-gui/lib/checkout_op.tcl
+@@ -304,12 +304,12 @@ The rescan will be automatically started now.
+               _readtree $this
+       } else {
+               ui_status [mc "Refreshing file status..."]
+-              set fd [git_read update-index \
++              set fd [git_read [list update-index \
+                       -q \
+                       --unmerged \
+                       --ignore-missing \
+                       --refresh \
+-                      ]
++                      ]]
+               fconfigure $fd -blocking 0 -translation binary
+               fileevent $fd readable [cb _refresh_wait $fd]
+       }
+@@ -345,14 +345,15 @@ method _readtree {} {
+               [mc "Updating working directory to '%s'..." [_name $this]] \
+               [mc "files checked out"]]
+-      set fd [git_read --stderr read-tree \
++      set fd [git_read [list read-tree \
+               -m \
+               -u \
+               -v \
+               --exclude-per-directory=.gitignore \
+               $HEAD \
+               $new_hash \
+-              ]
++              ] \
++              [list 2>@1]]
+       fconfigure $fd -blocking 0 -translation binary
+       fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation]
+ }
+@@ -510,18 +511,8 @@ method _update_repo_state {} {
+       delete_this
+ }
+-git-version proc _detach_HEAD {log new} {
+-      >= 1.5.3 {
+-              git update-ref --no-deref -m $log HEAD $new
+-      }
+-      default {
+-              set p [gitdir HEAD]
+-              file delete $p
+-              set fd [open $p w]
+-              fconfigure $fd -translation lf -encoding utf-8
+-              puts $fd $new
+-              close $fd
+-      }
++proc _detach_HEAD {log new} {
++      git update-ref --no-deref -m $log HEAD $new
+ }
+ method _confirm_reset {cur} {
+@@ -582,7 +573,7 @@ method _confirm_reset {cur} {
+       pack $w.buttons.cancel -side right -padx 5
+       pack $w.buttons -side bottom -fill x -pady 10 -padx 10
+-      set fd [git_read rev-list --pretty=oneline $cur ^$new_hash]
++      set fd [git_read [list rev-list --pretty=oneline $cur ^$new_hash]]
+       while {[gets $fd line] > 0} {
+               set abbr [string range $line 0 7]
+               set subj [string range $line 41 end]
+diff --git a/git-gui/lib/choose_repository.tcl b/git-gui/lib/choose_repository.tcl
+index af1fee7..76224d9 100644
+--- a/git-gui/lib/choose_repository.tcl
++++ b/git-gui/lib/choose_repository.tcl
+@@ -662,8 +662,8 @@ method _do_clone2 {} {
+                       set pwd [pwd]
+                       if {[catch {
+                               file mkdir [gitdir objects info]
+-                              set f_in [open [file join $objdir info alternates] r]
+-                              set f_cp [open [gitdir objects info alternates] w]
++                              set f_in [safe_open_file [file join $objdir info alternates] r]
++                              set f_cp [safe_open_file [gitdir objects info alternates] w]
+                               fconfigure $f_in -translation binary -encoding binary
+                               fconfigure $f_cp -translation binary -encoding binary
+                               cd $objdir
+@@ -752,7 +752,7 @@ method _do_clone2 {} {
+                       [cb _do_clone_tags]
+       }
+       shared {
+-              set fd [open [gitdir objects info alternates] w]
++              set fd [safe_open_file [gitdir objects info alternates] w]
+               fconfigure $fd -translation binary
+               puts $fd $objdir
+               close $fd
+@@ -785,8 +785,8 @@ method _copy_files {objdir tocopy} {
+       }
+       foreach p $tocopy {
+               if {[catch {
+-                              set f_in [open [file join $objdir $p] r]
+-                              set f_cp [open [file join .git objects $p] w]
++                              set f_in [safe_open_file [file join $objdir $p] r]
++                              set f_cp [safe_open_file [file join .git objects $p] w]
+                               fconfigure $f_in -translation binary -encoding binary
+                               fconfigure $f_cp -translation binary -encoding binary
+@@ -843,12 +843,12 @@ method _clone_refs {} {
+               error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
+               return 0
+       }
+-      set fd_in [git_read for-each-ref \
++      set fd_in [git_read [list for-each-ref \
+               --tcl \
+-              {--format=list %(refname) %(objectname) %(*objectname)}]
++              {--format=list %(refname) %(objectname) %(*objectname)}]]
+       cd $pwd
+-      set fd [open [gitdir packed-refs] w]
++      set fd [safe_open_file [gitdir packed-refs] w]
+       fconfigure $fd -translation binary
+       puts $fd "# pack-refs with: peeled"
+       while {[gets $fd_in line] >= 0} {
+@@ -902,7 +902,7 @@ method _do_clone_full_end {ok} {
+               set HEAD {}
+               if {[file exists [gitdir FETCH_HEAD]]} {
+-                      set fd [open [gitdir FETCH_HEAD] r]
++                      set fd [safe_open_file [gitdir FETCH_HEAD] r]
+                       while {[gets $fd line] >= 0} {
+                               if {[regexp "^(.{40})\t\t" $line line HEAD]} {
+                                       break
+@@ -978,13 +978,14 @@ method _do_clone_checkout {HEAD} {
+               [mc "files"]]
+       set readtree_err {}
+-      set fd [git_read --stderr read-tree \
++      set fd [git_read [list read-tree \
+               -m \
+               -u \
+               -v \
+               HEAD \
+               HEAD \
+-              ]
++              ] \
++              [list 2>@1]]
+       fconfigure $fd -blocking 0 -translation binary
+       fileevent $fd readable [cb _readtree_wait $fd]
+ }
+diff --git a/git-gui/lib/choose_rev.tcl b/git-gui/lib/choose_rev.tcl
+index 6dae793..8ae7e8a 100644
+--- a/git-gui/lib/choose_rev.tcl
++++ b/git-gui/lib/choose_rev.tcl
+@@ -146,14 +146,14 @@ constructor _new {path unmerged_only title} {
+       append fmt { %(*subject)}
+       append fmt {]}
+       set all_refn [list]
+-      set fr_fd [git_read for-each-ref \
++      set fr_fd [git_read [list for-each-ref \
+               --tcl \
+               --sort=-taggerdate \
+               --format=$fmt \
+               refs/heads \
+               refs/remotes \
+               refs/tags \
+-              ]
++              ]]
+       fconfigure $fr_fd -translation lf -encoding utf-8
+       while {[gets $fr_fd line] > 0} {
+               set line [eval $line]
+@@ -176,7 +176,7 @@ constructor _new {path unmerged_only title} {
+       close $fr_fd
+       if {$unmerged_only} {
+-              set fr_fd [git_read rev-list --all ^$::HEAD]
++              set fr_fd [git_read [list rev-list --all ^$::HEAD]]
+               while {[gets $fr_fd sha1] > 0} {
+                       if {[catch {set rlst $cmt_refn($sha1)}]} continue
+                       foreach refn $rlst {
+@@ -579,7 +579,7 @@ method _reflog_last {name} {
+       set last {}
+       if {[catch {set last [file mtime [gitdir $name]]}]
+-      && ![catch {set g [open [gitdir logs $name] r]}]} {
++      && ![catch {set g [safe_open_file [gitdir logs $name] r]}]} {
+               fconfigure $g -translation binary
+               while {[gets $g line] >= 0} {
+                       if {[regexp {> ([1-9][0-9]*) } $line line when]} {
+diff --git a/git-gui/lib/commit.tcl b/git-gui/lib/commit.tcl
+index 11379f8..bb6056d 100644
+--- a/git-gui/lib/commit.tcl
++++ b/git-gui/lib/commit.tcl
+@@ -27,7 +27,7 @@ You are currently in the middle of a merge that has not been fully completed.  Y
+       if {[catch {
+                       set name ""
+                       set email ""
+-                      set fd [git_read cat-file commit $curHEAD]
++                      set fd [git_read [list cat-file commit $curHEAD]]
+                       fconfigure $fd -encoding binary -translation lf
+                       # By default commits are assumed to be in utf-8
+                       set enc utf-8
+@@ -225,7 +225,7 @@ A good commit message has the following format:
+       # -- Build the message file.
+       #
+       set msg_p [gitdir GITGUI_EDITMSG]
+-      set msg_wt [open $msg_p w]
++      set msg_wt [safe_open_file $msg_p w]
+       fconfigure $msg_wt -translation lf
+       setup_commit_encoding $msg_wt
+       puts $msg_wt $msg
+@@ -325,7 +325,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {
+ proc commit_writetree {curHEAD msg_p} {
+       ui_status [mc "Committing changes..."]
+-      set fd_wt [git_read write-tree]
++      set fd_wt [git_read [list write-tree]]
+       fileevent $fd_wt readable \
+               [list commit_committree $fd_wt $curHEAD $msg_p]
+ }
+@@ -350,7 +350,7 @@ proc commit_committree {fd_wt curHEAD msg_p} {
+       # -- Verify this wasn't an empty change.
+       #
+       if {$commit_type eq {normal}} {
+-              set fd_ot [git_read cat-file commit $PARENT]
++              set fd_ot [git_read [list cat-file commit $PARENT]]
+               fconfigure $fd_ot -encoding binary -translation lf
+               set old_tree [gets $fd_ot]
+               close $fd_ot
+@@ -388,8 +388,8 @@ A rescan will be automatically started now.
+       foreach p [concat $PARENT $MERGE_HEAD] {
+               lappend cmd -p $p
+       }
+-      lappend cmd <$msg_p
+-      if {[catch {set cmt_id [eval git $cmd]} err]} {
++      set msgtxt [list <$msg_p]
++      if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} {
+               catch {file delete $msg_p}
+               error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
+               ui_status [mc "Commit failed."]
+@@ -409,7 +409,7 @@ A rescan will be automatically started now.
+       if {$commit_type ne {normal}} {
+               append reflogm " ($commit_type)"
+       }
+-      set msg_fd [open $msg_p r]
++      set msg_fd [safe_open_file $msg_p r]
+       setup_commit_encoding $msg_fd 1
+       gets $msg_fd subject
+       close $msg_fd
+diff --git a/git-gui/lib/console.tcl b/git-gui/lib/console.tcl
+index bb6b9c8..4715ce9 100644
+--- a/git-gui/lib/console.tcl
++++ b/git-gui/lib/console.tcl
+@@ -92,10 +92,9 @@ method _init {} {
+ method exec {cmd {after {}}} {
+       if {[lindex $cmd 0] eq {git}} {
+-              set fd_f [eval git_read --stderr [lrange $cmd 1 end]]
++              set fd_f [git_read [lrange $cmd 1 end] [list 2>@1]]
+       } else {
+-              lappend cmd 2>@1
+-              set fd_f [_open_stdout_stderr $cmd]
++              set fd_f [safe_open_command $cmd [list 2>@1]]
+       }
+       fconfigure $fd_f -blocking 0 -translation binary
+       fileevent $fd_f readable [cb _read $fd_f $after]
+diff --git a/git-gui/lib/database.tcl b/git-gui/lib/database.tcl
+index 8578308..1fc0ea0 100644
+--- a/git-gui/lib/database.tcl
++++ b/git-gui/lib/database.tcl
+@@ -3,7 +3,7 @@
+ proc do_stats {} {
+       global use_ttk NS
+-      set fd [git_read count-objects -v]
++      set fd [git_read [list count-objects -v]]
+       while {[gets $fd line] > 0} {
+               if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
+                       set stats($name) $value
+diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
+index 871ad48..8ec740e 100644
+--- a/git-gui/lib/diff.tcl
++++ b/git-gui/lib/diff.tcl
+@@ -202,7 +202,7 @@ proc show_other_diff {path w m cont_info} {
+                                       set sz [string length $content]
+                               }
+                               file {
+-                                      set fd [open $path r]
++                                      set fd [safe_open_file $path r]
+                                       fconfigure $fd \
+                                               -eofchar {} \
+                                               -encoding [get_path_encoding $path]
+@@ -226,7 +226,7 @@ proc show_other_diff {path w m cont_info} {
+                       $ui_diff insert end \
+                               "* [mc "Git Repository (subproject)"]\n" \
+                               d_info
+-              } elseif {![catch {set type [exec file $path]}]} {
++              } elseif {![catch {set type [safe_exec [list file $path]]}]} {
+                       set n [string length $path]
+                       if {[string equal -length $n $path $type]} {
+                               set type [string range $type $n end]
+@@ -338,7 +338,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
+               }
+       }
+-      if {[catch {set fd [eval git_read --nice $cmd]} err]} {
++      if {[catch {set fd [git_read_nice $cmd]} err]} {
+               set diff_active 0
+               unlock_index
+               ui_status [mc "Unable to display %s" [escape_path $path]]
+@@ -617,7 +617,7 @@ proc apply_or_revert_hunk {x y revert} {
+       if {[catch {
+               set enc [get_path_encoding $current_diff_path]
+-              set p [eval git_write $apply_cmd]
++              set p [git_write $apply_cmd]
+               fconfigure $p -translation binary -encoding $enc
+               puts -nonewline $p $wholepatch
+               close $p} err]} {
+@@ -853,7 +853,7 @@ proc apply_or_revert_range_or_line {x y revert} {
+       if {[catch {
+               set enc [get_path_encoding $current_diff_path]
+-              set p [eval git_write $apply_cmd]
++              set p [git_write $apply_cmd]
+               fconfigure $p -translation binary -encoding $enc
+               puts -nonewline $p $current_diff_header
+               puts -nonewline $p $wholepatch
+@@ -890,7 +890,7 @@ proc undo_last_revert {} {
+       if {[catch {
+               set enc $last_revert_enc
+-              set p [eval git_write $apply_cmd]
++              set p [git_write $apply_cmd]
+               fconfigure $p -translation binary -encoding $enc
+               puts -nonewline $p $last_revert
+               close $p} err]} {
+diff --git a/git-gui/lib/index.tcl b/git-gui/lib/index.tcl
+index d2ec24b..857864f 100644
+--- a/git-gui/lib/index.tcl
++++ b/git-gui/lib/index.tcl
+@@ -75,7 +75,7 @@ proc update_indexinfo {msg path_list after} {
+       if {$batch > 25} {set batch 25}
+       set status_bar_operation [$::main_status start $msg [mc "files"]]
+-      set fd [git_write update-index -z --index-info]
++      set fd [git_write [list update-index -z --index-info]]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+@@ -144,7 +144,7 @@ proc update_index {msg path_list after} {
+       if {$batch > 25} {set batch 25}
+       set status_bar_operation [$::main_status start $msg [mc "files"]]
+-      set fd [git_write update-index --add --remove -z --stdin]
++      set fd [git_write [list update-index --add --remove -z --stdin]]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+@@ -218,13 +218,13 @@ proc checkout_index {msg path_list after capture_error} {
+       if {$batch > 25} {set batch 25}
+       set status_bar_operation [$::main_status start $msg [mc "files"]]
+-      set fd [git_write checkout-index \
++      set fd [git_write [list checkout-index \
+               --index \
+               --quiet \
+               --force \
+               -z \
+               --stdin \
+-              ]
++              ]]
+       fconfigure $fd \
+               -blocking 0 \
+               -buffering full \
+diff --git a/git-gui/lib/merge.tcl b/git-gui/lib/merge.tcl
+index 664803c..44c3f93 100644
+--- a/git-gui/lib/merge.tcl
++++ b/git-gui/lib/merge.tcl
+@@ -93,7 +93,7 @@ method _start {} {
+       set spec [$w_rev get_tracking_branch]
+       set cmit [$w_rev get_commit]
+-      set fh [open [gitdir FETCH_HEAD] w]
++      set fh [safe_open_file [gitdir FETCH_HEAD] w]
+       fconfigure $fh -translation lf
+       if {$spec eq {}} {
+               set remote .
+@@ -118,7 +118,7 @@ method _start {} {
+               set cmd [list git]
+               lappend cmd merge
+               lappend cmd --strategy=recursive
+-              lappend cmd [git fmt-merge-msg <[gitdir FETCH_HEAD]]
++              lappend cmd [git_redir [list fmt-merge-msg] [list <[gitdir FETCH_HEAD]]]
+               lappend cmd HEAD
+               lappend cmd $name
+       }
+@@ -239,7 +239,7 @@ Continue with resetting the current changes?"]
+       }
+       if {[ask_popup $op_question] eq {yes}} {
+-              set fd [git_read --stderr read-tree --reset -u -v HEAD]
++              set fd [git_read [list read-tree --reset -u -v HEAD] [list 2>@1]]
+               fconfigure $fd -blocking 0 -translation binary
+               set status_bar_operation [$::main_status \
+                       start \
+diff --git a/git-gui/lib/mergetool.tcl b/git-gui/lib/mergetool.tcl
+index e688b01..6b26726 100644
+--- a/git-gui/lib/mergetool.tcl
++++ b/git-gui/lib/mergetool.tcl
+@@ -88,7 +88,7 @@ proc merge_load_stages {path cont} {
+       set merge_stages(3) {}
+       set merge_stages_buf {}
+-      set merge_stages_fd [eval git_read ls-files -u -z -- {$path}]
++      set merge_stages_fd [git_read [list ls-files -u -z -- $path]]
+       fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
+       fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont]
+@@ -293,7 +293,7 @@ proc merge_tool_get_stages {target stages} {
+       foreach fname $stages {
+               if {$merge_stages($i) eq {}} {
+                       file delete $fname
+-                      catch { close [open $fname w] }
++                      catch { close [safe_open_file $fname w] }
+               } else {
+                       # A hack to support autocrlf properly
+                       git checkout-index -f --stage=$i -- $target
+@@ -343,9 +343,9 @@ proc merge_tool_start {cmdline target backup stages} {
+       # Force redirection to avoid interpreting output on stderr
+       # as an error, and launch the tool
+-      lappend cmdline {2>@1}
++      set redir [list {2>@1}]
+-      if {[catch { set mtool_fd [_open_stdout_stderr $cmdline] } err]} {
++      if {[catch { set mtool_fd [safe_open_command $cmdline $redir] } err]} {
+               delete_temp_files $mtool_tmpfiles
+               error_popup [mc "Could not start the merge tool:\n\n%s" $err]
+               return
+diff --git a/git-gui/lib/remote.tcl b/git-gui/lib/remote.tcl
+index ef77ed7..cf796d1 100644
+--- a/git-gui/lib/remote.tcl
++++ b/git-gui/lib/remote.tcl
+@@ -32,7 +32,7 @@ proc all_tracking_branches {} {
+       }
+       if {$pat ne {}} {
+-              set fd [eval git_read for-each-ref --format=%(refname) $cmd]
++              set fd [git_read [concat for-each-ref --format=%(refname) $cmd]]
+               while {[gets $fd n] > 0} {
+                       foreach spec $pat {
+                               set dst [string range [lindex $spec 0] 0 end-2]
+@@ -75,7 +75,7 @@ proc load_all_remotes {} {
+               foreach name $all_remotes {
+                       catch {
+-                              set fd [open [file join $rm_dir $name] r]
++                              set fd [safe_open_file [file join $rm_dir $name] r]
+                               while {[gets $fd line] >= 0} {
+                                       if {[regexp {^URL:[     ]*(.+)$} $line line url]} {
+                                               set remote_url($name) $url
+@@ -145,7 +145,7 @@ proc add_fetch_entry {r} {
+               }
+       } else {
+               catch {
+-                      set fd [open [gitdir remotes $r] r]
++                      set fd [safe_open_file [gitdir remotes $r] r]
+                       while {[gets $fd n] >= 0} {
+                               if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
+                                       set enable 1
+@@ -182,7 +182,7 @@ proc add_push_entry {r} {
+               }
+       } else {
+               catch {
+-                      set fd [open [gitdir remotes $r] r]
++                      set fd [safe_open_file [gitdir remotes $r] r]
+                       while {[gets $fd n] >= 0} {
+                               if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
+                                       set enable 1
+diff --git a/git-gui/lib/remote_branch_delete.tcl b/git-gui/lib/remote_branch_delete.tcl
+index 5ba9fca..c8c99b1 100644
+--- a/git-gui/lib/remote_branch_delete.tcl
++++ b/git-gui/lib/remote_branch_delete.tcl
+@@ -308,7 +308,7 @@ method _load {cache uri} {
+               set full_list [list]
+               set head_cache($cache) [list]
+               set full_cache($cache) [list]
+-              set active_ls [git_read ls-remote $uri]
++              set active_ls [git_read [list ls-remote $uri]]
+               fconfigure $active_ls \
+                       -blocking 0 \
+                       -translation lf \
+diff --git a/git-gui/lib/shortcut.tcl b/git-gui/lib/shortcut.tcl
+index 97d1d7a..d97be99 100644
+--- a/git-gui/lib/shortcut.tcl
++++ b/git-gui/lib/shortcut.tcl
+@@ -12,7 +12,7 @@ proc do_windows_shortcut {} {
+                       set fn ${fn}.lnk
+               }
+               # Use git-gui.exe if available (ie: git-for-windows)
+-              set cmdLine [auto_execok git-gui.exe]
++              set cmdLine [list [_which git-gui]]
+               if {$cmdLine eq {}} {
+                       set cmdLine [list [info nameofexecutable] \
+                                                        [file normalize $::argv0]]
+@@ -30,7 +30,7 @@ proc do_cygwin_shortcut {} {
+       global argv0 _gitworktree
+       if {[catch {
+-              set desktop [exec cygpath \
++              set desktop [safe_exec [list cygpath \
+                       --windows \
+                       --absolute \
+                       --long-name \
+@@ -48,14 +48,14 @@ proc do_cygwin_shortcut {} {
+                       set fn ${fn}.lnk
+               }
+               if {[catch {
+-                              set sh [exec cygpath \
++                              set sh [safe_exec [list cygpath \
+                                       --windows \
+                                       --absolute \
+-                                      /bin/sh.exe]
+-                              set me [exec cygpath \
++                                      /bin/sh.exe]]
++                              set me [safe_exec [list cygpath \
+                                       --unix \
+                                       --absolute \
+-                                      $argv0]
++                                      $argv0]]
+                               win32_create_lnk $fn [list \
+                                       $sh -c \
+                                       "CHERE_INVOKING=1 source /etc/profile;[sq $me] &" \
+@@ -86,7 +86,7 @@ proc do_macosx_app {} {
+                               file mkdir $MacOS
+-                              set fd [open [file join $Contents Info.plist] w]
++                              set fd [safe_open_file [file join $Contents Info.plist] w]
+                               puts $fd {<?xml version="1.0" encoding="UTF-8"?>
+ <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+ <plist version="1.0">
+@@ -111,7 +111,7 @@ proc do_macosx_app {} {
+ </plist>}
+                               close $fd
+-                              set fd [open $exe w]
++                              set fd [safe_open_file $exe w]
+                               puts $fd "#!/bin/sh"
+                               foreach name [lsort [array names env]] {
+                                       set value $env($name)
+diff --git a/git-gui/lib/sshkey.tcl b/git-gui/lib/sshkey.tcl
+index 589ff8f..c3e681b 100644
+--- a/git-gui/lib/sshkey.tcl
++++ b/git-gui/lib/sshkey.tcl
+@@ -7,7 +7,7 @@ proc find_ssh_key {} {
+               ~/.ssh/id_rsa.pub ~/.ssh/identity.pub
+       } {
+               if {[file exists $name]} {
+-                      set fh    [open $name r]
++                      set fh    [safe_open_file $name r]
+                       set cont  [read $fh]
+                       close $fh
+                       return [list $name $cont]
+@@ -83,9 +83,10 @@ proc make_ssh_key {w} {
+       set sshkey_title [mc "Generating..."]
+       $w.header.gen configure -state disabled
+-      set cmdline [list sh -c {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
++      set cmdline [list [shellpath] -c \
++              {echo | ssh-keygen -q -t rsa -f ~/.ssh/id_rsa 2>&1}]
+-      if {[catch { set sshkey_fd [_open_stdout_stderr $cmdline] } err]} {
++      if {[catch { set sshkey_fd [safe_open_command $cmdline] } err]} {
+               error_popup [mc "Could not start ssh-keygen:\n\n%s" $err]
+               return
+       }
+diff --git a/git-gui/lib/tools.tcl b/git-gui/lib/tools.tcl
+index 413f1a1..48fddfd 100644
+--- a/git-gui/lib/tools.tcl
++++ b/git-gui/lib/tools.tcl
+@@ -110,14 +110,14 @@ proc tools_exec {fullname} {
+       set cmdline $repo_config(guitool.$fullname.cmd)
+       if {[is_config_true "guitool.$fullname.noconsole"]} {
+-              tools_run_silent [list sh -c $cmdline] \
++              tools_run_silent [list [shellpath] -c $cmdline] \
+                                [list tools_complete $fullname {}]
+       } else {
+               regsub {/} $fullname { / } title
+               set w [console::new \
+                       [mc "Tool: %s" $title] \
+                       [mc "Running: %s" $cmdline]]
+-              console::exec $w [list sh -c $cmdline] \
++              console::exec $w [list [shellpath] -c $cmdline] \
+                                [list tools_complete $fullname $w]
+       }
+@@ -130,8 +130,7 @@ proc tools_exec {fullname} {
+ }
+ proc tools_run_silent {cmd after} {
+-      lappend cmd 2>@1
+-      set fd [_open_stdout_stderr $cmd]
++      set fd [safe_open_command $cmd [list 2>@1]]
+       fconfigure $fd -blocking 0 -translation binary
+       fileevent $fd readable [list tools_consume_input $fd $after]
+diff --git a/git-gui/lib/win32.tcl b/git-gui/lib/win32.tcl
+index db91ab8..3aedae2 100644
+--- a/git-gui/lib/win32.tcl
++++ b/git-gui/lib/win32.tcl
+@@ -2,11 +2,11 @@
+ # Copyright (C) 2007 Shawn Pearce
+ proc win32_read_lnk {lnk_path} {
+-      return [exec cscript.exe \
++      return [safe_exec [list cscript.exe \
+               /E:jscript \
+               /nologo \
+               [file join $::oguilib win32_shortcut.js] \
+-              $lnk_path]
++              $lnk_path]]
+ }
+ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
+@@ -15,12 +15,13 @@ proc win32_create_lnk {lnk_path lnk_exec lnk_dir} {
+       set lnk_args [lrange $lnk_exec 1 end]
+       set lnk_exec [lindex $lnk_exec 0]
+-      eval [list exec wscript.exe \
++      set cmd [list wscript.exe \
+               /E:jscript \
+               /nologo \
+               [file nativename [file join $oguilib win32_shortcut.js]] \
+               $lnk_path \
+               [file nativename [file join $oguilib git-gui.ico]] \
+               $lnk_dir \
+-              $lnk_exec] $lnk_args
++              $lnk_exec]
++      safe_exec [concat $cmd $lnk_args]
+ }
+diff --git a/gitk-git/gitk b/gitk-git/gitk
+index 23d9dd1..1c8c9c0 100755
+--- a/gitk-git/gitk
++++ b/gitk-git/gitk
+@@ -9,6 +9,92 @@ exec wish "$0" -- "$@"
+ package require Tk
++
++# Wrap exec/open to sanitize arguments
++
++# unsafe arguments begin with redirections or the pipe or background operators
++proc is_arg_unsafe {arg} {
++    regexp {^([<|>&]|2>)} $arg
++}
++
++proc make_arg_safe {arg} {
++    if {[is_arg_unsafe $arg]} {
++        set arg [file join . $arg]
++    }
++    return $arg
++}
++
++proc make_arglist_safe {arglist} {
++    set res {}
++    foreach arg $arglist {
++        lappend res [make_arg_safe $arg]
++    }
++    return $res
++}
++
++# executes one command
++# no redirections or pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# calls `exec` and returns its value
++proc safe_exec {cmd} {
++    eval exec [make_arglist_safe $cmd]
++}
++
++# executes one command with redirections
++# no pipelines are possible
++# cmd is a list that specifies the command and its arguments
++# redir is a list that specifies redirections (output, background, constant(!) commands)
++# calls `exec` and returns its value
++proc safe_exec_redirect {cmd redir} {
++    eval exec [make_arglist_safe $cmd] $redir
++}
++
++proc safe_open_file {filename flags} {
++    # a file name starting with "|" would attempt to run a process
++    # but such a file name must be treated as a relative path
++    # hide the "|" behind "./"
++    if {[string index $filename 0] eq "|"} {
++        set filename [file join . $filename]
++    }
++    open $filename $flags
++}
++
++# opens a command pipeline for reading
++# cmd is a list that specifies the command and its arguments
++# calls `open` and returns the file id
++proc safe_open_command {cmd} {
++    open |[make_arglist_safe $cmd] r
++}
++
++# opens a command pipeline for reading and writing
++# cmd is a list that specifies the command and its arguments
++# calls `open` and returns the file id
++proc safe_open_command_rw {cmd} {
++    open |[make_arglist_safe $cmd] r+
++}
++
++# opens a command pipeline for reading with redirections
++# cmd is a list that specifies the command and its arguments
++# redir is a list that specifies redirections
++# calls `open` and returns the file id
++proc safe_open_command_redirect {cmd redir} {
++    set cmd [make_arglist_safe $cmd]
++    open |[concat $cmd $redir] r
++}
++
++# opens a pipeline with several commands for reading
++# cmds is a list of lists, each of which specifies a command and its arguments
++# calls `open` and returns the file id
++proc safe_open_pipeline {cmds} {
++    set cmd {}
++    foreach subcmd $cmds {
++        set cmd [concat $cmd | [make_arglist_safe $subcmd]]
++    }
++    open $cmd r
++}
++
++# End exec/open wrappers
++
+ proc hasworktree {} {
+     return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
+                   [exec git rev-parse --is-inside-git-dir] == "false"}]
+@@ -134,7 +220,7 @@ proc unmerged_files {files} {
+     set mlist {}
+     set nr_unmerged 0
+     if {[catch {
+-        set fd [open "| git ls-files -u" r]
++        set fd [safe_open_command {git ls-files -u}]
+     } err]} {
+         show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
+         exit 1
+@@ -296,7 +382,7 @@ proc parseviewrevs {view revs} {
+     } elseif {[lsearch -exact $revs --all] >= 0} {
+         lappend revs HEAD
+     }
+-    if {[catch {set ids [eval exec git rev-parse $revs]} err]} {
++    if {[catch {set ids [safe_exec [concat git rev-parse $revs]]} err]} {
+         # we get stdout followed by stderr in $err
+         # for an unknown rev, git rev-parse echoes it and then errors out
+         set errlines [split $err "\n"]
+@@ -374,7 +460,7 @@ proc start_rev_list {view} {
+     set args $viewargs($view)
+     if {$viewargscmd($view) ne {}} {
+         if {[catch {
+-            set str [exec sh -c $viewargscmd($view)]
++            set str [safe_exec [list sh -c $viewargscmd($view)]]
+         } err]} {
+             error_popup "[mc "Error executing --argscmd command:"] $err"
+             return 0
+@@ -405,14 +491,16 @@ proc start_rev_list {view} {
+         if {$revs eq {}} {
+             return 0
+         }
+-        set args [concat $vflags($view) $revs]
++        set args $vflags($view)
+     } else {
++        set revs {}
+         set args $vorigargs($view)
+     }
+     if {[catch {
+-        set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+-                        --parents --boundary $args "--" $files] r]
++        set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
++                        --parents --boundary $args --stdin] \
++                        [list "<<[join [concat $revs "--" $files] "\n"]"]]
+     } err]} {
+         error_popup "[mc "Error executing git log:"] $err"
+         return 0
+@@ -446,9 +534,9 @@ proc stop_instance {inst} {
+         set pid [pid $fd]
+         if {$::tcl_platform(platform) eq {windows}} {
+-            exec taskkill /pid $pid
++            safe_exec [list taskkill /pid $pid]
+         } else {
+-            exec kill $pid
++            safe_exec [list kill $pid]
+         }
+     }
+     catch {close $fd}
+@@ -554,13 +642,18 @@ proc updatecommits {} {
+             set revs $newrevs
+             set vposids($view) [lsort -unique [concat $oldpos $vposids($view)]]
+         }
+-        set args [concat $vflags($view) $revs --not $oldpos]
++        set args $vflags($view)
++        foreach r $oldpos {
++                lappend revs "^$r"
++        }
+     } else {
++        set revs {}
+         set args $vorigargs($view)
+     }
+     if {[catch {
+-        set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
+-                        --parents --boundary $args "--" $vfilelimit($view)] r]
++        set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
++                        --parents --boundary $args --stdin] \
++                        [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]]
+     } err]} {
+         error_popup "[mc "Error executing git log:"] $err"
+         return
+@@ -1527,8 +1620,8 @@ proc getcommitlines {fd inst view updating}  {
+             # and if we already know about it, using the rewritten
+             # parent as a substitute parent for $id's children.
+             if {![catch {
+-                set rwid [exec git rev-list --first-parent --max-count=1 \
+-                              $id -- $vfilelimit($view)]
++                set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \
++                              $id -- $vfilelimit($view)]]
+             }]} {
+                 if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
+                     # use $rwid in place of $id
+@@ -1648,7 +1741,7 @@ proc do_readcommit {id} {
+     global tclencoding
+     # Invoke git-log to handle automatic encoding conversion
+-    set fd [open [concat | git log --no-color --pretty=raw -1 $id] r]
++    set fd [safe_open_command [concat git log --no-color --pretty=raw -1 $id]]
+     # Read the results using i18n.logoutputencoding
+     fconfigure $fd -translation lf -eofchar {}
+     if {$tclencoding != {}} {
+@@ -1784,7 +1877,7 @@ proc readrefs {} {
+     foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
+         unset -nocomplain $v
+     }
+-    set refd [open [list | git show-ref -d] r]
++    set refd [safe_open_command [list git show-ref -d]]
+     if {$tclencoding != {}} {
+         fconfigure $refd -encoding $tclencoding
+     }
+@@ -1832,7 +1925,7 @@ proc readrefs {} {
+     set selectheadid {}
+     if {$selecthead ne {}} {
+         catch {
+-            set selectheadid [exec git rev-parse --verify $selecthead]
++            set selectheadid [safe_exec [list git rev-parse --verify $selecthead]]
+         }
+     }
+ }
+@@ -2092,7 +2185,7 @@ proc makewindow {} {
+             {mc "Reread re&ferences" command rereadrefs}
+             {mc "&List references" command showrefs -accelerator F2}
+             {xx "" separator}
+-            {mc "Start git &gui" command {exec git gui &}}
++            {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}}
+             {xx "" separator}
+             {mc "&Quit" command doquit -accelerator Meta1-Q}
+         }}
+@@ -2874,7 +2967,7 @@ proc savestuff {w} {
+     set remove_tmp 0
+     if {[catch {
+         set try_count 0
+-        while {[catch {set f [open $config_file_tmp {WRONLY CREAT EXCL}]}]} {
++        while {[catch {set f [safe_open_file $config_file_tmp {WRONLY CREAT EXCL}]}]} {
+             if {[incr try_count] > 50} {
+                 error "Unable to write config file: $config_file_tmp exists"
+             }
+@@ -2955,9 +3048,9 @@ proc savestuff {w} {
+ proc resizeclistpanes {win w} {
+     global oldwidth oldsash use_ttk
+     if {[info exists oldwidth($win)]} {
+-      if {[info exists oldsash($win)]} {
+-          set s0 [lindex $oldsash($win) 0]
+-          set s1 [lindex $oldsash($win) 1]
++        if {[info exists oldsash($win)]} {
++            set s0 [lindex $oldsash($win) 0]
++            set s1 [lindex $oldsash($win) 1]
+         } elseif {$use_ttk} {
+             set s0 [$win sashpos 0]
+             set s1 [$win sashpos 1]
+@@ -2991,8 +3084,10 @@ proc resizeclistpanes {win w} {
+         } else {
+             $win sash place 0 $sash0 [lindex $s0 1]
+             $win sash place 1 $sash1 [lindex $s1 1]
++            set sash0 [list $sash0 [lindex $s0 1]]
++            set sash1 [list $sash1 [lindex $s1 1]]
+         }
+-      set oldsash($win) [list $sash0 $sash1]
++        set oldsash($win) [list $sash0 $sash1]
+     }
+     set oldwidth($win) $w
+ }
+@@ -3000,8 +3095,8 @@ proc resizeclistpanes {win w} {
+ proc resizecdetpanes {win w} {
+     global oldwidth oldsash use_ttk
+     if {[info exists oldwidth($win)]} {
+-      if {[info exists oldsash($win)]} {
+-          set s0 $oldsash($win)
++        if {[info exists oldsash($win)]} {
++            set s0 $oldsash($win)
+         } elseif {$use_ttk} {
+             set s0 [$win sashpos 0]
+         } else {
+@@ -3023,8 +3118,9 @@ proc resizecdetpanes {win w} {
+             $win sashpos 0 $sash0
+         } else {
+             $win sash place 0 $sash0 [lindex $s0 1]
++            set sash0 [list $sash0 [lindex $s0 1]]
+         }
+-      set oldsash($win) $sash0
++        set oldsash($win) $sash0
+     }
+     set oldwidth($win) $w
+ }
+@@ -3587,7 +3683,7 @@ proc gitknewtmpdir {} {
+             set tmpdir $gitdir
+         }
+         set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"]
+-        if {[catch {set gitktmpdir [exec mktemp -d $gitktmpformat]}]} {
++        if {[catch {set gitktmpdir [safe_exec [list mktemp -d $gitktmpformat]]}]} {
+             set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
+         }
+         if {[catch {file mkdir $gitktmpdir} err]} {
+@@ -3609,7 +3705,7 @@ proc gitknewtmpdir {} {
+ proc save_file_from_commit {filename output what} {
+     global nullfile
+-    if {[catch {exec git show $filename -- > $output} err]} {
++    if {[catch {safe_exec_redirect [list git show $filename --] [list > $output]} err]} {
+         if {[string match "fatal: bad revision *" $err]} {
+             return $nullfile
+         }
+@@ -3674,7 +3770,7 @@ proc external_diff {} {
+     if {$difffromfile ne {} && $difftofile ne {}} {
+         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]
+-        if {[catch {set fl [open |$cmd r]} err]} {
++        if {[catch {set fl [safe_open_command $cmd]} err]} {
+             file delete -force $diffdir
+             error_popup "$extdifftool: [mc "command failed:"] $err"
+         } else {
+@@ -3778,7 +3874,7 @@ proc external_blame_diff {} {
+ # Find the SHA1 ID of the blob for file $fname in the index
+ # at stage 0 or 2
+ proc index_sha1 {fname} {
+-    set f [open [list | git ls-files -s $fname] r]
++    set f [safe_open_command [list git ls-files -s $fname]]
+     while {[gets $f line] >= 0} {
+         set info [lindex [split $line "\t"] 0]
+         set stage [lindex $info 2]
+@@ -3838,7 +3934,7 @@ proc external_blame {parent_idx {line {}}} {
+     # being given an absolute path...
+     set f [make_relative $f]
+     lappend cmdline $base_commit $f
+-    if {[catch {eval exec $cmdline &} err]} {
++    if {[catch {safe_exec_redirect $cmdline [list &]} err]} {
+         error_popup "[mc "git gui blame: command failed:"] $err"
+     }
+ }
+@@ -3866,7 +3962,7 @@ proc show_line_source {} {
+                 # must be a merge in progress...
+                 if {[catch {
+                     # get the last line from .git/MERGE_HEAD
+-                    set f [open [file join $gitdir MERGE_HEAD] r]
++                    set f [safe_open_file [file join $gitdir MERGE_HEAD] r]
+                     set id [lindex [split [read $f] "\n"] end-1]
+                     close $f
+                 } err]} {
+@@ -3889,19 +3985,17 @@ proc show_line_source {} {
+         }
+         set line [lindex $h 1]
+     }
+-    set blameargs {}
++    set blamefile [file join $cdup $flist_menu_file]
+     if {$from_index ne {}} {
+-        lappend blameargs | git cat-file blob $from_index
+-    }
+-    lappend blameargs | git blame -p -L$line,+1
+-    if {$from_index ne {}} {
+-        lappend blameargs --contents -
++        set blameargs [list \
++            [list git cat-file blob $from_index] \
++            [list git blame -p -L$line,+1 --contents - -- $blamefile]]
+     } else {
+-        lappend blameargs $id
++        set blameargs [list \
++            [list git blame -p -L$line,+1 $id -- $blamefile]]
+     }
+-    lappend blameargs -- [file join $cdup $flist_menu_file]
+     if {[catch {
+-        set f [open $blameargs r]
++        set f [safe_open_pipeline $blameargs]
+     } err]} {
+         error_popup [mc "Couldn't start git blame: %s" $err]
+         return
+@@ -4826,8 +4920,8 @@ proc do_file_hl {serial} {
+         # must be "containing:", i.e. we're searching commit info
+         return
+     }
+-    set cmd [concat | git diff-tree -r -s --stdin $gdtargs]
+-    set filehighlight [open $cmd r+]
++    set cmd [concat git diff-tree -r -s --stdin $gdtargs]
++    set filehighlight [safe_open_command_rw $cmd]
+     fconfigure $filehighlight -blocking 0
+     filerun $filehighlight readfhighlight
+     set fhl_list {}
+@@ -5256,8 +5350,8 @@ proc get_viewmainhead {view} {
+     global viewmainheadid vfilelimit viewinstances mainheadid
+     catch {
+-        set rfd [open [concat | git rev-list -1 $mainheadid \
+-                           -- $vfilelimit($view)] r]
++        set rfd [safe_open_command [concat git rev-list -1 $mainheadid \
++                           -- $vfilelimit($view)]]
+         set j [reg_instance $rfd]
+         lappend viewinstances($view) $j
+         fconfigure $rfd -blocking 0
+@@ -5322,14 +5416,14 @@ proc dodiffindex {} {
+     if {!$showlocalchanges || !$hasworktree} return
+     incr lserial
+     if {[package vcompare $git_version "1.7.2"] >= 0} {
+-        set cmd "|git diff-index --cached --ignore-submodules=dirty HEAD"
++        set cmd "git diff-index --cached --ignore-submodules=dirty HEAD"
+     } else {
+-        set cmd "|git diff-index --cached HEAD"
++        set cmd "git diff-index --cached HEAD"
+     }
+     if {$vfilelimit($curview) ne {}} {
+         set cmd [concat $cmd -- $vfilelimit($curview)]
+     }
+-    set fd [open $cmd r]
++    set fd [safe_open_command $cmd]
+     fconfigure $fd -blocking 0
+     set i [reg_instance $fd]
+     filerun $fd [list readdiffindex $fd $lserial $i]
+@@ -5354,11 +5448,11 @@ proc readdiffindex {fd serial inst} {
+     }
+     # now see if there are any local changes not checked in to the index
+-    set cmd "|git diff-files"
++    set cmd "git diff-files"
+     if {$vfilelimit($curview) ne {}} {
+         set cmd [concat $cmd -- $vfilelimit($curview)]
+     }
+-    set fd [open $cmd r]
++    set fd [safe_open_command $cmd]
+     fconfigure $fd -blocking 0
+     set i [reg_instance $fd]
+     filerun $fd [list readdifffiles $fd $serial $i]
+@@ -7147,8 +7241,8 @@ proc browseweb {url} {
+     global web_browser
+     if {$web_browser eq {}} return
+-    # Use eval here in case $web_browser is a command plus some arguments
+-    if {[catch {eval exec $web_browser [list $url] &} err]} {
++    # Use concat here in case $web_browser is a command plus some arguments
++    if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} {
+         error_popup "[mc "Error starting web browser:"] $err"
+     }
+ }
+@@ -7650,13 +7744,13 @@ proc gettree {id} {
+     if {![info exists treefilelist($id)]} {
+         if {![info exists treepending]} {
+             if {$id eq $nullid} {
+-                set cmd [list | git ls-files]
++                set cmd [list git ls-files]
+             } elseif {$id eq $nullid2} {
+-                set cmd [list | git ls-files --stage -t]
++                set cmd [list git ls-files --stage -t]
+             } else {
+-                set cmd [list | git ls-tree -r $id]
++                set cmd [list git ls-tree -r $id]
+             }
+-            if {[catch {set gtf [open $cmd r]}]} {
++            if {[catch {set gtf [safe_open_command $cmd]}]} {
+                 return
+             }
+             set treepending $id
+@@ -7720,13 +7814,13 @@ proc showfile {f} {
+         return
+     }
+     if {$diffids eq $nullid} {
+-        if {[catch {set bf [open $f r]} err]} {
++        if {[catch {set bf [safe_open_file $f r]} err]} {
+             puts "oops, can't read $f: $err"
+             return
+         }
+     } else {
+         set blob [lindex $treeidlist($diffids) $i]
+-        if {[catch {set bf [open [concat | git cat-file blob $blob] r]} err]} {
++        if {[catch {set bf [safe_open_command [concat git cat-file blob $blob]]} err]} {
+             puts "oops, error reading blob $blob: $err"
+             return
+         }
+@@ -7876,7 +7970,7 @@ proc diffcmd {ids flags} {
+     if {$i >= 0} {
+         if {[llength $ids] > 1 && $j < 0} {
+             # comparing working directory with some specific revision
+-            set cmd [concat | git diff-index $flags]
++            set cmd [concat git diff-index $flags]
+             if {$i == 0} {
+                 lappend cmd -R [lindex $ids 1]
+             } else {
+@@ -7884,7 +7978,7 @@ proc diffcmd {ids flags} {
+             }
+         } else {
+             # comparing working directory with index
+-            set cmd [concat | git diff-files $flags]
++            set cmd [concat git diff-files $flags]
+             if {$j == 1} {
+                 lappend cmd -R
+             }
+@@ -7893,7 +7987,7 @@ proc diffcmd {ids flags} {
+         if {[package vcompare $git_version "1.7.2"] >= 0} {
+             set flags "$flags --ignore-submodules=dirty"
+         }
+-        set cmd [concat | git diff-index --cached $flags]
++        set cmd [concat git diff-index --cached $flags]
+         if {[llength $ids] > 1} {
+             # comparing index with specific revision
+             if {$j == 0} {
+@@ -7909,7 +8003,7 @@ proc diffcmd {ids flags} {
+         if {$log_showroot} {
+             lappend flags --root
+         }
+-        set cmd [concat | git diff-tree -r $flags $ids]
++        set cmd [concat git diff-tree -r $flags $ids]
+     }
+     return $cmd
+ }
+@@ -7921,7 +8015,7 @@ proc gettreediffs {ids} {
+     if {$limitdiffs && $vfilelimit($curview) ne {}} {
+             set cmd [concat $cmd -- $vfilelimit($curview)]
+     }
+-    if {[catch {set gdtf [open $cmd r]}]} return
++    if {[catch {set gdtf [safe_open_command $cmd]}]} return
+     set treepending $ids
+     set treediff {}
+@@ -8041,7 +8135,7 @@ proc getblobdiffs {ids} {
+     if {$limitdiffs && $vfilelimit($curview) ne {}} {
+         set cmd [concat $cmd -- $vfilelimit($curview)]
+     }
+-    if {[catch {set bdf [open $cmd r]} err]} {
++    if {[catch {set bdf [safe_open_command $cmd]} err]} {
+         error_popup [mc "Error getting diffs: %s" $err]
+         return
+     }
+@@ -8758,7 +8852,7 @@ proc gotocommit {} {
+                 set id [lindex $matches 0]
+             }
+         } else {
+-            if {[catch {set id [exec git rev-parse --verify $sha1string]}]} {
++            if {[catch {set id [safe_exec [list git rev-parse --verify $sha1string]]}]} {
+                 error_popup [mc "Revision %s is not known" $sha1string]
+                 return
+             }
+@@ -9064,10 +9158,8 @@ proc getpatchid {id} {
+     if {![info exists patchids($id)]} {
+         set cmd [diffcmd [list $id] {-p --root}]
+-        # trim off the initial "|"
+-        set cmd [lrange $cmd 1 end]
+         if {[catch {
+-            set x [eval exec $cmd | git patch-id]
++            set x [safe_exec_redirect $cmd [list | git patch-id]]
+             set patchids($id) [lindex $x 0]
+         }]} {
+             set patchids($id) "error"
+@@ -9163,14 +9255,14 @@ proc diffcommits {a b} {
+     set fna [file join $tmpdir "commit-[string range $a 0 7]"]
+     set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
+     if {[catch {
+-        exec git diff-tree -p --pretty $a >$fna
+-        exec git diff-tree -p --pretty $b >$fnb
++        safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna]
++        safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb]
+     } err]} {
+         error_popup [mc "Error writing commit to file: %s" $err]
+         return
+     }
+     if {[catch {
+-        set fd [open "| diff -U$diffcontext $fna $fnb" r]
++        set fd [safe_open_command "diff -U$diffcontext $fna $fnb"]
+     } err]} {
+         error_popup [mc "Error diffing commits: %s" $err]
+         return
+@@ -9310,10 +9402,7 @@ proc mkpatchgo {} {
+     set newid [$patchtop.tosha1 get]
+     set fname [$patchtop.fname get]
+     set cmd [diffcmd [list $oldid $newid] -p]
+-    # trim off the initial "|"
+-    set cmd [lrange $cmd 1 end]
+-    lappend cmd >$fname &
+-    if {[catch {eval exec $cmd} err]} {
++    if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} {
+         error_popup "[mc "Error creating patch:"] $err" $patchtop
+     }
+     catch {destroy $patchtop}
+@@ -9382,9 +9471,9 @@ proc domktag {} {
+     }
+     if {[catch {
+         if {$msg != {}} {
+-            exec git tag -a -m $msg $tag $id
++            safe_exec [list git tag -a -m $msg $tag $id]
+         } else {
+-            exec git tag $tag $id
++            safe_exec [list git tag $tag $id]
+         }
+     } err]} {
+         error_popup "[mc "Error creating tag:"] $err" $mktagtop
+@@ -9452,7 +9541,7 @@ proc copyreference {} {
+     if {$autosellen < 40} {
+         lappend cmd --abbrev=$autosellen
+     }
+-    set reference [eval exec $cmd $rowmenuid]
++    set reference [safe_exec [concat $cmd $rowmenuid]]
+     clipboard clear
+     clipboard append $reference
+@@ -9502,7 +9591,7 @@ proc wrcomgo {} {
+     set id [$wrcomtop.sha1 get]
+     set cmd "echo $id | [$wrcomtop.cmd get]"
+     set fname [$wrcomtop.fname get]
+-    if {[catch {exec sh -c $cmd >$fname &} err]} {
++    if {[catch {safe_exec_redirect [list sh -c $cmd] [list >$fname &]} err]} {
+         error_popup "[mc "Error writing commit:"] $err" $wrcomtop
+     }
+     catch {destroy $wrcomtop}
+@@ -9606,7 +9695,7 @@ proc mkbrgo {top} {
+     nowbusy newbranch
+     update
+     if {[catch {
+-        eval exec git branch $cmdargs
++        safe_exec [concat git branch $cmdargs]
+     } err]} {
+         notbusy newbranch
+         error_popup $err
+@@ -9647,7 +9736,7 @@ proc mvbrgo {top prevname} {
+     nowbusy renamebranch
+     update
+     if {[catch {
+-        eval exec git branch $cmdargs
++        safe_exec [concat git branch $cmdargs]
+     } err]} {
+         notbusy renamebranch
+         error_popup $err
+@@ -9688,7 +9777,7 @@ proc exec_citool {tool_args {baseid {}}} {
+         }
+     }
+-    eval exec git citool $tool_args &
++    safe_exec_redirect [concat git citool $tool_args] [list &]
+     array unset env GIT_AUTHOR_*
+     array set env $save_env
+@@ -9711,7 +9800,7 @@ proc cherrypick {} {
+     update
+     # Unfortunately git-cherry-pick writes stuff to stderr even when
+     # no error occurs, and exec takes that as an indication of error...
+-    if {[catch {exec sh -c "git cherry-pick -r $rowmenuid 2>&1"} err]} {
++    if {[catch {safe_exec [list sh -c "git cherry-pick -r $rowmenuid 2>&1"]} err]} {
+         notbusy cherrypick
+         if {[regexp -line \
+                  {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
+@@ -9773,7 +9862,7 @@ proc revert {} {
+     nowbusy revert [mc "Reverting"]
+     update
+-    if [catch {exec git revert --no-edit $rowmenuid} err] {
++    if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] {
+         notbusy revert
+         if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\
+                 $err match files] {
+@@ -9849,8 +9938,8 @@ proc resethead {} {
+     bind $w <Visibility> "grab $w; focus $w"
+     tkwait window $w
+     if {!$confirm_ok} return
+-    if {[catch {set fd [open \
+-            [list | git reset --$resettype $rowmenuid 2>@1] r]} err]} {
++    if {[catch {set fd [safe_open_command_redirect \
++            [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} {
+         error_popup $err
+     } else {
+         dohidelocalchanges
+@@ -9921,7 +10010,7 @@ proc cobranch {} {
+     # check the tree is clean first??
+     set newhead $headmenuhead
+-    set command [list | git checkout]
++    set command [list git checkout]
+     if {[string match "remotes/*" $newhead]} {
+         set remote $newhead
+         set newhead [string range $newhead [expr [string last / $newhead] + 1] end]
+@@ -9935,12 +10024,11 @@ proc cobranch {} {
+     } else {
+         lappend command $newhead
+     }
+-    lappend command 2>@1
+     nowbusy checkout [mc "Checking out"]
+     update
+     dohidelocalchanges
+     if {[catch {
+-        set fd [open $command r]
++        set fd [safe_open_command_redirect $command [list 2>@1]]
+     } err]} {
+         notbusy checkout
+         error_popup $err
+@@ -10006,7 +10094,7 @@ proc rmbranch {} {
+     }
+     nowbusy rmbranch
+     update
+-    if {[catch {exec git branch -D $head} err]} {
++    if {[catch {safe_exec [list git branch -D $head]} err]} {
+         notbusy rmbranch
+         error_popup $err
+         return
+@@ -10197,7 +10285,7 @@ proc getallcommits {} {
+         set cachedarcs 0
+         set allccache [file join $gitdir "gitk.cache"]
+         if {![catch {
+-            set f [open $allccache r]
++            set f [safe_open_file $allccache r]
+             set allcwait 1
+             getcache $f
+         }]} return
+@@ -10206,7 +10294,7 @@ proc getallcommits {} {
+     if {$allcwait} {
+         return
+     }
+-    set cmd [list | git rev-list --parents]
++    set cmd [list git rev-list --parents]
+     set allcupdate [expr {$seeds ne {}}]
+     if {!$allcupdate} {
+         set ids "--all"
+@@ -10228,10 +10316,17 @@ proc getallcommits {} {
+             foreach id $seeds {
+                 lappend ids "^$id"
+             }
++            lappend ids "--"
+         }
+     }
+     if {$ids ne {}} {
+-        set fd [open [concat $cmd $ids] r]
++        if {$ids eq "--all"} {
++            set cmd [concat $cmd "--all"]
++            set fd [safe_open_command $cmd]
++        } else {
++            set cmd [concat $cmd --stdin]
++            set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]]
++        }
+         fconfigure $fd -blocking 0
+         incr allcommits
+         nowbusy allcommits
+@@ -10621,7 +10716,7 @@ proc savecache {} {
+     set cachearc 0
+     set cachedarcs $nextarc
+     catch {
+-        set f [open $allccache w]
++        set f [safe_open_file $allccache w]
+         puts $f [list 1 $cachedarcs]
+         run writecache $f
+     }
+@@ -11324,7 +11419,7 @@ proc add_tag_ctext {tag} {
+     if {![info exists cached_tagcontent($tag)]} {
+         catch {
+-            set cached_tagcontent($tag) [exec git cat-file -p $tag]
++            set cached_tagcontent($tag) [safe_exec [list git cat-file -p $tag]]
+         }
+     }
+     $ctext insert end "[mc "Tag"]: $tag\n" bold
+@@ -11927,7 +12022,7 @@ proc formatdate {d} {
+ }
+ # This list of encoding names and aliases is distilled from
+-# http://www.iana.org/assignments/character-sets.
++# https://www.iana.org/assignments/character-sets.
+ # Not all of them are supported by Tcl.
+ set encoding_aliases {
+     { ANSI_X3.4-1968 iso-ir-6 ANSI_X3.4-1986 ISO_646.irv:1991 ASCII
+@@ -12210,7 +12305,7 @@ proc gitattr {path attr default} {
+         set r $path_attr_cache($attr,$path)
+     } else {
+         set r "unspecified"
+-        if {![catch {set line [exec git check-attr $attr -- $path]}]} {
++        if {![catch {set line [safe_exec [list git check-attr $attr -- $path]]}]} {
+             regexp "(.*): $attr: (.*)" $line m f r
+         }
+         set path_attr_cache($attr,$path) $r
+@@ -12237,7 +12332,7 @@ proc cache_gitattr {attr pathlist} {
+     while {$newlist ne {}} {
+         set head [lrange $newlist 0 [expr {$lim - 1}]]
+         set newlist [lrange $newlist $lim end]
+-        if {![catch {set rlist [eval exec git check-attr $attr -- $head]}]} {
++        if {![catch {set rlist [safe_exec [concat git check-attr $attr -- $head]]}]} {
+             foreach row [split $rlist "\n"] {
+                 if {[regexp "(.*): $attr: (.*)" $row m path value]} {
+                     if {[string index $path 0] eq "\""} {
+@@ -12290,11 +12385,11 @@ if {[catch {package require Tk 8.4} err]} {
+ # on OSX bring the current Wish process window to front
+ if {[tk windowingsystem] eq "aqua"} {
+-    exec osascript -e [format {
++    safe_exec [list osascript -e [format {
+         tell application "System Events"
+             set frontmost of processes whose unix id is %d to true
+         end tell
+-    } [pid] ]
++    } [pid] ]]
+ }
+ # Unset GIT_TRACE var if set
+@@ -12443,7 +12538,7 @@ if {[tk windowingsystem] eq "aqua"} {
+ catch {
+     # follow the XDG base directory specification by default. See
+-    # http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
++    # https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html
+     if {[info exists env(XDG_CONFIG_HOME)] && $env(XDG_CONFIG_HOME) ne ""} {
+         # XDG_CONFIG_HOME environment variable is set
+         set config_file [file join $env(XDG_CONFIG_HOME) git gitk]
+@@ -12539,7 +12634,7 @@ if {$selecthead eq "HEAD"} {
+ if {$i >= [llength $argv] && $revtreeargs ne {}} {
+     # no -- on command line, but some arguments (other than --argscmd)
+     if {[catch {
+-        set f [eval exec git rev-parse --no-revs --no-flags $revtreeargs]
++        set f [safe_exec [concat git rev-parse --no-revs --no-flags $revtreeargs]]
+         set cmdline_files [split $f "\n"]
+         set n [llength $cmdline_files]
+         set revtreeargs [lrange $revtreeargs 0 end-$n]
+@@ -12705,3 +12800,4 @@ getcommits {}
+ # indent-tabs-mode: t
+ # tab-width: 8
+ # End:
++
+-- 
+2.50.1
+
index 765180a38d4866f0582aed2bd164370a22a571cb..3520b4db90e694b08d47c3e28d521dc7981f61b3 100644 (file)
@@ -26,6 +26,7 @@ SRC_URI = "${KERNELORG_MIRROR}/software/scm/git/git-${PV}.tar.gz;name=tarball \
            file://CVE-2024-50349-0001.patch \
            file://CVE-2024-50349-0002.patch \
            file://CVE-2024-52006.patch \
+           file://CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch \
            "
 
 S = "${WORKDIR}/git-${PV}"