diff options
Diffstat (limited to 'git-gui/git-gui.sh')
-rwxr-xr-x | git-gui/git-gui.sh | 1066 |
1 files changed, 813 insertions, 253 deletions
diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh index e018e076f8..5bc21b878d 100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@ -10,8 +10,8 @@ exec wish "$argv0" -- "$@" set appvers {@@GITGUI_VERSION@@} -set copyright [encoding convertfrom utf-8 { -Copyright © 2006, 2007 Shawn Pearce, et. al. +set copyright [string map [list (c) \u00a9] { +Copyright (c) 2006-2010 Shawn Pearce, et. al. This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by @@ -38,7 +38,7 @@ if {[catch {package require Tcl 8.4} err] tk_messageBox \ -icon error \ -type ok \ - -title [mc "git-gui: fatal error"] \ + -title "git-gui: fatal error" \ -message $err exit 1 } @@ -49,7 +49,11 @@ catch {rename send {}} ; # What an evil concept... ## ## locate our library -set oguilib {@@GITGUI_LIBDIR@@} +if { [info exists ::env(GIT_GUI_LIB_DIR) ] } { + set oguilib $::env(GIT_GUI_LIB_DIR) +} else { + set oguilib {@@GITGUI_LIBDIR@@} +} set oguirel {@@GITGUI_RELATIVE@@} if {$oguirel eq {1}} { set oguilib [file dirname [file normalize $argv0]] @@ -79,10 +83,11 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { return [uplevel 1 real__auto_load $name $args] } rename source real__source - proc source {name} { - puts stderr "source $name" - uplevel 1 real__source $name + proc source {args} { + puts stderr "source $args" + uplevel 1 [linsert $args 0 real__source] } + if {[tk windowingsystem] eq "win32"} { console show } } ###################################################################### @@ -92,6 +97,25 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { package require msgcat +# Check for Windows 7 MUI language pack (missed by msgcat < 1.4.4) +if {[tk windowingsystem] eq "win32" + && [package vcompare [package provide msgcat] 1.4.4] < 0 +} then { + proc _mc_update_locale {} { + set key {HKEY_CURRENT_USER\Control Panel\Desktop} + if {![catch { + package require registry + set uilocale [registry get $key "PreferredUILanguages"] + msgcat::ConvertLocale [string map {- _} [lindex $uilocale 0]] + } uilocale]} { + if {[string length $uilocale] > 0} { + msgcat::mclocale $uilocale + } + } + } + _mc_update_locale +} + proc _mc_trim {fmt} { set cmk [string first @@ $fmt] if {$cmk > 0} { @@ -117,23 +141,58 @@ unset oguimsg ###################################################################### ## +## On Mac, bring the current Wish process window to front + +if {[tk windowingsystem] eq "aqua"} { + catch { + exec osascript -e [format { + tell application "System Events" + set frontmost of processes whose unix id is %d to true + end tell + } [pid]] + } +} + +###################################################################### +## ## read only globals set _appname {Git Gui} set _gitdir {} +set _gitworktree {} +set _isbare {} set _gitexec {} +set _githtmldir {} set _reponame {} set _iscygwin {} set _search_path {} +set _shellpath {@@SHELL_PATH@@} set _trace [lsearch -exact $argv --trace] if {$_trace >= 0} { set argv [lreplace $argv $_trace $_trace] set _trace 1 + if {[tk windowingsystem] eq "win32"} { console show } } else { set _trace 0 } +# variable for the last merged branch (useful for a default when deleting +# branches). +set _last_merged_branch {} + +proc shellpath {} { + global _shellpath env + if {[string match @@* $_shellpath]} { + if {[info exists env(SHELL)]} { + return $env(SHELL) + } else { + return /bin/sh + } + } + return $_shellpath +} + proc appname {} { global _appname return $_appname @@ -168,6 +227,28 @@ proc gitexec {args} { return [eval [list file join $_gitexec] $args] } +proc githtmldir {args} { + global _githtmldir + if {$_githtmldir eq {}} { + if {[catch {set _githtmldir [git --html-path]}]} { + # Git not installed or option not yet supported + return {} + } + if {[is_Cygwin]} { + set _githtmldir [exec cygpath \ + --windows \ + --absolute \ + $_githtmldir] + } else { + set _githtmldir [file normalize $_githtmldir] + } + } + if {$args eq {}} { + return $_githtmldir + } + return [eval [list file join $_githtmldir] $args] +} + proc reponame {} { return $::_reponame } @@ -194,6 +275,10 @@ proc is_Cygwin {} { set _iscygwin 0 } else { set _iscygwin 1 + # Handle MSys2 which is only cygwin when MSYSTEM is MSYS. + if {[info exists ::env(MSYSTEM)] && $::env(MSYSTEM) ne "MSYS"} { + set _iscygwin 0 + } } } else { set _iscygwin 0 @@ -237,7 +322,22 @@ proc is_config_true {name} { global repo_config if {[catch {set v $repo_config($name)}]} { return 0 - } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} { + } + set v [string tolower $v] + if {$v eq {} || $v eq {true} || $v eq {1} || $v eq {yes} || $v eq {on}} { + return 1 + } else { + return 0 + } +} + +proc is_config_false {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return 0 + } + set v [string tolower $v] + if {$v eq {false} || $v eq {0} || $v eq {no} || $v eq {off}} { return 1 } else { return 0 @@ -253,6 +353,32 @@ proc get_config {name} { } } +proc is_bare {} { + global _isbare + global _gitdir + global _gitworktree + + if {$_isbare eq {}} { + if {[catch { + set _bare [git rev-parse --is-bare-repository] + switch -- $_bare { + true { set _isbare 1 } + false { set _isbare 0} + default { throw } + } + }]} { + if {[is_config_true core.bare] + || ($_gitworktree eq {} + && [lindex [file split $_gitdir] end] ne {.git})} { + set _isbare 1 + } else { + set _isbare 0 + } + } + } + return $_isbare +} + ###################################################################### ## ## handy utils @@ -272,6 +398,8 @@ proc _trace_exec {cmd} { puts stderr $d } +#'" fix poor old emacs font-lock mode + proc _git_cmd {name} { global _git_cmd_path @@ -359,12 +487,46 @@ proc _which {what args} { return {} } +# Test a file for a hashbang to identify executable scripts on Windows. +proc is_shellscript {filename} { + if {![file exists $filename]} {return 0} + set f [open $filename r] + fconfigure $f -encoding binary + set magic [read $f 2] + close $f + return [expr {$magic eq "#!"}] +} + +# Run a command connected via pipes on stdout. +# This is for use with textconv filters and uses sh -c "..." to allow it to +# contain a command with arguments. On windows we must check for shell +# scripts specifically otherwise just call the filter command. +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] + } + return [open |$run r] +} + proc _lappend_nice {cmd_var} { global _nice upvar $cmd_var cmd if {![info exists _nice]} { set _nice [_which nice] + if {[catch {exec $_nice git version}]} { + set _nice {} + } elseif {[is_Windows] && [file dirname $_nice] ne [file dirname $::_git]} { + set _nice {} + } } if {$_nice ne {}} { lappend cmd $_nice @@ -372,28 +534,10 @@ proc _lappend_nice {cmd_var} { } proc git {args} { - set opt [list] - - while {1} { - switch -- [lindex $args 0] { - --nice { - _lappend_nice opt - } - - default { - break - } - - } - - set args [lrange $args 1 end] - } - - set cmdp [_git_cmd [lindex $args 0]] - set args [lrange $args 1 end] - - _trace_exec [concat $opt $cmdp $args] - set result [eval exec $opt $cmdp $args] + set fd [eval [list git_read] $args] + fconfigure $fd -translation binary -encoding utf-8 + set result [string trimright [read $fd] "\n"] + close $fd if {$::_trace} { puts stderr "< $result" } @@ -512,9 +656,7 @@ proc kill_file_process {fd} { catch { if {[is_Windows]} { - # Use a Cygwin-specific flag to allow killing - # native Windows processes - exec kill -f $process + exec taskkill /pid $process } else { exec kill $process } @@ -583,6 +725,7 @@ proc rmsel_tag {text} { return $text } +wm withdraw . set root_exists 0 bind . <Visibility> { bind . <Visibility> {} @@ -592,6 +735,7 @@ bind . <Visibility> { if {[is_Windows]} { wm iconbitmap . -default $oguilib/git-gui.ico set ::tk::AlwaysShowSelection 1 + bind . <Control-F2> {console show} # Spoof an X11 display for SSH if {![info exists env(DISPLAY)]} { @@ -617,7 +761,10 @@ if {[is_Windows]} { gitlogo put gray26 -to 5 15 11 16 gitlogo redither - wm iconphoto . -default gitlogo + image create photo gitlogo32 -width 32 -height 32 + gitlogo32 copy gitlogo -zoom 2 2 + + wm iconphoto . -default gitlogo gitlogo32 } } @@ -626,12 +773,17 @@ if {[is_Windows]} { ## config defaults set cursor_ptr arrow -font create font_diff -family Courier -size 10 font create font_ui -catch { - label .dummy - eval font configure font_ui [font actual [.dummy cget -font]] - destroy .dummy +if {[lsearch -exact [font names] TkDefaultFont] != -1} { + eval [linsert [font actual TkDefaultFont] 0 font configure font_ui] + eval [linsert [font actual TkFixedFont] 0 font create font_diff] +} else { + font create font_diff -family Courier -size 10 + catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy + } } font create font_uiitalic @@ -640,10 +792,16 @@ font create font_diffbold font create font_diffitalic foreach class {Button Checkbutton Entry Label - Labelframe Listbox Menu Message + Labelframe Listbox Message Radiobutton Spinbox Text} { option add *$class.font font_ui } +if {![is_MacOSX]} { + option add *Menu.font font_ui + option add *Entry.borderWidth 1 startupFile + option add *Entry.relief sunken startupFile + option add *RadioButton.anchor w startupFile +} unset class if {[is_Windows] || [is_MacOSX]} { @@ -695,11 +853,23 @@ proc apply_config {} { font configure ${font}bold -weight bold font configure ${font}italic -slant italic } + + global use_ttk NS + set use_ttk 0 + set NS {} + if {$repo_config(gui.usettk)} { + set use_ttk [package vsatisfies [package provide Tk] 8.5] + if {$use_ttk} { + set NS ttk + bind [winfo class .] <<ThemeChanged>> [list InitTheme] + pave_toplevel . + } + } } set default_config(branch.autosetupmerge) true set default_config(merge.tool) {} -set default_config(merge.keepbackup) true +set default_config(mergetool.keepbackup) true set default_config(merge.diffstat) true set default_config(merge.summary) false set default_config(merge.verbosity) 2 @@ -708,21 +878,31 @@ set default_config(user.email) {} set default_config(gui.encoding) [encoding system] set default_config(gui.matchtrackingbranch) false +set default_config(gui.textconv) true set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.fastcopyblame) false +set default_config(gui.maxrecentrepo) 10 set default_config(gui.copyblamethreshold) 40 set default_config(gui.blamehistoryctx) 7 set default_config(gui.diffcontext) 5 +set default_config(gui.diffopts) {} set default_config(gui.commitmsgwidth) 75 set default_config(gui.newbranchtemplate) {} set default_config(gui.spellingdictionary) {} set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] +# TODO: this option should be added to the git-config documentation +set default_config(gui.maxfilesdisplayed) 5000 +set default_config(gui.usettk) 1 +set default_config(gui.warndetachedcommit) 1 +set default_config(gui.tabsize) 8 set font_descs { {fontui font_ui {mc "Main Font"}} {fontdiff font_diff {mc "Diff/Console Font"}} } +set default_config(gui.stageuntracked) ask +set default_config(gui.displayuntracked) true ###################################################################### ## @@ -766,12 +946,19 @@ if {![regsub {^git version } $_git_version {} _git_version]} { exit 1 } +proc get_trimmed_version {s} { + set r {} + foreach x [split $s -._] { + if {[string is integer -strict $x]} { + lappend r $x + } else { + break + } + } + return [join $r .] +} set _real_git_version $_git_version -regsub -- {[\-\.]dirty$} $_git_version {} _git_version -regsub {\.[0-9]+\.g[0-9a-f]+$} $_git_version {} _git_version -regsub {\.rc[0-9]+$} $_git_version {} _git_version -regsub {\.GIT$} $_git_version {} _git_version -regsub {\.[a-zA-Z]+\.[0-9]+$} $_git_version {} _git_version +set _git_version [get_trimmed_version $_git_version] if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { catch {wm withdraw .} @@ -906,7 +1093,7 @@ git-version proc _parse_config {arr_name args} { [list git_read config] \ $args \ [list --null --list]] - fconfigure $fd_rc -translation binary + fconfigure $fd_rc -translation binary -encoding utf-8 set buf [read $fd_rc] close $fd_rc } @@ -917,6 +1104,10 @@ git-version proc _parse_config {arr_name args} { } else { set arr($name) $value } + } elseif {[regexp {^([^\n]+)$} $line line name]} { + # no value given, but interpreting them as + # boolean will be handled as true + set arr($name) {} } } } @@ -932,6 +1123,10 @@ git-version proc _parse_config {arr_name args} { } 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 @@ -1046,6 +1241,8 @@ if {[catch { set _prefix {} }] && [catch { + # beware that from the .git dir this sets _gitdir to . + # and _prefix to the empty string set _gitdir [git rev-parse --git-dir] set _prefix [git rev-parse --show-prefix] } err]} { @@ -1054,6 +1251,14 @@ if {[catch { choose_repository::pick set picked 1 } + +# we expand the _gitdir when it's just a single dot (i.e. when we're being +# run from the .git dir itself) lest the routines to find the worktree +# get confused +if {$_gitdir eq "."} { + set _gitdir [pwd] +} + if {![file isdirectory $_gitdir] && [is_Cygwin]} { catch {set _gitdir [exec cygpath --windows $_gitdir]} } @@ -1062,25 +1267,57 @@ if {![file isdirectory $_gitdir]} { error_popup [strcat [mc "Git directory not found:"] "\n\n$_gitdir"] exit 1 } +# _gitdir exists, so try loading the config +load_config 0 +apply_config + +# v1.7.0 introduced --show-toplevel to return the canonical work-tree +if {[package vcompare $_git_version 1.7.0] >= 0} { + if { [is_Cygwin] } { + catch {set _gitworktree [exec cygpath --windows [git rev-parse --show-toplevel]]} + } else { + set _gitworktree [git rev-parse --show-toplevel] + } +} else { + # try to set work tree from environment, core.worktree or use + # cdup to obtain a relative path to the top of the worktree. If + # run from the top, the ./ prefix ensures normalize expands pwd. + if {[catch { set _gitworktree $env(GIT_WORK_TREE) }]} { + set _gitworktree [get_config core.worktree] + if {$_gitworktree eq ""} { + set _gitworktree [file normalize ./[git rev-parse --show-cdup]] + } + } +} + if {$_prefix ne {}} { - regsub -all {[^/]+/} $_prefix ../ cdup + if {$_gitworktree eq {}} { + regsub -all {[^/]+/} $_prefix ../ cdup + } else { + set cdup $_gitworktree + } if {[catch {cd $cdup} err]} { catch {wm withdraw .} error_popup [strcat [mc "Cannot move to top of working directory:"] "\n\n$err"] exit 1 } + set _gitworktree [pwd] unset cdup } elseif {![is_enabled bare]} { - if {[lindex [file split $_gitdir] end] ne {.git}} { + if {[is_bare]} { catch {wm withdraw .} - error_popup [strcat [mc "Cannot use funny .git directory:"] "\n\n$_gitdir"] + error_popup [strcat [mc "Cannot use bare repository:"] "\n\n$_gitdir"] exit 1 } - if {[catch {cd [file dirname $_gitdir]} err]} { + if {$_gitworktree eq {}} { + set _gitworktree [file dirname $_gitdir] + } + if {[catch {cd $_gitworktree} err]} { catch {wm withdraw .} - error_popup [strcat [mc "No working directory"] " [file dirname $_gitdir]:\n\n$err"] + error_popup [strcat [mc "No working directory"] " $_gitworktree:\n\n$err"] exit 1 } + set _gitworktree [pwd] } set _reponame [file split [file normalize $_gitdir]] if {[lindex $_reponame end] eq {.git}} { @@ -1089,6 +1326,9 @@ if {[lindex $_reponame end] eq {.git}} { set _reponame [lindex $_reponame end] } +set env(GIT_DIR) $_gitdir +set env(GIT_WORK_TREE) $_gitworktree + ###################################################################### ## ## global init @@ -1106,8 +1346,10 @@ set current_branch {} set is_detached 0 set current_diff_path {} set is_3way_diff 0 +set is_submodule_diff 0 set is_conflict_diff 0 set selected_commit_type new +set diff_empty_count 0 set nullid "0000000000000000000000000000000000000000" set nullid2 "0000000000000000000000000000000000000001" @@ -1231,7 +1473,7 @@ proc rescan {after {honor_trustmtime 1}} { (![$ui_comm edit modified] || [string trim [$ui_comm get 0.0 end]] eq {})} { if {[string match amend* $commit_type]} { - } elseif {[load_message GITGUI_MSG]} { + } elseif {[load_message GITGUI_MSG utf-8]} { } elseif {[run_prepare_commit_msg_hook]} { } elseif {[load_message MERGE_MSG]} { } elseif {[load_message SQUASH_MSG]} { @@ -1286,34 +1528,47 @@ proc rescan_stage2 {fd after} { close $fd } - set ls_others [list --exclude-per-directory=.gitignore] - if {[have_info_exclude]} { - lappend ls_others "--exclude-from=[gitdir info exclude]" - } - set user_exclude [get_config core.excludesfile] - if {$user_exclude ne {} && [file readable $user_exclude]} { - lappend ls_others "--exclude-from=$user_exclude" + if {[package vcompare $::_git_version 1.6.3] >= 0} { + set ls_others [list --exclude-standard] + } else { + set ls_others [list --exclude-per-directory=.gitignore] + if {[have_info_exclude]} { + lappend ls_others "--exclude-from=[gitdir info exclude]" + } + set user_exclude [get_config core.excludesfile] + if {$user_exclude ne {} && [file readable $user_exclude]} { + lappend ls_others "--exclude-from=[file normalize $user_exclude]" + } } set buf_rdi {} set buf_rdf {} set buf_rlo {} - set rescan_active 3 + set rescan_active 2 ui_status [mc "Scanning for modified files ..."] - set fd_di [git_read diff-index --cached -z [PARENT]] + if {[git-version >= "1.7.2"]} { + set fd_di [git_read diff-index --cached --ignore-submodules=dirty -z [PARENT]] + } else { + set fd_di [git_read diff-index --cached -z [PARENT]] + } set fd_df [git_read diff-files -z] - set fd_lo [eval git_read ls-files --others -z $ls_others] fconfigure $fd_di -blocking 0 -translation binary -encoding binary fconfigure $fd_df -blocking 0 -translation binary -encoding binary - fconfigure $fd_lo -blocking 0 -translation binary -encoding binary + fileevent $fd_di readable [list read_diff_index $fd_di $after] fileevent $fd_df readable [list read_diff_files $fd_df $after] - fileevent $fd_lo readable [list read_ls_others $fd_lo $after] + + if {[is_config_true gui.displayuntracked]} { + set fd_lo [eval git_read ls-files --others -z $ls_others] + fconfigure $fd_lo -blocking 0 -translation binary -encoding binary + fileevent $fd_lo readable [list read_ls_others $fd_lo $after] + incr rescan_active + } } -proc load_message {file} { +proc load_message {file {encoding {}}} { global ui_comm set f [gitdir $file] @@ -1322,6 +1577,9 @@ proc load_message {file} { return 0 } fconfigure $fd -eofchar {} + if {$encoding ne {}} { + fconfigure $fd -encoding $encoding + } set content [string trim [read $fd]] close $fd regsub -all -line {[ \r\t]+$} $content {} content @@ -1337,18 +1595,20 @@ proc run_prepare_commit_msg_hook {} { # prepare-commit-msg requires PREPARE_COMMIT_MSG exist. From git-gui # it will be .git/MERGE_MSG (merge), .git/SQUASH_MSG (squash), or an - # empty file but existant file. + # empty file but existent file. set fd_pcm [open [gitdir PREPARE_COMMIT_MSG] a] if {[file isfile [gitdir MERGE_MSG]]} { set pcm_source "merge" set fd_mm [open [gitdir MERGE_MSG] r] + fconfigure $fd_mm -encoding utf-8 puts -nonewline $fd_pcm [read $fd_mm] close $fd_mm } elseif {[file isfile [gitdir SQUASH_MSG]]} { set pcm_source "squash" set fd_sm [open [gitdir SQUASH_MSG] r] + fconfigure $fd_sm -encoding utf-8 puts -nonewline $fd_pcm [read $fd_sm] close $fd_sm } else { @@ -1413,7 +1673,7 @@ proc read_diff_index {fd after} { set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] merge_state \ - [encoding convertfrom $p] \ + [encoding convertfrom utf-8 $p] \ [lindex $i 4]? \ [list [lindex $i 0] [lindex $i 2]] \ [list] @@ -1446,7 +1706,7 @@ proc read_diff_files {fd after} { set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] merge_state \ - [encoding convertfrom $p] \ + [encoding convertfrom utf-8 $p] \ ?[lindex $i 4] \ [list] \ [list [lindex $i 0] [lindex $i 2]] @@ -1469,7 +1729,7 @@ proc read_ls_others {fd after} { set pck [split $buf_rlo "\0"] set buf_rlo [lindex $pck end] foreach p [lrange $pck 0 end-1] { - set p [encoding convertfrom $p] + set p [encoding convertfrom utf-8 $p] if {[string index $p end] eq {/}} { set p [string range $p 0 end-1] } @@ -1583,6 +1843,9 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} { } elseif {$s0 ne {_} && [string index $state 0] eq {_} && $head_info eq {}} { set head_info $index_info + } elseif {$s0 eq {_} && [string index $state 0] ne {_}} { + set index_info $head_info + set head_info {} } set file_states($path) [list $s0$s1 $icon \ @@ -1671,10 +1934,12 @@ proc display_all_files_helper {w path icon_name m} { $w insert end "[escape_path $path]\n" } +set files_warning 0 proc display_all_files {} { global ui_index ui_workdir global file_states file_lists global last_clicked + global files_warning $ui_index conf -state normal $ui_workdir conf -state normal @@ -1686,11 +1951,24 @@ proc display_all_files {} { set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] - foreach path [lsort [array names file_states]] { + set to_display [lsort [array names file_states]] + set display_limit [get_config gui.maxfilesdisplayed] + set displayed 0 + foreach path $to_display { set s $file_states($path) set m [lindex $s 0] set icon_name [lindex $s 1] + if {$displayed > $display_limit && [string index $m 1] eq {O} } { + if {!$files_warning} { + # do not repeatedly warn: + set files_warning 1 + info_popup [mc "Display limit (gui.maxfilesdisplayed = %s) reached, not showing all %s files." \ + $display_limit [llength $to_display]] + } + continue + } + set s [string index $m 0] if {$s ne {U} && $s ne {_}} { display_all_files_helper $ui_index $path \ @@ -1705,6 +1983,7 @@ proc display_all_files {} { if {$s ne {_}} { display_all_files_helper $ui_workdir $path \ $icon_name $s + incr displayed } } @@ -1752,15 +2031,6 @@ static unsigned char file_fulltick_bits[] = { 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask -image create bitmap file_parttick -background white -foreground "#005050" -data { -#define parttick_width 14 -#define parttick_height 15 -static unsigned char parttick_bits[] = { - 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, - 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10, - 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f}; -} -maskdata $filemask - image create bitmap file_question -background white -foreground black -data { #define file_question_width 14 #define file_question_height 15 @@ -1789,8 +2059,8 @@ static unsigned char file_merge_bits[] = { } -maskdata $filemask image create bitmap file_statechange -background white -foreground green -data { -#define file_merge_width 14 -#define file_merge_height 15 +#define file_statechange_width 14 +#define file_statechange_height 15 static unsigned char file_statechange_bits[] = { 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x62, 0x10, 0x62, 0x10, 0xba, 0x11, 0xba, 0x11, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, @@ -1801,7 +2071,7 @@ set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list set all_icons(_$ui_index) file_plain -set all_icons(A$ui_index) file_fulltick +set all_icons(A$ui_index) file_plain set all_icons(M$ui_index) file_fulltick set all_icons(D$ui_index) file_removed set all_icons(U$ui_index) file_merge @@ -1824,7 +2094,11 @@ foreach i { {MD {mc "Staged for commit, missing"}} {_T {mc "File type changed, not staged"}} + {MT {mc "File type changed, old type staged for commit"}} + {AT {mc "File type changed, old type staged for commit"}} {T_ {mc "File type changed, staged"}} + {TM {mc "File type change staged, modification not staged"}} + {TD {mc "File type change staged, file missing"}} {_O {mc "Untracked, not staged"}} {A_ {mc "Staged for commit"}} @@ -1877,7 +2151,10 @@ proc incr_font_size {font {amt 1}} { set starting_gitk_msg [mc "Starting gitk... please wait..."] -proc do_gitk {revs} { +proc do_gitk {revs {is_submodule false}} { + global current_diff_path file_states current_diff_side ui_index + global _gitdir _gitworktree + # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. # @@ -1888,23 +2165,78 @@ proc do_gitk {revs} { } else { global env - if {[info exists env(GIT_DIR)]} { - set old_GIT_DIR $env(GIT_DIR) + set pwd [pwd] + + if {!$is_submodule} { + if {![is_bare]} { + cd $_gitworktree + } } else { - set old_GIT_DIR {} + cd $current_diff_path + if {$revs eq {--}} { + set s $file_states($current_diff_path) + set old_sha1 {} + set new_sha1 {} + switch -glob -- [lindex $s 0] { + M_ { set old_sha1 [lindex [lindex $s 2] 1] } + _M { set old_sha1 [lindex [lindex $s 3] 1] } + MM { + if {$current_diff_side eq $ui_index} { + set old_sha1 [lindex [lindex $s 2] 1] + set new_sha1 [lindex [lindex $s 3] 1] + } else { + set old_sha1 [lindex [lindex $s 3] 1] + } + } + } + set revs $old_sha1...$new_sha1 + } + # GIT_DIR and GIT_WORK_TREE for the submodule are not the ones + # we've been using for the main repository, so unset them. + # TODO we could make life easier (start up faster?) for gitk + # by setting these to the appropriate values to allow gitk + # to skip the heuristics to find their proper value + unset env(GIT_DIR) + unset env(GIT_WORK_TREE) } + eval exec $cmd $revs "--" "--" & + + set env(GIT_DIR) $_gitdir + set env(GIT_WORK_TREE) $_gitworktree + cd $pwd + + ui_status $::starting_gitk_msg + after 10000 { + ui_ready $starting_gitk_msg + } + } +} + +proc do_git_gui {} { + global current_diff_path + + # -- Always start git gui through whatever we were loaded with. This + # lets us bypass using shell process on Windows systems. + # + set exe [list [_which git]] + if {$exe eq {}} { + error_popup [mc "Couldn't find git gui in PATH"] + } else { + global env + global _gitdir _gitworktree + + # see note in do_gitk about unsetting these vars when + # running tools in a submodule + unset env(GIT_DIR) + unset env(GIT_WORK_TREE) set pwd [pwd] - cd [file dirname [gitdir]] - set env(GIT_DIR) [file tail [gitdir]] + cd $current_diff_path - eval exec $cmd $revs & + eval exec $exe gui & - if {$old_GIT_DIR eq {}} { - unset env(GIT_DIR) - } else { - set env(GIT_DIR) $old_GIT_DIR - } + set env(GIT_DIR) $_gitdir + set env(GIT_WORK_TREE) $_gitworktree cd $pwd ui_status $::starting_gitk_msg @@ -1915,6 +2247,7 @@ proc do_gitk {revs} { } proc do_explore {} { + global _gitworktree set explorer {} if {[is_Cygwin] || [is_Windows]} { set explorer "explorer.exe" @@ -1924,7 +2257,7 @@ proc do_explore {} { # freedesktop.org-conforming system is our best shot set explorer "xdg-open" } - eval exec $explorer [file dirname [gitdir]] & + eval exec $explorer [list [file nativename $_gitworktree]] & } set is_quitting 0 @@ -1940,7 +2273,7 @@ proc do_quit {{rc {1}}} { global ui_comm is_quitting repo_config commit_type global GITGUI_BCK_exists GITGUI_BCK_i global ui_comm_spell - global ret_code + global ret_code use_ttk if {$is_quitting} return set is_quitting 1 @@ -1960,6 +2293,7 @@ proc do_quit {{rc {1}}} { && $msg ne {}} { catch { set fd [open $save w] + fconfigure $fd -encoding utf-8 puts -nonewline $fd $msg close $fd } @@ -1983,10 +2317,28 @@ proc do_quit {{rc {1}}} { # -- Stash our current window geometry into this repository. # + set cfg_wmstate [wm state .] + if {[catch {set rc_wmstate $repo_config(gui.wmstate)}]} { + set rc_wmstate {} + } + if {$cfg_wmstate ne $rc_wmstate} { + catch {git config gui.wmstate $cfg_wmstate} + } + if {$cfg_wmstate eq {zoomed}} { + # on Windows wm geometry will lie about window + # position (but not size) when window is zoomed + # restore the window before querying wm geometry + wm state . normal + } set cfg_geometry [list] lappend cfg_geometry [wm geometry .] - lappend cfg_geometry [lindex [.vpane sash coord 0] 0] - lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1] + if {$use_ttk} { + lappend cfg_geometry [.vpane sashpos 0] + lappend cfg_geometry [.vpane.files sashpos 0] + } else { + lappend cfg_geometry [lindex [.vpane sash coord 0] 0] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 1] + } if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { set rc_geometry {} } @@ -1996,6 +2348,11 @@ proc do_quit {{rc {1}}} { } set ret_code $rc + + # Briefly enable send again, working around Tk bug + # http://sourceforge.net/tracker/?func=detail&atid=112997&aid=1821174&group_id=12997 + tk appname [appname] + destroy . } @@ -2136,13 +2493,28 @@ proc force_first_diff {after} { } } -proc toggle_or_diff {w x y} { +proc toggle_or_diff {mode w args} { global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] - set col [lindex $pos 1] + if {$mode eq "click"} { + foreach {x y} $args break + set pos [split [$w index @$x,$y] .] + foreach {lno col} $pos break + } else { + if {$last_clicked ne {}} { + set lno [lindex $last_clicked 1] + } else { + set lno [expr {int([lindex [$w tag ranges in_diff] 0])}] + } + if {$mode eq "toggle"} { + set col 0; set y 2 + } else { + incr lno [expr {$mode eq "up" ? -1 : 1}] + set col 1 + } + } + set path [lindex $file_lists($w) [expr {$lno - 1}]] if {$path eq {}} { set last_clicked {} @@ -2150,6 +2522,7 @@ proc toggle_or_diff {w x y} { } set last_clicked [list $w $lno] + focus $w array unset selected_paths $ui_index tag remove in_sel 0.0 end $ui_workdir tag remove in_sel 0.0 end @@ -2191,6 +2564,7 @@ proc toggle_or_diff {w x y} { [concat $after [list ui_ready]] } } else { + set selected_paths($path) 1 show_diff $path $w $lno } } @@ -2228,7 +2602,7 @@ proc add_range_to_selection {w x y} { global file_lists last_clicked selected_paths if {[lindex $last_clicked 0] ne $w} { - toggle_or_diff $w $x $y + toggle_or_diff click $w $x $y return } @@ -2270,13 +2644,17 @@ proc show_less_context {} { ## ## ui construction -load_config 0 -apply_config set ui_comm {} # -- Menu Bar # menu .mbar -tearoff 0 +if {[is_MacOSX]} { + # -- Apple Menu (Mac OS X only) + # + .mbar add cascade -label Apple -menu .mbar.apple + menu .mbar.apple +} .mbar add cascade -label [mc Repository] -menu .mbar.repository .mbar add cascade -label [mc Edit] -menu .mbar.edit if {[is_enabled branch]} { @@ -2292,16 +2670,27 @@ if {[is_enabled transport]} { if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar add cascade -label [mc Tools] -menu .mbar.tools } -. configure -menu .mbar # -- Repository Menu # menu .mbar.repository -.mbar.repository add command \ - -label [mc "Explore Working Copy"] \ - -command {do_explore} -.mbar.repository add separator +if {![is_bare]} { + .mbar.repository add command \ + -label [mc "Explore Working Copy"] \ + -command {do_explore} +} + +if {[is_Windows]} { + .mbar.repository add command \ + -label [mc "Git Bash"] \ + -command {eval exec [auto_execok start] \ + [list "Git Bash" bash --login -l &]} +} + +if {[is_Windows] || ![is_bare]} { + .mbar.repository add separator +} .mbar.repository add command \ -label [mc "Browse Current Branch's Files"] \ @@ -2477,12 +2866,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label [mc "Unstage From Commit"] \ - -command do_unstage_selection + -command do_unstage_selection \ + -accelerator $M1T-U lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label [mc "Revert Changes"] \ - -command do_revert_selection + -command do_revert_selection \ + -accelerator $M1T-J lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -2545,19 +2936,7 @@ if {[is_enabled transport]} { } if {[is_MacOSX]} { - # -- Apple Menu (Mac OS X only) - # - .mbar add cascade -label Apple -menu .mbar.apple - menu .mbar.apple - - .mbar.apple add command -label [mc "About %s" [appname]] \ - -command do_about - .mbar.apple add separator - .mbar.apple add command \ - -label [mc "Preferences..."] \ - -command do_options \ - -accelerator $M1T-, - bind . <$M1B-,> do_options + proc ::tk::mac::ShowPreferences {} {do_options} } else { # -- Edit Menu # @@ -2585,17 +2964,23 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { .mbar add cascade -label [mc Help] -menu .mbar.help menu .mbar.help -if {![is_MacOSX]} { +if {[is_MacOSX]} { + .mbar.apple add command -label [mc "About %s" [appname]] \ + -command do_about + .mbar.apple add separator +} else { .mbar.help add command -label [mc "About %s" [appname]] \ -command do_about } +. configure -menu .mbar +set doc_path [githtmldir] +if {$doc_path ne {}} { + set doc_path [file join $doc_path index.html] -set doc_path [file dirname [gitexec]] -set doc_path [file join $doc_path Documentation index.html] - -if {[is_Cygwin]} { - set doc_path [exec cygpath --mixed $doc_path] + if {[is_Cygwin]} { + set doc_path [exec cygpath --mixed $doc_path] + } } if {[file isfile $doc_path]} { @@ -2626,7 +3011,14 @@ bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} set subcommand_args {} proc usage {} { - puts stderr "usage: $::argv0 $::subcommand $::subcommand_args" + set s "[mc usage:] $::argv0 $::subcommand $::subcommand_args" + if {[tk windowingsystem] eq "win32"} { + wm withdraw . + tk_messageBox -icon info -message $s \ + -title [mc "Usage"] + } else { + puts stderr $s + } exit 1 } @@ -2660,9 +3052,11 @@ blame { set jump_spec {} set is_path 0 foreach a $argv { - if {$is_path || [file exists $_prefix$a]} { + set p [file join $_prefix $a] + + if {$is_path || [file exists $p]} { if {$path ne {}} usage - set path [normalize_relpath $_prefix$a] + set path [normalize_relpath $p] break } elseif {$a eq {--}} { if {$path ne {}} { @@ -2685,8 +3079,13 @@ blame { unset is_path if {$head ne {} && $path eq {}} { - set path [normalize_relpath $_prefix$head] - set head {} + if {[string index $head 0] eq {/}} { + set path [normalize_relpath $head] + set head {} + } else { + set path [normalize_relpath $_prefix$head] + set head {} + } } if {$head eq {}} { @@ -2696,13 +3095,18 @@ blame { if {[catch { set head [git rev-parse --verify $head] } err]} { - puts stderr $err + if {[tk windowingsystem] eq "win32"} { + tk_messageBox -icon error -title [mc Error] -message $err + } else { + puts stderr $err + } exit 1 } } set current_branch $head } + wm deiconify . switch -- $subcommand { browser { if {$jump_spec ne {}} usage @@ -2718,7 +3122,12 @@ blame { } blame { if {$head eq {} && ![file exists $path]} { - puts stderr [mc "fatal: cannot stat path %s: No such file or directory" $path] + catch {wm withdraw .} + tk_messageBox \ + -icon error \ + -type ok \ + -title [mc "git-gui: fatal error"] \ + -message [mc "fatal: cannot stat path %s: No such file or directory" $path] exit 1 } blame::new $head $path $jump_spec @@ -2729,32 +3138,32 @@ blame { citool - gui { if {[llength $argv] != 0} { - puts -nonewline stderr "usage: $argv0" - if {$subcommand ne {gui} - && [file tail $argv0] ne "git-$subcommand"} { - puts -nonewline stderr " $subcommand" - } - puts stderr {} - exit 1 + usage } # fall through to setup UI for commits } default { - puts stderr "usage: $argv0 \[{blame|browser|citool}\]" + set err "[mc usage:] $argv0 \[{blame|browser|citool}\]" + if {[tk windowingsystem] eq "win32"} { + wm withdraw . + tk_messageBox -icon error -message $err \ + -title [mc "Usage"] + } else { + puts stderr $err + } exit 1 } } # -- Branch Control # -frame .branch \ - -borderwidth 1 \ - -relief sunken -label .branch.l1 \ +${NS}::frame .branch +if {!$use_ttk} {.branch configure -borderwidth 1 -relief sunken} +${NS}::label .branch.l1 \ -text [mc "Current Branch:"] \ -anchor w \ -justify left -label .branch.cb \ +${NS}::label .branch.cb \ -textvariable current_branch \ -anchor w \ -justify left @@ -2764,53 +3173,66 @@ pack .branch -side top -fill x # -- Main Window Layout # -panedwindow .vpane -orient horizontal -panedwindow .vpane.files -orient vertical -.vpane add .vpane.files -sticky nsew -height 100 -width 200 +${NS}::panedwindow .vpane -orient horizontal +${NS}::panedwindow .vpane.files -orient vertical +if {$use_ttk} { + .vpane add .vpane.files +} else { + .vpane add .vpane.files -sticky nsew -height 100 -width 200 +} pack .vpane -anchor n -side top -fill both -expand 1 +# -- Working Directory File List + +textframe .vpane.files.workdir -height 100 -width 200 +tlabel .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ + -background lightsalmon -foreground black +ttext $ui_workdir -background white -foreground black \ + -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ + -takefocus 1 -highlightthickness 1\ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ + -state disabled +${NS}::scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +${NS}::scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] +pack .vpane.files.workdir.title -side top -fill x +pack .vpane.files.workdir.sx -side bottom -fill x +pack .vpane.files.workdir.sy -side right -fill y +pack $ui_workdir -side left -fill both -expand 1 + # -- Index File List # -frame .vpane.files.index -height 100 -width 200 -label .vpane.files.index.title -text [mc "Staged Changes (Will Commit)"] \ +textframe .vpane.files.index -height 100 -width 200 +tlabel .vpane.files.index.title \ + -text [mc "Staged Changes (Will Commit)"] \ -background lightgreen -foreground black -text $ui_index -background white -foreground black \ +ttext $ui_index -background white -foreground black \ -borderwidth 0 \ -width 20 -height 10 \ -wrap none \ + -takefocus 1 -highlightthickness 1\ -cursor $cursor_ptr \ -xscrollcommand {.vpane.files.index.sx set} \ -yscrollcommand {.vpane.files.index.sy set} \ -state disabled -scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] -scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] +${NS}::scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +${NS}::scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] pack .vpane.files.index.title -side top -fill x pack .vpane.files.index.sx -side bottom -fill x pack .vpane.files.index.sy -side right -fill y pack $ui_index -side left -fill both -expand 1 -# -- Working Directory File List +# -- Insert the workdir and index into the panes # -frame .vpane.files.workdir -height 100 -width 200 -label .vpane.files.workdir.title -text [mc "Unstaged Changes"] \ - -background lightsalmon -foreground black -text $ui_workdir -background white -foreground black \ - -borderwidth 0 \ - -width 20 -height 10 \ - -wrap none \ - -cursor $cursor_ptr \ - -xscrollcommand {.vpane.files.workdir.sx set} \ - -yscrollcommand {.vpane.files.workdir.sy set} \ - -state disabled -scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] -scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] -pack .vpane.files.workdir.title -side top -fill x -pack .vpane.files.workdir.sx -side bottom -fill x -pack .vpane.files.workdir.sy -side right -fill y -pack $ui_workdir -side left -fill both -expand 1 - -.vpane.files add .vpane.files.workdir -sticky nsew -.vpane.files add .vpane.files.index -sticky nsew +.vpane.files add .vpane.files.workdir +.vpane.files add .vpane.files.index +if {!$use_ttk} { + .vpane.files paneconfigure .vpane.files.workdir -sticky news + .vpane.files paneconfigure .vpane.files.index -sticky news +} foreach i [list $ui_index $ui_workdir] { rmsel_tag $i @@ -2820,68 +3242,85 @@ unset i # -- Diff and Commit Area # -frame .vpane.lower -height 300 -width 400 -frame .vpane.lower.commarea -frame .vpane.lower.diff -relief sunken -borderwidth 1 -pack .vpane.lower.diff -fill both -expand 1 -pack .vpane.lower.commarea -side bottom -fill x -.vpane add .vpane.lower -sticky nsew +if {$have_tk85} { + ${NS}::panedwindow .vpane.lower -orient vertical + ${NS}::frame .vpane.lower.commarea + ${NS}::frame .vpane.lower.diff -relief sunken -borderwidth 1 -height 500 + .vpane.lower add .vpane.lower.diff + .vpane.lower add .vpane.lower.commarea + .vpane add .vpane.lower + if {$use_ttk} { + .vpane.lower pane .vpane.lower.diff -weight 1 + .vpane.lower pane .vpane.lower.commarea -weight 0 + } else { + .vpane.lower paneconfigure .vpane.lower.diff -stretch always + .vpane.lower paneconfigure .vpane.lower.commarea -stretch never + } +} else { + frame .vpane.lower -height 300 -width 400 + frame .vpane.lower.commarea + frame .vpane.lower.diff -relief sunken -borderwidth 1 + pack .vpane.lower.diff -fill both -expand 1 + pack .vpane.lower.commarea -side bottom -fill x + .vpane add .vpane.lower + .vpane paneconfigure .vpane.lower -sticky nsew +} # -- Commit Area Buttons # -frame .vpane.lower.commarea.buttons -label .vpane.lower.commarea.buttons.l -text {} \ +${NS}::frame .vpane.lower.commarea.buttons +${NS}::label .vpane.lower.commarea.buttons.l -text {} \ -anchor w \ -justify left pack .vpane.lower.commarea.buttons.l -side top -fill x pack .vpane.lower.commarea.buttons -side left -fill y -button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ +${NS}::button .vpane.lower.commarea.buttons.rescan -text [mc Rescan] \ -command ui_do_rescan pack .vpane.lower.commarea.buttons.rescan -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} -button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ +${NS}::button .vpane.lower.commarea.buttons.incall -text [mc "Stage Changed"] \ -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.incall conf -state} if {![is_enabled nocommitmsg]} { - button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ + ${NS}::button .vpane.lower.commarea.buttons.signoff -text [mc "Sign Off"] \ -command do_signoff pack .vpane.lower.commarea.buttons.signoff -side top -fill x } -button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \ +${NS}::button .vpane.lower.commarea.buttons.commit -text [commit_btn_caption] \ -command do_commit pack .vpane.lower.commarea.buttons.commit -side top -fill x lappend disable_on_lock \ {.vpane.lower.commarea.buttons.commit conf -state} if {![is_enabled nocommit]} { - button .vpane.lower.commarea.buttons.push -text [mc Push] \ + ${NS}::button .vpane.lower.commarea.buttons.push -text [mc Push] \ -command do_push_anywhere pack .vpane.lower.commarea.buttons.push -side top -fill x } # -- Commit Message Buffer # -frame .vpane.lower.commarea.buffer -frame .vpane.lower.commarea.buffer.header -set ui_comm .vpane.lower.commarea.buffer.t +${NS}::frame .vpane.lower.commarea.buffer +${NS}::frame .vpane.lower.commarea.buffer.header +set ui_comm .vpane.lower.commarea.buffer.frame.t set ui_coml .vpane.lower.commarea.buffer.header.l if {![is_enabled nocommit]} { - radiobutton .vpane.lower.commarea.buffer.header.new \ + ${NS}::radiobutton .vpane.lower.commarea.buffer.header.new \ -text [mc "New Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ -value new lappend disable_on_lock \ [list .vpane.lower.commarea.buffer.header.new conf -state] - radiobutton .vpane.lower.commarea.buffer.header.amend \ + ${NS}::radiobutton .vpane.lower.commarea.buffer.header.amend \ -text [mc "Amend Last Commit"] \ -command do_select_commit_type \ -variable selected_commit_type \ @@ -2890,7 +3329,7 @@ if {![is_enabled nocommit]} { [list .vpane.lower.commarea.buffer.header.amend conf -state] } -label $ui_coml \ +${NS}::label $ui_coml \ -anchor w \ -justify left proc trace_commit_type {varname args} { @@ -2913,20 +3352,25 @@ if {![is_enabled nocommit]} { pack .vpane.lower.commarea.buffer.header.new -side right } -text $ui_comm -background white -foreground black \ +textframe .vpane.lower.commarea.buffer.frame +ttext $ui_comm -background white -foreground black \ -borderwidth 1 \ -undo true \ -maxundo 20 \ -autoseparators true \ + -takefocus 1 \ + -highlightthickness 1 \ -relief sunken \ -width $repo_config(gui.commitmsgwidth) -height 9 -wrap none \ -font font_diff \ - -yscrollcommand {.vpane.lower.commarea.buffer.sby set} -scrollbar .vpane.lower.commarea.buffer.sby \ + -yscrollcommand {.vpane.lower.commarea.buffer.frame.sby set} +${NS}::scrollbar .vpane.lower.commarea.buffer.frame.sby \ -command [list $ui_comm yview] -pack .vpane.lower.commarea.buffer.header -side top -fill x -pack .vpane.lower.commarea.buffer.sby -side right -fill y + +pack .vpane.lower.commarea.buffer.frame.sby -side right -fill y pack $ui_comm -side left -fill y +pack .vpane.lower.commarea.buffer.header -side top -fill x +pack .vpane.lower.commarea.buffer.frame -side left -fill y pack .vpane.lower.commarea.buffer -side left -fill y # -- Commit Message Buffer Context Menu @@ -2944,7 +3388,7 @@ $ctxm add command \ -command {tk_textPaste $ui_comm} $ctxm add command \ -label [mc Delete] \ - -command {$ui_comm delete sel.first sel.last} + -command {catch {$ui_comm delete sel.first sel.last}} $ctxm add separator $ctxm add command \ -label [mc "Select All"] \ @@ -2988,19 +3432,19 @@ proc trace_current_diff_path {varname args} { } trace add variable current_diff_path write trace_current_diff_path -frame .vpane.lower.diff.header -background gold -label .vpane.lower.diff.header.status \ +gold_frame .vpane.lower.diff.header +tlabel .vpane.lower.diff.header.status \ -background gold \ -foreground black \ -width $max_status_desc \ -anchor w \ -justify left -label .vpane.lower.diff.header.file \ +tlabel .vpane.lower.diff.header.file \ -background gold \ -foreground black \ -anchor w \ -justify left -label .vpane.lower.diff.header.path \ +tlabel .vpane.lower.diff.header.path \ -background gold \ -foreground black \ -anchor w \ @@ -3024,18 +3468,20 @@ bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" # -- Diff Body # -frame .vpane.lower.diff.body +textframe .vpane.lower.diff.body set ui_diff .vpane.lower.diff.body.t -text $ui_diff -background white -foreground black \ +ttext $ui_diff -background white -foreground black \ -borderwidth 0 \ - -width 80 -height 15 -wrap none \ + -width 80 -height 5 -wrap none \ -font font_diff \ + -takefocus 1 -highlightthickness 1 \ -xscrollcommand {.vpane.lower.diff.body.sbx set} \ -yscrollcommand {.vpane.lower.diff.body.sby set} \ -state disabled -scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ +catch {$ui_diff configure -tabstyle wordprocessor} +${NS}::scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ -command [list $ui_diff xview] -scrollbar .vpane.lower.diff.body.sby -orient vertical \ +${NS}::scrollbar .vpane.lower.diff.body.sby -orient vertical \ -command [list $ui_diff yview] pack .vpane.lower.diff.body.sbx -side bottom -fill x pack .vpane.lower.diff.body.sby -side right -fill y @@ -3043,8 +3489,19 @@ pack $ui_diff -side left -fill both -expand 1 pack .vpane.lower.diff.header -side top -fill x pack .vpane.lower.diff.body -side bottom -fill both -expand 1 +foreach {n c} {0 black 1 red4 2 green4 3 yellow4 4 blue4 5 magenta4 6 cyan4 7 grey60} { + $ui_diff tag configure clr4$n -background $c + $ui_diff tag configure clri4$n -foreground $c + $ui_diff tag configure clr3$n -foreground $c + $ui_diff tag configure clri3$n -background $c +} +$ui_diff tag configure clr1 -font font_diffbold +$ui_diff tag configure clr4 -underline 1 + +$ui_diff tag conf d_info -foreground blue -font font_diffbold + $ui_diff tag conf d_cr -elide true -$ui_diff tag conf d_@ -foreground blue -font font_diffbold +$ui_diff tag conf d_@ -font font_diffbold $ui_diff tag conf d_+ -foreground {#00a000} $ui_diff tag conf d_- -foreground red @@ -3063,13 +3520,13 @@ $ui_diff tag conf d_s- \ -foreground red \ -background ivory1 -$ui_diff tag conf d<<<<<<< \ +$ui_diff tag conf d< \ -foreground orange \ -font font_diffbold -$ui_diff tag conf d======= \ +$ui_diff tag conf d= \ -foreground orange \ -font font_diffbold -$ui_diff tag conf d>>>>>>> \ +$ui_diff tag conf d> \ -foreground orange \ -font font_diffbold @@ -3080,15 +3537,6 @@ $ui_diff tag raise sel proc create_common_diff_popup {ctxm} { $ctxm add command \ - -label [mc "Show Less Context"] \ - -command show_less_context - lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] - $ctxm add command \ - -label [mc "Show More Context"] \ - -command show_more_context - lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] - $ctxm add separator - $ctxm add command \ -label [mc Refresh] \ -command reshow_diff lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] @@ -3139,10 +3587,19 @@ set ui_diff_applyhunk [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] $ctxm add command \ -label [mc "Apply/Reverse Line"] \ - -command {apply_line $cursorX $cursorY; do_rescan} + -command {apply_range_or_line $cursorX $cursorY; do_rescan} set ui_diff_applyline [$ctxm index last] lappend diff_actions [list $ctxm entryconf $ui_diff_applyline -state] $ctxm add separator +$ctxm add command \ + -label [mc "Show Less Context"] \ + -command show_less_context +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label [mc "Show More Context"] \ + -command show_more_context +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator create_common_diff_popup $ctxm set ctxmmg .vpane.lower.diff.body.ctxmmg @@ -3165,9 +3622,53 @@ $ctxmmg add command \ -command {merge_resolve_one 1} lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] $ctxmmg add separator +$ctxmmg add command \ + -label [mc "Show Less Context"] \ + -command show_less_context +lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] +$ctxmmg add command \ + -label [mc "Show More Context"] \ + -command show_more_context +lappend diff_actions [list $ctxmmg entryconf [$ctxmmg index last] -state] +$ctxmmg add separator create_common_diff_popup $ctxmmg -proc popup_diff_menu {ctxm ctxmmg x y X Y} { +set ctxmsm .vpane.lower.diff.body.ctxmsm +menu $ctxmsm -tearoff 0 +$ctxmsm add command \ + -label [mc "Visualize These Changes In The Submodule"] \ + -command {do_gitk -- true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add command \ + -label [mc "Visualize Current Branch History In The Submodule"] \ + -command {do_gitk {} true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add command \ + -label [mc "Visualize All Branch History In The Submodule"] \ + -command {do_gitk --all true} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add separator +$ctxmsm add command \ + -label [mc "Start git gui In The Submodule"] \ + -command {do_git_gui} +lappend diff_actions [list $ctxmsm entryconf [$ctxmsm index last] -state] +$ctxmsm add separator +create_common_diff_popup $ctxmsm + +proc has_textconv {path} { + if {[is_config_false gui.textconv]} { + return 0 + } + set filter [gitattr $path diff set] + set textconv [get_config [join [list diff $filter textconv] .]] + if {$filter ne {set} && $textconv ne {}} { + return 1 + } else { + return 0 + } +} + +proc popup_diff_menu {ctxm ctxmmg ctxmsm x y X Y} { global current_diff_path file_states set ::cursorX $x set ::cursorY $y @@ -3178,20 +3679,32 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { } if {[string first {U} $state] >= 0} { tk_popup $ctxmmg $X $Y + } elseif {$::is_submodule_diff} { + tk_popup $ctxmsm $X $Y } else { + set has_range [expr {[$::ui_diff tag nextrange sel 0.0] != {}}] if {$::ui_index eq $::current_diff_side} { set l [mc "Unstage Hunk From Commit"] - set t [mc "Unstage Line From Commit"] + if {$has_range} { + set t [mc "Unstage Lines From Commit"] + } else { + set t [mc "Unstage Line From Commit"] + } } else { set l [mc "Stage Hunk For Commit"] - set t [mc "Stage Line For Commit"] + if {$has_range} { + set t [mc "Stage Lines For Commit"] + } else { + set t [mc "Stage Line For Commit"] + } } if {$::is_3way_diff || $current_diff_path eq {} || {__} eq $state || {_O} eq $state - || {_T} eq $state - || {T_} eq $state} { + || [string match {?T} $state] + || [string match {T?} $state] + || [has_textconv $current_diff_path]} { set s disabled } else { set s normal @@ -3201,7 +3714,7 @@ proc popup_diff_menu {ctxm ctxmmg x y X Y} { tk_popup $ctxm $X $Y } } -bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg %x %y %X %Y] +bind_button3 $ui_diff [list popup_diff_menu $ctxm $ctxmmg $ctxmsm %x %y %X %Y] # -- Status Bar # @@ -3211,16 +3724,44 @@ $main_status show [mc "Initializing..."] # -- Load geometry # -catch { -set gm $repo_config(gui.geometry) -wm geometry . [lindex $gm 0] -.vpane sash place 0 \ - [lindex $gm 1] \ - [lindex [.vpane sash coord 0] 1] -.vpane.files sash place 0 \ - [lindex [.vpane.files sash coord 0] 0] \ - [lindex $gm 2] -unset gm +proc on_ttk_pane_mapped {w pane pos} { + bind $w <Map> {} + after 0 [list after idle [list $w sashpos $pane $pos]] +} +proc on_tk_pane_mapped {w pane x y} { + bind $w <Map> {} + after 0 [list after idle [list $w sash place $pane $x $y]] +} +proc on_application_mapped {} { + global repo_config use_ttk + bind . <Map> {} + set gm $repo_config(gui.geometry) + if {$use_ttk} { + bind .vpane <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 1]] + bind .vpane.files <Map> \ + [list on_ttk_pane_mapped %W 0 [lindex $gm 2]] + } else { + bind .vpane <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex $gm 1] \ + [lindex [.vpane sash coord 0] 1]] + bind .vpane.files <Map> \ + [list on_tk_pane_mapped %W 0 \ + [lindex [.vpane.files sash coord 0] 0] \ + [lindex $gm 2]] + } + wm geometry . [lindex $gm 0] +} +if {[info exists repo_config(gui.geometry)]} { + bind . <Map> [list on_application_mapped] + wm geometry . [lindex $repo_config(gui.geometry) 0] +} + +# -- Load window state +# +if {[info exists repo_config(gui.wmstate)]} { + catch {wm state . $repo_config(gui.wmstate)} } # -- Key Bindings @@ -3228,6 +3769,10 @@ unset gm bind $ui_comm <$M1B-Key-Return> {do_commit;break} bind $ui_comm <$M1B-Key-t> {do_add_selection;break} bind $ui_comm <$M1B-Key-T> {do_add_selection;break} +bind $ui_comm <$M1B-Key-u> {do_unstage_selection;break} +bind $ui_comm <$M1B-Key-U> {do_unstage_selection;break} +bind $ui_comm <$M1B-Key-j> {do_revert_selection;break} +bind $ui_comm <$M1B-Key-J> {do_revert_selection;break} bind $ui_comm <$M1B-Key-i> {do_add_all;break} bind $ui_comm <$M1B-Key-I> {do_add_all;break} bind $ui_comm <$M1B-Key-x> {tk_textCut %W;break} @@ -3252,6 +3797,8 @@ bind $ui_diff <$M1B-Key-v> {break} bind $ui_diff <$M1B-Key-V> {break} bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} +bind $ui_diff <$M1B-Key-j> {do_revert_selection;break} +bind $ui_diff <$M1B-Key-J> {do_revert_selection;break} bind $ui_diff <Key-Up> {catch {%W yview scroll -1 units};break} bind $ui_diff <Key-Down> {catch {%W yview scroll 1 units};break} bind $ui_diff <Key-Left> {catch {%W xview scroll -1 units};break} @@ -3282,8 +3829,12 @@ bind . <$M1B-Key-r> ui_do_rescan bind . <$M1B-Key-R> ui_do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-t> do_add_selection -bind . <$M1B-Key-T> do_add_selection +bind . <$M1B-Key-t> { toggle_or_diff toggle %W } +bind . <$M1B-Key-T> { toggle_or_diff toggle %W } +bind . <$M1B-Key-u> { toggle_or_diff toggle %W } +bind . <$M1B-Key-U> { toggle_or_diff toggle %W } +bind . <$M1B-Key-j> do_revert_selection +bind . <$M1B-Key-J> do_revert_selection bind . <$M1B-Key-i> do_add_all bind . <$M1B-Key-I> do_add_all bind . <$M1B-Key-minus> {show_less_context;break} @@ -3293,16 +3844,18 @@ bind . <$M1B-Key-plus> {show_more_context;break} bind . <$M1B-Key-KP_Add> {show_more_context;break} bind . <$M1B-Key-Return> do_commit foreach i [list $ui_index $ui_workdir] { - bind $i <Button-1> "toggle_or_diff $i %x %y; break" - bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" - bind $i <Shift-Button-1> "add_range_to_selection $i %x %y; break" + bind $i <Button-1> { toggle_or_diff click %W %x %y; break } + bind $i <$M1B-Button-1> { add_one_to_selection %W %x %y; break } + bind $i <Shift-Button-1> { add_range_to_selection %W %x %y; break } + bind $i <Key-Up> { toggle_or_diff up %W; break } + bind $i <Key-Down> { toggle_or_diff down %W; break } } unset i set file_lists($ui_index) [list] set file_lists($ui_workdir) [list] -wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]" +wm title . "[appname] ([reponame]) [file normalize $_gitworktree]" focus -force $ui_comm # -- Warn the user about environmental problems. Cygwin's Tcl @@ -3375,7 +3928,7 @@ if {[is_enabled transport]} { } if {[winfo exists $ui_comm]} { - set GITGUI_BCK_exists [load_message GITGUI_BCK] + set GITGUI_BCK_exists [load_message GITGUI_BCK utf-8] # -- If both our backup and message files exist use the # newer of the two files to initialize the buffer. @@ -3412,6 +3965,7 @@ if {[winfo exists $ui_comm]} { } elseif {$m} { catch { set fd [open [gitdir GITGUI_BCK] w] + fconfigure $fd -encoding utf-8 puts -nonewline $fd $msg close $fd set GITGUI_BCK_exists 1 @@ -3466,7 +4020,7 @@ after 1 { $ui_comm configure -state disabled -background gray } } -if {[is_enabled multicommit]} { +if {[is_enabled multicommit] && ![is_config_false gui.gcwarning]} { after 1000 hint_gc } if {[is_enabled retcode]} { @@ -3475,3 +4029,9 @@ if {[is_enabled retcode]} { if {$picked && [is_config_true gui.autoexplore]} { do_explore } + +# Local variables: +# mode: tcl +# indent-tabs-mode: t +# tab-width: 4 +# End: |