From patchwork Mon Aug 18 12:31:25 2025 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Hitendra Prajapati X-Patchwork-Id: 68715 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from aws-us-west-2-korg-lkml-1.web.codeaurora.org (localhost.localdomain [127.0.0.1]) by smtp.lore.kernel.org (Postfix) with ESMTP id F0516CA0ED1 for ; Mon, 18 Aug 2025 12:32:07 +0000 (UTC) Received: from mail-pj1-f53.google.com (mail-pj1-f53.google.com [209.85.216.53]) by mx.groups.io with SMTP id smtpd.web10.73881.1755520320642733496 for ; Mon, 18 Aug 2025 05:32:00 -0700 Authentication-Results: mx.groups.io; dkim=pass header.i=@mvista.com header.s=google header.b=J5WRgM5o; spf=pass (domain: mvista.com, ip: 209.85.216.53, mailfrom: hprajapati@mvista.com) Received: by mail-pj1-f53.google.com with SMTP id 98e67ed59e1d1-323266baa22so3109916a91.0 for ; Mon, 18 Aug 2025 05:32:00 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=mvista.com; s=google; t=1755520320; x=1756125120; darn=lists.openembedded.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=1Hl9ctzTPrITIRi5ZJVmMr89mYH2H2TkAMxZ0RhBQMo=; b=J5WRgM5oq9LW5Wpjk5vnNt2Cb90hA4LD6J2OW6KoV5QKzoGgL9OtXGzFurgfBUD0xt 80ZBaRkW8g2WLdKgd7cwGx7GTfkSi6x8YWvZg/oGNGNQS4+oOWTY8+0YBHvjwKujd+3D GzLfAFvqN50s2Yd7nOYgFP2hDDUhItWJLNAaI= X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1755520320; x=1756125120; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=1Hl9ctzTPrITIRi5ZJVmMr89mYH2H2TkAMxZ0RhBQMo=; b=RbvhQvjgq1+ZUQ8jtz91abhjsXUqBba/376kG6MsnNCsYb2+3sNHG7E8LQnMJCtKcm yWni0HbBKmLvAddIoKw6VS2Eak9jE1rsmuctC0oD62UhCGXGoWY9BlDBk4DwVc8FM2sn bJQDiVGENQph9d08VrjeRUOQhyV/9dsKyz0Hd2sbYD2kjvGt0z+2dFCIThT7oOvsJVFl Ml9v9mtVZRKF1OKC4SQZL+DStJ0VyHn3mFmVW3EIMxlXw8xPt4qmLTlPQIjlroYdzd3S CnumBkQGCVBQH9Hh7cCwArJS/yYnhoveTr4n5z/V/cpMs94FvcqEL0hbRKZghCYPT9cz cs6A== X-Gm-Message-State: AOJu0YzZ2xgPzVC7Godgybdq23oOd15dUSB5CGghR8BYUqSdKpez2Xm+ Ckq4aLbDfguGapXjBK3H63hyqewitqxXpEhQvS7hjCX42T5jyZro3/4rn70uWqY3k44vnsqE1z7 sSX2U X-Gm-Gg: ASbGncs+Z8MUUnVfhc75KSUKl/vEb4hvAoBCyRkxrwiD7NpW4yf2i4wKE7LCoicqXvn td2zwMtzluYCcE1EZKKdB7THkfgd1Zb1GRbsbf1BrD6wzHOQsQect4XvOmsM+EIZcVn6mtreJfl I/CCXmyeXgBAjEZ8zrGzKMTvtQ77+cruwC8q/QFpFmDzYrjypJKBgGXhisE5b+yb4DmbYSqUsfO GZR/qnHcYVF6bsS+pUEdsxzKpLWWZW/hugDY1MpF2van/347jZ1gg7Bb115hdNFSa6N2nPZMuZB QZV0fmM7k+1rH0Vq2R8I2K42dr+uq9TTbIgC/1ZfDHaPoIvopZU1BfhUXES7Os6Y8GYxOcg0lqi BYM5yzJRmckrYUe4F4cvYU4HIJWsyC4YsN1+k X-Google-Smtp-Source: AGHT+IE+nlpevoQEIZgkXs+irRpJudFVsnTCu4RWpS2Hr5o7HDj/oIF0y9KOlP9TYfq5A5aXLZZ7lw== X-Received: by 2002:a17:90b:3b48:b0:31c:36f5:d95 with SMTP id 98e67ed59e1d1-3234dbbeb11mr12740752a91.2.1755520318639; Mon, 18 Aug 2025 05:31:58 -0700 (PDT) Received: from MVIN00016.mvista.com ([103.250.136.130]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-32343994891sm8101074a91.8.2025.08.18.05.31.50 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 18 Aug 2025 05:31:58 -0700 (PDT) From: Hitendra Prajapati To: openembedded-core@lists.openembedded.org Cc: Hitendra Prajapati Subject: [kirkstone][PATCH] git: fix CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835 Date: Mon, 18 Aug 2025 18:01:25 +0530 Message-ID: <20250818123125.71140-1-hprajapati@mvista.com> X-Mailer: git-send-email 2.50.1 MIME-Version: 1.0 List-Id: X-Webhook-Received: from li982-79.members.linode.com [45.33.32.79] by aws-us-west-2-korg-lkml-1.web.codeaurora.org with HTTPS for ; Mon, 18 Aug 2025 12:32:07 -0000 X-Groupsio-URL: https://lists.openembedded.org/g/openembedded-core/message/222036 Upstream-Status: Backport from from https://github.com/git/git/commit/d61cfed2c23705fbeb9c0d08f59e75ee08738950 Signed-off-by: Hitendra Prajapati --- ...-27613-CVE-2025-46334-CVE-2025-46835.patch | 2500 +++++++++++++++++ meta/recipes-devtools/git/git_2.35.7.bb | 1 + 2 files changed, 2501 insertions(+) create mode 100644 meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch diff --git a/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch b/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch new file mode 100644 index 0000000000..e08bf41b3c --- /dev/null +++ b/meta/recipes-devtools/git/git/CVE-2025-27614-CVE-2025-27613-CVE-2025-46334-CVE-2025-46835.patch @@ -0,0 +1,2500 @@ +From: d61cfed2c23705fbeb9c0d08f59e75ee08738950 Merge: 664d4fa692 311d9ada3a +Author: Taylor Blau +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 + +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 +--- + 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 .}] ++along with this program; if not, see .}] + + ###################################################################### + ## +@@ -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 { + + +@@ -111,7 +111,7 @@ proc do_macosx_app {} { + } + 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 "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 + diff --git a/meta/recipes-devtools/git/git_2.35.7.bb b/meta/recipes-devtools/git/git_2.35.7.bb index 765180a38d..3520b4db90 100644 --- a/meta/recipes-devtools/git/git_2.35.7.bb +++ b/meta/recipes-devtools/git/git_2.35.7.bb @@ -26,6 +26,7 @@ SRC_URI = "${KERNELORG_MIRROR}/software/scm/git/git-${PV}.tar.gz;name=tarball \ 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}"