Sync with 2.49.1

maint
Junio C Hamano 2025-06-15 21:52:28 -07:00
commit e1775c0646
34 changed files with 760 additions and 460 deletions

View File

@ -0,0 +1,73 @@
Git v2.43.7 Release Notes
=========================

This release includes fixes for CVE-2025-27613, CVE-2025-27614,
CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and
CVE-2025-48386.

Fixes since v2.43.6
-------------------

* CVE-2025-27613, Gitk:

When a user clones an untrusted repository and runs Gitk without
additional command arguments, any writable file can be created and
truncated. The option "Support per-file encoding" must have been
enabled. The operation "Show origin of this line" is affected as
well, regardless of the option being enabled or not.

* CVE-2025-27614, Gitk:

A Git repository can be crafted in such a way that a user who has
cloned the repository can be tricked into running any script
supplied by the attacker by invoking `gitk filename`, where
`filename` has a particular structure.

* CVE-2025-46334, Git GUI (Windows only):

A malicious repository can ship versions of sh.exe or typical
textconv filter programs such as astextplain. On Windows, path
lookup can find such executables in the worktree. These programs
are invoked when the user selects "Git Bash" or "Browse Files" from
the menu.

* CVE-2025-46835, Git GUI:

When a user clones an untrusted repository and is tricked into
editing a file located in a maliciously named directory in the
repository, then Git GUI can create and overwrite any writable
file.

* CVE-2025-48384, Git:

When reading a config value, Git strips any trailing carriage
return and line feed (CRLF). When writing a config entry, values
with a trailing CR are not quoted, causing the CR to be lost when
the config is later read. When initializing a submodule, if the
submodule path contains a trailing CR, the altered path is read
resulting in the submodule being checked out to an incorrect
location. If a symlink exists that points the altered path to the
submodule hooks directory, and the submodule contains an executable
post-checkout hook, the script may be unintentionally executed
after checkout.

* CVE-2025-48385, Git:

When cloning a repository Git knows to optionally fetch a bundle
advertised by the remote server, which allows the server-side to
offload parts of the clone to a CDN. The Git client does not
perform sufficient validation of the advertised bundles, which
allows the remote side to perform protocol injection.

This protocol injection can cause the client to write the fetched
bundle to a location controlled by the adversary. The fetched
content is fully controlled by the server, which can in the worst
case lead to arbitrary code execution.

* CVE-2025-48386, Git:

The wincred credential helper uses a static buffer (`target`) as a
unique key for storing and comparing against internal storage. This
credential helper does not properly bounds check the available
space remaining in the buffer before appending to it with
`wcsncat()`, leading to potential buffer overflows.

View File

@ -0,0 +1,7 @@
Git v2.44.4 Release Notes
=========================

This release merges up the fixes that appears in v2.43.7 to address
the following CVEs: CVE-2025-27613, CVE-2025-27614, CVE-2025-46334,
CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386.
See the release notes for v2.43.7 for details.

View File

@ -0,0 +1,7 @@
Git v2.45.4 Release Notes
=========================

This release merges up the fixes that appears in v2.43.7, and v2.44.4
to address the following CVEs: CVE-2025-27613, CVE-2025-27614,
CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and
CVE-2025-48386. See the release notes for v2.43.7 for details.

View File

@ -0,0 +1,7 @@
Git v2.46.4 Release Notes
=========================

This release merges up the fixes that appears in v2.43.7, v2.44.4, and
v2.45.4 to address the following CVEs: CVE-2025-27613, CVE-2025-27614,
CVE-2025-46334, CVE-2025-46835, CVE-2025-48384, CVE-2025-48385, and
CVE-2025-48386. See the release notes for v2.43.7 for details.

View File

@ -0,0 +1,8 @@
Git v2.47.3 Release Notes
=========================

This release merges up the fixes that appears in v2.43.7, v2.44.4,
v2.45.4, and v2.46.4 to address the following CVEs: CVE-2025-27613,
CVE-2025-27614, CVE-2025-46334, CVE-2025-46835, CVE-2025-48384,
CVE-2025-48385, and CVE-2025-48386. See the release notes for v2.43.7
for details.

View File

@ -0,0 +1,8 @@
Git v2.48.2 Release Notes
=========================

This release merges up the fixes that appears in v2.43.7, v2.44.4,
v2.45.4, v2.46.4, and v2.47.3 to address the following CVEs:
CVE-2025-27613, CVE-2025-27614, CVE-2025-46334, CVE-2025-46835,
CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386. See the release
notes for v2.43.7 for details.

View File

@ -0,0 +1,12 @@
Git v2.49.1 Release Notes
=========================

This release merges up the fixes that appear in v2.43.7, v2.44.4,
v2.45.4, v2.46.4, v2.47.3, and v2.48.2 to address the following CVEs:
CVE-2025-27613, CVE-2025-27614, CVE-2025-46334, CVE-2025-46835,
CVE-2025-48384, CVE-2025-48385, and CVE-2025-48386. See the release
notes for v2.43.7 for details.

It also contains some updates to various CI bits to work around
and/or to adjust to the deprecation of use of Ubuntu 20.04 GitHub
Actions CI, updates to to Fedora base image.

View File

@ -297,6 +297,28 @@ static int download_https_uri_to_file(const char *file, const char *uri)
struct strbuf line = STRBUF_INIT;
int found_get = 0;

/*
* The protocol we speak with git-remote-https(1) uses a space to
* separate between URI and file, so the URI itself must not contain a
* space. If it did, an adversary could change the location where the
* downloaded file is being written to.
*
* Similarly, we use newlines to separate commands from one another.
* Consequently, neither the URI nor the file must contain a newline or
* otherwise an adversary could inject arbitrary commands.
*
* TODO: Restricting newlines in the target paths may break valid
* usecases, even if those are a bit more on the esoteric side.
* If this ever becomes a problem we should probably think about
* alternatives. One alternative could be to use NUL-delimited
* requests in git-remote-http(1). Another alternative could be
* to use URL quoting.
*/
if (strpbrk(uri, " \n"))
return error("bundle-uri: URI is malformed: '%s'", file);
if (strchr(file, '\n'))
return error("bundle-uri: filename is malformed: '%s'", file);

strvec_pushl(&cp.args, "git-remote-https", uri, NULL);
cp.err = -1;
cp.in = -1;

View File

@ -2940,7 +2940,7 @@ static ssize_t write_pair(int fd, const char *key, const char *value,
if (value[0] == ' ')
quote = "\"";
for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#')
if (value[i] == ';' || value[i] == '#' || value[i] == '\r')
quote = "\"";
if (i && value[i - 1] == ' ')
quote = "\"";

View File

@ -39,6 +39,14 @@ static void *xmalloc(size_t size)
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024],
*password_expiry_utc, *oauth_refresh_token;

static void target_append(const WCHAR *src)
{
size_t avail = ARRAY_SIZE(target) - wcslen(target) - 1; /* -1 for NUL */
if (avail < wcslen(src))
die("target buffer overflow");
wcsncat(target, src, avail);
}

static void write_item(const char *what, LPCWSTR wbuf, int wlen)
{
char *buf;
@ -330,17 +338,17 @@ int main(int argc, char *argv[])

/* prepare 'target', the unique key for the credential */
wcscpy(target, L"git:");
wcsncat(target, protocol, ARRAY_SIZE(target));
wcsncat(target, L"://", ARRAY_SIZE(target));
target_append(protocol);
target_append(L"://");
if (wusername) {
wcsncat(target, wusername, ARRAY_SIZE(target));
wcsncat(target, L"@", ARRAY_SIZE(target));
target_append(wusername);
target_append(L"@");
}
if (host)
wcsncat(target, host, ARRAY_SIZE(target));
target_append(host);
if (path) {
wcsncat(target, L"/", ARRAY_SIZE(target));
wcsncat(target, path, ARRAY_SIZE(target));
target_append(L"/");
target_append(path);
}

if (!strcmp(argv[1], "get"))

View File

@ -77,99 +77,178 @@ proc is_Cygwin {} {

######################################################################
##
## PATH lookup
## PATH lookup. Sanitize $PATH, assure exec/open use only that

set _search_path {}
proc _which {what args} {
global env _search_exe _search_path

if {$_search_path eq {}} {
if {[is_Windows]} {
set gitguidir [file dirname [info script]]
regsub -all ";" $gitguidir "\\;" gitguidir
set env(PATH) "$gitguidir;$env(PATH)"
set _search_path [split $env(PATH) {;}]
# Skip empty `PATH` elements
set _search_path [lsearch -all -inline -not -exact \
$_search_path ""]
set _search_exe .exe
} else {
set _search_path [split $env(PATH) :]
set _search_exe {}
}
}

if {[is_Windows] && [lsearch -exact $args -script] >= 0} {
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 {}
if {[is_Windows]} {
set _path_sep {;}
set _search_exe .exe
} else {
set _path_sep {:}
set _search_exe {}
}

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
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
}

# handle piped commands, e.g. `exec A | B`
for {incr i} {$i < [llength $command_line]} {incr i} {
if {[lindex $command_line $i] eq "|"} {
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]
}
return $command_line
}

# Override `exec` to avoid unsafe PATH lookup
# Wrap exec/open to sanitize arguments

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
# unsafe arguments begin with redirections or the pipe or background operators
proc is_arg_unsafe {arg} {
regexp {^([<|>&]|2>)} $arg
}

# 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]"
proc make_arg_safe {arg} {
if {[is_arg_unsafe $arg]} {
set arg [file join . $arg]
}
uplevel 1 real_open $args
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
@ -270,11 +349,11 @@ 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]]]
}
}

@ -304,15 +383,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
}

@ -494,7 +595,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

@ -524,32 +625,14 @@ proc _git_cmd {name} {
return $v
}

# 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]
}

@ -559,7 +642,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 {}
@ -571,7 +654,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
@ -581,88 +668,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]
proc git_read {cmd {redir {}}} {
set cmdp [_git_cmd [lindex $cmd 0]]
set cmd [lrange $cmd 1 end]

while {1} {
switch -- [lindex $args 0] {
--nice {
_lappend_nice opt
}

--stderr {
lappend args 2>@1
}

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]

}
return [safe_open_command [concat $opt $cmdp $cmd]]
}

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]

set cmdp [_git_cmd [lindex $args 0]]
set args [lrange $args 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 cmd [concat git hook run --ignore-missing $hook_name -- $args 2>@1]
return [_open_stdout_stderr $cmd]
git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1]
}

proc kill_file_process {fd} {
@ -670,9 +716,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]
}
}
}
@ -698,7 +744,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 {}
@ -1068,7 +1114,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 \
@ -1106,53 +1152,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) {}
}
}
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
}
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) {}
}
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) {}
}
}
}
@ -1427,7 +1450,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
}
@ -1446,7 +1469,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
}
@ -1505,12 +1528,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]
@ -1550,11 +1573,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
@ -1563,7 +1586,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
@ -1575,7 +1598,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 {}
@ -1599,23 +1622,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
@ -2205,7 +2228,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
@ -2242,7 +2265,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
@ -2273,16 +2296,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
@ -2316,7 +2341,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
@ -2760,17 +2785,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]} {
@ -4079,7 +4103,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

View File

@ -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

View File

@ -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

View File

@ -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]
}

View File

@ -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]

View File

@ -641,8 +641,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
@ -727,7 +727,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
@ -760,8 +760,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

@ -818,12 +818,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} {
@ -877,7 +877,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
@ -953,13 +953,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]
}

View File

@ -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]} {

View File

@ -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
@ -236,7 +236,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
@ -336,7 +336,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]
}
@ -361,7 +361,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
@ -399,8 +399,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."]
@ -420,7 +420,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

View File

@ -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 -encoding [encoding system]
fileevent $fd_f readable [cb _read $fd_f $after]

View File

@ -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

View File

@ -191,7 +191,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]
@ -215,7 +215,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]
@ -327,7 +327,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]]
@ -603,7 +603,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]} {
@ -839,7 +839,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
@ -876,7 +876,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]} {

View File

@ -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 \

View File

@ -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 \

View File

@ -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]
@ -310,7 +310,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
@ -360,9 +360,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

View File

@ -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

View File

@ -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 \

View File

@ -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,8 +30,8 @@ proc do_cygwin_shortcut {} {
global argv0 _gitworktree oguilib

if {[catch {
set desktop [exec cygpath \
--desktop]
set desktop [safe_exec [list cygpath \
--desktop]]
}]} {
set desktop .
}
@ -50,14 +50,14 @@ proc do_cygwin_shortcut {} {
"CHERE_INVOKING=1 \
source /etc/profile; \
git gui"}
exec /bin/mkshortcut.exe \
safe_exec [list /bin/mkshortcut.exe \
--arguments $shargs \
--desc "git-gui on $repodir" \
--icon $oguilib/git-gui.ico \
--name $fn \
--show min \
--workingdir $repodir \
/bin/sh.exe
/bin/sh.exe]
} err]} {
error_popup [strcat [mc "Cannot write shortcut:"] "\n\n$err"]
}
@ -83,7 +83,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">
@ -108,7 +108,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)

View File

@ -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
}

View File

@ -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]

View File

@ -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]
}

View File

@ -113,6 +113,91 @@ if {[is_Windows]} {

# End of safe PATH lookup stuff

# 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"}]
@ -238,7 +323,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
@ -400,7 +485,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"]
@ -457,16 +542,6 @@ proc parseviewrevs {view revs} {
return $ret
}

# Escapes a list of filter paths to be passed to git log via stdin. Note that
# paths must not be quoted.
proc escape_filter_paths {paths} {
set escaped [list]
foreach path $paths {
lappend escaped [string map {\\ \\\\ "\ " "\\\ "} $path]
}
return $escaped
}

# Start off a git log process and arrange to read its output
proc start_rev_list {view} {
global startmsecs commitidx viewcomplete curview
@ -488,7 +563,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
@ -526,10 +601,9 @@ proc start_rev_list {view} {
}

if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
--parents --boundary $args --stdin \
"<<[join [concat $revs "--" \
[escape_filter_paths $files]] "\\n"]"] 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
@ -563,9 +637,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}
@ -680,11 +754,9 @@ proc updatecommits {} {
set args $vorigargs($view)
}
if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \
--parents --boundary $args --stdin \
"<<[join [concat $revs "--" \
[escape_filter_paths \
$vfilelimit($view)]] "\\n"]"] 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
@ -1651,8 +1723,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
@ -1772,7 +1844,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 != {}} {
@ -1908,7 +1980,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
}
@ -1956,7 +2028,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]]
}
}
}
@ -2220,7 +2292,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}
}}
@ -3007,7 +3079,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"
}
@ -3723,7 +3795,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]} {
@ -3745,7 +3817,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
}
@ -3810,7 +3882,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 {
@ -3914,7 +3986,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]
@ -3974,7 +4046,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"
}
}
@ -4002,7 +4074,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]} {
@ -4025,19 +4097,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
@ -4962,8 +5032,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 {}
@ -5392,8 +5462,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
@ -5458,14 +5528,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]
@ -5490,11 +5560,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]
@ -7283,8 +7353,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"
}
}
@ -7790,13 +7860,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
@ -7860,13 +7930,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
}
@ -8016,7 +8086,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 {
@ -8024,7 +8094,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
}
@ -8033,7 +8103,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} {
@ -8049,7 +8119,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
}
@ -8061,7 +8131,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 {}
@ -8181,7 +8251,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
}
@ -8899,7 +8969,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
}
@ -9205,10 +9275,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"
@ -9304,14 +9372,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
@ -9451,10 +9519,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}
@ -9523,9 +9588,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
@ -9593,7 +9658,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
@ -9643,7 +9708,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}
@ -9747,7 +9812,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
@ -9788,7 +9853,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
@ -9829,7 +9894,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
@ -9852,7 +9917,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)} \
@ -9914,7 +9979,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] {
@ -9990,8 +10055,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
@ -10062,7 +10127,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]
@ -10076,12 +10141,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
@ -10147,7 +10211,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
@ -10338,7 +10402,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
@ -10347,7 +10411,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"
@ -10375,10 +10439,11 @@ proc getallcommits {} {
if {$ids ne {}} {
if {$ids eq "--all"} {
set cmd [concat $cmd "--all"]
set fd [safe_open_command $cmd]
} else {
set cmd [concat $cmd --stdin "<<[join $ids "\\n"]"]
set cmd [concat $cmd --stdin]
set fd [safe_open_command_redirect $cmd [list "<<[join $ids "\n"]"]]
}
set fd [open $cmd r]
fconfigure $fd -blocking 0
incr allcommits
nowbusy allcommits
@ -10768,7 +10833,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
}
@ -11471,7 +11536,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
@ -12382,7 +12447,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
@ -12409,7 +12474,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 "\""} {
@ -12461,11 +12526,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
@ -12713,7 +12778,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]

View File

@ -2851,4 +2851,15 @@ test_expect_success 'writing to stdin is rejected' '

done

test_expect_success 'writing value with trailing CR not stripped on read' '
test_when_finished "rm -rf cr-test" &&

printf "bar\r\n" >expect &&
git init cr-test &&
git -C cr-test config set core.foo $(printf "bar\r") &&
git -C cr-test config get core.foo >actual &&

test_cmp expect actual
'

test_done

View File

@ -1279,6 +1279,29 @@ test_expect_success 'bundles are downloaded once during fetch --all' '
trace-mult.txt >bundle-fetches &&
test_line_count = 1 bundle-fetches
'

test_expect_success 'bundles with space in URI are rejected' '
test_when_finished "rm -rf busted repo" &&
mkdir -p "$HOME/busted/ /$HOME/repo/.git/objects/bundles" &&
git clone --bundle-uri="$HTTPD_URL/bogus $HOME/busted/" "$HTTPD_URL/smart/fetch.git" repo 2>err &&
test_grep "error: bundle-uri: URI is malformed: " err &&
find busted -type f >files &&
test_must_be_empty files
'

test_expect_success 'bundles with newline in URI are rejected' '
test_when_finished "rm -rf busted repo" &&
git clone --bundle-uri="$HTTPD_URL/bogus\nget $HTTPD_URL/bogus $HOME/busted" "$HTTPD_URL/smart/fetch.git" repo 2>err &&
test_grep "error: bundle-uri: URI is malformed: " err &&
test_path_is_missing "$HOME/busted"
'

test_expect_success 'bundles with newline in target path are rejected' '
git clone --bundle-uri="$HTTPD_URL/bogus" "$HTTPD_URL/smart/fetch.git" "$(printf "escape\nget $HTTPD_URL/bogus .")" 2>err &&
test_grep "error: bundle-uri: filename is malformed: " err &&
test_path_is_missing escape
'

# Do not add tests here unless they use the HTTP server, as they will
# not run unless the HTTP dependencies exist.


View File

@ -372,4 +372,37 @@ test_expect_success 'checkout -f --recurse-submodules must not use a nested gitd
test_path_is_missing nested_checkout/thing2/.git
'

test_expect_success SYMLINKS,!WINDOWS,!MINGW 'submodule must not checkout into different directory' '
test_when_finished "rm -rf sub repo bad-clone" &&

git init sub &&
write_script sub/post-checkout <<-\EOF &&
touch "$PWD/foo"
EOF
git -C sub add post-checkout &&
git -C sub commit -m hook &&

git init repo &&
git -C repo -c protocol.file.allow=always submodule add "$PWD/sub" sub &&
git -C repo mv sub $(printf "sub\r") &&

# Ensure config values containing CR are wrapped in quotes.
git config unset -f repo/.gitmodules submodule.sub.path &&
printf "\tpath = \"sub\r\"\n" >>repo/.gitmodules &&

git config unset -f repo/.git/modules/sub/config core.worktree &&
{
printf "[core]\n" &&
printf "\tworktree = \"../../../sub\r\"\n"
} >>repo/.git/modules/sub/config &&

ln -s .git/modules/sub/hooks repo/sub &&
git -C repo add -A &&
git -C repo commit -m submodule &&

git -c protocol.file.allow=always clone --recurse-submodules repo bad-clone &&
! test -f "$PWD/foo" &&
test -f $(printf "bad-clone/sub\r/post-checkout")
'

test_done