Sync with 2.49.1
						commit
						e1775c0646
					
				|  | @ -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. | ||||||
|  | @ -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. | ||||||
|  | @ -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. | ||||||
|  | @ -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. | ||||||
|  | @ -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. | ||||||
|  | @ -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. | ||||||
|  | @ -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. | ||||||
							
								
								
									
										22
									
								
								bundle-uri.c
								
								
								
								
							
							
						
						
									
										22
									
								
								bundle-uri.c
								
								
								
								
							|  | @ -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; | ||||||
|  |  | ||||||
							
								
								
									
										2
									
								
								config.c
								
								
								
								
							
							
						
						
									
										2
									
								
								config.c
								
								
								
								
							|  | @ -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 = "\""; | ||||||
|  |  | ||||||
|  | @ -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")) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -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]} { | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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]} { | ||||||
|  |  | ||||||
|  | @ -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 \ | ||||||
|  |  | ||||||
|  | @ -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 \ | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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 \ | ||||||
|  |  | ||||||
|  | @ -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) | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
|  |  | ||||||
|  | @ -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] | ||||||
| } | } | ||||||
|  |  | ||||||
							
								
								
									
										277
									
								
								gitk-git/gitk
								
								
								
								
							
							
						
						
									
										277
									
								
								gitk-git/gitk
								
								
								
								
							|  | @ -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] | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
|  | @ -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. | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @ -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 | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue
	
	 Junio C Hamano
						Junio C Hamano