X-Git-Url: https://git.tokkee.org/?a=blobdiff_plain;ds=sidebyside;f=git-gui.sh;h=c4ab824b9d860d06e0fe9239671cf2653cc4e396;hb=19e283f5c25b64a55fca099342f9bebddef4e17e;hp=0770ad03f94bcec4602b932afc94d2c03ba27f5c;hpb=41bdcda37376a5faa63028f01260890723c3fcfa;p=git.git diff --git a/git-gui.sh b/git-gui.sh index 0770ad03f..c4ab824b9 100755 --- a/git-gui.sh +++ b/git-gui.sh @@ -2,18 +2,49 @@ # Tcl ignores the next line -*- tcl -*- \ exec wish "$0" -- "$@" +set appvers {@@GIT_VERSION@@} set copyright { Copyright © 2006, 2007 Shawn Pearce, Paul Mackerras. -All rights reserved. +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 +the Free Software Foundation; either version 2 of the License, or +(at your option) any later version. -This program is free software; it may be used, copied, modified -and distributed under the terms of the GNU General Public Licence, -either version 2, or (at your option) any later version.} +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. -set appvers {@@GITGUI_VERSION@@} -set appname [lindex [file split $argv0] end] -set gitdir {} +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA} + +###################################################################### +## +## read only globals + +set _appname [lindex [file split $argv0] end] +set _gitdir {} +set _reponame {} + +proc appname {} { + global _appname + return $_appname +} + +proc gitdir {args} { + global _gitdir + if {$args eq {}} { + return $_gitdir + } + return [eval [concat [list file join $_gitdir] $args]] +} + +proc reponame {} { + global _reponame + return $_reponame +} ###################################################################### ## @@ -125,15 +156,9 @@ proc save_config {} { } proc error_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } set cmd [list tk_messageBox \ -icon error \ @@ -147,15 +172,9 @@ proc error_popup {msg} { } proc warn_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } set cmd [list tk_messageBox \ -icon warning \ @@ -169,15 +188,9 @@ proc warn_popup {msg} { } proc info_popup {msg} { - global gitdir appname - - set title $appname - if {$gitdir ne {}} { - append title { (} - append title [lindex \ - [file split [file normalize [file dirname $gitdir]]] \ - end] - append title {)} + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" } tk_messageBox \ -parent . \ @@ -187,34 +200,50 @@ proc info_popup {msg} { -message $msg } +proc ask_popup {msg} { + set title [appname] + if {[reponame] ne {}} { + append title " ([reponame])" + } + return [tk_messageBox \ + -parent . \ + -icon question \ + -type yesno \ + -title $title \ + -message $msg] +} + ###################################################################### ## ## repository setup -if { [catch {set gitdir $env(GIT_DIR)}] - && [catch {set gitdir [exec git rev-parse --git-dir]} err]} { +if { [catch {set _gitdir $env(GIT_DIR)}] + && [catch {set _gitdir [exec git rev-parse --git-dir]} err]} { catch {wm withdraw .} error_popup "Cannot find the git directory:\n\n$err" exit 1 } -if {![file isdirectory $gitdir]} { +if {![file isdirectory $_gitdir]} { catch {wm withdraw .} - error_popup "Git directory not found:\n\n$gitdir" + error_popup "Git directory not found:\n\n$_gitdir" exit 1 } -if {[lindex [file split $gitdir] end] ne {.git}} { +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]} { +if {[catch {cd [file dirname $_gitdir]} err]} { catch {wm withdraw .} - error_popup "No working directory [file dirname $gitdir]:\n\n$err" + error_popup "No working directory [file dirname $_gitdir]:\n\n$err" exit 1 } +set _reponame [lindex [file split \ + [file normalize [file dirname $_gitdir]]] \ + end] set single_commit 0 -if {$appname eq {git-citool}} { +if {[appname] eq {git-citool}} { set single_commit 1 } @@ -259,7 +288,7 @@ proc unlock_index {} { ## status proc repository_state {ctvar hdvar mhvar} { - global gitdir current_branch + global current_branch upvar $ctvar ct $hdvar hd $mhvar mh set mh [list] @@ -279,7 +308,7 @@ proc repository_state {ctvar hdvar mhvar} { return } - set merge_head [file join $gitdir MERGE_HEAD] + set merge_head [gitdir MERGE_HEAD] if {[file exists $merge_head]} { set ct merge set fd_mh [open $merge_head r] @@ -308,7 +337,7 @@ proc PARENT {} { proc rescan {after} { global HEAD PARENT MERGE_HEAD commit_type - global ui_index ui_other ui_status_value ui_comm + global ui_index ui_workdir ui_status_value ui_comm global rescan_active file_states global repo_config @@ -355,7 +384,7 @@ proc rescan {after} { } proc rescan_stage2 {fd after} { - global gitdir ui_status_value + global ui_status_value global rescan_active buf_rdi buf_rdf buf_rlo if {$fd ne {}} { @@ -366,7 +395,7 @@ proc rescan_stage2 {fd after} { set ls_others [list | git ls-files --others -z \ --exclude-per-directory=.gitignore] - set info_exclude [file join $gitdir info exclude] + set info_exclude [gitdir info exclude] if {[file readable $info_exclude]} { lappend ls_others "--exclude-from=$info_exclude" } @@ -390,9 +419,9 @@ proc rescan_stage2 {fd after} { } proc load_message {file} { - global gitdir ui_comm + global ui_comm - set f [file join $gitdir $file] + set f [gitdir $file] if {[file isfile $f]} { if {[catch {set fd [open $f r]}]} { return 0 @@ -495,24 +524,6 @@ proc rescan_done {fd buf after} { prune_selection unlock_index display_all_files - - if {$repo_config(gui.partialinclude) ne {true}} { - set pathList [list] - foreach path [array names file_states] { - switch -- [lindex $file_states($path) 0] { - A? - - M? {lappend pathList $path} - } - } - if {$pathList ne {}} { - update_index \ - "Updating included files" \ - $pathList \ - [concat {reshow_diff;} $after] - return - } - } - reshow_diff uplevel #0 $after } @@ -532,33 +543,37 @@ proc prune_selection {} { ## diff proc clear_diff {} { - global ui_diff current_diff ui_index ui_other + global ui_diff current_diff_path ui_index ui_workdir $ui_diff conf -state normal $ui_diff delete 0.0 end $ui_diff conf -state disabled - set current_diff {} + set current_diff_path {} $ui_index tag remove in_diff 0.0 end - $ui_other tag remove in_diff 0.0 end + $ui_workdir tag remove in_diff 0.0 end } proc reshow_diff {} { - global current_diff ui_status_value file_states - - if {$current_diff eq {} - || [catch {set s $file_states($current_diff)}]} { + global ui_status_value file_states file_lists + global current_diff_path current_diff_side + + set p $current_diff_path + if {$p eq {} + || $current_diff_side eq {} + || [catch {set s $file_states($p)}] + || [lsearch -sorted $file_lists($current_diff_side) $p] == -1} { clear_diff } else { - show_diff $current_diff + show_diff $p $current_diff_side } } proc handle_empty_diff {} { - global current_diff file_states file_lists + global current_diff_path file_states file_lists - set path $current_diff + set path $current_diff_path set s $file_states($path) if {[lindex $s 0] ne {_M}} return @@ -581,22 +596,14 @@ files list, to prevent possible confusion. } clear_diff - set old_w [mapcol [lindex $file_states($path) 0] $path] - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } + display_file $path __ } -proc show_diff {path {w {}} {lno {}}} { +proc show_diff {path w {lno {}}} { global file_states file_lists global is_3way_diff diff_active repo_config - global ui_diff current_diff ui_status_value + global ui_diff ui_status_value ui_index ui_workdir + global current_diff_path current_diff_side if {$diff_active || ![lock_index read]} return @@ -618,21 +625,13 @@ proc show_diff {path {w {}} {lno {}}} { set m [lindex $s 0] set is_3way_diff 0 set diff_active 1 - set current_diff $path + set current_diff_path $path + set current_diff_side $w set ui_status_value "Loading diff of [escape_path $path]..." - set cmd [list | git diff-index] - lappend cmd --no-color - if {$repo_config(gui.diffcontext) > 0} { - lappend cmd "-U$repo_config(gui.diffcontext)" - } - lappend cmd -p - - switch $m { - MM { - lappend cmd -c - } - _O { + # - Git won't give us the diff, there's nothing to compare to! + # + if {$m eq {_O}} { if {[catch { set fd [open $path r] set content [read $fd] @@ -652,9 +651,27 @@ proc show_diff {path {w {}} {lno {}}} { set ui_status_value {Ready.} return } + + set cmd [list | git] + if {$w eq $ui_index} { + lappend cmd diff-index + lappend cmd --cached + } elseif {$w eq $ui_workdir} { + if {[string index $m 0] eq {U}} { + lappend cmd diff + } else { + lappend cmd diff-files + } } - lappend cmd [PARENT] + lappend cmd -p + lappend cmd --no-color + if {$repo_config(gui.diffcontext) > 0} { + lappend cmd "-U$repo_config(gui.diffcontext)" + } + if {$w eq $ui_index} { + lappend cmd [PARENT] + } lappend cmd -- lappend cmd $path @@ -679,6 +696,7 @@ proc read_diff {fd} { # -- Cleanup uninteresting diff header lines. # if {[string match {diff --git *} $line]} continue + if {[string match {diff --cc *} $line]} continue if {[string match {diff --combined *} $line]} continue if {[string match {--- *} $line]} continue if {[string match {+++ *} $line]} continue @@ -690,27 +708,50 @@ proc read_diff {fd} { # if {[string match {@@@ *} $line]} {set is_3way_diff 1} - # -- Reformat a 3 way diff, 'cause its too weird. - # - if {$is_3way_diff} { + if {[string match {index *} $line] + || [regexp {^\* Unmerged path } $line]} { + set tags {} + } elseif {$is_3way_diff} { set op [string range $line 0 1] switch -- $op { + { } {set tags {}} {@@} {set tags d_@} - {++} {set tags d_+ ; set op { +}} - {--} {set tags d_- ; set op { -}} - { +} {set tags d_++; set op {++}} - { -} {set tags d_--; set op {--}} - {+ } {set tags d_-+; set op {-+}} - {- } {set tags d_+-; set op {+-}} - default {set tags {}} + { +} {set tags d_s+} + { -} {set tags d_s-} + {+ } {set tags d_+s} + {- } {set tags d_-s} + {--} {set tags d_--} + {++} { + if {[regexp {^\+\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 1 { }] + set tags d$op + } else { + set tags d_++ + } + } + default { + puts "error: Unhandled 3 way diff marker: {$op}" + set tags {} + } } - set line [string replace $line 0 1 $op] } else { - switch -- [string index $line 0] { - @ {set tags d_@} - + {set tags d_+} - - {set tags d_-} - default {set tags {}} + set op [string index $line 0] + switch -- $op { + { } {set tags {}} + {@} {set tags d_@} + {-} {set tags d_-} + {+} { + if {[regexp {^\+([<>]{7} |={7})} $line _g op]} { + set line [string replace $line 0 0 { }] + set tags d$op + } else { + set tags d_+ + } + } + default { + puts "error: Unhandled 2 way diff marker: {$op}" + set tags {} + } } } $ui_diff insert end $line $tags @@ -823,6 +864,7 @@ proc committer_ident {} { proc commit_tree {} { global HEAD commit_type file_states ui_comm repo_config + global ui_status_value pch_error if {![lock_index update]} return if {[committer_ident] eq {}} return @@ -855,12 +897,12 @@ The rescan will be automatically started now. _? {continue} A? - D? - - M? {set files_ready 1; break} + M? {set files_ready 1} U? { error_popup "Unmerged files cannot be committed. File [short_path $path] has merge conflicts. -You must resolve them and include the file before committing. +You must resolve them and add the file before committing. " unlock_index return @@ -874,9 +916,9 @@ File [short_path $path] cannot be committed by this program. } } if {!$files_ready} { - error_popup {No included files to commit. + info_popup {No changes to commit. -You must include at least 1 file before you can commit. +You must add at least 1 file before you can commit. } unlock_index return @@ -898,34 +940,9 @@ A good commit message has the following format: return } - # -- Update included files if partialincludes are off. + # -- Run the pre-commit hook. # - if {$repo_config(gui.partialinclude) ne {true}} { - set pathList [list] - foreach path [array names file_states] { - switch -glob -- [lindex $file_states($path) 0] { - A? - - M? {lappend pathList $path} - } - } - if {$pathList ne {}} { - unlock_index - update_index \ - "Updating included files" \ - $pathList \ - [concat {lock_index update;} \ - [list commit_prehook $curHEAD $msg]] - return - } - } - - commit_prehook $curHEAD $msg -} - -proc commit_prehook {curHEAD msg} { - global gitdir ui_status_value pch_error - - set pchook [file join $gitdir hooks pre-commit] + set pchook [gitdir hooks pre-commit] # On Cygwin [file executable] might lie so we need to ask # the shell if the hook is executable. Yes that's annoying. @@ -980,7 +997,7 @@ proc commit_writetree {curHEAD msg} { proc commit_committree {fd_wt curHEAD msg} { global HEAD PARENT MERGE_HEAD commit_type - global single_commit gitdir + global single_commit global ui_status_value ui_comm selected_commit_type global file_states selected_paths rescan_active @@ -1034,20 +1051,20 @@ proc commit_committree {fd_wt curHEAD msg} { # -- Cleanup after ourselves. # - catch {file delete [file join $gitdir MERGE_HEAD]} - catch {file delete [file join $gitdir MERGE_MSG]} - catch {file delete [file join $gitdir SQUASH_MSG]} - catch {file delete [file join $gitdir GITGUI_MSG]} + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir GITGUI_MSG]} # -- Let rerere do its thing. # - if {[file isdirectory [file join $gitdir rr-cache]]} { + if {[file isdirectory [gitdir rr-cache]]} { catch {exec git rerere} } # -- Run the post-commit hook. # - set pchook [file join $gitdir hooks post-commit] + set pchook [gitdir hooks post-commit] if {[is_Windows] && [file isfile $pchook]} { set pchook [list sh -c [concat \ "if test -x \"$pchook\";" \ @@ -1084,7 +1101,7 @@ proc commit_committree {fd_wt curHEAD msg} { __ - A_ - M_ - - DD { + D_ { unset file_states($path) catch {unset selected_paths($path)} } @@ -1094,8 +1111,7 @@ proc commit_committree {fd_wt curHEAD msg} { AM - AD - MM - - MD - - DM { + MD { set file_states($path) [list \ _[string index $m 1] \ [lindex $s 1] \ @@ -1201,21 +1217,11 @@ proc push_to {remote} { ## ## ui helpers -proc mapcol {state path} { - global all_cols ui_other - - if {[catch {set r $all_cols($state)}]} { - puts "error: no column for state={$state} $path" - return $ui_other - } - return $r -} - -proc mapicon {state path} { +proc mapicon {w state path} { global all_icons - if {[catch {set r $all_icons($state)}]} { - puts "error: no icon for state={$state} $path" + if {[catch {set r $all_icons($state$w)}]} { + puts "error: no icon for $w state={$state} $path" return file_plain } return $r @@ -1278,102 +1284,125 @@ proc merge_state {path new_state {head_info {}} {index_info {}}} { return $state } +proc display_file_helper {w path icon_name old_m new_m} { + global file_lists + + if {$new_m eq {_}} { + set lno [lsearch -sorted $file_lists($w) $path] + if {$lno >= 0} { + set file_lists($w) [lreplace $file_lists($w) $lno $lno] + incr lno + $w conf -state normal + $w delete $lno.0 [expr {$lno + 1}].0 + $w conf -state disabled + } + } elseif {$old_m eq {_} && $new_m ne {_}} { + lappend file_lists($w) $path + set file_lists($w) [lsort -unique $file_lists($w)] + set lno [lsearch -sorted $file_lists($w) $path] + incr lno + $w conf -state normal + $w image create $lno.0 \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $new_m $path] + $w insert $lno.1 "[escape_path $path]\n" + $w conf -state disabled + } elseif {$old_m ne $new_m} { + $w conf -state normal + $w image conf $icon_name -image [mapicon $w $new_m $path] + $w conf -state disabled + } +} + proc display_file {path state} { - global file_states file_lists selected_paths + global file_states selected_paths + global ui_index ui_workdir set old_m [merge_state $path $state] set s $file_states($path) set new_m [lindex $s 0] - set new_w [mapcol $new_m $path] - set old_w [mapcol $old_m $path] - set new_icon [mapicon $new_m $path] + set icon_name [lindex $s 1] + + set o [string index $old_m 0] + set n [string index $new_m 0] + if {$o eq {U}} { + set o _ + } + if {$n eq {U}} { + set n _ + } + display_file_helper $ui_index $path $icon_name $o $n + + if {[string index $old_m 0] eq {U}} { + set o U + } else { + set o [string index $old_m 1] + } + if {[string index $new_m 0] eq {U}} { + set n U + } else { + set n [string index $new_m 1] + } + display_file_helper $ui_workdir $path $icon_name $o $n if {$new_m eq {__}} { - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } unset file_states($path) catch {unset selected_paths($path)} - return } +} - if {$new_w ne $old_w} { - set lno [lsearch -sorted $file_lists($old_w) $path] - if {$lno >= 0} { - set file_lists($old_w) \ - [lreplace $file_lists($old_w) $lno $lno] - incr lno - $old_w conf -state normal - $old_w delete $lno.0 [expr {$lno + 1}].0 - $old_w conf -state disabled - } +proc display_all_files_helper {w path icon_name m} { + global file_lists - lappend file_lists($new_w) $path - set file_lists($new_w) [lsort $file_lists($new_w)] - set lno [lsearch -sorted $file_lists($new_w) $path] - incr lno - $new_w conf -state normal - $new_w image create $lno.0 \ - -align center -padx 5 -pady 1 \ - -name [lindex $s 1] \ - -image $new_icon - $new_w insert $lno.1 "[escape_path $path]\n" - if {[catch {set in_sel $selected_paths($path)}]} { - set in_sel 0 - } - if {$in_sel} { - $new_w tag add in_sel $lno.0 [expr {$lno + 1}].0 - } - $new_w conf -state disabled - } elseif {$new_icon ne [mapicon $old_m $path]} { - $new_w conf -state normal - $new_w image conf [lindex $s 1] -image $new_icon - $new_w conf -state disabled - } + lappend file_lists($w) $path + set lno [expr {[lindex [split [$w index end] .] 0] - 1}] + $w image create end \ + -align center -padx 5 -pady 1 \ + -name $icon_name \ + -image [mapicon $w $m $path] + $w insert end "[escape_path $path]\n" } proc display_all_files {} { - global ui_index ui_other + global ui_index ui_workdir global file_states file_lists - global last_clicked selected_paths + global last_clicked $ui_index conf -state normal - $ui_other conf -state normal + $ui_workdir conf -state normal $ui_index delete 0.0 end - $ui_other delete 0.0 end + $ui_workdir delete 0.0 end set last_clicked {} set file_lists($ui_index) [list] - set file_lists($ui_other) [list] + set file_lists($ui_workdir) [list] foreach path [lsort [array names file_states]] { set s $file_states($path) set m [lindex $s 0] - set w [mapcol $m $path] - lappend file_lists($w) $path - set lno [expr {[lindex [split [$w index end] .] 0] - 1}] - $w image create end \ - -align center -padx 5 -pady 1 \ - -name [lindex $s 1] \ - -image [mapicon $m $path] - $w insert end "[escape_path $path]\n" - if {[catch {set in_sel $selected_paths($path)}]} { - set in_sel 0 + set icon_name [lindex $s 1] + + set s [string index $m 0] + if {$s ne {U} && $s ne {_}} { + display_all_files_helper $ui_index $path \ + $icon_name $s + } + + if {[string index $m 0] eq {U}} { + set s U + } else { + set s [string index $m 1] } - if {$in_sel} { - $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {$s ne {_}} { + display_all_files_helper $ui_workdir $path \ + $icon_name $s } } $ui_index conf -state disabled - $ui_other conf -state disabled + $ui_workdir conf -state disabled } proc update_indexinfo {msg pathList after} { @@ -1411,7 +1440,7 @@ proc update_indexinfo {msg pathList after} { proc write_update_indexinfo {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -1437,10 +1466,7 @@ proc write_update_indexinfo {fd pathList totalCnt batch msg after} { set info [lindex $s 2] if {$info eq {}} continue - puts -nonewline $fd $info - puts -nonewline $fd "\t" - puts -nonewline $fd $path - puts -nonewline $fd "\0" + puts -nonewline $fd "$info\t$path\0" display_file $path $new } @@ -1486,7 +1512,7 @@ proc update_index {msg pathList after} { proc write_update_index {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -1502,26 +1528,21 @@ proc write_update_index {fd pathList totalCnt batch msg after} { incr update_index_cp switch -glob -- [lindex $file_states($path) 0] { - AD - - MD - - UD - - _D {set new DD} - - _M - - MM - - UM - - U_ - - M_ {set new M_} - + AD {set new __} + ?D {set new D_} _O - - AM - - A_ {set new A_} - + AM {set new A_} + U? { + if {[file exists $path]} { + set new M_ + } else { + set new D_ + } + } + ?M {set new M_} ?? {continue} } - - puts -nonewline $fd $path - puts -nonewline $fd "\0" + puts -nonewline $fd "$path\0" display_file $path $new } @@ -1573,7 +1594,7 @@ proc checkout_index {msg pathList after} { proc write_checkout_index {fd pathList totalCnt batch msg after} { global update_index_cp ui_status_value - global file_states current_diff + global file_states current_diff_path if {$update_index_cp >= $totalCnt} { close $fd @@ -1587,20 +1608,14 @@ proc write_checkout_index {fd pathList totalCnt batch msg after} { {incr i -1} { set path [lindex $pathList $update_index_cp] incr update_index_cp - switch -glob -- [lindex $file_states($path) 0] { - AM - - AD {set new A_} - MM - - MD {set new M_} - _M - - _D {set new __} - ?? {continue} + U? {continue} + ?M - + ?D { + puts -nonewline $fd "$path\0" + display_file $path ?_ + } } - - puts -nonewline $fd $path - puts -nonewline $fd "\0" - display_file $path $new } set ui_status_value [format \ @@ -1632,9 +1647,25 @@ proc load_all_heads {} { set all_heads [lsort $all_heads] } -proc populate_branch_menu {m} { +proc populate_branch_menu {} { global all_heads disable_on_lock + set m .mbar.branch + set last [$m index last] + for {set i 0} {$i <= $last} {incr i} { + if {[$m type $i] eq {separator}} { + $m delete $i last + set new_dol [list] + foreach a $disable_on_lock { + if {[lindex $a 0] ne $m || [lindex $a 2] < $i} { + lappend new_dol $a + } + } + set disable_on_lock $new_dol + break + } + } + $m add separator foreach b $all_heads { $m add radiobutton \ @@ -1648,12 +1679,368 @@ proc populate_branch_menu {m} { } } +proc all_tracking_branches {} { + global tracking_branches + + set all_trackings [list] + foreach b [array names tracking_branches] { + regsub ^refs/(heads|remotes)/ $b {} b + lappend all_trackings $b + } + return [lsort -unique $all_trackings] +} + +proc do_create_branch_action {w} { + global all_heads null_sha1 + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + + set newbranch [string trim [$w.desc.name_t get 0.0 end]] + if {![catch {exec git show-ref --verify -- "refs/heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Branch '$newbranch' already exists." + focus $w.desc.name_t + return + } + if {[catch {exec git check-ref-format "heads/$newbranch"}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "We do not like '$newbranch' as a branch name." + focus $w.desc.name_t + return + } + + set rev {} + switch -- $create_branch_revtype { + head {set rev $create_branch_head} + tracking {set rev $create_branch_trackinghead} + expression {set rev [string trim [$w.from.exp_t get 0.0 end]]} + } + if {[catch {set cmt [exec git rev-parse --verify "${rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid starting revision: $rev" + return + } + set cmd [list git update-ref] + lappend cmd -m + lappend cmd "branch: Created from $rev" + lappend cmd "refs/heads/$newbranch" + lappend cmd $cmt + lappend cmd $null_sha1 + if {[catch {eval exec $cmd} err]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to create '$newbranch'.\n\n$err" + return + } + + lappend all_heads $newbranch + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w + if {$create_branch_checkout} { + switch_branch $newbranch + } +} + proc do_create_branch {} { - error "NOT IMPLEMENTED" + global all_heads current_branch + global create_branch_checkout create_branch_revtype + global create_branch_head create_branch_trackinghead + + set create_branch_checkout 1 + set create_branch_revtype head + set create_branch_head $current_branch + set create_branch_trackinghead {} + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Create New Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Create \ + -font font_ui \ + -default active \ + -command [list do_create_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -font font_ui \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.desc \ + -text {Branch Description} \ + -font font_ui + label $w.desc.name_l -text {Name:} -font font_ui + text $w.desc.name_t \ + -borderwidth 1 \ + -relief sunken \ + -height 1 \ + -width 40 \ + -font font_ui + grid $w.desc.name_l $w.desc.name_t -stick we -padx {0 5} + bind $w.desc.name_t "focus $w.postActions.checkout;break" + bind $w.desc.name_t "focus $w.from.exp_t;break" + bind $w.desc.name_t "do_create_branch_action $w;break" + bind $w.desc.name_t { + if {{%K} ne {BackSpace} + && {%K} ne {Tab} + && {%K} ne {Escape} + && {%K} ne {Return}} { + if {%k <= 32} break + if {[string first %A {~^:?*[}] >= 0} break + } + } + grid columnconfigure $w.desc 1 -weight 1 + pack $w.desc -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.from \ + -text {Starting Revision} \ + -font font_ui + radiobutton $w.from.head_r \ + -text {Local Branch:} \ + -value head \ + -variable create_branch_revtype \ + -font font_ui + eval tk_optionMenu $w.from.head_m create_branch_head $all_heads + grid $w.from.head_r $w.from.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set create_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.from.tracking_r \ + -text {Tracking Branch:} \ + -value tracking \ + -variable create_branch_revtype \ + -font font_ui + eval tk_optionMenu $w.from.tracking_m \ + create_branch_trackinghead \ + $all_trackings + grid $w.from.tracking_r $w.from.tracking_m -sticky w + } + radiobutton $w.from.exp_r \ + -text {Revision Expression:} \ + -value expression \ + -variable create_branch_revtype \ + -font font_ui + text $w.from.exp_t \ + -borderwidth 1 \ + -relief sunken \ + -height 1 \ + -width 50 \ + -font font_ui + grid $w.from.exp_r $w.from.exp_t -stick we -padx {0 5} + bind $w.from.exp_t "focus $w.desc.name_t;break" + bind $w.from.exp_t "focus $w.postActions.checkout;break" + bind $w.from.exp_t "do_create_branch_action $w;break" + grid columnconfigure $w.from 1 -weight 1 + pack $w.from -anchor nw -fill x -pady 5 -padx 5 + + labelframe $w.postActions \ + -text {Post Creation Actions} \ + -font font_ui + checkbutton $w.postActions.checkout \ + -text {Checkout after creation} \ + -variable create_branch_checkout \ + -font font_ui + pack $w.postActions.checkout -anchor nw + pack $w.postActions -anchor nw -fill x -pady 5 -padx 5 + + bind $w "grab $w; focus $w.desc.name_t" + bind $w "destroy $w" + bind $w "do_create_branch_action $w;break" + wm title $w "[appname] ([reponame]): Create Branch" + tkwait window $w +} + +proc do_delete_branch_action {w} { + global all_heads + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set check_rev {} + switch -- $delete_branch_checktype { + head {set check_rev $delete_branch_head} + tracking {set check_rev $delete_branch_trackinghead} + always {set check_rev {:none}} + } + if {$check_rev eq {:none}} { + set check_cmt {} + } elseif {[catch {set check_cmt [exec git rev-parse --verify "${check_rev}^0"]}]} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Invalid check revision: $check_rev" + return + } + + set to_delete [list] + set not_merged [list] + foreach i [$w.list.l curselection] { + set b [$w.list.l get $i] + if {[catch {set o [exec git rev-parse --verify $b]}]} continue + if {$check_cmt ne {}} { + if {$b eq $check_rev} continue + if {[catch {set m [exec git merge-base $o $check_cmt]}]} continue + if {$o ne $m} { + lappend not_merged $b + continue + } + } + lappend to_delete [list $b $o] + } + if {$not_merged ne {}} { + set msg "The following branches are not completely merged into $check_rev: + + - [join $not_merged "\n - "]" + tk_messageBox \ + -icon info \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message $msg + } + if {$to_delete eq {}} return + if {$delete_branch_checktype eq {always}} { + set msg {Recovering deleted branches is difficult. + +Delete the selected branches?} + if {[tk_messageBox \ + -icon warning \ + -type yesno \ + -title [wm title $w] \ + -parent $w \ + -message $msg] ne yes} { + return + } + } + + set failed {} + foreach i $to_delete { + set b [lindex $i 0] + set o [lindex $i 1] + if {[catch {exec git update-ref -d "refs/heads/$b" $o} err]} { + append failed " - $b: $err\n" + } else { + set x [lsearch -sorted $all_heads $b] + if {$x >= 0} { + set all_heads [lreplace $all_heads $x $x] + } + } + } + + if {$failed ne {}} { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Failed to delete branches:\n$failed" + } + + set all_heads [lsort $all_heads] + populate_branch_menu + destroy $w } proc do_delete_branch {} { - error "NOT IMPLEMENTED" + global all_heads tracking_branches current_branch + global delete_branch_checktype delete_branch_head delete_branch_trackinghead + + set delete_branch_checktype head + set delete_branch_head $current_branch + set delete_branch_trackinghead {} + + set w .branch_editor + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Delete Local Branch} \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.create -text Delete \ + -font font_ui \ + -command [list do_delete_branch_action $w] + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} \ + -font font_ui \ + -command [list destroy $w] + pack $w.buttons.cancel -side right -padx 5 + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + labelframe $w.list \ + -text {Local Branches} \ + -font font_ui + listbox $w.list.l \ + -height 10 \ + -width 50 \ + -selectmode extended \ + -font font_ui + foreach h $all_heads { + if {$h ne $current_branch} { + $w.list.l insert end $h + } + } + pack $w.list.l -fill both -pady 5 -padx 5 + pack $w.list -fill both -pady 5 -padx 5 + + labelframe $w.validate \ + -text {Delete Only If} \ + -font font_ui + radiobutton $w.validate.head_r \ + -text {Merged Into Local Branch:} \ + -value head \ + -variable delete_branch_checktype \ + -font font_ui + eval tk_optionMenu $w.validate.head_m delete_branch_head $all_heads + grid $w.validate.head_r $w.validate.head_m -sticky w + set all_trackings [all_tracking_branches] + if {$all_trackings ne {}} { + set delete_branch_trackinghead [lindex $all_trackings 0] + radiobutton $w.validate.tracking_r \ + -text {Merged Into Tracking Branch:} \ + -value tracking \ + -variable delete_branch_checktype \ + -font font_ui + eval tk_optionMenu $w.validate.tracking_m \ + delete_branch_trackinghead \ + $all_trackings + grid $w.validate.tracking_r $w.validate.tracking_m -sticky w + } + radiobutton $w.validate.always_r \ + -text {Always (Do not perform merge checks)} \ + -value always \ + -variable delete_branch_checktype \ + -font font_ui + grid $w.validate.always_r -columnspan 2 -sticky w + grid columnconfigure $w.validate 1 -weight 1 + pack $w.validate -anchor nw -fill x -pady 5 -padx 5 + + bind $w "grab $w; focus $w" + bind $w "destroy $w" + wm title $w "[appname] ([reponame]): Delete Branch" + tkwait window $w } proc switch_branch {b} { @@ -1706,13 +2093,13 @@ The rescan will be automatically started now. ## remote management proc load_all_remotes {} { - global gitdir repo_config + global repo_config global all_remotes tracking_branches set all_remotes [list] array unset tracking_branches - set rm_dir [file join $gitdir remotes] + set rm_dir [gitdir remotes] if {[file isdirectory $rm_dir]} { set all_remotes [glob \ -types f \ @@ -1756,7 +2143,7 @@ proc load_all_remotes {} { } proc populate_fetch_menu {m} { - global gitdir all_remotes repo_config + global all_remotes repo_config foreach r $all_remotes { set enable 0 @@ -1766,7 +2153,7 @@ proc populate_fetch_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $r] r] + set fd [open [gitdir remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1787,7 +2174,7 @@ proc populate_fetch_menu {m} { } proc populate_push_menu {m} { - global gitdir all_remotes repo_config + global all_remotes repo_config foreach r $all_remotes { set enable 0 @@ -1797,7 +2184,7 @@ proc populate_push_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $r] r] + set fd [open [gitdir remotes $r] r] while {[gets $fd n] >= 0} { if {[regexp {^Push:[ \t]*([^:]+):} $n]} { set enable 1 @@ -1818,7 +2205,7 @@ proc populate_push_menu {m} { } proc populate_pull_menu {m} { - global gitdir repo_config all_remotes disable_on_lock + global repo_config all_remotes disable_on_lock foreach remote $all_remotes { set rb_list [list] @@ -1832,7 +2219,7 @@ proc populate_pull_menu {m} { } } else { catch { - set fd [open [file join $gitdir remotes $remote] r] + set fd [open [gitdir remotes $remote] r] while {[gets $fd line] >= 0} { if {[regexp {^Pull:[ \t]*([^:]+):} $line line rb]} { lappend rb_list $rb @@ -1931,42 +2318,49 @@ static unsigned char file_merge_bits[] = { } -maskdata $filemask set ui_index .vpane.files.index.list -set ui_other .vpane.files.other.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(M$ui_index) file_fulltick +set all_icons(D$ui_index) file_removed +set all_icons(U$ui_index) file_merge + +set all_icons(_$ui_workdir) file_plain +set all_icons(M$ui_workdir) file_mod +set all_icons(D$ui_workdir) file_question +set all_icons(U$ui_workdir) file_merge +set all_icons(O$ui_workdir) file_plain + set max_status_desc 0 foreach i { - {__ i plain "Unmodified"} - {_M i mod "Modified"} - {M_ i fulltick "Added to commit"} - {MM i parttick "Partially included"} - {MD i question "Added (but gone)"} - - {_O o plain "Untracked"} - {A_ o fulltick "Added by commit"} - {AM o parttick "Partially added"} - {AD o question "Added (but gone)"} - - {_D i question "Missing"} - {DD i removed "Removed by commit"} - {D_ i removed "Removed by commit"} - {DO i removed "Removed (still exists)"} - {DM i removed "Removed (but modified)"} - - {UD i merge "Merge conflicts"} - {UM i merge "Merge conflicts"} - {U_ i merge "Merge conflicts"} + {__ "Unmodified"} + + {_M "Modified, not staged"} + {M_ "Staged for commit"} + {MM "Portions staged for commit"} + {MD "Staged for commit, missing"} + + {_O "Untracked, not staged"} + {A_ "Staged for commit"} + {AM "Portions staged for commit"} + {AD "Staged for commit, missing"} + + {_D "Missing"} + {D_ "Staged for removal"} + {DO "Staged for removal, still present"} + + {U_ "Requires merge resolution"} + {UU "Requires merge resolution"} + {UM "Requires merge resolution"} + {UD "Requires merge resolution"} } { - if {$max_status_desc < [string length [lindex $i 3]]} { - set max_status_desc [string length [lindex $i 3]] + if {$max_status_desc < [string length [lindex $i 1]]} { + set max_status_desc [string length [lindex $i 1]] } - if {[lindex $i 1] eq {i}} { - set all_cols([lindex $i 0]) $ui_index - } else { - set all_cols([lindex $i 0]) $ui_other - } - set all_icons([lindex $i 0]) file_[lindex $i 2] - set all_descs([lindex $i 0]) [lindex $i 3] + set all_descs([lindex $i 0]) [lindex $i 1] } -unset filemask i +unset i ###################################################################### ## @@ -2003,8 +2397,6 @@ proc incr_font_size {font {amt 1}} { } proc hook_failed_popup {hook msg} { - global gitdir appname - set w .hookfail toplevel $w @@ -2042,9 +2434,7 @@ proc hook_failed_popup {hook msg} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "$appname ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): error" + wm title $w "[appname] ([reponame]): error" tkwait window $w } @@ -2058,8 +2448,7 @@ proc new_console {short_title long_title} { } proc console_init {w} { - global console_cr console_data - global gitdir appname M1B + global console_cr console_data M1B set console_cr($w) 1.0 toplevel $w @@ -2111,9 +2500,7 @@ proc console_init {w} { bind $w.m.t <$M1B-Key-a> "$w.m.t tag add sel 0.0 end;break" bind $w.m.t <$M1B-Key-A> "$w.m.t tag add sel 0.0 end;break" bind $w "focus $w" - wm title $w "$appname ([lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end]): [lindex $console_data($w) 0]" + wm title $w "[appname] ([reponame]): [lindex $console_data($w) 0]" return $w } @@ -2193,7 +2580,7 @@ proc console_read {w fd after} { ## ## ui commands -set starting_gitk_msg {Please wait... Starting gitk...} +set starting_gitk_msg {Starting gitk... please wait...} proc do_gitk {revs} { global ui_status_value starting_gitk_msg @@ -2238,14 +2625,14 @@ proc do_fsck_objects {} { set is_quitting 0 proc do_quit {} { - global gitdir ui_comm is_quitting repo_config commit_type + global ui_comm is_quitting repo_config commit_type if {$is_quitting} return set is_quitting 1 # -- Stash our current commit buffer. # - set save [file join $gitdir GITGUI_MSG] + set save [gitdir GITGUI_MSG] set msg [string trim [$ui_comm get 0.0 end]] if {![string match amend* $commit_type] && [$ui_comm edit modified] @@ -2279,8 +2666,8 @@ proc do_rescan {} { rescan {set ui_status_value {Ready.}} } -proc remove_helper {txt paths} { - global file_states current_diff +proc unstage_helper {txt paths} { + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2292,7 +2679,7 @@ proc remove_helper {txt paths} { M? - D? { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2308,22 +2695,22 @@ proc remove_helper {txt paths} { } } -proc do_remove_selection {} { - global current_diff selected_paths +proc do_unstage_selection {} { + global current_diff_path selected_paths if {[array size selected_paths] > 0} { - remove_helper \ - {Removing selected files from commit} \ + unstage_helper \ + {Unstaging selected files from commit} \ [array names selected_paths] - } elseif {$current_diff ne {}} { - remove_helper \ - "Removing [short_path $current_diff] from commit" \ - [list $current_diff] + } elseif {$current_diff_path ne {}} { + unstage_helper \ + "Unstaging [short_path $current_diff_path] from commit" \ + [list $current_diff_path] } } -proc include_helper {txt paths} { - global file_states current_diff +proc add_helper {txt paths} { + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2331,16 +2718,12 @@ proc include_helper {txt paths} { set after {} foreach path $paths { switch -glob -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - U? - - _M - - _D - - _O { + _O - + ?M - + ?D - + U? { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2356,42 +2739,36 @@ proc include_helper {txt paths} { } } -proc do_include_selection {} { - global current_diff selected_paths +proc do_add_selection {} { + global current_diff_path selected_paths if {[array size selected_paths] > 0} { - include_helper \ + add_helper \ {Adding selected files} \ [array names selected_paths] - } elseif {$current_diff ne {}} { - include_helper \ - "Adding [short_path $current_diff]" \ - [list $current_diff] + } elseif {$current_diff_path ne {}} { + add_helper \ + "Adding [short_path $current_diff_path]" \ + [list $current_diff_path] } } -proc do_include_all {} { +proc do_add_all {} { global file_states set paths [list] foreach path [array names file_states] { - switch -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - _M - - _D {lappend paths $path} + switch -glob -- [lindex $file_states($path) 0] { + U? {continue} + ?M - + ?D {lappend paths $path} } } - include_helper \ - {Adding all modified files} \ - $paths + add_helper {Adding all changed files} $paths } proc revert_helper {txt paths} { - global gitdir appname - global file_states current_diff + global file_states current_diff_path if {![lock_index begin-update]} return @@ -2399,14 +2776,11 @@ proc revert_helper {txt paths} { set after {} foreach path $paths { switch -glob -- [lindex $file_states($path) 0] { - AM - - AD - - MM - - MD - - _M - - _D { + U? {continue} + ?M - + ?D { lappend pathList $path - if {$path eq $current_diff} { + if {$path eq $current_diff_path} { set after {reshow_diff;} } } @@ -2423,13 +2797,9 @@ proc revert_helper {txt paths} { set s "these $n files" } - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] - set reply [tk_dialog \ .confirm_revert \ - "$appname ($reponame)" \ + "[appname] ([reponame])" \ "Revert changes in $s? Any unadded changes will be permanently lost by the revert." \ @@ -2449,16 +2819,16 @@ Any unadded changes will be permanently lost by the revert." \ } proc do_revert_selection {} { - global current_diff selected_paths + global current_diff_path selected_paths if {[array size selected_paths] > 0} { revert_helper \ {Reverting selected files} \ [array names selected_paths] - } elseif {$current_diff ne {}} { + } elseif {$current_diff_path ne {}} { revert_helper \ - "Reverting [short_path $current_diff]" \ - [list $current_diff] + "Reverting [short_path $current_diff_path]" \ + [list $current_diff_path] } } @@ -2505,14 +2875,14 @@ proc do_commit {} { } proc do_about {} { - global appname copyright + global appvers copyright global tcl_patchLevel tk_patchLevel set w .about_dialog toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text "About $appname" \ + label $w.header -text "About [appname]" \ -font font_uibold pack $w.header -side top -fill x @@ -2524,7 +2894,7 @@ proc do_about {} { pack $w.buttons -side bottom -fill x -pady 10 -padx 10 label $w.desc \ - -text "$appname - a commit creation tool for Git. + -text "[appname] - a commit creation tool for Git. $copyright" \ -padx 5 -pady 5 \ -justify left \ @@ -2534,8 +2904,10 @@ $copyright" \ -font font_ui pack $w.desc -side top -fill x -padx 5 -pady 5 - set v [exec git --version] - append v "\n\n" + set v {} + append v "[appname] version $appvers\n" + append v "[exec git version]\n" + append v "\n" if {$tcl_patchLevel eq $tk_patchLevel} { append v "Tcl/Tk version $tcl_patchLevel" } else { @@ -2553,15 +2925,24 @@ $copyright" \ -font font_ui pack $w.vers -side top -fill x -padx 5 -pady 5 + menu $w.ctxm -tearoff 0 + $w.ctxm add command \ + -label {Copy} \ + -font font_ui \ + -command " + clipboard clear + clipboard append -format STRING -type STRING -- \[$w.vers cget -text\] + " + bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "About $appname" + bind_button3 $w.vers "tk_popup $w.ctxm %X %Y; grab $w; focus $w" + wm title $w "About [appname]" tkwait window $w } proc do_options {} { - global appname gitdir font_descs - global repo_config global_config + global repo_config global_config font_descs global repo_config_new global_config_new array unset repo_config_new @@ -2579,15 +2960,12 @@ proc do_options {} { foreach name [array names global_config] { set global_config_new($name) $global_config($name) } - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] set w .options_editor toplevel $w wm geometry $w "+[winfo rootx .]+[winfo rooty .]" - label $w.header -text "$appname Options" \ + label $w.header -text "[appname] Options" \ -font font_uibold pack $w.header -side top -fill x @@ -2603,10 +2981,10 @@ proc do_options {} { button $w.buttons.cancel -text {Cancel} \ -font font_ui \ -command [list destroy $w] - pack $w.buttons.cancel -side right + pack $w.buttons.cancel -side right -padx 5 pack $w.buttons -side bottom -fill x -pady 10 -padx 10 - labelframe $w.repo -text "$reponame Repository" \ + labelframe $w.repo -text "[reponame] Repository" \ -font font_ui \ -relief raised -borderwidth 2 labelframe $w.global -text {Global (All Repositories)} \ @@ -2616,7 +2994,6 @@ proc do_options {} { pack $w.global -side right -fill both -expand 1 -pady 5 -padx 5 foreach option { - {b partialinclude {Allow Partially Added Files}} {b pullsummary {Show Pull Summary}} {b trustmtime {Trust File Modification Timestamps}} {i diffcontext {Number of Diff Context Lines}} @@ -2679,7 +3056,7 @@ proc do_options {} { bind $w "grab $w; focus $w" bind $w "destroy $w" - wm title $w "$appname ($reponame): Options" + wm title $w "[appname] ([reponame]): Options" tkwait window $w } @@ -2717,11 +3094,7 @@ proc do_save_config {w} { } proc do_windows_shortcut {} { - global gitdir appname argv0 - - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] + global argv0 if {[catch { set desktop [exec cygpath \ @@ -2734,9 +3107,9 @@ proc do_windows_shortcut {} { } set fn [tk_getSaveFile \ -parent . \ - -title "$appname ($reponame): Create Desktop Icon" \ + -title "[appname] ([reponame]): Create Desktop Icon" \ -initialdir $desktop \ - -initialfile "Git $reponame.bat"] + -initialfile "Git [reponame].bat"] if {$fn != {}} { if {[catch { set fd [open $fn w] @@ -2751,10 +3124,15 @@ proc do_windows_shortcut {} { set gd [exec cygpath \ --unix \ --absolute \ - $gitdir] + [gitdir]] + set gw [exec cygpath \ + --windows \ + --absolute \ + [file dirname [gitdir]]] regsub -all ' $me "'\\''" me regsub -all ' $gd "'\\''" gd - puts $fd "@ECHO Starting git-gui... Please wait..." + puts $fd "@ECHO Entering $gw" + puts $fd "@ECHO Starting git-gui... please wait..." puts -nonewline $fd "@\"$sh\" --login -c \"" puts -nonewline $fd "GIT_DIR='$gd'" puts -nonewline $fd " '$me'" @@ -2767,17 +3145,13 @@ proc do_windows_shortcut {} { } proc do_macosx_app {} { - global gitdir appname argv0 env - - set reponame [lindex [file split \ - [file normalize [file dirname $gitdir]]] \ - end] + global argv0 env set fn [tk_getSaveFile \ -parent . \ - -title "$appname ($reponame): Create Desktop Icon" \ + -title "[appname] ([reponame]): Create Desktop Icon" \ -initialdir [file join $env(HOME) Desktop] \ - -initialfile "Git $reponame.app"] + -initialfile "Git [reponame].app"] if {$fn != {}} { if {[catch { set Contents [file join $fn Contents] @@ -2812,7 +3186,7 @@ proc do_macosx_app {} { close $fd set fd [open $exe w] - set gd [file normalize $gitdir] + set gd [file normalize [gitdir]] set ep [file normalize [exec git --exec-path]] regsub -all ' $gd "'\\''" gd regsub -all ' $ep "'\\''" ep @@ -2836,7 +3210,7 @@ proc do_macosx_app {} { } proc toggle_or_diff {w x y} { - global file_states file_lists current_diff ui_index ui_other + global file_states file_lists current_diff_path ui_index ui_workdir global last_clicked selected_paths set pos [split [$w index @$x,$y] .] @@ -2851,50 +3225,46 @@ proc toggle_or_diff {w x y} { set last_clicked [list $w $lno] array unset selected_paths $ui_index tag remove in_sel 0.0 end - $ui_other tag remove in_sel 0.0 end + $ui_workdir tag remove in_sel 0.0 end if {$col == 0} { - if {$current_diff eq $path} { + if {$current_diff_path eq $path} { set after {reshow_diff;} } else { set after {} } - switch -glob -- [lindex $file_states($path) 0] { - A_ - - M_ - - DD - - DO - - DM { + if {$w eq $ui_index} { update_indexinfo \ - "Removing [short_path $path] from commit" \ + "Unstaging [short_path $path] from commit" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] - } - ?? { + } elseif {$w eq $ui_workdir} { update_index \ "Adding [short_path $path]" \ [list $path] \ [concat $after {set ui_status_value {Ready.}}] } - } } else { show_diff $path $w $lno } } proc add_one_to_selection {w x y} { - global file_lists - global last_clicked selected_paths + global file_lists last_clicked selected_paths - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] - set col [lindex $pos 1] + set lno [lindex [split [$w index @$x,$y] .] 0] set path [lindex $file_lists($w) [expr {$lno - 1}]] if {$path eq {}} { set last_clicked {} return } + if {$last_clicked ne {} + && [lindex $last_clicked 0] ne $w} { + array unset selected_paths + [lindex $last_clicked 0] tag remove in_sel 0.0 end + } + set last_clicked [list $w $lno] if {[catch {set in_sel $selected_paths($path)}]} { set in_sel 0 @@ -2909,16 +3279,14 @@ proc add_one_to_selection {w x y} { } proc add_range_to_selection {w x y} { - global file_lists - global last_clicked selected_paths + global file_lists last_clicked selected_paths if {[lindex $last_clicked 0] ne $w} { toggle_or_diff $w $x $y return } - set pos [split [$w index @$x,$y] .] - set lno [lindex $pos 0] + set lno [lindex [split [$w index @$x,$y] .] 0] set lc [lindex $last_clicked 1] if {$lc < $lno} { set begin $lc @@ -2985,7 +3353,6 @@ proc apply_config {} { set default_config(gui.trustmtime) false set default_config(gui.pullsummary) true -set default_config(gui.partialinclude) false set default_config(gui.diffcontext) 5 set default_config(gui.fontui) [font configure font_ui] set default_config(gui.fontdiff) [font configure font_diff] @@ -3101,6 +3468,7 @@ if {!$single_commit} { .mbar.branch add command -label {Create...} \ -command do_create_branch \ + -accelerator $M1T-N \ -font font_ui lappend disable_on_lock [list .mbar.branch entryconf \ [.mbar.branch index last] -state] @@ -3144,20 +3512,20 @@ lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add To Commit} \ - -command do_include_selection \ + -command do_add_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] .mbar.commit add command -label {Add All To Commit} \ - -command do_include_all \ + -command do_add_all \ -accelerator $M1T-I \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] -.mbar.commit add command -label {Remove From Commit} \ - -command do_remove_selection \ +.mbar.commit add command -label {Unstage From Commit} \ + -command do_unstage_selection \ -font font_ui lappend disable_on_lock \ [list .mbar.commit entryconf [.mbar.commit index last] -state] @@ -3196,10 +3564,10 @@ if {[is_MacOSX]} { .mbar add cascade -label Apple -menu .mbar.apple menu .mbar.apple - .mbar.apple add command -label "About $appname" \ + .mbar.apple add command -label "About [appname]" \ -command do_about \ -font font_ui - .mbar.apple add command -label "$appname Options..." \ + .mbar.apple add command -label "[appname] Options..." \ -command do_options \ -font font_ui } else { @@ -3212,9 +3580,10 @@ if {[is_MacOSX]} { # -- Tools Menu # - if {[file exists /usr/local/miga/lib/gui-miga]} { + if {[file exists /usr/local/miga/lib/gui-miga] + && [file exists .pvcsrc]} { proc do_miga {} { - global gitdir ui_status_value + 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] @@ -3244,7 +3613,7 @@ if {[is_MacOSX]} { .mbar add cascade -label Help -menu .mbar.help menu .mbar.help - .mbar.help add command -label "About $appname" \ + .mbar.help add command -label "About [appname]" \ -command do_about \ -font font_ui } @@ -3279,40 +3648,48 @@ pack .vpane -anchor n -side top -fill both -expand 1 # -- Index File List # frame .vpane.files.index -height 100 -width 400 -label .vpane.files.index.title -text {Modified Files} \ +label .vpane.files.index.title -text {Changes To Be Committed} \ -background green \ -font font_ui text $ui_index -background white -borderwidth 0 \ -width 40 -height 10 \ + -wrap none \ -font font_ui \ -cursor $cursor_ptr \ - -yscrollcommand {.vpane.files.index.sb set} \ + -xscrollcommand {.vpane.files.index.sx set} \ + -yscrollcommand {.vpane.files.index.sy set} \ -state disabled -scrollbar .vpane.files.index.sb -command [list $ui_index yview] +scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +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.sb -side right -fill y +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 .vpane.files add .vpane.files.index -sticky nsew -# -- Other (Add) File List +# -- Working Directory File List # -frame .vpane.files.other -height 100 -width 100 -label .vpane.files.other.title -text {Untracked Files} \ +frame .vpane.files.workdir -height 100 -width 100 +label .vpane.files.workdir.title -text {Changed But Not Updated} \ -background red \ -font font_ui -text $ui_other -background white -borderwidth 0 \ +text $ui_workdir -background white -borderwidth 0 \ -width 40 -height 10 \ + -wrap none \ -font font_ui \ -cursor $cursor_ptr \ - -yscrollcommand {.vpane.files.other.sb set} \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ -state disabled -scrollbar .vpane.files.other.sb -command [list $ui_other yview] -pack .vpane.files.other.title -side top -fill x -pack .vpane.files.other.sb -side right -fill y -pack $ui_other -side left -fill both -expand 1 -.vpane.files add .vpane.files.other -sticky nsew - -foreach i [list $ui_index $ui_other] { +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 + +foreach i [list $ui_index $ui_workdir] { $i tag conf in_diff -font font_uibold $i tag conf in_sel \ -background [$i cget -foreground] \ @@ -3347,7 +3724,7 @@ lappend disable_on_lock \ {.vpane.lower.commarea.buttons.rescan conf -state} button .vpane.lower.commarea.buttons.incall -text {Add All} \ - -command do_include_all \ + -command do_add_all \ -font font_ui pack .vpane.lower.commarea.buttons.incall -side top -fill x lappend disable_on_lock \ @@ -3465,17 +3842,17 @@ bind_button3 $ui_comm "tk_popup $ctxm %X %Y" # -- Diff Header # -set current_diff {} +set current_diff_path {} set diff_actions [list] -proc trace_current_diff {varname args} { - global current_diff diff_actions file_states - if {$current_diff eq {}} { +proc trace_current_diff_path {varname args} { + global current_diff_path diff_actions file_states + if {$current_diff_path eq {}} { set s {} set f {} set p {} set o disabled } else { - set p $current_diff + set p $current_diff_path set s [mapdesc [lindex $file_states($p) 0] $p] set f {File:} set p [escape_path $p] @@ -3489,7 +3866,7 @@ proc trace_current_diff {varname args} { uplevel #0 $w $o } } -trace add variable current_diff write trace_current_diff +trace add variable current_diff_path write trace_current_diff_path frame .vpane.lower.diff.header -background orange label .vpane.lower.diff.header.status \ @@ -3521,7 +3898,7 @@ $ctxm add command \ clipboard append \ -format STRING \ -type STRING \ - -- $current_diff + -- $current_diff_path } lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] bind_button3 .vpane.lower.diff.header.path "tk_popup $ctxm %X %Y" @@ -3546,22 +3923,45 @@ 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 -$ui_diff tag conf d_@ -font font_diffbold -$ui_diff tag conf d_+ -foreground blue -$ui_diff tag conf d_- -foreground red +$ui_diff tag conf d_@ -foreground blue -font font_diffbold +$ui_diff tag conf d_+ -foreground {#00a000} +$ui_diff tag conf d_- -foreground red + $ui_diff tag conf d_++ -foreground {#00a000} -$ui_diff tag conf d_-- -foreground {#a000a0} -$ui_diff tag conf d_+- \ +$ui_diff tag conf d_-- -foreground red +$ui_diff tag conf d_+s \ + -foreground {#00a000} \ + -background {#e2effa} +$ui_diff tag conf d_-s \ -foreground red \ - -background {light goldenrod yellow} -$ui_diff tag conf d_-+ \ - -foreground blue \ - -background azure2 + -background {#e2effa} +$ui_diff tag conf d_s+ \ + -foreground {#00a000} \ + -background ivory1 +$ui_diff tag conf d_s- \ + -foreground red \ + -background ivory1 + +$ui_diff tag conf d<<<<<<< \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d======= \ + -foreground orange \ + -font font_diffbold +$ui_diff tag conf d>>>>>>> \ + -foreground orange \ + -font font_diffbold + +$ui_diff tag raise sel # -- Diff Body Context Menu # set ctxm .vpane.lower.diff.body.ctxm menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Refresh} \ + -font font_ui \ + -command reshow_diff $ctxm add command \ -label {Copy} \ -font font_ui \ @@ -3643,8 +4043,8 @@ unset gm # -- Key Bindings # bind $ui_comm <$M1B-Key-Return> {do_commit;break} -bind $ui_comm <$M1B-Key-i> {do_include_all;break} -bind $ui_comm <$M1B-Key-I> {do_include_all;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} bind $ui_comm <$M1B-Key-X> {tk_textCut %W;break} bind $ui_comm <$M1B-Key-c> {tk_textCopy %W;break} @@ -3667,20 +4067,25 @@ bind $ui_diff {catch {%W yview scroll 1 units};break} bind $ui_diff {catch {%W xview scroll -1 units};break} bind $ui_diff {catch {%W xview scroll 1 units};break} +if {!$single_commit} { + bind . <$M1B-Key-n> do_create_branch + bind . <$M1B-Key-N> do_create_branch +} + bind . do_quit bind all do_rescan bind all <$M1B-Key-r> do_rescan bind all <$M1B-Key-R> do_rescan bind . <$M1B-Key-s> do_signoff bind . <$M1B-Key-S> do_signoff -bind . <$M1B-Key-i> do_include_all -bind . <$M1B-Key-I> do_include_all +bind . <$M1B-Key-i> do_add_all +bind . <$M1B-Key-I> do_add_all bind . <$M1B-Key-Return> do_commit bind all <$M1B-Key-q> do_quit bind all <$M1B-Key-Q> do_quit bind all <$M1B-Key-w> {destroy [winfo toplevel %W]} bind all <$M1B-Key-W> {destroy [winfo toplevel %W]} -foreach i [list $ui_index $ui_other] { +foreach i [list $ui_index $ui_workdir] { bind $i "toggle_or_diff $i %x %y; break" bind $i <$M1B-Button-1> "add_one_to_selection $i %x %y; break" bind $i "add_range_to_selection $i %x %y; break" @@ -3688,7 +4093,7 @@ foreach i [list $ui_index $ui_other] { unset i set file_lists($ui_index) [list] -set file_lists($ui_other) [list] +set file_lists($ui_workdir) [list] set HEAD {} set PARENT {} @@ -3696,10 +4101,10 @@ set MERGE_HEAD [list] set commit_type {} set empty_tree {} set current_branch {} -set current_diff {} +set current_diff_path {} set selected_commit_type new -wm title . "$appname ([file normalize [file dirname $gitdir]])" +wm title . "[appname] ([file normalize [file dirname [gitdir]]])" focus -force $ui_comm # -- Warn the user about environmental problems. Cygwin's Tcl @@ -3713,7 +4118,7 @@ if {[is_Windows]} { The following environment variables are probably going to be ignored by any Git subprocess run -by $appname: +by [appname]: " foreach name [array names env] { @@ -3763,11 +4168,32 @@ if {!$single_commit} { load_all_remotes load_all_heads - populate_branch_menu .mbar.branch + populate_branch_menu populate_fetch_menu .mbar.fetch populate_pull_menu .mbar.pull populate_push_menu .mbar.push } +# -- Only suggest a gc run if we are going to stay running. +# +if {!$single_commit} { + set object_limit 2000 + if {[is_Windows]} {set object_limit 200} + regexp {^([0-9]+) objects,} [exec git count-objects] _junk objects_current + if {$objects_current >= $object_limit} { + if {[ask_popup \ + "This repository currently has $objects_current loose objects. + +To maintain optimal performance it is strongly +recommended that you compress the database +when more than $object_limit loose objects exist. + +Compress the database now?"] eq yes} { + do_gc + } + } + unset object_limit _junk objects_current +} + lock_index begin-read after 1 do_rescan