X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;f=git-gui.sh;h=62e165227633338cdc8deaede8cfd07ea6ddd804;hb=299077fb40eac1e128b7bc09d5d992960e6f11c2;hp=c38aa067acf12135fbd203b028326dddbad97448;hpb=87b49a533b0ee0da06c06fb466844885ae0d35bd;p=git.git diff --git a/git-gui.sh b/git-gui.sh index c38aa067a..62e165227 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -42,46 +42,11 @@ if {[catch {package require Tcl 8.4} err] exit 1 } +catch {rename send {}} ; # What an evil concept... + ###################################################################### ## -## configure our library - -set oguilib {@@GITGUI_LIBDIR@@} -set oguirel {@@GITGUI_RELATIVE@@} -if {$oguirel eq {1}} { - set oguilib [file dirname [file dirname [file normalize $argv0]]] - set oguilib [file join $oguilib share git-gui lib] -} elseif {[string match @@* $oguirel]} { - set oguilib [file join [file dirname [file normalize $argv0]] lib] -} -set idx [file join $oguilib tclIndex] -catch { - set fd [open $idx r] - if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} { - set idx [list] - while {[gets $fd n] >= 0} { - if {$n ne {} && ![string match #* $n]} { - lappend idx $n - } - } - } else { - set idx {} - } - close $fd -} -if {$idx ne {}} { - set loaded [list] - foreach p $idx { - if {[lsearch -exact $loaded $p] >= 0} continue - puts $p - source [file join $oguilib $p] - lappend loaded $p - } - unset loaded p -} else { - set auto_path [concat [list $oguilib] $auto_path] -} -unset -nocomplain oguilib oguirel idx fd +## enable verbose loading? if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { unset _verbose @@ -97,6 +62,18 @@ if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { } } +###################################################################### +## +## Fake internationalization to ease backporting of changes. + +proc mc {fmt args} { + set cmk [string first @@ $fmt] + if {$cmk > 0} { + set fmt [string range $fmt 0 [expr {$cmk - 1}]] + } + return [eval [list format $fmt] $args] +} + ###################################################################### ## ## read only globals @@ -106,6 +83,7 @@ set _gitdir {} set _gitexec {} set _reponame {} set _iscygwin {} +set _search_path {} proc appname {} { global _appname @@ -117,7 +95,7 @@ proc gitdir {args} { if {$args eq {}} { return $_gitdir } - return [eval [concat [list file join $_gitdir] $args]] + return [eval [list file join $_gitdir] $args] } proc gitexec {args} { @@ -126,20 +104,26 @@ proc gitexec {args} { if {[catch {set _gitexec [git --exec-path]} err]} { error "Git not installed?\n\n$err" } + if {[is_Cygwin]} { + set _gitexec [exec cygpath \ + --windows \ + --absolute \ + $_gitexec] + } else { + set _gitexec [file normalize $_gitexec] + } } if {$args eq {}} { return $_gitexec } - return [eval [concat [list file join $_gitexec] $args]] + return [eval [list file join $_gitexec] $args] } proc reponame {} { - global _reponame - return $_reponame + return $::_reponame } proc is_MacOSX {} { - global tcl_platform tk_library if {[tk windowingsystem] eq {aqua}} { return 1 } @@ -147,17 +131,16 @@ proc is_MacOSX {} { } proc is_Windows {} { - global tcl_platform - if {$tcl_platform(platform) eq {windows}} { + if {$::tcl_platform(platform) eq {windows}} { return 1 } return 0 } proc is_Cygwin {} { - global tcl_platform _iscygwin + global _iscygwin if {$_iscygwin eq {}} { - if {$tcl_platform(platform) eq {windows}} { + if {$::tcl_platform(platform) eq {windows}} { if {[catch {set p [exec cygpath --windir]} err]} { set _iscygwin 0 } else { @@ -211,13 +194,22 @@ proc is_config_true {name} { } } +proc get_config {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return {} + } else { + return $v + } +} + proc load_config {include_global} { global repo_config global_config default_config array unset global_config if {$include_global} { catch { - set fd_rc [open "| git config --global --list" r] + set fd_rc [git_read config --global --list] while {[gets $fd_rc line] >= 0} { if {[regexp {^([^=]+)=(.*)$} $line line name value]} { if {[is_many_config $name]} { @@ -233,7 +225,7 @@ proc load_config {include_global} { array unset repo_config catch { - set fd_rc [open "| git config --list" r] + set fd_rc [git_read config --list] while {[gets $fd_rc line] >= 0} { if {[regexp {^([^=]+)=(.*)$} $line line name value]} { if {[is_many_config $name]} { @@ -260,8 +252,224 @@ proc load_config {include_global} { ## ## handy utils +proc _git_cmd {name} { + global _git_cmd_path + + if {[catch {set v $_git_cmd_path($name)}]} { + switch -- $name { + version - + --version - + --exec-path { return [list $::_git $name] } + } + + set p [gitexec git-$name$::_search_exe] + if {[file exists $p]} { + set v [list $p] + } elseif {[is_Windows] && [file exists [gitexec git-$name]]} { + # Try to determine what sort of magic will make + # git-$name go and do its thing, because native + # Tcl on Windows doesn't know it. + # + set p [gitexec git-$name] + set f [open $p r] + set s [gets $f] + close $f + + switch -glob -- [lindex $s 0] { + #!*sh { set i sh } + #!*perl { set i perl } + #!*python { set i python } + default { error "git-$name is not supported: $s" } + } + + upvar #0 _$i interp + if {![info exists interp]} { + set interp [_which $i] + } + if {$interp eq {}} { + error "git-$name requires $i (not in PATH)" + } + set v [concat [list $interp] [lrange $s 1 end] [list $p]] + } else { + # Assume it is builtin to git somehow and we + # aren't actually able to see a file for it. + # + set v [list $::_git $name] + } + set _git_cmd_path($name) $v + } + return $v +} + +proc _which {what} { + global env _search_exe _search_path + + if {$_search_path eq {}} { + if {[is_Cygwin] && [regexp {^(/|\.:)} $env(PATH)]} { + set _search_path [split [exec cygpath \ + --windows \ + --path \ + --absolute \ + $env(PATH)] {;}] + set _search_exe .exe + } elseif {[is_Windows]} { + set _search_path [split $env(PATH) {;}] + set _search_exe .exe + } else { + set _search_path [split $env(PATH) :] + set _search_exe {} + } + } + + foreach p $_search_path { + set p [file join $p $what$_search_exe] + if {[file exists $p]} { + return [file normalize $p] + } + } + return {} +} + +proc _lappend_nice {cmd_var} { + global _nice + upvar $cmd_var cmd + + if {![info exists _nice]} { + set _nice [_which nice] + } + if {$_nice ne {}} { + lappend cmd $_nice + } +} + proc git {args} { - return [eval exec git $args] + set opt [list exec] + + 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] + + return [eval $opt $cmdp $args] +} + +proc _open_stdout_stderr {cmd} { + if {[catch { + set fd [open $cmd r] + } err]} { + if { [lindex $cmd end] eq {2>@1} + && $err eq {can not find channel named "1"} + } { + # Older versions of Tcl 8.4 don't have this 2>@1 IO + # redirect operator. Fallback to |& cat for those. + # The command was not actually started, so its safe + # to try to start it a second time. + # + set fd [open [concat \ + [lrange $cmd 0 end-1] \ + [list |& cat] \ + ] r] + } else { + error $err + } + } + fconfigure $fd -eofchar {} + return $fd +} + +proc git_read {args} { + set opt [list |] + + while {1} { + switch -- [lindex $args 0] { + --nice { + _lappend_nice opt + } + + --stderr { + lappend args 2>@1 + } + + 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} { + 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] + + return [open [concat $opt $cmdp $args] w] +} + +proc sq {value} { + regsub -all ' $value "'\\''" value + return "'$value'" +} + +proc load_current_branch {} { + global current_branch is_detached + + set fd [open [gitdir HEAD] r] + if {[gets $fd ref] < 1} { + set ref {} + } + close $fd + + set pfx {ref: refs/heads/} + set len [string length $pfx] + if {[string equal -length $len $pfx $ref]} { + # We're on a branch. It might not exist. But + # HEAD looks good enough to be a branch. + # + set current_branch [string range $ref $len end] + set is_detached 0 + } else { + # Assume this is a detached head. + # + set current_branch HEAD + set is_detached 1 + } } auto_load tk_optionMenu @@ -273,37 +481,231 @@ proc tk_optionMenu {w varName args} { return $m } +proc rmsel_tag {text} { + $text tag conf sel \ + -background [$text cget -background] \ + -foreground [$text cget -foreground] \ + -borderwidth 0 + $text tag conf in_sel -background lightgray + bind $text break + return $text +} + ###################################################################### ## -## version check +## find git -set req_maj 1 -set req_min 5 +set _git [_which git] +if {$_git eq {}} { + catch {wm withdraw .} + tk_messageBox \ + -icon error \ + -type ok \ + -title [mc "git-gui: fatal error"] \ + -message [mc "Cannot find git in PATH."] + exit 1 +} + +###################################################################### +## +## version check -if {[catch {set v [git --version]} err]} { +if {[catch {set _git_version [git --version]} err]} { catch {wm withdraw .} - error_popup "Cannot determine Git version: + tk_messageBox \ + -icon error \ + -type ok \ + -title "git-gui: fatal error" \ + -message "Cannot determine Git version: $err -[appname] requires Git $req_maj.$req_min or later." +[appname] requires Git 1.5.0 or later." exit 1 } -if {[regexp {^git version (\d+)\.(\d+)} $v _junk act_maj act_min]} { - if {$act_maj < $req_maj - || ($act_maj == $req_maj && $act_min < $req_min)} { - catch {wm withdraw .} - error_popup "[appname] requires Git $req_maj.$req_min or later. +if {![regsub {^git version } $_git_version {} _git_version]} { + catch {wm withdraw .} + tk_messageBox \ + -icon error \ + -type ok \ + -title "git-gui: fatal error" \ + -message "Cannot parse Git version string:\n\n$_git_version" + exit 1 +} + +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 -You are using $v." +if {![regexp {^[1-9]+(\.[0-9]+)+$} $_git_version]} { + catch {wm withdraw .} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -default no \ + -title "[appname]: warning" \ + -message "Git version cannot be determined. + +$_git claims it is version '$_real_git_version'. + +[appname] requires at least Git 1.5.0 or later. + +Assume '$_real_git_version' is version 1.5.0? +"] eq {yes}} { + set _git_version 1.5.0 + } else { exit 1 } -} else { +} +unset _real_git_version + +proc git-version {args} { + global _git_version + + switch [llength $args] { + 0 { + return $_git_version + } + + 2 { + set op [lindex $args 0] + set vr [lindex $args 1] + set cm [package vcompare $_git_version $vr] + return [expr $cm $op 0] + } + + 4 { + set type [lindex $args 0] + set name [lindex $args 1] + set parm [lindex $args 2] + set body [lindex $args 3] + + if {($type ne {proc} && $type ne {method})} { + error "Invalid arguments to git-version" + } + if {[llength $body] < 2 || [lindex $body end-1] ne {default}} { + error "Last arm of $type $name must be default" + } + + foreach {op vr cb} [lrange $body 0 end-2] { + if {[git-version $op $vr]} { + return [uplevel [list $type $name $parm $cb]] + } + } + + return [uplevel [list $type $name $parm [lindex $body end]]] + } + + default { + error "git-version >= x" + } + + } +} + +if {[git-version < 1.5]} { + catch {wm withdraw .} + tk_messageBox \ + -icon error \ + -type ok \ + -title "git-gui: fatal error" \ + -message "[appname] requires Git 1.5.0 or later. + +You are using [git-version]: + +[git --version]" + exit 1 +} + +###################################################################### +## +## configure our library + +set oguilib {@@GITGUI_LIBDIR@@} +set oguirel {@@GITGUI_RELATIVE@@} +if {$oguirel eq {1}} { + set oguilib [file dirname [file dirname [file normalize $argv0]]] + set oguilib [file join $oguilib share git-gui lib] +} elseif {[string match @@* $oguirel]} { + set oguilib [file join [file dirname [file normalize $argv0]] lib] +} + +set idx [file join $oguilib tclIndex] +if {[catch {set fd [open $idx r]} err]} { catch {wm withdraw .} - error_popup "Cannot parse Git version string:\n\n$v" + tk_messageBox \ + -icon error \ + -type ok \ + -title "git-gui: fatal error" \ + -message $err exit 1 } -unset -nocomplain v _junk act_maj act_min req_maj req_min +if {[gets $fd] eq {# Autogenerated by git-gui Makefile}} { + set idx [list] + while {[gets $fd n] >= 0} { + if {$n ne {} && ![string match #* $n]} { + lappend idx $n + } + } +} else { + set idx {} +} +close $fd + +if {$idx ne {}} { + set loaded [list] + foreach p $idx { + if {[lsearch -exact $loaded $p] >= 0} continue + source [file join $oguilib $p] + lappend loaded $p + } + unset loaded p +} else { + set auto_path [concat [list $oguilib] $auto_path] +} +unset -nocomplain oguirel idx fd + +###################################################################### +## +## feature option selection + +if {[regexp {^git-(.+)$} [appname] _junk subcommand]} { + unset _junk +} else { + set subcommand gui +} +if {$subcommand eq {gui.sh}} { + set subcommand gui +} +if {$subcommand eq {gui} && [llength $argv] > 0} { + set subcommand [lindex $argv 0] + set argv [lrange $argv 1 end] +} + +enable_option multicommit +enable_option branch +enable_option transport +disable_option bare + +switch -- $subcommand { +browser - +blame { + enable_option bare + + disable_option multicommit + disable_option branch + disable_option transport +} +citool { + enable_option singlecommit + + disable_option multicommit + disable_option branch + disable_option transport +} +} ###################################################################### ## @@ -329,19 +731,32 @@ if {![file isdirectory $_gitdir]} { error_popup "Git directory not found:\n\n$_gitdir" exit 1 } -if {[lindex [file split $_gitdir] end] ne {.git}} { - catch {wm withdraw .} - error_popup "Cannot use funny .git directory:\n\n$_gitdir" - exit 1 +if {$_prefix ne {}} { + regsub -all {[^/]+/} $_prefix ../ cdup + if {[catch {cd $cdup} err]} { + catch {wm withdraw .} + error_popup "Cannot move to top of working directory:\n\n$err" + exit 1 + } + unset cdup +} elseif {![is_enabled bare]} { + if {[lindex [file split $_gitdir] end] ne {.git}} { + catch {wm withdraw .} + error_popup "Cannot use funny .git directory:\n\n$_gitdir" + exit 1 + } + if {[catch {cd [file dirname $_gitdir]} err]} { + catch {wm withdraw .} + error_popup "No working directory [file dirname $_gitdir]:\n\n$err" + exit 1 + } } -if {[catch {cd [file dirname $_gitdir]} err]} { - catch {wm withdraw .} - error_popup "No working directory [file dirname $_gitdir]:\n\n$err" - exit 1 +set _reponame [file split [file normalize $_gitdir]] +if {[lindex $_reponame end] eq {.git}} { + set _reponame [lindex $_reponame end-1] +} else { + set _reponame [lindex $_reponame end] } -set _reponame [lindex [file split \ - [file normalize [file dirname $_gitdir]]] \ - end] ###################################################################### ## @@ -350,7 +765,6 @@ set _reponame [lindex [file split \ set current_diff_path {} set current_diff_side {} set diff_actions [list] -set ui_status_value {Initializing...} set HEAD {} set PARENT {} @@ -358,7 +772,9 @@ set MERGE_HEAD [list] set commit_type {} set empty_tree {} set current_branch {} +set is_detached 0 set current_diff_path {} +set is_3way_diff 0 set selected_commit_type new ###################################################################### @@ -407,15 +823,7 @@ proc repository_state {ctvar hdvar mhvar} { set mh [list] - if {[catch {set current_branch [git symbolic-ref HEAD]}]} { - set current_branch {} - } else { - regsub ^refs/((heads|tags|remotes)/)? \ - $current_branch \ - {} \ - current_branch - } - + load_current_branch if {[catch {set hd [git rev-parse --verify HEAD]}]} { set hd {} set ct initial @@ -451,7 +859,7 @@ proc PARENT {} { proc rescan {after {honor_trustmtime 1}} { global HEAD PARENT MERGE_HEAD commit_type - global ui_index ui_workdir ui_status_value ui_comm + global ui_index ui_workdir ui_comm global rescan_active file_states global repo_config @@ -470,8 +878,9 @@ proc rescan {after {honor_trustmtime 1}} { array unset file_states - if {![$ui_comm edit modified] - || [string trim [$ui_comm get 0.0 end]] eq {}} { + if {!$::GITGUI_BCK_exists && + (![$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 MERGE_MSG]} { @@ -481,22 +890,17 @@ proc rescan {after {honor_trustmtime 1}} { $ui_comm edit modified false } - if {[is_enabled branch]} { - load_all_heads - populate_branch_menu - } - if {$honor_trustmtime && $repo_config(gui.trustmtime) eq {true}} { rescan_stage2 {} $after } else { set rescan_active 1 - set ui_status_value {Refreshing file status...} - set cmd [list git update-index] - lappend cmd -q - lappend cmd --unmerged - lappend cmd --ignore-missing - lappend cmd --refresh - set fd_rf [open "| $cmd" r] + ui_status {Refreshing file status...} + set fd_rf [git_read update-index \ + -q \ + --unmerged \ + --ignore-missing \ + --refresh \ + ] fconfigure $fd_rf -blocking 0 -translation binary fileevent $fd_rf readable \ [list rescan_stage2 $fd_rf $after] @@ -504,7 +908,6 @@ proc rescan {after {honor_trustmtime 1}} { } proc rescan_stage2 {fd after} { - global ui_status_value global rescan_active buf_rdi buf_rdf buf_rlo if {$fd ne {}} { @@ -513,22 +916,25 @@ proc rescan_stage2 {fd after} { close $fd } - set ls_others [list | git ls-files --others -z \ - --exclude-per-directory=.gitignore] + set ls_others [list --exclude-per-directory=.gitignore] set info_exclude [gitdir info exclude] if {[file readable $info_exclude]} { lappend ls_others "--exclude-from=$info_exclude" } + set user_exclude [get_config core.excludesfile] + if {$user_exclude ne {} && [file readable $user_exclude]} { + lappend ls_others "--exclude-from=$user_exclude" + } set buf_rdi {} set buf_rdf {} set buf_rlo {} set rescan_active 3 - set ui_status_value {Scanning for modified files ...} - set fd_di [open "| git diff-index --cached -z [PARENT]" r] - set fd_df [open "| git diff-files -z" r] - set fd_lo [open $ls_others r] + ui_status {Scanning for modified files ...} + 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 @@ -546,6 +952,7 @@ proc load_message {file} { if {[catch {set fd [open $f r]}]} { return 0 } + fconfigure $fd -eofchar {} set content [string trim [read $fd]] close $fd regsub -all -line {[ \r\t]+$} $content {} content @@ -629,7 +1036,11 @@ 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] { - merge_state [encoding convertfrom $p] ?O + set p [encoding convertfrom $p] + if {[string index $p end] eq {/}} { + set p [string range $p 0 end-1] + } + merge_state $p ?O } rescan_done $fd buf_rlo $after } @@ -685,6 +1096,14 @@ proc mapdesc {state path} { return $r } +proc ui_status {msg} { + $::main_status show $msg +} + +proc ui_ready {{test {}}} { + $::main_status show {Ready.} $test +} + proc escape_path {path} { regsub -all {\\} $path "\\\\" path regsub -all "\n" $path "\\n" path @@ -930,32 +1349,6 @@ static unsigned char file_merge_bits[] = { 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; } -maskdata $filemask -set file_dir_data { -#define file_width 18 -#define file_height 18 -static unsigned char file_bits[] = { - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x03, 0x00, - 0x0c, 0x03, 0x00, 0x04, 0xfe, 0x00, 0x06, 0x80, 0x00, 0xff, 0x9f, 0x00, - 0x03, 0x98, 0x00, 0x02, 0x90, 0x00, 0x06, 0xb0, 0x00, 0x04, 0xa0, 0x00, - 0x0c, 0xe0, 0x00, 0x08, 0xc0, 0x00, 0xf8, 0xff, 0x00, 0x00, 0x00, 0x00, - 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}; -} -image create bitmap file_dir -background white -foreground blue \ - -data $file_dir_data -maskdata $file_dir_data -unset file_dir_data - -set file_uplevel_data { -#define up_width 15 -#define up_height 15 -static unsigned char up_bits[] = { - 0x80, 0x00, 0xc0, 0x01, 0xe0, 0x03, 0xf0, 0x07, 0xf8, 0x0f, 0xfc, 0x1f, - 0xfe, 0x3f, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, 0xc0, 0x01, - 0xc0, 0x01, 0xc0, 0x01, 0x00, 0x00}; -} -image create bitmap file_uplevel -background white -foreground red \ - -data $file_uplevel_data -maskdata $file_uplevel_data -unset file_uplevel_data - set ui_index .vpane.files.index.list set ui_workdir .vpane.files.workdir.list @@ -1008,6 +1401,9 @@ unset i proc bind_button3 {w cmd} { bind $w $cmd if {[is_MacOSX]} { + # Mac OS X sends Button-2 on right click through three-button mouse, + # or through trackpad right-clicking (two-finger touch + click). + bind $w $cmd bind $w $cmd } } @@ -1036,28 +1432,18 @@ proc incr_font_size {font {amt 1}} { set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { - global env ui_status_value starting_gitk_msg - # -- Always start gitk through whatever we were loaded with. This # lets us bypass using shell process on Windows systems. # - set cmd [list [info nameofexecutable]] - set exe [gitexec gitk] - lappend cmd $exe - if {$revs ne {}} { - append cmd { } - append cmd $revs - } - + set exe [file join [file dirname $::_git] gitk] + set cmd [list [info nameofexecutable] $exe] if {! [file exists $exe]} { error_popup "Unable to start gitk:\n\n$exe does not exist" } else { - eval exec $cmd & - set ui_status_value $starting_gitk_msg + eval exec $cmd $revs & + ui_status $::starting_gitk_msg after 10000 { - if {$ui_status_value eq $starting_gitk_msg} { - set ui_status_value {Ready.} - } + ui_ready $starting_gitk_msg } } } @@ -1066,6 +1452,7 @@ set is_quitting 0 proc do_quit {} { global ui_comm is_quitting repo_config commit_type + global GITGUI_BCK_exists GITGUI_BCK_i if {$is_quitting} return set is_quitting 1 @@ -1074,18 +1461,30 @@ proc do_quit {} { # -- Stash our current commit buffer. # set save [gitdir GITGUI_MSG] - set msg [string trim [$ui_comm get 0.0 end]] - regsub -all -line {[ \r\t]+$} $msg {} msg - if {(![string match amend* $commit_type] - || [$ui_comm edit modified]) - && $msg ne {}} { - catch { - set fd [open $save w] - puts -nonewline $fd $msg - close $fd - } + if {$GITGUI_BCK_exists && ![$ui_comm edit modified]} { + file rename -force [gitdir GITGUI_BCK] $save + set GITGUI_BCK_exists 0 } else { - catch {file delete $save} + set msg [string trim [$ui_comm get 0.0 end]] + regsub -all -line {[ \r\t]+$} $msg {} msg + if {(![string match amend* $commit_type] + || [$ui_comm edit modified]) + && $msg ne {}} { + catch { + set fd [open $save w] + puts -nonewline $fd $msg + close $fd + } + } else { + catch {file delete $save} + } + } + + # -- Remove our editor backup, its not needed. + # + after cancel $GITGUI_BCK_i + if {$GITGUI_BCK_exists} { + catch {file delete [gitdir GITGUI_BCK]} } # -- Stash our current window geometry into this repository. @@ -1106,7 +1505,7 @@ proc do_quit {} { } proc do_rescan {} { - rescan {set ui_status_value {Ready.}} + rescan ui_ready } proc do_commit {} { @@ -1141,12 +1540,12 @@ proc toggle_or_diff {w x y} { update_indexinfo \ "Unstaging [short_path $path] from commit" \ [list $path] \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } elseif {$w eq $ui_workdir} { update_index \ "Adding [short_path $path]" \ [list $path] \ - [concat $after {set ui_status_value {Ready.}}] + [concat $after [list ui_ready]] } } else { show_diff $path $w $lno @@ -1233,6 +1632,10 @@ foreach class {Button Checkbutton Entry Label } unset class +if {[is_Windows] || [is_MacOSX]} { + option add *Menu.tearOff 0 +} + if {[is_MacOSX]} { set M1B M1 set M1T Cmd @@ -1249,7 +1652,7 @@ proc apply_config {} { set font [lindex $option 1] if {[catch { foreach {cn cv} $repo_config(gui.$name) { - font configure $font $cn $cv + font configure $font $cn $cv -weight normal } } err]} { error_popup "Invalid font specified in gui.$name:\n\n$err" @@ -1263,11 +1666,14 @@ proc apply_config {} { } } +set default_config(merge.diffstat) true set default_config(merge.summary) false set default_config(merge.verbosity) 2 set default_config(user.name) {} set default_config(user.email) {} +set default_config(gui.matchtrackingbranch) false +set default_config(gui.pruneduringfetch) false set default_config(gui.trustmtime) false set default_config(gui.diffcontext) 5 set default_config(gui.newbranchtemplate) {} @@ -1280,43 +1686,6 @@ set font_descs { load_config 0 apply_config -###################################################################### -## -## feature option selection - -if {[regexp {^git-(.+)$} [appname] _junk subcommand]} { - unset _junk -} else { - set subcommand gui -} -if {$subcommand eq {gui.sh}} { - set subcommand gui -} -if {$subcommand eq {gui} && [llength $argv] > 0} { - set subcommand [lindex $argv 0] - set argv [lrange $argv 1 end] -} - -enable_option multicommit -enable_option branch -enable_option transport - -switch -- $subcommand { -browser - -blame { - disable_option multicommit - disable_option branch - disable_option transport -} -citool { - enable_option singlecommit - - disable_option multicommit - disable_option branch - disable_option transport -} -} - ###################################################################### ## ## ui construction @@ -1346,20 +1715,32 @@ if {[is_enabled transport]} { menu .mbar.repository .mbar.repository add command \ - -label {Browse Current Branch} \ + -label {Browse Current Branch's Files} \ -command {browser::new $current_branch} -trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" +set ui_browse_current [.mbar.repository index last] +.mbar.repository add command \ + -label {Browse Branch Files...} \ + -command browser_open::dialog .mbar.repository add separator .mbar.repository add command \ - -label {Visualize Current Branch} \ + -label {Visualize Current Branch's History} \ -command {do_gitk $current_branch} -trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#" +set ui_visualize_current [.mbar.repository index last] .mbar.repository add command \ - -label {Visualize All Branches} \ + -label {Visualize All Branch History} \ -command {do_gitk --all} .mbar.repository add separator +proc current_branch_write {args} { + global current_branch + .mbar.repository entryconf $::ui_browse_current \ + -label "Browse $current_branch's Files" + .mbar.repository entryconf $::ui_visualize_current \ + -label "Visualize $current_branch's History" +} +trace add variable current_branch write current_branch_write + if {[is_enabled multicommit]} { .mbar.repository add command -label {Database Statistics} \ -command do_stats @@ -1424,13 +1805,24 @@ if {[is_enabled branch]} { menu .mbar.branch .mbar.branch add command -label {Create...} \ - -command do_create_branch \ + -command branch_create::dialog \ -accelerator $M1T-N lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] + .mbar.branch add command -label {Checkout...} \ + -command branch_checkout::dialog \ + -accelerator $M1T-O + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + + .mbar.branch add command -label {Rename...} \ + -command branch_rename::dialog + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + .mbar.branch add command -label {Delete...} \ - -command do_delete_branch + -command branch_delete::dialog lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] @@ -1469,12 +1861,12 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Add To Commit} \ + .mbar.commit add command -label {Stage To Commit} \ -command do_add_selection lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] - .mbar.commit add command -label {Add Existing To Commit} \ + .mbar.commit add command -label {Stage Changed Files To Commit} \ -command do_add_all \ -accelerator $M1T-I lappend disable_on_lock \ @@ -1508,14 +1900,14 @@ if {[is_enabled multicommit] || [is_enabled singlecommit]} { if {[is_enabled branch]} { menu .mbar.merge .mbar.merge add command -label {Local Merge...} \ - -command merge::dialog + -command merge::dialog \ + -accelerator $M1T-M lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] .mbar.merge add command -label {Abort Merge...} \ -command merge::reset_hard lappend disable_on_lock \ [list .mbar.merge entryconf [.mbar.merge index last] -state] - } # -- Transport Menu @@ -1527,6 +1919,8 @@ if {[is_enabled transport]} { .mbar.push add command -label {Push...} \ -command do_push_anywhere \ -accelerator $M1T-P + .mbar.push add command -label {Delete...} \ + -command remote_branch_delete::dialog } if {[is_MacOSX]} { @@ -1545,34 +1939,6 @@ if {[is_MacOSX]} { .mbar.edit add separator .mbar.edit add command -label {Options...} \ -command do_options - - # -- Tools Menu - # - if {[is_Cygwin] && [file exists /usr/local/miga/lib/gui-miga]} { - proc do_miga {} { - global ui_status_value - if {![lock_index update]} return - set cmd [list sh --login -c "/usr/local/miga/lib/gui-miga \"[pwd]\""] - set miga_fd [open "|$cmd" r] - fconfigure $miga_fd -blocking 0 - fileevent $miga_fd readable [list miga_done $miga_fd] - set ui_status_value {Running miga...} - } - proc miga_done {fd} { - read $fd 512 - if {[eof $fd]} { - close $fd - unlock_index - rescan [list set ui_status_value {Ready.}] - } - } - .mbar add cascade -label Tools -menu .mbar.tools - menu .mbar.tools - .mbar.tools add command -label "Migrate" \ - -command do_miga - lappend disable_on_lock \ - [list .mbar.tools entryconf [.mbar.tools index last] -state] - } } # -- Help Menu @@ -1623,6 +1989,12 @@ if {$browser ne {}} { } unset browser doc_path doc_url +set root_exists 0 +bind . { + bind . {} + set root_exists 1 +} + # -- Standard bindings # wm protocol . WM_DELETE_WINDOW do_quit @@ -1640,24 +2012,10 @@ proc usage {} { # -- Not a normal commit type invocation? Do that instead! # switch -- $subcommand { -browser { - set subcommand_args {rev?} - switch [llength $argv] { - 0 { - set current_branch [git symbolic-ref HEAD] - regsub ^refs/((heads|tags|remotes)/)? \ - $current_branch {} current_branch - } - 1 { - set current_branch [lindex $argv 0] - } - default usage - } - browser::new $current_branch - return -} +browser - blame { - set subcommand_args {rev? path?} + set subcommand_args {rev? path} + if {$argv eq {}} usage set head {} set path {} set is_path 0 @@ -1676,22 +2034,52 @@ blame { } elseif {$head eq {}} { if {$head ne {}} usage set head $a + set is_path 1 } else { usage } } unset is_path + if {$head ne {} && $path eq {}} { + set path $_prefix$head + set head {} + } + if {$head eq {}} { - set current_branch [git symbolic-ref HEAD] - regsub ^refs/((heads|tags|remotes)/)? \ - $current_branch {} current_branch + load_current_branch } else { + if {[regexp {^[0-9a-f]{1,39}$} $head]} { + if {[catch { + set head [git rev-parse --verify $head] + } err]} { + puts stderr $err + exit 1 + } + } set current_branch $head } - if {$path eq {}} usage - blame::new $head $path + switch -- $subcommand { + browser { + if {$head eq {}} { + if {$path ne {} && [file isdirectory $path]} { + set head $current_branch + } else { + set head $path + set path {} + } + } + browser::new $head $path + } + blame { + if {$head eq {} && ![file exists $path]} { + puts stderr "fatal: cannot stat path $path: No such file or directory" + exit 1 + } + blame::new $head $path + } + } return } citool - @@ -1777,8 +2165,8 @@ pack $ui_workdir -side left -fill both -expand 1 .vpane.files add .vpane.files.workdir -sticky nsew foreach i [list $ui_index $ui_workdir] { - $i tag conf in_diff -background lightgray - $i tag conf in_sel -background lightgray + rmsel_tag $i + $i tag conf in_diff -background [$i tag cget in_sel -background] } unset i @@ -1806,7 +2194,7 @@ 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 {Add Existing} \ +button .vpane.lower.commarea.buttons.incall -text {Stage Changed} \ -command do_add_all pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ @@ -2080,26 +2468,33 @@ lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] $ctxm add separator $ctxm add command -label {Options...} \ -command do_options -bind_button3 $ui_diff " - set cursorX %x - set cursorY %y - if {\$ui_index eq \$current_diff_side} { - $ctxm entryconf $ui_diff_applyhunk -label {Unstage Hunk From Commit} +proc popup_diff_menu {ctxm x y X Y} { + global current_diff_path file_states + set ::cursorX $x + set ::cursorY $y + if {$::ui_index eq $::current_diff_side} { + set l "Unstage Hunk From Commit" } else { - $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit} + set l "Stage Hunk For Commit" } - tk_popup $ctxm %X %Y -" -unset ui_diff_applyhunk + if {$::is_3way_diff + || $current_diff_path eq {} + || ![info exists file_states($current_diff_path)] + || {_O} eq [lindex $file_states($current_diff_path) 0]} { + set s disabled + } else { + set s normal + } + $ctxm entryconf $::ui_diff_applyhunk -state $s -label $l + tk_popup $ctxm $X $Y +} +bind_button3 $ui_diff [list popup_diff_menu $ctxm %x %y %X %Y] # -- Status Bar # -label .status -textvariable ui_status_value \ - -anchor w \ - -justify left \ - -borderwidth 1 \ - -relief sunken +set main_status [::status_bar::new .status] pack .status -anchor w -side bottom -fill x +$main_status show {Initializing...} # -- Load geometry # @@ -2150,8 +2545,12 @@ bind $ui_diff {catch {%W yview scroll 1 pages};break} bind $ui_diff {focus %W} if {[is_enabled branch]} { - bind . <$M1B-Key-n> do_create_branch - bind . <$M1B-Key-N> do_create_branch + bind . <$M1B-Key-n> branch_create::dialog + bind . <$M1B-Key-N> branch_create::dialog + bind . <$M1B-Key-o> branch_checkout::dialog + bind . <$M1B-Key-O> branch_checkout::dialog + bind . <$M1B-Key-m> merge::dialog + bind . <$M1B-Key-M> merge::dialog } if {[is_enabled transport]} { bind . <$M1B-Key-p> do_push_anywhere @@ -2238,31 +2637,69 @@ user.email settings into your personal # if {[is_enabled transport]} { load_all_remotes - load_all_heads - populate_branch_menu populate_fetch_menu populate_push_menu } -# -- Only suggest a gc run if we are going to stay running. -# -if {[is_enabled multicommit]} { - set object_limit 2000 - if {[is_Windows]} {set object_limit 200} - regexp {^([0-9]+) objects,} [git count-objects] _junk objects_current - if {$objects_current >= $object_limit} { - if {[ask_popup \ - "This repository currently has $objects_current loose objects. +if {[winfo exists $ui_comm]} { + set GITGUI_BCK_exists [load_message GITGUI_BCK] -To maintain optimal performance it is strongly recommended that you compress the database when more than $object_limit loose objects exist. + # -- If both our backup and message files exist use the + # newer of the two files to initialize the buffer. + # + if {$GITGUI_BCK_exists} { + set m [gitdir GITGUI_MSG] + if {[file isfile $m]} { + if {[file mtime [gitdir GITGUI_BCK]] > [file mtime $m]} { + catch {file delete [gitdir GITGUI_MSG]} + } else { + $ui_comm delete 0.0 end + $ui_comm edit reset + $ui_comm edit modified false + catch {file delete [gitdir GITGUI_BCK]} + set GITGUI_BCK_exists 0 + } + } + unset m + } + + proc backup_commit_buffer {} { + global ui_comm GITGUI_BCK_exists + + set m [$ui_comm edit modified] + if {$m || $GITGUI_BCK_exists} { + set msg [string trim [$ui_comm get 0.0 end]] + regsub -all -line {[ \r\t]+$} $msg {} msg + + if {$msg eq {}} { + if {$GITGUI_BCK_exists} { + catch {file delete [gitdir GITGUI_BCK]} + set GITGUI_BCK_exists 0 + } + } elseif {$m} { + catch { + set fd [open [gitdir GITGUI_BCK] w] + puts -nonewline $fd $msg + close $fd + set GITGUI_BCK_exists 1 + } + } -Compress the database now?"] eq yes} { - do_gc + $ui_comm edit modified false } + + set ::GITGUI_BCK_i [after 2000 backup_commit_buffer] } - unset object_limit _junk objects_current + + backup_commit_buffer } lock_index begin-read +if {![winfo ismapped .]} { + wm deiconify . +} after 1 do_rescan +if {[is_enabled multicommit]} { + after 1000 hint_gc +}