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; struct strbuf line = STRBUF_INIT;
int found_get = 0; 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); strvec_pushl(&cp.args, "git-remote-https", uri, NULL);
cp.err = -1; cp.err = -1;
cp.in = -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] == ' ') if (value[0] == ' ')
quote = "\""; quote = "\"";
for (i = 0; value[i]; i++) for (i = 0; value[i]; i++)
if (value[i] == ';' || value[i] == '#') if (value[i] == ';' || value[i] == '#' || value[i] == '\r')
quote = "\""; quote = "\"";
if (i && value[i - 1] == ' ') if (i && value[i - 1] == ' ')
quote = "\""; quote = "\"";

View File

@ -39,6 +39,14 @@ static void *xmalloc(size_t size)
static WCHAR *wusername, *password, *protocol, *host, *path, target[1024], static WCHAR *wusername, *password, *protocol, *host, *path, target[1024],
*password_expiry_utc, *oauth_refresh_token; *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) static void write_item(const char *what, LPCWSTR wbuf, int wlen)
{ {
char *buf; char *buf;
@ -330,17 +338,17 @@ int main(int argc, char *argv[])


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


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

View File

@ -77,29 +77,49 @@ proc is_Cygwin {} {


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


set _search_path {} if {[is_Windows]} {
proc _which {what args} { set _path_sep {;}
global env _search_exe _search_path set _search_exe .exe
} else {
set _path_sep {:}
set _search_exe {}
}


if {$_search_path eq {}} { if {[is_Windows]} {
if {[is_Windows]} {
set gitguidir [file dirname [info script]] set gitguidir [file dirname [info script]]
regsub -all ";" $gitguidir "\\;" gitguidir regsub -all ";" $gitguidir "\\;" gitguidir
set env(PATH) "$gitguidir;$env(PATH)" 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 _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 {} set suffix {}
} else { } else {
set suffix $_search_exe set suffix $_search_exe
@ -112,9 +132,9 @@ proc _which {what args} {
} }
} }
return {} return {}
} }


proc sanitize_command_line {command_line from_index} { proc sanitize_command_line {command_line from_index} {
set i $from_index set i $from_index
while {$i < [llength $command_line]} { while {$i < [llength $command_line]} {
set cmd [lindex $command_line $i] set cmd [lindex $command_line $i]
@ -135,13 +155,13 @@ proc sanitize_command_line {command_line from_index} {
} }
} }
return $command_line return $command_line
} }


# Override `exec` to avoid unsafe PATH lookup # Override `exec` to avoid unsafe PATH lookup


rename exec real_exec rename exec real_exec


proc exec {args} { proc exec {args} {
# skip options # skip options
for {set i 0} {$i < [llength $args]} {incr i} { for {set i 0} {$i < [llength $args]} {incr i} {
set arg [lindex $args $i] set arg [lindex $args $i]
@ -155,21 +175,80 @@ proc exec {args} {
} }
set args [sanitize_command_line $args $i] set args [sanitize_command_line $args $i]
uplevel 1 real_exec $args uplevel 1 real_exec $args
} }


# Override `open` to avoid unsafe PATH lookup # Override `open` to avoid unsafe PATH lookup


rename open real_open rename open real_open


proc open {args} { proc open {args} {
set arg0 [lindex $args 0] set arg0 [lindex $args 0]
if {[string range $arg0 0 0] eq "|"} { if {[string range $arg0 0 0] eq "|"} {
set command_line [string trim [string range $arg0 1 end]] set command_line [string trim [string range $arg0 1 end]]
lset args 0 "| [sanitize_command_line $command_line 0]" lset args 0 "| [sanitize_command_line $command_line 0]"
} }
uplevel 1 real_open $args 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 ## locate our library
@ -270,11 +349,11 @@ unset oguimsg


if {[tk windowingsystem] eq "aqua"} { if {[tk windowingsystem] eq "aqua"} {
catch { catch {
exec osascript -e [format { safe_exec [list osascript -e [format {
tell application "System Events" tell application "System Events"
set frontmost of processes whose unix id is %d to true set frontmost of processes whose unix id is %d to true
end tell end tell
} [pid]] } [pid]]]
} }
} }


@ -304,15 +383,37 @@ if {$_trace >= 0} {
# branches). # branches).
set _last_merged_branch {} set _last_merged_branch {}


proc shellpath {} { # for testing, allow unconfigured _shellpath
global _shellpath env if {[string match @@* $_shellpath]} {
if {[string match @@* $_shellpath]} {
if {[info exists env(SHELL)]} { if {[info exists env(SHELL)]} {
return $env(SHELL) set _shellpath $env(SHELL)
} else { } else {
return /bin/sh 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 return $_shellpath
} }


@ -494,7 +595,7 @@ proc _git_cmd {name} {
# Tcl on Windows doesn't know it. # Tcl on Windows doesn't know it.
# #
set p [gitexec git-$name] set p [gitexec git-$name]
set f [open $p r] set f [safe_open_file $p r]
set s [gets $f] set s [gets $f]
close $f close $f


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


# Test a file for a hashbang to identify executable scripts on Windows. # Run a shell command connected via pipes on stdout.
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.
# This is for use with textconv filters and uses sh -c "..." to allow it to # 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 # contain a command with arguments. We presume this
# scripts specifically otherwise just call the filter command. # to be a shellscript that the configured shell (/bin/sh by default) knows
# how to run.
proc open_cmd_pipe {cmd path} { 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] return [open |$run r]
} }


@ -559,7 +642,7 @@ proc _lappend_nice {cmd_var} {


if {![info exists _nice]} { if {![info exists _nice]} {
set _nice [_which nice] set _nice [_which nice]
if {[catch {exec $_nice git version}]} { if {[catch {safe_exec [list $_nice git version]}]} {
set _nice {} set _nice {}
} elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} { } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} {
set _nice {} set _nice {}
@ -571,7 +654,11 @@ proc _lappend_nice {cmd_var} {
} }


proc git {args} { 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 fconfigure $fd -translation binary -encoding utf-8
set result [string trimright [read $fd] "\n"] set result [string trimright [read $fd] "\n"]
close $fd close $fd
@ -581,88 +668,47 @@ proc git {args} {
return $result return $result
} }


proc _open_stdout_stderr {cmd} { proc safe_open_command {cmd {redir {}}} {
_trace_exec $cmd set cmd [make_arglist_safe $cmd]
_trace_exec [concat $cmd $redir]
if {[catch { if {[catch {
set fd [open [concat [list | ] $cmd] r] set fd [open [concat [list | ] $cmd $redir] r]
} err]} { } 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 error $err
} }
}
fconfigure $fd -eofchar {} fconfigure $fd -eofchar {}
return $fd return $fd
} }


proc git_read {args} { proc git_read {cmd {redir {}}} {
set opt [list] set cmdp [_git_cmd [lindex $cmd 0]]
set cmd [lrange $cmd 1 end]


while {1} { return [safe_open_command [concat $cmdp $cmd] $redir]
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]]
} }


proc git_write {args} { proc git_read_nice {cmd} {
set opt [list] set opt [list]


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


default { set cmdp [_git_cmd [lindex $cmd 0]]
break 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]] _trace_exec [concat $cmdp $cmd]
set args [lrange $args 1 end] return [open [concat [list | ] $cmdp $cmd] w]

_trace_exec [concat $opt $cmdp $args]
return [open [concat [list | ] $opt $cmdp $args] w]
} }


proc githook_read {hook_name args} { proc githook_read {hook_name args} {
set cmd [concat git hook run --ignore-missing $hook_name -- $args 2>@1] git_read [concat [list hook run --ignore-missing $hook_name --] $args] [list 2>@1]
return [_open_stdout_stderr $cmd]
} }


proc kill_file_process {fd} { proc kill_file_process {fd} {
@ -670,9 +716,9 @@ proc kill_file_process {fd} {


catch { catch {
if {[is_Windows]} { if {[is_Windows]} {
exec taskkill /pid $process safe_exec [list taskkill /pid $process]
} else { } else {
exec kill $process safe_exec [list kill $process]
} }
} }
} }
@ -698,7 +744,7 @@ proc sq {value} {
proc load_current_branch {} { proc load_current_branch {} {
global current_branch is_detached 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 fconfigure $fd -translation binary -encoding utf-8
if {[gets $fd ref] < 1} { if {[gets $fd ref] < 1} {
set ref {} set ref {}
@ -1068,7 +1114,7 @@ You are using [git-version]:
## configure our library ## configure our library


set idx [file join $oguilib tclIndex] 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 .} catch {wm withdraw .}
tk_messageBox \ tk_messageBox \
-icon error \ -icon error \
@ -1106,16 +1152,15 @@ unset -nocomplain idx fd
## ##
## config file parsing ## config file parsing


git-version proc _parse_config {arr_name args} { proc _parse_config {arr_name args} {
>= 1.5.3 {
upvar $arr_name arr upvar $arr_name arr
array unset arr array unset arr
set buf {} set buf {}
catch { catch {
set fd_rc [eval \ set fd_rc [git_read \
[list git_read config] \ [concat config \
$args \ $args \
[list --null --list]] --null --list]]
fconfigure $fd_rc -translation binary -encoding utf-8 fconfigure $fd_rc -translation binary -encoding utf-8
set buf [read $fd_rc] set buf [read $fd_rc]
close $fd_rc close $fd_rc
@ -1133,28 +1178,6 @@ git-version proc _parse_config {arr_name args} {
set arr($name) {} 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) {}
}
}
close $fd_rc
}
}
} }


proc load_config {include_global} { proc load_config {include_global} {
@ -1427,7 +1450,7 @@ proc repository_state {ctvar hdvar mhvar} {
set merge_head [gitdir MERGE_HEAD] set merge_head [gitdir MERGE_HEAD]
if {[file exists $merge_head]} { if {[file exists $merge_head]} {
set ct merge 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} { while {[gets $fd_mh line] >= 0} {
lappend mh $line lappend mh $line
} }
@ -1446,7 +1469,7 @@ proc PARENT {} {
return $p return $p
} }
if {$empty_tree eq {}} { if {$empty_tree eq {}} {
set empty_tree [git mktree << {}] set empty_tree [git_redir [list mktree] [list << {}]]
} }
return $empty_tree return $empty_tree
} }
@ -1505,12 +1528,12 @@ proc rescan {after {honor_trustmtime 1}} {
} else { } else {
set rescan_active 1 set rescan_active 1
ui_status [mc "Refreshing file status..."] ui_status [mc "Refreshing file status..."]
set fd_rf [git_read update-index \ set fd_rf [git_read [list update-index \
-q \ -q \
--unmerged \ --unmerged \
--ignore-missing \ --ignore-missing \
--refresh \ --refresh \
] ]]
fconfigure $fd_rf -blocking 0 -translation binary fconfigure $fd_rf -blocking 0 -translation binary
fileevent $fd_rf readable \ fileevent $fd_rf readable \
[list rescan_stage2 $fd_rf $after] [list rescan_stage2 $fd_rf $after]
@ -1550,11 +1573,11 @@ proc rescan_stage2 {fd after} {
set rescan_active 2 set rescan_active 2
ui_status [mc "Scanning for modified files ..."] ui_status [mc "Scanning for modified files ..."]
if {[git-version >= "1.7.2"]} { 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 { } 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_di -blocking 0 -translation binary -encoding binary
fconfigure $fd_df -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] fileevent $fd_df readable [list read_diff_files $fd_df $after]


if {[is_config_true gui.displayuntracked]} { 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 fconfigure $fd_lo -blocking 0 -translation binary -encoding binary
fileevent $fd_lo readable [list read_ls_others $fd_lo $after] fileevent $fd_lo readable [list read_ls_others $fd_lo $after]
incr rescan_active incr rescan_active
@ -1575,7 +1598,7 @@ proc load_message {file {encoding {}}} {


set f [gitdir $file] set f [gitdir $file]
if {[file isfile $f]} { if {[file isfile $f]} {
if {[catch {set fd [open $f r]}]} { if {[catch {set fd [safe_open_file $f r]}]} {
return 0 return 0
} }
fconfigure $fd -eofchar {} 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 # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an
# empty file but existent file. # 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]]} { if {[file isfile [gitdir MERGE_MSG]]} {
set pcm_source "merge" 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 fconfigure $fd_mm -encoding utf-8
puts -nonewline $fd_pcm [read $fd_mm] puts -nonewline $fd_pcm [read $fd_mm]
close $fd_mm close $fd_mm
} elseif {[file isfile [gitdir SQUASH_MSG]]} { } elseif {[file isfile [gitdir SQUASH_MSG]]} {
set pcm_source "squash" 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 fconfigure $fd_sm -encoding utf-8
puts -nonewline $fd_pcm [read $fd_sm] puts -nonewline $fd_pcm [read $fd_sm]
close $fd_sm close $fd_sm
} elseif {[file isfile [get_config commit.template]]} { } elseif {[file isfile [get_config commit.template]]} {
set pcm_source "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 fconfigure $fd_sm -encoding utf-8
puts -nonewline $fd_pcm [read $fd_sm] puts -nonewline $fd_pcm [read $fd_sm]
close $fd_sm close $fd_sm
@ -2205,7 +2228,7 @@ proc do_gitk {revs {is_submodule false}} {
unset env(GIT_DIR) unset env(GIT_DIR)
unset env(GIT_WORK_TREE) unset env(GIT_WORK_TREE)
} }
eval exec $cmd $revs "--" "--" & safe_exec_bg [concat $cmd $revs "--" "--"]


set env(GIT_DIR) $_gitdir set env(GIT_DIR) $_gitdir
set env(GIT_WORK_TREE) $_gitworktree set env(GIT_WORK_TREE) $_gitworktree
@ -2242,7 +2265,7 @@ proc do_git_gui {} {
set pwd [pwd] set pwd [pwd]
cd $current_diff_path cd $current_diff_path


eval exec $exe gui & safe_exec_bg [concat $exe gui]


set env(GIT_DIR) $_gitdir set env(GIT_DIR) $_gitdir
set env(GIT_WORK_TREE) $_gitworktree set env(GIT_WORK_TREE) $_gitworktree
@ -2273,16 +2296,18 @@ proc get_explorer {} {


proc do_explore {} { proc do_explore {} {
global _gitworktree global _gitworktree
set explorer [get_explorer] set cmd [get_explorer]
eval exec $explorer [list [file nativename $_gitworktree]] & lappend cmd [file nativename $_gitworktree]
safe_exec_bg $cmd
} }


# Open file relative to the working tree by the default associated app. # Open file relative to the working tree by the default associated app.
proc do_file_open {file} { proc do_file_open {file} {
global _gitworktree global _gitworktree
set explorer [get_explorer] set cmd [get_explorer]
set full_file_path [file join $_gitworktree $file] 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 set is_quitting 0
@ -2316,7 +2341,7 @@ proc do_quit {{rc {1}}} {
if {![string match amend* $commit_type] if {![string match amend* $commit_type]
&& $msg ne {}} { && $msg ne {}} {
catch { catch {
set fd [open $save w] set fd [safe_open_file $save w]
fconfigure $fd -encoding utf-8 fconfigure $fd -encoding utf-8
puts -nonewline $fd $msg puts -nonewline $fd $msg
close $fd close $fd
@ -2760,17 +2785,16 @@ if {![is_bare]} {


if {[is_Windows]} { if {[is_Windows]} {
# Use /git-bash.exe if available # Use /git-bash.exe if available
set normalized [file normalize $::argv0] set _git_bash [safe_exec [list cygpath -m /git-bash.exe]]
regsub "/mingw../libexec/git-core/git-gui$" \ if {[file executable $_git_bash]} {
$normalized "/git-bash.exe" cmdLine set _bash_cmdline [list "Git Bash" $_git_bash]
if {$cmdLine != $normalized && [file exists $cmdLine]} {
set cmdLine [list "Git Bash" $cmdLine &]
} else { } else {
set cmdLine [list "Git Bash" bash --login -l &] set _bash_cmdline [list "Git Bash" bash --login -l]
} }
.mbar.repository add command \ .mbar.repository add command \
-label [mc "Git Bash"] \ -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]} { if {[is_Windows] || ![is_bare]} {
@ -4079,7 +4103,7 @@ if {[winfo exists $ui_comm]} {
} }
} elseif {$m} { } elseif {$m} {
catch { catch {
set fd [open [gitdir GITGUI_BCK] w] set fd [safe_open_file [gitdir GITGUI_BCK] w]
fconfigure $fd -encoding utf-8 fconfigure $fd -encoding utf-8
puts -nonewline $fd $msg puts -nonewline $fd $msg
close $fd close $fd

View File

@ -481,14 +481,14 @@ method _load {jump} {
if {$do_textconv ne 0} { if {$do_textconv ne 0} {
set fd [open_cmd_pipe $textconv $path] set fd [open_cmd_pipe $textconv $path]
} else { } else {
set fd [open $path r] set fd [safe_open_file $path r]
} }
fconfigure $fd -eofchar {} fconfigure $fd -eofchar {}
} else { } else {
if {$do_textconv ne 0} { 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 { } else {
set fd [git_read cat-file blob "$commit:$path"] set fd [git_read [list cat-file blob "$commit:$path"]]
} }
} }
fconfigure $fd \ fconfigure $fd \
@ -617,7 +617,7 @@ method _exec_blame {cur_w cur_d options cur_s} {
} }


lappend options -- $path 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 fconfigure $fd -blocking 0 -translation lf -encoding utf-8
fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d] fileevent $fd readable [cb _read_blame $fd $cur_w $cur_d]
set current_fd $fd set current_fd $fd
@ -986,7 +986,7 @@ method _showcommit {cur_w lno} {
if {[catch {set msg $header($cmit,message)}]} { if {[catch {set msg $header($cmit,message)}]} {
set msg {} set msg {}
catch { catch {
set fd [git_read cat-file commit $cmit] set fd [git_read [list cat-file commit $cmit]]
fconfigure $fd -encoding binary -translation lf fconfigure $fd -encoding binary -translation lf
# By default commits are assumed to be in utf-8 # By default commits are assumed to be in utf-8
set enc utf-8 set enc utf-8
@ -1134,7 +1134,7 @@ method _blameparent {} {
} else { } else {
set diffcmd [list diff-tree --unified=0 $cparent $cmit -- $new_path] 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"] $status_operation stop [mc "Unable to display parent"]
error_popup [strcat [mc "Error loading diff:"] "\n\n$err"] error_popup [strcat [mc "Error loading diff:"] "\n\n$err"]
return return

View File

@ -7,7 +7,7 @@ proc load_all_heads {} {
set rh refs/heads set rh refs/heads
set rh_len [expr {[string length $rh] + 1}] set rh_len [expr {[string length $rh] + 1}]
set all_heads [list] 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 fconfigure $fd -translation binary -encoding utf-8
while {[gets $fd line] > 0} { while {[gets $fd line] > 0} {
if {!$some_heads_tracking || ![is_tracking_branch $line]} { if {!$some_heads_tracking || ![is_tracking_branch $line]} {
@ -21,10 +21,10 @@ proc load_all_heads {} {


proc load_all_tags {} { proc load_all_tags {} {
set all_tags [list] set all_tags [list]
set fd [git_read for-each-ref \ set fd [git_read [list for-each-ref \
--sort=-taggerdate \ --sort=-taggerdate \
--format=%(refname) \ --format=%(refname) \
refs/tags] refs/tags]]
fconfigure $fd -translation binary -encoding utf-8 fconfigure $fd -translation binary -encoding utf-8
while {[gets $fd line] > 0} { while {[gets $fd line] > 0} {
if {![regsub ^refs/tags/ $line {} name]} continue 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] lappend browser_stack [list $tree_id $name]
$w conf -state disabled $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 fconfigure $fd -blocking 0 -translation binary -encoding utf-8
fileevent $fd readable [cb _read $fd] fileevent $fd readable [cb _read $fd]
} }

View File

@ -304,12 +304,12 @@ The rescan will be automatically started now.
_readtree $this _readtree $this
} else { } else {
ui_status [mc "Refreshing file status..."] ui_status [mc "Refreshing file status..."]
set fd [git_read update-index \ set fd [git_read [list update-index \
-q \ -q \
--unmerged \ --unmerged \
--ignore-missing \ --ignore-missing \
--refresh \ --refresh \
] ]]
fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [cb _refresh_wait $fd] fileevent $fd readable [cb _refresh_wait $fd]
} }
@ -345,14 +345,15 @@ method _readtree {} {
[mc "Updating working directory to '%s'..." [_name $this]] \ [mc "Updating working directory to '%s'..." [_name $this]] \
[mc "files checked out"]] [mc "files checked out"]]


set fd [git_read --stderr read-tree \ set fd [git_read [list read-tree \
-m \ -m \
-u \ -u \
-v \ -v \
--exclude-per-directory=.gitignore \ --exclude-per-directory=.gitignore \
$HEAD \ $HEAD \
$new_hash \ $new_hash \
] ] \
[list 2>@1]]
fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation] fileevent $fd readable [cb _readtree_wait $fd $status_bar_operation]
} }
@ -510,18 +511,8 @@ method _update_repo_state {} {
delete_this delete_this
} }


git-version proc _detach_HEAD {log new} { proc _detach_HEAD {log new} {
>= 1.5.3 {
git update-ref --no-deref -m $log HEAD $new 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
}
} }


method _confirm_reset {cur} { method _confirm_reset {cur} {
@ -582,7 +573,7 @@ method _confirm_reset {cur} {
pack $w.buttons.cancel -side right -padx 5 pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10 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} { while {[gets $fd line] > 0} {
set abbr [string range $line 0 7] set abbr [string range $line 0 7]
set subj [string range $line 41 end] set subj [string range $line 41 end]

View File

@ -641,8 +641,8 @@ method _do_clone2 {} {
set pwd [pwd] set pwd [pwd]
if {[catch { if {[catch {
file mkdir [gitdir objects info] file mkdir [gitdir objects info]
set f_in [open [file join $objdir info alternates] r] set f_in [safe_open_file [file join $objdir info alternates] r]
set f_cp [open [gitdir objects info alternates] w] set f_cp [safe_open_file [gitdir objects info alternates] w]
fconfigure $f_in -translation binary -encoding binary fconfigure $f_in -translation binary -encoding binary
fconfigure $f_cp -translation binary -encoding binary fconfigure $f_cp -translation binary -encoding binary
cd $objdir cd $objdir
@ -727,7 +727,7 @@ method _do_clone2 {} {
[cb _do_clone_tags] [cb _do_clone_tags]
} }
shared { shared {
set fd [open [gitdir objects info alternates] w] set fd [safe_open_file [gitdir objects info alternates] w]
fconfigure $fd -translation binary fconfigure $fd -translation binary
puts $fd $objdir puts $fd $objdir
close $fd close $fd
@ -760,8 +760,8 @@ method _copy_files {objdir tocopy} {
} }
foreach p $tocopy { foreach p $tocopy {
if {[catch { if {[catch {
set f_in [open [file join $objdir $p] r] set f_in [safe_open_file [file join $objdir $p] r]
set f_cp [open [file join .git objects $p] w] set f_cp [safe_open_file [file join .git objects $p] w]
fconfigure $f_in -translation binary -encoding binary fconfigure $f_in -translation binary -encoding binary
fconfigure $f_cp -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]] error_popup [mc "Not a Git repository: %s" [file tail $origin_url]]
return 0 return 0
} }
set fd_in [git_read for-each-ref \ set fd_in [git_read [list for-each-ref \
--tcl \ --tcl \
{--format=list %(refname) %(objectname) %(*objectname)}] {--format=list %(refname) %(objectname) %(*objectname)}]]
cd $pwd cd $pwd


set fd [open [gitdir packed-refs] w] set fd [safe_open_file [gitdir packed-refs] w]
fconfigure $fd -translation binary fconfigure $fd -translation binary
puts $fd "# pack-refs with: peeled" puts $fd "# pack-refs with: peeled"
while {[gets $fd_in line] >= 0} { while {[gets $fd_in line] >= 0} {
@ -877,7 +877,7 @@ method _do_clone_full_end {ok} {


set HEAD {} set HEAD {}
if {[file exists [gitdir FETCH_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} { while {[gets $fd line] >= 0} {
if {[regexp "^(.{40})\t\t" $line line HEAD]} { if {[regexp "^(.{40})\t\t" $line line HEAD]} {
break break
@ -953,13 +953,14 @@ method _do_clone_checkout {HEAD} {
[mc "files"]] [mc "files"]]


set readtree_err {} set readtree_err {}
set fd [git_read --stderr read-tree \ set fd [git_read [list read-tree \
-m \ -m \
-u \ -u \
-v \ -v \
HEAD \ HEAD \
HEAD \ HEAD \
] ] \
[list 2>@1]]
fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [cb _readtree_wait $fd] fileevent $fd readable [cb _readtree_wait $fd]
} }

View File

@ -146,14 +146,14 @@ constructor _new {path unmerged_only title} {
append fmt { %(*subject)} append fmt { %(*subject)}
append fmt {]} append fmt {]}
set all_refn [list] set all_refn [list]
set fr_fd [git_read for-each-ref \ set fr_fd [git_read [list for-each-ref \
--tcl \ --tcl \
--sort=-taggerdate \ --sort=-taggerdate \
--format=$fmt \ --format=$fmt \
refs/heads \ refs/heads \
refs/remotes \ refs/remotes \
refs/tags \ refs/tags \
] ]]
fconfigure $fr_fd -translation lf -encoding utf-8 fconfigure $fr_fd -translation lf -encoding utf-8
while {[gets $fr_fd line] > 0} { while {[gets $fr_fd line] > 0} {
set line [eval $line] set line [eval $line]
@ -176,7 +176,7 @@ constructor _new {path unmerged_only title} {
close $fr_fd close $fr_fd


if {$unmerged_only} { 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} { while {[gets $fr_fd sha1] > 0} {
if {[catch {set rlst $cmt_refn($sha1)}]} continue if {[catch {set rlst $cmt_refn($sha1)}]} continue
foreach refn $rlst { foreach refn $rlst {
@ -579,7 +579,7 @@ method _reflog_last {name} {


set last {} set last {}
if {[catch {set last [file mtime [gitdir $name]]}] 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 fconfigure $g -translation binary
while {[gets $g line] >= 0} { while {[gets $g line] >= 0} {
if {[regexp {> ([1-9][0-9]*) } $line line when]} { 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 { if {[catch {
set name "" set name ""
set email "" 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 fconfigure $fd -encoding binary -translation lf
# By default commits are assumed to be in utf-8 # By default commits are assumed to be in utf-8
set enc utf-8 set enc utf-8
@ -236,7 +236,7 @@ A good commit message has the following format:
# -- Build the message file. # -- Build the message file.
# #
set msg_p [gitdir GITGUI_EDITMSG] 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 fconfigure $msg_wt -translation lf
setup_commit_encoding $msg_wt setup_commit_encoding $msg_wt
puts $msg_wt $msg puts $msg_wt $msg
@ -336,7 +336,7 @@ proc commit_commitmsg_wait {fd_ph curHEAD msg_p} {


proc commit_writetree {curHEAD msg_p} { proc commit_writetree {curHEAD msg_p} {
ui_status [mc "Committing changes..."] ui_status [mc "Committing changes..."]
set fd_wt [git_read write-tree] set fd_wt [git_read [list write-tree]]
fileevent $fd_wt readable \ fileevent $fd_wt readable \
[list commit_committree $fd_wt $curHEAD $msg_p] [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. # -- Verify this wasn't an empty change.
# #
if {$commit_type eq {normal}} { 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 fconfigure $fd_ot -encoding binary -translation lf
set old_tree [gets $fd_ot] set old_tree [gets $fd_ot]
close $fd_ot close $fd_ot
@ -399,8 +399,8 @@ A rescan will be automatically started now.
foreach p [concat $PARENT $MERGE_HEAD] { foreach p [concat $PARENT $MERGE_HEAD] {
lappend cmd -p $p lappend cmd -p $p
} }
lappend cmd <$msg_p set msgtxt [list <$msg_p]
if {[catch {set cmt_id [eval git $cmd]} err]} { if {[catch {set cmt_id [git_redir $cmd $msgtxt]} err]} {
catch {file delete $msg_p} catch {file delete $msg_p}
error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"] error_popup [strcat [mc "commit-tree failed:"] "\n\n$err"]
ui_status [mc "Commit failed."] ui_status [mc "Commit failed."]
@ -420,7 +420,7 @@ A rescan will be automatically started now.
if {$commit_type ne {normal}} { if {$commit_type ne {normal}} {
append reflogm " ($commit_type)" 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 setup_commit_encoding $msg_fd 1
gets $msg_fd subject gets $msg_fd subject
close $msg_fd close $msg_fd

View File

@ -92,10 +92,9 @@ method _init {} {


method exec {cmd {after {}}} { method exec {cmd {after {}}} {
if {[lindex $cmd 0] eq {git}} { 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 { } else {
lappend cmd 2>@1 set fd_f [safe_open_command $cmd [list 2>@1]]
set fd_f [_open_stdout_stderr $cmd]
} }
fconfigure $fd_f -blocking 0 -translation binary -encoding [encoding system] fconfigure $fd_f -blocking 0 -translation binary -encoding [encoding system]
fileevent $fd_f readable [cb _read $fd_f $after] fileevent $fd_f readable [cb _read $fd_f $after]

View File

@ -3,7 +3,7 @@


proc do_stats {} { proc do_stats {} {
global use_ttk NS global use_ttk NS
set fd [git_read count-objects -v] set fd [git_read [list count-objects -v]]
while {[gets $fd line] > 0} { while {[gets $fd line] > 0} {
if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { if {[regexp {^([^:]+): (\d+)$} $line _ name value]} {
set stats($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] set sz [string length $content]
} }
file { file {
set fd [open $path r] set fd [safe_open_file $path r]
fconfigure $fd \ fconfigure $fd \
-eofchar {} \ -eofchar {} \
-encoding [get_path_encoding $path] -encoding [get_path_encoding $path]
@ -215,7 +215,7 @@ proc show_other_diff {path w m cont_info} {
$ui_diff insert end \ $ui_diff insert end \
"* [mc "Git Repository (subproject)"]\n" \ "* [mc "Git Repository (subproject)"]\n" \
d_info d_info
} elseif {![catch {set type [exec file $path]}]} { } elseif {![catch {set type [safe_exec [list file $path]]}]} {
set n [string length $path] set n [string length $path]
if {[string equal -length $n $path $type]} { if {[string equal -length $n $path $type]} {
set type [string range $type $n end] 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 set diff_active 0
unlock_index unlock_index
ui_status [mc "Unable to display %s" [escape_path $path]] ui_status [mc "Unable to display %s" [escape_path $path]]
@ -603,7 +603,7 @@ proc apply_or_revert_hunk {x y revert} {


if {[catch { if {[catch {
set enc [get_path_encoding $current_diff_path] 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 fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $wholepatch puts -nonewline $p $wholepatch
close $p} err]} { close $p} err]} {
@ -839,7 +839,7 @@ proc apply_or_revert_range_or_line {x y revert} {


if {[catch { if {[catch {
set enc [get_path_encoding $current_diff_path] 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 fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $current_diff_header puts -nonewline $p $current_diff_header
puts -nonewline $p $wholepatch puts -nonewline $p $wholepatch
@ -876,7 +876,7 @@ proc undo_last_revert {} {


if {[catch { if {[catch {
set enc $last_revert_enc set enc $last_revert_enc
set p [eval git_write $apply_cmd] set p [git_write $apply_cmd]
fconfigure $p -translation binary -encoding $enc fconfigure $p -translation binary -encoding $enc
puts -nonewline $p $last_revert puts -nonewline $p $last_revert
close $p} err]} { close $p} err]} {

View File

@ -75,7 +75,7 @@ proc update_indexinfo {msg path_list after} {
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}


set status_bar_operation [$::main_status start $msg [mc "files"]] 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 \ fconfigure $fd \
-blocking 0 \ -blocking 0 \
-buffering full \ -buffering full \
@ -144,7 +144,7 @@ proc update_index {msg path_list after} {
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}


set status_bar_operation [$::main_status start $msg [mc "files"]] 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 \ fconfigure $fd \
-blocking 0 \ -blocking 0 \
-buffering full \ -buffering full \
@ -218,13 +218,13 @@ proc checkout_index {msg path_list after capture_error} {
if {$batch > 25} {set batch 25} if {$batch > 25} {set batch 25}


set status_bar_operation [$::main_status start $msg [mc "files"]] set status_bar_operation [$::main_status start $msg [mc "files"]]
set fd [git_write checkout-index \ set fd [git_write [list checkout-index \
--index \ --index \
--quiet \ --quiet \
--force \ --force \
-z \ -z \
--stdin \ --stdin \
] ]]
fconfigure $fd \ fconfigure $fd \
-blocking 0 \ -blocking 0 \
-buffering full \ -buffering full \

View File

@ -93,7 +93,7 @@ method _start {} {
set spec [$w_rev get_tracking_branch] set spec [$w_rev get_tracking_branch]
set cmit [$w_rev get_commit] 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 fconfigure $fh -translation lf
if {$spec eq {}} { if {$spec eq {}} {
set remote . set remote .
@ -118,7 +118,7 @@ method _start {} {
set cmd [list git] set cmd [list git]
lappend cmd merge lappend cmd merge
lappend cmd --strategy=recursive 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 HEAD
lappend cmd $name lappend cmd $name
} }
@ -239,7 +239,7 @@ Continue with resetting the current changes?"]
} }


if {[ask_popup $op_question] eq {yes}} { 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 fconfigure $fd -blocking 0 -translation binary
set status_bar_operation [$::main_status \ set status_bar_operation [$::main_status \
start \ start \

View File

@ -88,7 +88,7 @@ proc merge_load_stages {path cont} {
set merge_stages(3) {} set merge_stages(3) {}
set merge_stages_buf {} 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 fconfigure $merge_stages_fd -blocking 0 -translation binary -encoding binary
fileevent $merge_stages_fd readable [list read_merge_stages $merge_stages_fd $cont] 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 { foreach fname $stages {
if {$merge_stages($i) eq {}} { if {$merge_stages($i) eq {}} {
file delete $fname file delete $fname
catch { close [open $fname w] } catch { close [safe_open_file $fname w] }
} else { } else {
# A hack to support autocrlf properly # A hack to support autocrlf properly
git checkout-index -f --stage=$i -- $target 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 # Force redirection to avoid interpreting output on stderr
# as an error, and launch the tool # 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 delete_temp_files $mtool_tmpfiles
error_popup [mc "Could not start the merge tool:\n\n%s" $err] error_popup [mc "Could not start the merge tool:\n\n%s" $err]
return return

View File

@ -32,7 +32,7 @@ proc all_tracking_branches {} {
} }


if {$pat ne {}} { 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} { while {[gets $fd n] > 0} {
foreach spec $pat { foreach spec $pat {
set dst [string range [lindex $spec 0] 0 end-2] set dst [string range [lindex $spec 0] 0 end-2]
@ -75,7 +75,7 @@ proc load_all_remotes {} {


foreach name $all_remotes { foreach name $all_remotes {
catch { 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} { while {[gets $fd line] >= 0} {
if {[regexp {^URL:[ ]*(.+)$} $line line url]} { if {[regexp {^URL:[ ]*(.+)$} $line line url]} {
set remote_url($name) $url set remote_url($name) $url
@ -145,7 +145,7 @@ proc add_fetch_entry {r} {
} }
} else { } else {
catch { catch {
set fd [open [gitdir remotes $r] r] set fd [safe_open_file [gitdir remotes $r] r]
while {[gets $fd n] >= 0} { while {[gets $fd n] >= 0} {
if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { if {[regexp {^Pull:[ \t]*([^:]+):} $n]} {
set enable 1 set enable 1
@ -182,7 +182,7 @@ proc add_push_entry {r} {
} }
} else { } else {
catch { catch {
set fd [open [gitdir remotes $r] r] set fd [safe_open_file [gitdir remotes $r] r]
while {[gets $fd n] >= 0} { while {[gets $fd n] >= 0} {
if {[regexp {^Push:[ \t]*([^:]+):} $n]} { if {[regexp {^Push:[ \t]*([^:]+):} $n]} {
set enable 1 set enable 1

View File

@ -308,7 +308,7 @@ method _load {cache uri} {
set full_list [list] set full_list [list]
set head_cache($cache) [list] set head_cache($cache) [list]
set full_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 \ fconfigure $active_ls \
-blocking 0 \ -blocking 0 \
-translation lf \ -translation lf \

View File

@ -12,7 +12,7 @@ proc do_windows_shortcut {} {
set fn ${fn}.lnk set fn ${fn}.lnk
} }
# Use git-gui.exe if available (ie: git-for-windows) # 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 {}} { if {$cmdLine eq {}} {
set cmdLine [list [info nameofexecutable] \ set cmdLine [list [info nameofexecutable] \
[file normalize $::argv0]] [file normalize $::argv0]]
@ -30,8 +30,8 @@ proc do_cygwin_shortcut {} {
global argv0 _gitworktree oguilib global argv0 _gitworktree oguilib


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


file mkdir $MacOS 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"?> 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"> <!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"> <plist version="1.0">
@ -108,7 +108,7 @@ proc do_macosx_app {} {
</plist>} </plist>}
close $fd close $fd


set fd [open $exe w] set fd [safe_open_file $exe w]
puts $fd "#!/bin/sh" puts $fd "#!/bin/sh"
foreach name [lsort [array names env]] { foreach name [lsort [array names env]] {
set value $env($name) set value $env($name)

View File

@ -7,7 +7,7 @@ proc find_ssh_key {} {
~/.ssh/id_rsa.pub ~/.ssh/identity.pub ~/.ssh/id_rsa.pub ~/.ssh/identity.pub
} { } {
if {[file exists $name]} { if {[file exists $name]} {
set fh [open $name r] set fh [safe_open_file $name r]
set cont [read $fh] set cont [read $fh]
close $fh close $fh
return [list $name $cont] return [list $name $cont]
@ -83,9 +83,10 @@ proc make_ssh_key {w} {
set sshkey_title [mc "Generating..."] set sshkey_title [mc "Generating..."]
$w.header.gen configure -state disabled $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] error_popup [mc "Could not start ssh-keygen:\n\n%s" $err]
return return
} }

View File

@ -110,14 +110,14 @@ proc tools_exec {fullname} {


set cmdline $repo_config(guitool.$fullname.cmd) set cmdline $repo_config(guitool.$fullname.cmd)
if {[is_config_true "guitool.$fullname.noconsole"]} { 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 {}] [list tools_complete $fullname {}]
} else { } else {
regsub {/} $fullname { / } title regsub {/} $fullname { / } title
set w [console::new \ set w [console::new \
[mc "Tool: %s" $title] \ [mc "Tool: %s" $title] \
[mc "Running: %s" $cmdline]] [mc "Running: %s" $cmdline]]
console::exec $w [list sh -c $cmdline] \ console::exec $w [list [shellpath] -c $cmdline] \
[list tools_complete $fullname $w] [list tools_complete $fullname $w]
} }


@ -130,8 +130,7 @@ proc tools_exec {fullname} {
} }


proc tools_run_silent {cmd after} { proc tools_run_silent {cmd after} {
lappend cmd 2>@1 set fd [safe_open_command $cmd [list 2>@1]]
set fd [_open_stdout_stderr $cmd]


fconfigure $fd -blocking 0 -translation binary fconfigure $fd -blocking 0 -translation binary
fileevent $fd readable [list tools_consume_input $fd $after] fileevent $fd readable [list tools_consume_input $fd $after]

View File

@ -2,11 +2,11 @@
# Copyright (C) 2007 Shawn Pearce # Copyright (C) 2007 Shawn Pearce


proc win32_read_lnk {lnk_path} { proc win32_read_lnk {lnk_path} {
return [exec cscript.exe \ return [safe_exec [list cscript.exe \
/E:jscript \ /E:jscript \
/nologo \ /nologo \
[file join $::oguilib win32_shortcut.js] \ [file join $::oguilib win32_shortcut.js] \
$lnk_path] $lnk_path]]
} }


proc win32_create_lnk {lnk_path lnk_exec lnk_dir} { 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_args [lrange $lnk_exec 1 end]
set lnk_exec [lindex $lnk_exec 0] set lnk_exec [lindex $lnk_exec 0]


eval [list exec wscript.exe \ set cmd [list wscript.exe \
/E:jscript \ /E:jscript \
/nologo \ /nologo \
[file nativename [file join $oguilib win32_shortcut.js]] \ [file nativename [file join $oguilib win32_shortcut.js]] \
$lnk_path \ $lnk_path \
[file nativename [file join $oguilib git-gui.ico]] \ [file nativename [file join $oguilib git-gui.ico]] \
$lnk_dir \ $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 # 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 {} { proc hasworktree {} {
return [expr {[exec git rev-parse --is-bare-repository] == "false" && return [expr {[exec git rev-parse --is-bare-repository] == "false" &&
[exec git rev-parse --is-inside-git-dir] == "false"}] [exec git rev-parse --is-inside-git-dir] == "false"}]
@ -238,7 +323,7 @@ proc unmerged_files {files} {
set mlist {} set mlist {}
set nr_unmerged 0 set nr_unmerged 0
if {[catch { if {[catch {
set fd [open "| git ls-files -u" r] set fd [safe_open_command {git ls-files -u}]
} err]} { } err]} {
show_error {} . "[mc "Couldn't get list of unmerged files:"] $err" show_error {} . "[mc "Couldn't get list of unmerged files:"] $err"
exit 1 exit 1
@ -400,7 +485,7 @@ proc parseviewrevs {view revs} {
} elseif {[lsearch -exact $revs --all] >= 0} { } elseif {[lsearch -exact $revs --all] >= 0} {
lappend revs HEAD 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 # we get stdout followed by stderr in $err
# for an unknown rev, git rev-parse echoes it and then errors out # for an unknown rev, git rev-parse echoes it and then errors out
set errlines [split $err "\n"] set errlines [split $err "\n"]
@ -457,16 +542,6 @@ proc parseviewrevs {view revs} {
return $ret 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 # Start off a git log process and arrange to read its output
proc start_rev_list {view} { proc start_rev_list {view} {
global startmsecs commitidx viewcomplete curview global startmsecs commitidx viewcomplete curview
@ -488,7 +563,7 @@ proc start_rev_list {view} {
set args $viewargs($view) set args $viewargs($view)
if {$viewargscmd($view) ne {}} { if {$viewargscmd($view) ne {}} {
if {[catch { if {[catch {
set str [exec sh -c $viewargscmd($view)] set str [safe_exec [list sh -c $viewargscmd($view)]]
} err]} { } err]} {
error_popup "[mc "Error executing --argscmd command:"] $err" error_popup "[mc "Error executing --argscmd command:"] $err"
return 0 return 0
@ -526,10 +601,9 @@ proc start_rev_list {view} {
} }


if {[catch { if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
--parents --boundary $args --stdin \ --parents --boundary $args --stdin] \
"<<[join [concat $revs "--" \ [list "<<[join [concat $revs "--" $files] "\n"]"]]
[escape_filter_paths $files]] "\\n"]"] r]
} err]} { } err]} {
error_popup "[mc "Error executing git log:"] $err" error_popup "[mc "Error executing git log:"] $err"
return 0 return 0
@ -563,9 +637,9 @@ proc stop_instance {inst} {
set pid [pid $fd] set pid [pid $fd]


if {$::tcl_platform(platform) eq {windows}} { if {$::tcl_platform(platform) eq {windows}} {
exec taskkill /pid $pid safe_exec [list taskkill /pid $pid]
} else { } else {
exec kill $pid safe_exec [list kill $pid]
} }
} }
catch {close $fd} catch {close $fd}
@ -680,11 +754,9 @@ proc updatecommits {} {
set args $vorigargs($view) set args $vorigargs($view)
} }
if {[catch { if {[catch {
set fd [open [concat | git log --no-color -z --pretty=raw $show_notes \ set fd [safe_open_command_redirect [concat git log --no-color -z --pretty=raw $show_notes \
--parents --boundary $args --stdin \ --parents --boundary $args --stdin] \
"<<[join [concat $revs "--" \ [list "<<[join [concat $revs "--" $vfilelimit($view)] "\n"]"]]
[escape_filter_paths \
$vfilelimit($view)]] "\\n"]"] r]
} err]} { } err]} {
error_popup "[mc "Error executing git log:"] $err" error_popup "[mc "Error executing git log:"] $err"
return return
@ -1651,8 +1723,8 @@ proc getcommitlines {fd inst view updating} {
# and if we already know about it, using the rewritten # and if we already know about it, using the rewritten
# parent as a substitute parent for $id's children. # parent as a substitute parent for $id's children.
if {![catch { if {![catch {
set rwid [exec git rev-list --first-parent --max-count=1 \ set rwid [safe_exec [list git rev-list --first-parent --max-count=1 \
$id -- $vfilelimit($view)] $id -- $vfilelimit($view)]]
}]} { }]} {
if {$rwid ne {} && [info exists varcid($view,$rwid)]} { if {$rwid ne {} && [info exists varcid($view,$rwid)]} {
# use $rwid in place of $id # use $rwid in place of $id
@ -1772,7 +1844,7 @@ proc do_readcommit {id} {
global tclencoding global tclencoding


# Invoke git-log to handle automatic encoding conversion # 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 # Read the results using i18n.logoutputencoding
fconfigure $fd -translation lf -eofchar {} fconfigure $fd -translation lf -eofchar {}
if {$tclencoding != {}} { if {$tclencoding != {}} {
@ -1908,7 +1980,7 @@ proc readrefs {} {
foreach v {tagids idtags headids idheads otherrefids idotherrefs} { foreach v {tagids idtags headids idheads otherrefids idotherrefs} {
unset -nocomplain $v unset -nocomplain $v
} }
set refd [open [list | git show-ref -d] r] set refd [safe_open_command [list git show-ref -d]]
if {$tclencoding != {}} { if {$tclencoding != {}} {
fconfigure $refd -encoding $tclencoding fconfigure $refd -encoding $tclencoding
} }
@ -1956,7 +2028,7 @@ proc readrefs {} {
set selectheadid {} set selectheadid {}
if {$selecthead ne {}} { if {$selecthead ne {}} {
catch { 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 "Reread re&ferences" command rereadrefs}
{mc "&List references" command showrefs -accelerator F2} {mc "&List references" command showrefs -accelerator F2}
{xx "" separator} {xx "" separator}
{mc "Start git &gui" command {exec git gui &}} {mc "Start git &gui" command {safe_exec_redirect [list git gui] [list &]}}
{xx "" separator} {xx "" separator}
{mc "&Quit" command doquit -accelerator Meta1-Q} {mc "&Quit" command doquit -accelerator Meta1-Q}
}} }}
@ -3007,7 +3079,7 @@ proc savestuff {w} {
set remove_tmp 0 set remove_tmp 0
if {[catch { if {[catch {
set try_count 0 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} { if {[incr try_count] > 50} {
error "Unable to write config file: $config_file_tmp exists" error "Unable to write config file: $config_file_tmp exists"
} }
@ -3723,7 +3795,7 @@ proc gitknewtmpdir {} {
set tmpdir $gitdir set tmpdir $gitdir
} }
set gitktmpformat [file join $tmpdir ".gitk-tmp.XXXXXX"] 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]]] set gitktmpdir [file join $gitdir [format ".gitk-tmp.%s" [pid]]]
} }
if {[catch {file mkdir $gitktmpdir} err]} { if {[catch {file mkdir $gitktmpdir} err]} {
@ -3745,7 +3817,7 @@ proc gitknewtmpdir {} {
proc save_file_from_commit {filename output what} { proc save_file_from_commit {filename output what} {
global nullfile 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]} { if {[string match "fatal: bad revision *" $err]} {
return $nullfile return $nullfile
} }
@ -3810,7 +3882,7 @@ proc external_diff {} {


if {$difffromfile ne {} && $difftofile ne {}} { if {$difffromfile ne {} && $difftofile ne {}} {
set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] 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 file delete -force $diffdir
error_popup "$extdifftool: [mc "command failed:"] $err" error_popup "$extdifftool: [mc "command failed:"] $err"
} else { } else {
@ -3914,7 +3986,7 @@ proc external_blame_diff {} {
# Find the SHA1 ID of the blob for file $fname in the index # Find the SHA1 ID of the blob for file $fname in the index
# at stage 0 or 2 # at stage 0 or 2
proc index_sha1 {fname} { 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} { while {[gets $f line] >= 0} {
set info [lindex [split $line "\t"] 0] set info [lindex [split $line "\t"] 0]
set stage [lindex $info 2] set stage [lindex $info 2]
@ -3974,7 +4046,7 @@ proc external_blame {parent_idx {line {}}} {
# being given an absolute path... # being given an absolute path...
set f [make_relative $f] set f [make_relative $f]
lappend cmdline $base_commit $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" error_popup "[mc "git gui blame: command failed:"] $err"
} }
} }
@ -4002,7 +4074,7 @@ proc show_line_source {} {
# must be a merge in progress... # must be a merge in progress...
if {[catch { if {[catch {
# get the last line from .git/MERGE_HEAD # 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] set id [lindex [split [read $f] "\n"] end-1]
close $f close $f
} err]} { } err]} {
@ -4025,19 +4097,17 @@ proc show_line_source {} {
} }
set line [lindex $h 1] set line [lindex $h 1]
} }
set blameargs {} set blamefile [file join $cdup $flist_menu_file]
if {$from_index ne {}} { if {$from_index ne {}} {
lappend blameargs | git cat-file blob $from_index set blameargs [list \
} [list git cat-file blob $from_index] \
lappend blameargs | git blame -p -L$line,+1 [list git blame -p -L$line,+1 --contents - -- $blamefile]]
if {$from_index ne {}} {
lappend blameargs --contents -
} else { } 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 { if {[catch {
set f [open $blameargs r] set f [safe_open_pipeline $blameargs]
} err]} { } err]} {
error_popup [mc "Couldn't start git blame: %s" $err] error_popup [mc "Couldn't start git blame: %s" $err]
return return
@ -4962,8 +5032,8 @@ proc do_file_hl {serial} {
# must be "containing:", i.e. we're searching commit info # must be "containing:", i.e. we're searching commit info
return return
} }
set cmd [concat | git diff-tree -r -s --stdin $gdtargs] set cmd [concat git diff-tree -r -s --stdin $gdtargs]
set filehighlight [open $cmd r+] set filehighlight [safe_open_command_rw $cmd]
fconfigure $filehighlight -blocking 0 fconfigure $filehighlight -blocking 0
filerun $filehighlight readfhighlight filerun $filehighlight readfhighlight
set fhl_list {} set fhl_list {}
@ -5392,8 +5462,8 @@ proc get_viewmainhead {view} {
global viewmainheadid vfilelimit viewinstances mainheadid global viewmainheadid vfilelimit viewinstances mainheadid


catch { catch {
set rfd [open [concat | git rev-list -1 $mainheadid \ set rfd [safe_open_command [concat git rev-list -1 $mainheadid \
-- $vfilelimit($view)] r] -- $vfilelimit($view)]]
set j [reg_instance $rfd] set j [reg_instance $rfd]
lappend viewinstances($view) $j lappend viewinstances($view) $j
fconfigure $rfd -blocking 0 fconfigure $rfd -blocking 0
@ -5458,14 +5528,14 @@ proc dodiffindex {} {
if {!$showlocalchanges || !$hasworktree} return if {!$showlocalchanges || !$hasworktree} return
incr lserial incr lserial
if {[package vcompare $git_version "1.7.2"] >= 0} { 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 { } else {
set cmd "|git diff-index --cached HEAD" set cmd "git diff-index --cached HEAD"
} }
if {$vfilelimit($curview) ne {}} { if {$vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)] set cmd [concat $cmd -- $vfilelimit($curview)]
} }
set fd [open $cmd r] set fd [safe_open_command $cmd]
fconfigure $fd -blocking 0 fconfigure $fd -blocking 0
set i [reg_instance $fd] set i [reg_instance $fd]
filerun $fd [list readdiffindex $fd $lserial $i] 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 # 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 {}} { if {$vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)] set cmd [concat $cmd -- $vfilelimit($curview)]
} }
set fd [open $cmd r] set fd [safe_open_command $cmd]
fconfigure $fd -blocking 0 fconfigure $fd -blocking 0
set i [reg_instance $fd] set i [reg_instance $fd]
filerun $fd [list readdifffiles $fd $serial $i] filerun $fd [list readdifffiles $fd $serial $i]
@ -7283,8 +7353,8 @@ proc browseweb {url} {
global web_browser global web_browser


if {$web_browser eq {}} return if {$web_browser eq {}} return
# Use eval here in case $web_browser is a command plus some arguments # Use concat here in case $web_browser is a command plus some arguments
if {[catch {eval exec $web_browser [list $url] &} err]} { if {[catch {safe_exec_redirect [concat $web_browser [list $url]] [list &]} err]} {
error_popup "[mc "Error starting web browser:"] $err" error_popup "[mc "Error starting web browser:"] $err"
} }
} }
@ -7790,13 +7860,13 @@ proc gettree {id} {
if {![info exists treefilelist($id)]} { if {![info exists treefilelist($id)]} {
if {![info exists treepending]} { if {![info exists treepending]} {
if {$id eq $nullid} { if {$id eq $nullid} {
set cmd [list | git ls-files] set cmd [list git ls-files]
} elseif {$id eq $nullid2} { } elseif {$id eq $nullid2} {
set cmd [list | git ls-files --stage -t] set cmd [list git ls-files --stage -t]
} else { } 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 return
} }
set treepending $id set treepending $id
@ -7860,13 +7930,13 @@ proc showfile {f} {
return return
} }
if {$diffids eq $nullid} { 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" puts "oops, can't read $f: $err"
return return
} }
} else { } else {
set blob [lindex $treeidlist($diffids) $i] 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" puts "oops, error reading blob $blob: $err"
return return
} }
@ -8016,7 +8086,7 @@ proc diffcmd {ids flags} {
if {$i >= 0} { if {$i >= 0} {
if {[llength $ids] > 1 && $j < 0} { if {[llength $ids] > 1 && $j < 0} {
# comparing working directory with some specific revision # comparing working directory with some specific revision
set cmd [concat | git diff-index $flags] set cmd [concat git diff-index $flags]
if {$i == 0} { if {$i == 0} {
lappend cmd -R [lindex $ids 1] lappend cmd -R [lindex $ids 1]
} else { } else {
@ -8024,7 +8094,7 @@ proc diffcmd {ids flags} {
} }
} else { } else {
# comparing working directory with index # comparing working directory with index
set cmd [concat | git diff-files $flags] set cmd [concat git diff-files $flags]
if {$j == 1} { if {$j == 1} {
lappend cmd -R lappend cmd -R
} }
@ -8033,7 +8103,7 @@ proc diffcmd {ids flags} {
if {[package vcompare $git_version "1.7.2"] >= 0} { if {[package vcompare $git_version "1.7.2"] >= 0} {
set flags "$flags --ignore-submodules=dirty" 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} { if {[llength $ids] > 1} {
# comparing index with specific revision # comparing index with specific revision
if {$j == 0} { if {$j == 0} {
@ -8049,7 +8119,7 @@ proc diffcmd {ids flags} {
if {$log_showroot} { if {$log_showroot} {
lappend flags --root lappend flags --root
} }
set cmd [concat | git diff-tree -r $flags $ids] set cmd [concat git diff-tree -r $flags $ids]
} }
return $cmd return $cmd
} }
@ -8061,7 +8131,7 @@ proc gettreediffs {ids} {
if {$limitdiffs && $vfilelimit($curview) ne {}} { if {$limitdiffs && $vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)] 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 treepending $ids
set treediff {} set treediff {}
@ -8181,7 +8251,7 @@ proc getblobdiffs {ids} {
if {$limitdiffs && $vfilelimit($curview) ne {}} { if {$limitdiffs && $vfilelimit($curview) ne {}} {
set cmd [concat $cmd -- $vfilelimit($curview)] 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] error_popup [mc "Error getting diffs: %s" $err]
return return
} }
@ -8899,7 +8969,7 @@ proc gotocommit {} {
set id [lindex $matches 0] set id [lindex $matches 0]
} }
} else { } 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] error_popup [mc "Revision %s is not known" $sha1string]
return return
} }
@ -9205,10 +9275,8 @@ proc getpatchid {id} {


if {![info exists patchids($id)]} { if {![info exists patchids($id)]} {
set cmd [diffcmd [list $id] {-p --root}] set cmd [diffcmd [list $id] {-p --root}]
# trim off the initial "|"
set cmd [lrange $cmd 1 end]
if {[catch { 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) [lindex $x 0]
}]} { }]} {
set patchids($id) "error" set patchids($id) "error"
@ -9304,14 +9372,14 @@ proc diffcommits {a b} {
set fna [file join $tmpdir "commit-[string range $a 0 7]"] set fna [file join $tmpdir "commit-[string range $a 0 7]"]
set fnb [file join $tmpdir "commit-[string range $b 0 7]"] set fnb [file join $tmpdir "commit-[string range $b 0 7]"]
if {[catch { if {[catch {
exec git diff-tree -p --pretty $a >$fna safe_exec_redirect [list git diff-tree -p --pretty $a] [list >$fna]
exec git diff-tree -p --pretty $b >$fnb safe_exec_redirect [list git diff-tree -p --pretty $b] [list >$fnb]
} err]} { } err]} {
error_popup [mc "Error writing commit to file: %s" $err] error_popup [mc "Error writing commit to file: %s" $err]
return return
} }
if {[catch { if {[catch {
set fd [open "| diff -U$diffcontext $fna $fnb" r] set fd [safe_open_command "diff -U$diffcontext $fna $fnb"]
} err]} { } err]} {
error_popup [mc "Error diffing commits: %s" $err] error_popup [mc "Error diffing commits: %s" $err]
return return
@ -9451,10 +9519,7 @@ proc mkpatchgo {} {
set newid [$patchtop.tosha1 get] set newid [$patchtop.tosha1 get]
set fname [$patchtop.fname get] set fname [$patchtop.fname get]
set cmd [diffcmd [list $oldid $newid] -p] set cmd [diffcmd [list $oldid $newid] -p]
# trim off the initial "|" if {[catch {safe_exec_redirect $cmd [list >$fname &]} err]} {
set cmd [lrange $cmd 1 end]
lappend cmd >$fname &
if {[catch {eval exec $cmd} err]} {
error_popup "[mc "Error creating patch:"] $err" $patchtop error_popup "[mc "Error creating patch:"] $err" $patchtop
} }
catch {destroy $patchtop} catch {destroy $patchtop}
@ -9523,9 +9588,9 @@ proc domktag {} {
} }
if {[catch { if {[catch {
if {$msg != {}} { if {$msg != {}} {
exec git tag -a -m $msg $tag $id safe_exec [list git tag -a -m $msg $tag $id]
} else { } else {
exec git tag $tag $id safe_exec [list git tag $tag $id]
} }
} err]} { } err]} {
error_popup "[mc "Error creating tag:"] $err" $mktagtop error_popup "[mc "Error creating tag:"] $err" $mktagtop
@ -9593,7 +9658,7 @@ proc copyreference {} {
if {$autosellen < 40} { if {$autosellen < 40} {
lappend cmd --abbrev=$autosellen lappend cmd --abbrev=$autosellen
} }
set reference [eval exec $cmd $rowmenuid] set reference [safe_exec [concat $cmd $rowmenuid]]


clipboard clear clipboard clear
clipboard append $reference clipboard append $reference
@ -9643,7 +9708,7 @@ proc wrcomgo {} {
set id [$wrcomtop.sha1 get] set id [$wrcomtop.sha1 get]
set cmd "echo $id | [$wrcomtop.cmd get]" set cmd "echo $id | [$wrcomtop.cmd get]"
set fname [$wrcomtop.fname 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 error_popup "[mc "Error writing commit:"] $err" $wrcomtop
} }
catch {destroy $wrcomtop} catch {destroy $wrcomtop}
@ -9747,7 +9812,7 @@ proc mkbrgo {top} {
nowbusy newbranch nowbusy newbranch
update update
if {[catch { if {[catch {
eval exec git branch $cmdargs safe_exec [concat git branch $cmdargs]
} err]} { } err]} {
notbusy newbranch notbusy newbranch
error_popup $err error_popup $err
@ -9788,7 +9853,7 @@ proc mvbrgo {top prevname} {
nowbusy renamebranch nowbusy renamebranch
update update
if {[catch { if {[catch {
eval exec git branch $cmdargs safe_exec [concat git branch $cmdargs]
} err]} { } err]} {
notbusy renamebranch notbusy renamebranch
error_popup $err 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 unset env GIT_AUTHOR_*
array set env $save_env array set env $save_env
@ -9852,7 +9917,7 @@ proc cherrypick {} {
update update
# Unfortunately git-cherry-pick writes stuff to stderr even when # Unfortunately git-cherry-pick writes stuff to stderr even when
# no error occurs, and exec takes that as an indication of error... # 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 notbusy cherrypick
if {[regexp -line \ if {[regexp -line \
{Entry '(.*)' (would be overwritten by merge|not uptodate)} \ {Entry '(.*)' (would be overwritten by merge|not uptodate)} \
@ -9914,7 +9979,7 @@ proc revert {} {
nowbusy revert [mc "Reverting"] nowbusy revert [mc "Reverting"]
update update


if [catch {exec git revert --no-edit $rowmenuid} err] { if [catch {safe_exec [list git revert --no-edit $rowmenuid]} err] {
notbusy revert notbusy revert
if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\ if [regexp {files would be overwritten by merge:(\n(( |\t)+[^\n]+\n)+)}\
$err match files] { $err match files] {
@ -9990,8 +10055,8 @@ proc resethead {} {
bind $w <Visibility> "grab $w; focus $w" bind $w <Visibility> "grab $w; focus $w"
tkwait window $w tkwait window $w
if {!$confirm_ok} return if {!$confirm_ok} return
if {[catch {set fd [open \ if {[catch {set fd [safe_open_command_redirect \
[list | git reset --$resettype $rowmenuid 2>@1] r]} err]} { [list git reset --$resettype $rowmenuid] [list 2>@1]]} err]} {
error_popup $err error_popup $err
} else { } else {
dohidelocalchanges dohidelocalchanges
@ -10062,7 +10127,7 @@ proc cobranch {} {


# check the tree is clean first?? # check the tree is clean first??
set newhead $headmenuhead set newhead $headmenuhead
set command [list | git checkout] set command [list git checkout]
if {[string match "remotes/*" $newhead]} { if {[string match "remotes/*" $newhead]} {
set remote $newhead set remote $newhead
set newhead [string range $newhead [expr [string last / $newhead] + 1] end] set newhead [string range $newhead [expr [string last / $newhead] + 1] end]
@ -10076,12 +10141,11 @@ proc cobranch {} {
} else { } else {
lappend command $newhead lappend command $newhead
} }
lappend command 2>@1
nowbusy checkout [mc "Checking out"] nowbusy checkout [mc "Checking out"]
update update
dohidelocalchanges dohidelocalchanges
if {[catch { if {[catch {
set fd [open $command r] set fd [safe_open_command_redirect $command [list 2>@1]]
} err]} { } err]} {
notbusy checkout notbusy checkout
error_popup $err error_popup $err
@ -10147,7 +10211,7 @@ proc rmbranch {} {
} }
nowbusy rmbranch nowbusy rmbranch
update update
if {[catch {exec git branch -D $head} err]} { if {[catch {safe_exec [list git branch -D $head]} err]} {
notbusy rmbranch notbusy rmbranch
error_popup $err error_popup $err
return return
@ -10338,7 +10402,7 @@ proc getallcommits {} {
set cachedarcs 0 set cachedarcs 0
set allccache [file join $gitdir "gitk.cache"] set allccache [file join $gitdir "gitk.cache"]
if {![catch { if {![catch {
set f [open $allccache r] set f [safe_open_file $allccache r]
set allcwait 1 set allcwait 1
getcache $f getcache $f
}]} return }]} return
@ -10347,7 +10411,7 @@ proc getallcommits {} {
if {$allcwait} { if {$allcwait} {
return return
} }
set cmd [list | git rev-list --parents] set cmd [list git rev-list --parents]
set allcupdate [expr {$seeds ne {}}] set allcupdate [expr {$seeds ne {}}]
if {!$allcupdate} { if {!$allcupdate} {
set ids "--all" set ids "--all"
@ -10375,10 +10439,11 @@ proc getallcommits {} {
if {$ids ne {}} { if {$ids ne {}} {
if {$ids eq "--all"} { if {$ids eq "--all"} {
set cmd [concat $cmd "--all"] set cmd [concat $cmd "--all"]
set fd [safe_open_command $cmd]
} else { } 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 fconfigure $fd -blocking 0
incr allcommits incr allcommits
nowbusy allcommits nowbusy allcommits
@ -10768,7 +10833,7 @@ proc savecache {} {
set cachearc 0 set cachearc 0
set cachedarcs $nextarc set cachedarcs $nextarc
catch { catch {
set f [open $allccache w] set f [safe_open_file $allccache w]
puts $f [list 1 $cachedarcs] puts $f [list 1 $cachedarcs]
run writecache $f run writecache $f
} }
@ -11471,7 +11536,7 @@ proc add_tag_ctext {tag} {


if {![info exists cached_tagcontent($tag)]} { if {![info exists cached_tagcontent($tag)]} {
catch { 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 $ctext insert end "[mc "Tag"]: $tag\n" bold
@ -12382,7 +12447,7 @@ proc gitattr {path attr default} {
set r $path_attr_cache($attr,$path) set r $path_attr_cache($attr,$path)
} else { } else {
set r "unspecified" 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 regexp "(.*): $attr: (.*)" $line m f r
} }
set path_attr_cache($attr,$path) $r set path_attr_cache($attr,$path) $r
@ -12409,7 +12474,7 @@ proc cache_gitattr {attr pathlist} {
while {$newlist ne {}} { while {$newlist ne {}} {
set head [lrange $newlist 0 [expr {$lim - 1}]] set head [lrange $newlist 0 [expr {$lim - 1}]]
set newlist [lrange $newlist $lim end] 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"] { foreach row [split $rlist "\n"] {
if {[regexp "(.*): $attr: (.*)" $row m path value]} { if {[regexp "(.*): $attr: (.*)" $row m path value]} {
if {[string index $path 0] eq "\""} { 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 # on OSX bring the current Wish process window to front
if {[tk windowingsystem] eq "aqua"} { if {[tk windowingsystem] eq "aqua"} {
exec osascript -e [format { safe_exec [list osascript -e [format {
tell application "System Events" tell application "System Events"
set frontmost of processes whose unix id is %d to true set frontmost of processes whose unix id is %d to true
end tell end tell
} [pid] ] } [pid] ]]
} }


# Unset GIT_TRACE var if set # Unset GIT_TRACE var if set
@ -12713,7 +12778,7 @@ if {$selecthead eq "HEAD"} {
if {$i >= [llength $argv] && $revtreeargs ne {}} { if {$i >= [llength $argv] && $revtreeargs ne {}} {
# no -- on command line, but some arguments (other than --argscmd) # no -- on command line, but some arguments (other than --argscmd)
if {[catch { 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 cmdline_files [split $f "\n"]
set n [llength $cmdline_files] set n [llength $cmdline_files]
set revtreeargs [lrange $revtreeargs 0 end-$n] set revtreeargs [lrange $revtreeargs 0 end-$n]

View File

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


done 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 test_done

View File

@ -1279,6 +1279,29 @@ test_expect_success 'bundles are downloaded once during fetch --all' '
trace-mult.txt >bundle-fetches && trace-mult.txt >bundle-fetches &&
test_line_count = 1 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 # Do not add tests here unless they use the HTTP server, as they will
# not run unless the HTTP dependencies exist. # 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_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 test_done