From: Junio C Hamano Date: Thu, 10 May 2007 22:08:18 +0000 (-0700) Subject: Merge branch 'master' of git://repo.or.cz/git-gui X-Git-Tag: v1.5.2-rc3~3 X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=2b93bfac0f5bcabbf60f174f4e7bfa9e318e64d5;p=git.git Merge branch 'master' of git://repo.or.cz/git-gui * 'master' of git://repo.or.cz/git-gui: git gui 0.7.0 git-gui: Paperbag fix blame in subdirectory git-gui: Format author/committer times in ISO format git-gui: Cleanup minor nits in blame code git-gui: Generate blame on uncommitted working tree file git-gui: Smarter command line parsing for browser, blame git-gui: Use prefix if blame is run in a subdirectory git-gui: Convert blame to the "class" way of doing things git-gui: Don't attempt to inline array reads in methods git-gui: Convert browser, console to "class" format git-gui: Define a simple class/method system git-gui: Allow shift-{k,j} to select a range of branches to merge git-gui: Call changes "Staged" and "Unstaged" in file list titles. --- 2b93bfac0f5bcabbf60f174f4e7bfa9e318e64d5 diff --cc git-gui/GIT-VERSION-GEN index 2741c1e14,000000000..25647c806 mode 100755,000000..100755 --- a/git-gui/GIT-VERSION-GEN +++ b/git-gui/GIT-VERSION-GEN @@@ -1,82 -1,0 +1,82 @@@ +#!/bin/sh + +GVF=GIT-VERSION-FILE - DEF_VER=0.6.GITGUI ++DEF_VER=0.7.GITGUI + +LF=' +' + +tree_search () +{ + head=$1 + tree=$2 + for p in $(git rev-list --parents --max-count=1 $head 2>/dev/null) + do + test $tree = $(git rev-parse $p^{tree} 2>/dev/null) && + vn=$(git describe --abbrev=4 $p 2>/dev/null) && + case "$vn" in + gitgui-[0-9]*) echo $vn; break;; + esac + done +} + +# Always use the tarball version file if found, just +# in case we are somehow contained in a larger git +# repository that doesn't actually track our state. +# (At least one package manager is doing this.) +# +# We may be a subproject, so try looking for the merge +# commit that supplied this directory content if we are +# not at the toplevel. We probably will always be the +# second parent in the commit, but we shouldn't rely on +# that fact. +# +# If we are at the toplevel or the merge assumption fails +# try looking for a gitgui-* tag. + +if test -f version && + VN=$(cat version) +then + : happy +elif prefix="$(git rev-parse --show-prefix 2>/dev/null)" + test -n "$prefix" && + head=$(git rev-list --max-count=1 HEAD -- . 2>/dev/null) && + tree=$(git rev-parse --verify "HEAD:$prefix" 2>/dev/null) && + VN=$(tree_search $head $tree) + case "$VN" in + gitgui-[0-9]*) : happy ;; + *) (exit 1) ;; + esac +then + VN=$(echo "$VN" | sed -e 's/^gitgui-//;s/-/./g'); +elif VN=$(git describe --abbrev=4 HEAD 2>/dev/null) && + case "$VN" in + gitgui-[0-9]*) : happy ;; + *) (exit 1) ;; + esac +then + VN=$(echo "$VN" | sed -e 's/^gitgui-//;s/-/./g'); +else + VN="$DEF_VER" +fi + +dirty=$(sh -c 'git diff-index --name-only HEAD' 2>/dev/null) || dirty= +case "$dirty" in +'') + ;; +*) + VN="$VN-dirty" ;; +esac + +if test -r $GVF +then + VC=$(sed -e 's/^GITGUI_VERSION = //' <$GVF) +else + VC=unset +fi +test "$VN" = "$VC" || { + echo >&2 "GITGUI_VERSION = $VN" + echo "GITGUI_VERSION = $VN" >$GVF +} + + diff --cc git-gui/Makefile index ba1e33ba8,000000000..e73b6453d mode 100644,000000..100644 --- a/git-gui/Makefile +++ b/git-gui/Makefile @@@ -1,105 -1,0 +1,108 @@@ +all:: + +# Define V=1 to have a more verbose compile. +# + +GIT-VERSION-FILE: .FORCE-GIT-VERSION-FILE + @$(SHELL_PATH) ./GIT-VERSION-GEN +-include GIT-VERSION-FILE + +SCRIPT_SH = git-gui.sh +GITGUI_BUILT_INS = git-citool +ALL_PROGRAMS = $(GITGUI_BUILT_INS) $(patsubst %.sh,%,$(SCRIPT_SH)) +ALL_LIBFILES = $(wildcard lib/*.tcl) + +ifndef SHELL_PATH + SHELL_PATH = /bin/sh +endif + +ifndef gitexecdir + gitexecdir := $(shell git --exec-path) +endif + +ifndef sharedir + sharedir := $(dir $(gitexecdir))/share +endif + +ifndef INSTALL + INSTALL = install +endif + +ifndef V + QUIET_GEN = @echo ' ' GEN $@; + QUIET_BUILT_IN = @echo ' ' BUILTIN $@; + QUIET_INDEX = @echo ' ' INDEX $(dir $@); +endif + +TCL_PATH ?= tclsh +TCLTK_PATH ?= wish + +ifeq ($(findstring $(MAKEFLAGS),s),s) +QUIET_GEN = +QUIET_BUILT_IN = +endif + +DESTDIR_SQ = $(subst ','\'',$(DESTDIR)) +gitexecdir_SQ = $(subst ','\'',$(gitexecdir)) +SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) +TCLTK_PATH_SQ = $(subst ','\'',$(TCLTK_PATH)) + +libdir ?= $(sharedir)/git-gui/lib +libdir_SQ = $(subst ','\'',$(libdir)) + +$(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh + $(QUIET_GEN)rm -f $@ $@+ && \ + sed -e '1s|#!.*/sh|#!$(SHELL_PATH_SQ)|' \ + -e 's|^exec wish "$$0"|exec $(subst |,'\|',$(TCLTK_PATH_SQ)) "$$0"|' \ + -e 's/@@GITGUI_VERSION@@/$(GITGUI_VERSION)/g' \ + -e 's|@@GITGUI_LIBDIR@@|$(libdir_SQ)|' \ + $@.sh >$@+ && \ + chmod +x $@+ && \ + mv $@+ $@ + +$(GITGUI_BUILT_INS): git-gui + $(QUIET_BUILT_IN)rm -f $@ && ln git-gui $@ + +lib/tclIndex: $(ALL_LIBFILES) - $(QUIET_INDEX)echo auto_mkindex lib '*.tcl' | $(TCL_PATH) ++ $(QUIET_INDEX)echo \ ++ source lib/class.tcl \; \ ++ auto_mkindex lib '*.tcl' \ ++ | $(TCL_PATH) + +# These can record GITGUI_VERSION +$(patsubst %.sh,%,$(SCRIPT_SH)): GIT-VERSION-FILE GIT-GUI-VARS + +TRACK_VARS = \ + $(subst ','\'',SHELL_PATH='$(SHELL_PATH_SQ)') \ + $(subst ','\'',TCLTK_PATH='$(TCLTK_PATH_SQ)') \ + $(subst ','\'',libdir='$(libdir_SQ)') \ +#end TRACK_VARS + +GIT-GUI-VARS: .FORCE-GIT-GUI-VARS + @VARS='$(TRACK_VARS)'; \ + if test x"$$VARS" != x"`cat $@ 2>/dev/null`" ; then \ + echo 1>&2 " * new locations or Tcl/Tk interpreter"; \ + echo 1>$@ "$$VARS"; \ + fi + +all:: $(ALL_PROGRAMS) lib/tclIndex + +install: all + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(INSTALL) git-gui '$(DESTDIR_SQ)$(gitexecdir_SQ)' + $(foreach p,$(GITGUI_BUILT_INS), rm -f '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' && ln '$(DESTDIR_SQ)$(gitexecdir_SQ)/git-gui' '$(DESTDIR_SQ)$(gitexecdir_SQ)/$p' ;) + $(INSTALL) -d -m755 '$(DESTDIR_SQ)$(libdir_SQ)' + $(INSTALL) -m644 lib/tclIndex '$(DESTDIR_SQ)$(libdir_SQ)' + $(foreach p,$(ALL_LIBFILES), $(INSTALL) -m644 $p '$(DESTDIR_SQ)$(libdir_SQ)' ;) + +dist-version: + @mkdir -p $(TARDIR) + @echo $(GITGUI_VERSION) > $(TARDIR)/version + +clean:: + rm -f $(ALL_PROGRAMS) lib/tclIndex + rm -f GIT-VERSION-FILE GIT-GUI-VARS + +.PHONY: all install dist-version clean +.PHONY: .FORCE-GIT-VERSION-FILE +.PHONY: .FORCE-GIT-GUI-VARS diff --cc git-gui/git-gui.sh index 46358258b,000000000..2fda4c229 mode 100755,000000..100755 --- a/git-gui/git-gui.sh +++ b/git-gui/git-gui.sh @@@ -1,2161 -1,0 +1,2209 @@@ +#!/bin/sh +# Tcl ignores the next line -*- tcl -*- \ +exec wish "$0" -- "$@" + +set appvers {@@GITGUI_VERSION@@} +set copyright { +Copyright © 2006, 2007 Shawn Pearce, et. al. + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, 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. + +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} + +###################################################################### +## +## configure our library + +set oguilib {@@GITGUI_LIBDIR@@} +if {[string match @@* $oguilib]} { + set oguilib [file join [file dirname [file normalize $argv0]] lib] +} +set auto_path [concat [list $oguilib] $auto_path] + +if {![catch {set _verbose $env(GITGUI_VERBOSE)}]} { + unset _verbose + rename auto_load real__auto_load + proc auto_load {name args} { + puts stderr "auto_load $name" + return [uplevel 1 real__auto_load $name $args] + } + rename source real__source + proc source {name} { + puts stderr "source $name" + uplevel 1 real__source $name + } +} + +###################################################################### +## +## read only globals + +set _appname [lindex [file split $argv0] end] +set _gitdir {} +set _gitexec {} +set _reponame {} +set _iscygwin {} + +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 gitexec {args} { + global _gitexec + if {$_gitexec eq {}} { + if {[catch {set _gitexec [git --exec-path]} err]} { + error "Git not installed?\n\n$err" + } + } + if {$args eq {}} { + return $_gitexec + } + return [eval [concat [list file join $_gitexec] $args]] +} + +proc reponame {} { + global _reponame + return $_reponame +} + +proc is_MacOSX {} { + global tcl_platform tk_library + if {[tk windowingsystem] eq {aqua}} { + return 1 + } + return 0 +} + +proc is_Windows {} { + global tcl_platform + if {$tcl_platform(platform) eq {windows}} { + return 1 + } + return 0 +} + +proc is_Cygwin {} { + global tcl_platform _iscygwin + if {$_iscygwin eq {}} { + if {$tcl_platform(platform) eq {windows}} { + if {[catch {set p [exec cygpath --windir]} err]} { + set _iscygwin 0 + } else { + set _iscygwin 1 + } + } else { + set _iscygwin 0 + } + } + return $_iscygwin +} + +proc is_enabled {option} { + global enabled_options + if {[catch {set on $enabled_options($option)}]} {return 0} + return $on +} + +proc enable_option {option} { + global enabled_options + set enabled_options($option) 1 +} + +proc disable_option {option} { + global enabled_options + set enabled_options($option) 0 +} + +###################################################################### +## +## config + +proc is_many_config {name} { + switch -glob -- $name { + remote.*.fetch - + remote.*.push + {return 1} + * + {return 0} + } +} + +proc is_config_true {name} { + global repo_config + if {[catch {set v $repo_config($name)}]} { + return 0 + } elseif {$v eq {true} || $v eq {1} || $v eq {yes}} { + return 1 + } else { + return 0 + } +} + +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] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend global_config($name) $value + } else { + set global_config($name) $value + } + } + } + close $fd_rc + } + } + + array unset repo_config + catch { + set fd_rc [open "| git config --list" r] + while {[gets $fd_rc line] >= 0} { + if {[regexp {^([^=]+)=(.*)$} $line line name value]} { + if {[is_many_config $name]} { + lappend repo_config($name) $value + } else { + set repo_config($name) $value + } + } + } + close $fd_rc + } + + foreach name [array names default_config] { + if {[catch {set v $global_config($name)}]} { + set global_config($name) $default_config($name) + } + if {[catch {set v $repo_config($name)}]} { + set repo_config($name) $default_config($name) + } + } +} + +###################################################################### +## +## handy utils + +proc git {args} { + return [eval exec git $args] +} + +auto_load tk_optionMenu +rename tk_optionMenu real__tkOptionMenu +proc tk_optionMenu {w varName args} { + set m [eval real__tkOptionMenu $w $varName $args] + $m configure -font font_ui + $w configure -font font_ui + return $m +} + +###################################################################### +## +## version check + +if {{--version} eq $argv || {version} eq $argv} { + puts "git-gui version $appvers" + exit +} + +set req_maj 1 +set req_min 5 + +if {[catch {set v [git --version]} err]} { + catch {wm withdraw .} + error_popup "Cannot determine Git version: + +$err + +[appname] requires Git $req_maj.$req_min 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. + +You are using $v." + exit 1 + } +} else { + catch {wm withdraw .} + error_popup "Cannot parse Git version string:\n\n$v" + exit 1 +} +unset -nocomplain v _junk act_maj act_min req_maj req_min + +###################################################################### +## +## repository setup + - if { [catch {set _gitdir $env(GIT_DIR)}] - && [catch {set _gitdir [git rev-parse --git-dir]} err]} { ++if {[catch { ++ set _gitdir $env(GIT_DIR) ++ set _prefix {} ++ }] ++ && [catch { ++ set _gitdir [git rev-parse --git-dir] ++ set _prefix [git rev-parse --show-prefix] ++ } err]} { + catch {wm withdraw .} + error_popup "Cannot find the git directory:\n\n$err" + exit 1 +} +if {![file isdirectory $_gitdir] && [is_Cygwin]} { + catch {set _gitdir [exec cygpath --unix $_gitdir]} +} +if {![file isdirectory $_gitdir]} { + catch {wm withdraw .} + 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 {[catch {cd [file dirname $_gitdir]} err]} { + catch {wm withdraw .} + error_popup "No working directory [file dirname $_gitdir]:\n\n$err" + exit 1 +} +set _reponame [lindex [file split \ + [file normalize [file dirname $_gitdir]]] \ + end] + +###################################################################### +## +## global init + +set current_diff_path {} +set current_diff_side {} +set diff_actions [list] +set ui_status_value {Initializing...} + +set HEAD {} +set PARENT {} +set MERGE_HEAD [list] +set commit_type {} +set empty_tree {} +set current_branch {} +set current_diff_path {} +set selected_commit_type new + +###################################################################### +## +## task management + +set rescan_active 0 +set diff_active 0 +set last_clicked {} + +set disable_on_lock [list] +set index_lock_type none + +proc lock_index {type} { + global index_lock_type disable_on_lock + + if {$index_lock_type eq {none}} { + set index_lock_type $type + foreach w $disable_on_lock { + uplevel #0 $w disabled + } + return 1 + } elseif {$index_lock_type eq "begin-$type"} { + set index_lock_type $type + return 1 + } + return 0 +} + +proc unlock_index {} { + global index_lock_type disable_on_lock + + set index_lock_type none + foreach w $disable_on_lock { + uplevel #0 $w normal + } +} + +###################################################################### +## +## status + +proc repository_state {ctvar hdvar mhvar} { + global current_branch + upvar $ctvar ct $hdvar hd $mhvar mh + + 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 + } + + if {[catch {set hd [git rev-parse --verify HEAD]}]} { + set hd {} + set ct initial + return + } + + set merge_head [gitdir MERGE_HEAD] + if {[file exists $merge_head]} { + set ct merge + set fd_mh [open $merge_head r] + while {[gets $fd_mh line] >= 0} { + lappend mh $line + } + close $fd_mh + return + } + + set ct normal +} + +proc PARENT {} { + global PARENT empty_tree + + set p [lindex $PARENT 0] + if {$p ne {}} { + return $p + } + if {$empty_tree eq {}} { + set empty_tree [git mktree << {}] + } + return $empty_tree +} + +proc rescan {after {honor_trustmtime 1}} { + global HEAD PARENT MERGE_HEAD commit_type + global ui_index ui_workdir ui_status_value ui_comm + global rescan_active file_states + global repo_config + + if {$rescan_active > 0 || ![lock_index read]} return + + repository_state newType newHEAD newMERGE_HEAD + if {[string match amend* $commit_type] + && $newType eq {normal} + && $newHEAD eq $HEAD} { + } else { + set HEAD $newHEAD + set PARENT $newHEAD + set MERGE_HEAD $newMERGE_HEAD + set commit_type $newType + } + + array unset file_states + + if {![$ui_comm edit modified] + || [string trim [$ui_comm get 0.0 end]] eq {}} { + if {[load_message GITGUI_MSG]} { + } elseif {[load_message MERGE_MSG]} { + } elseif {[load_message SQUASH_MSG]} { + } + $ui_comm edit reset + $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] + fconfigure $fd_rf -blocking 0 -translation binary + fileevent $fd_rf readable \ + [list rescan_stage2 $fd_rf $after] + } +} + +proc rescan_stage2 {fd after} { + global ui_status_value + global rescan_active buf_rdi buf_rdf buf_rlo + + if {$fd ne {}} { + read $fd + if {![eof $fd]} return + close $fd + } + + set ls_others [list | git ls-files --others -z \ + --exclude-per-directory=.gitignore] + set info_exclude [gitdir info exclude] + if {[file readable $info_exclude]} { + lappend ls_others "--exclude-from=$info_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] + + fconfigure $fd_di -blocking 0 -translation binary -encoding binary + fconfigure $fd_df -blocking 0 -translation binary -encoding binary + fconfigure $fd_lo -blocking 0 -translation binary -encoding binary + fileevent $fd_di readable [list read_diff_index $fd_di $after] + fileevent $fd_df readable [list read_diff_files $fd_df $after] + fileevent $fd_lo readable [list read_ls_others $fd_lo $after] +} + +proc load_message {file} { + global ui_comm + + set f [gitdir $file] + if {[file isfile $f]} { + if {[catch {set fd [open $f r]}]} { + return 0 + } + set content [string trim [read $fd]] + close $fd + regsub -all -line {[ \r\t]+$} $content {} content + $ui_comm delete 0.0 end + $ui_comm insert end $content + return 1 + } + return 0 +} + +proc read_diff_index {fd after} { + global buf_rdi + + append buf_rdi [read $fd] + set c 0 + set n [string length $buf_rdi] + while {$c < $n} { + set z1 [string first "\0" $buf_rdi $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdi $z1] + if {$z2 == -1} break + + incr c + set i [split [string range $buf_rdi $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdi $z1 [expr {$z2 - 1}]] + merge_state \ + [encoding convertfrom $p] \ + [lindex $i 4]? \ + [list [lindex $i 0] [lindex $i 2]] \ + [list] + set c $z2 + incr c + } + if {$c < $n} { + set buf_rdi [string range $buf_rdi $c end] + } else { + set buf_rdi {} + } + + rescan_done $fd buf_rdi $after +} + +proc read_diff_files {fd after} { + global buf_rdf + + append buf_rdf [read $fd] + set c 0 + set n [string length $buf_rdf] + while {$c < $n} { + set z1 [string first "\0" $buf_rdf $c] + if {$z1 == -1} break + incr z1 + set z2 [string first "\0" $buf_rdf $z1] + if {$z2 == -1} break + + incr c + set i [split [string range $buf_rdf $c [expr {$z1 - 2}]] { }] + set p [string range $buf_rdf $z1 [expr {$z2 - 1}]] + merge_state \ + [encoding convertfrom $p] \ + ?[lindex $i 4] \ + [list] \ + [list [lindex $i 0] [lindex $i 2]] + set c $z2 + incr c + } + if {$c < $n} { + set buf_rdf [string range $buf_rdf $c end] + } else { + set buf_rdf {} + } + + rescan_done $fd buf_rdf $after +} + +proc read_ls_others {fd after} { + global buf_rlo + + append buf_rlo [read $fd] + 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 + } + rescan_done $fd buf_rlo $after +} + +proc rescan_done {fd buf after} { + global rescan_active current_diff_path + global file_states repo_config + upvar $buf to_clear + + if {![eof $fd]} return + set to_clear {} + close $fd + if {[incr rescan_active -1] > 0} return + + prune_selection + unlock_index + display_all_files + if {$current_diff_path ne {}} reshow_diff + uplevel #0 $after +} + +proc prune_selection {} { + global file_states selected_paths + + foreach path [array names selected_paths] { + if {[catch {set still_here $file_states($path)}]} { + unset selected_paths($path) + } + } +} + +###################################################################### +## +## ui helpers + +proc mapicon {w state path} { + global all_icons + + if {[catch {set r $all_icons($state$w)}]} { + puts "error: no icon for $w state={$state} $path" + return file_plain + } + return $r +} + +proc mapdesc {state path} { + global all_descs + + if {[catch {set r $all_descs($state)}]} { + puts "error: no desc for state={$state} $path" + return $state + } + return $r +} + +proc escape_path {path} { + regsub -all {\\} $path "\\\\" path + regsub -all "\n" $path "\\n" path + return $path +} + +proc short_path {path} { + return [escape_path [lindex [file split $path] end]] +} + +set next_icon_id 0 +set null_sha1 [string repeat 0 40] + +proc merge_state {path new_state {head_info {}} {index_info {}}} { + global file_states next_icon_id null_sha1 + + set s0 [string index $new_state 0] + set s1 [string index $new_state 1] + + if {[catch {set info $file_states($path)}]} { + set state __ + set icon n[incr next_icon_id] + } else { + set state [lindex $info 0] + set icon [lindex $info 1] + if {$head_info eq {}} {set head_info [lindex $info 2]} + if {$index_info eq {}} {set index_info [lindex $info 3]} + } + + if {$s0 eq {?}} {set s0 [string index $state 0]} \ + elseif {$s0 eq {_}} {set s0 _} + + if {$s1 eq {?}} {set s1 [string index $state 1]} \ + elseif {$s1 eq {_}} {set s1 _} + + if {$s0 eq {A} && $s1 eq {_} && $head_info eq {}} { + set head_info [list 0 $null_sha1] + } elseif {$s0 ne {_} && [string index $state 0] eq {_} + && $head_info eq {}} { + set head_info $index_info + } + + set file_states($path) [list $s0$s1 $icon \ + $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 -exact $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 -exact $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 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 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 {__}} { + unset file_states($path) + catch {unset selected_paths($path)} + } +} + +proc display_all_files_helper {w path icon_name m} { + global file_lists + + 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_workdir + global file_states file_lists + global last_clicked + + $ui_index conf -state normal + $ui_workdir conf -state normal + + $ui_index delete 0.0 end + $ui_workdir delete 0.0 end + set last_clicked {} + + set file_lists($ui_index) [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 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 {$s ne {_}} { + display_all_files_helper $ui_workdir $path \ + $icon_name $s + } + } + + $ui_index conf -state disabled + $ui_workdir conf -state disabled +} + +###################################################################### +## +## icons + +set filemask { +#define mask_width 14 +#define mask_height 15 +static unsigned char mask_bits[] = { + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f, + 0xfe, 0x1f, 0xfe, 0x1f, 0xfe, 0x1f}; +} + +image create bitmap file_plain -background white -foreground black -data { +#define plain_width 14 +#define plain_height 15 +static unsigned char plain_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, 0x02, 0x10, + 0x02, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_mod -background white -foreground blue -data { +#define mod_width 14 +#define mod_height 15 +static unsigned char mod_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 0xfa, 0x17, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_fulltick -background white -foreground "#007000" -data { +#define file_fulltick_width 14 +#define file_fulltick_height 15 +static unsigned char file_fulltick_bits[] = { + 0xfe, 0x01, 0x02, 0x1a, 0x02, 0x0c, 0x02, 0x0c, 0x02, 0x16, 0x02, 0x16, + 0x02, 0x13, 0x00, 0x13, 0x86, 0x11, 0x8c, 0x11, 0xd8, 0x10, 0xf2, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_parttick -background white -foreground "#005050" -data { +#define parttick_width 14 +#define parttick_height 15 +static unsigned char parttick_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x7a, 0x05, 0x02, 0x09, 0x7a, 0x1f, 0x02, 0x10, + 0x7a, 0x14, 0x02, 0x16, 0x02, 0x13, 0x8a, 0x11, 0xda, 0x10, 0x72, 0x10, + 0x22, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_question -background white -foreground black -data { +#define file_question_width 14 +#define file_question_height 15 +static unsigned char file_question_bits[] = { + 0xfe, 0x01, 0x02, 0x02, 0xe2, 0x04, 0xf2, 0x09, 0x1a, 0x1b, 0x0a, 0x13, + 0x82, 0x11, 0xc2, 0x10, 0x62, 0x10, 0x62, 0x10, 0x02, 0x10, 0x62, 0x10, + 0x62, 0x10, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_removed -background white -foreground red -data { +#define file_removed_width 14 +#define file_removed_height 15 +static unsigned char file_removed_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x02, 0x05, 0x02, 0x09, 0x02, 0x1f, 0x02, 0x10, + 0x1a, 0x16, 0x32, 0x13, 0xe2, 0x11, 0xc2, 0x10, 0xe2, 0x11, 0x32, 0x13, + 0x1a, 0x16, 0x02, 0x10, 0xfe, 0x1f}; +} -maskdata $filemask + +image create bitmap file_merge -background white -foreground blue -data { +#define file_merge_width 14 +#define file_merge_height 15 +static unsigned char file_merge_bits[] = { + 0xfe, 0x01, 0x02, 0x03, 0x62, 0x05, 0x62, 0x09, 0x62, 0x1f, 0x62, 0x10, + 0xfa, 0x11, 0xf2, 0x10, 0x62, 0x10, 0x02, 0x10, 0xfa, 0x17, 0x02, 0x10, + 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 + +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 { + {__ "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 1]]} { + set max_status_desc [string length [lindex $i 1]] + } + set all_descs([lindex $i 0]) [lindex $i 1] +} +unset i + +###################################################################### +## +## util + +proc bind_button3 {w cmd} { + bind $w $cmd + if {[is_MacOSX]} { + bind $w $cmd + } +} + +proc scrollbar2many {list mode args} { + foreach w $list {eval $w $mode $args} +} + +proc many2scrollbar {list mode sb top bottom} { + $sb set $top $bottom + foreach w $list {$w $mode moveto $top} +} + +proc incr_font_size {font {amt 1}} { + set sz [font configure $font -size] + incr sz $amt + font configure $font -size $sz + font configure ${font}bold -size $sz +} + +###################################################################### +## +## ui commands + +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]] + lappend cmd [gitexec gitk] + if {$revs ne {}} { + append cmd { } + append cmd $revs + } + + if {[catch {eval exec $cmd &} err]} { + error_popup "Failed to start gitk:\n\n$err" + } else { + set ui_status_value $starting_gitk_msg + after 10000 { + if {$ui_status_value eq $starting_gitk_msg} { + set ui_status_value {Ready.} + } + } + } +} + +set is_quitting 0 + +proc do_quit {} { + global ui_comm is_quitting repo_config commit_type + + if {$is_quitting} return + set is_quitting 1 + + if {[winfo exists $ui_comm]} { + # -- 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 + } + } else { + catch {file delete $save} + } + + # -- Stash our current window geometry into this repository. + # + set cfg_geometry [list] + lappend cfg_geometry [wm geometry .] + lappend cfg_geometry [lindex [.vpane sash coord 0] 1] + lappend cfg_geometry [lindex [.vpane.files sash coord 0] 0] + if {[catch {set rc_geometry $repo_config(gui.geometry)}]} { + set rc_geometry {} + } + if {$cfg_geometry ne $rc_geometry} { + catch {git config gui.geometry $cfg_geometry} + } + } + + destroy . +} + +proc do_rescan {} { + rescan {set ui_status_value {Ready.}} +} + +proc do_commit {} { + commit_tree +} + +proc toggle_or_diff {w x y} { + global file_states file_lists current_diff_path ui_index ui_workdir + global last_clicked selected_paths + + set pos [split [$w index @$x,$y] .] + set lno [lindex $pos 0] + set col [lindex $pos 1] + set path [lindex $file_lists($w) [expr {$lno - 1}]] + if {$path eq {}} { + set last_clicked {} + return + } + + set last_clicked [list $w $lno] + array unset selected_paths + $ui_index tag remove in_sel 0.0 end + $ui_workdir tag remove in_sel 0.0 end + + if {$col == 0} { + if {$current_diff_path eq $path} { + set after {reshow_diff;} + } else { + set after {} + } + if {$w eq $ui_index} { + update_indexinfo \ + "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 last_clicked selected_paths + + 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 + } + if {$in_sel} { + unset selected_paths($path) + $w tag remove in_sel $lno.0 [expr {$lno + 1}].0 + } else { + set selected_paths($path) 1 + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + } +} + +proc add_range_to_selection {w x y} { + global file_lists last_clicked selected_paths + + if {[lindex $last_clicked 0] ne $w} { + toggle_or_diff $w $x $y + return + } + + set lno [lindex [split [$w index @$x,$y] .] 0] + set lc [lindex $last_clicked 1] + if {$lc < $lno} { + set begin $lc + set end $lno + } else { + set begin $lno + set end $lc + } + + foreach path [lrange $file_lists($w) \ + [expr {$begin - 1}] \ + [expr {$end - 1}]] { + set selected_paths($path) 1 + } + $w tag add in_sel $begin.0 [expr {$end + 1}].0 +} + +###################################################################### +## +## config defaults + +set cursor_ptr arrow +font create font_diff -family Courier -size 10 +font create font_ui +catch { + label .dummy + eval font configure font_ui [font actual [.dummy cget -font]] + destroy .dummy +} + +font create font_uibold +font create font_diffbold + +foreach class {Button Checkbutton Entry Label + Labelframe Listbox Menu Message + Radiobutton Text} { + option add *$class.font font_ui +} +unset class + +if {[is_Windows]} { + set M1B Control + set M1T Ctrl +} elseif {[is_MacOSX]} { + set M1B M1 + set M1T Cmd +} else { + set M1B M1 + set M1T M1 +} + +proc apply_config {} { + global repo_config font_descs + + foreach option $font_descs { + set name [lindex $option 0] + set font [lindex $option 1] + if {[catch { + foreach {cn cv} $repo_config(gui.$name) { + font configure $font $cn $cv + } + } err]} { + error_popup "Invalid font specified in gui.$name:\n\n$err" + } + foreach {cn cv} [font configure $font] { + font configure ${font}bold $cn $cv + } + font configure ${font}bold -weight bold + } +} + +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.trustmtime) false +set default_config(gui.diffcontext) 5 +set default_config(gui.newbranchtemplate) {} +set default_config(gui.fontui) [font configure font_ui] +set default_config(gui.fontdiff) [font configure font_diff] +set font_descs { + {fontui font_ui {Main Font}} + {fontdiff font_diff {Diff/Console Font}} +} +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 + +set ui_comm {} + +# -- Menu Bar +# +menu .mbar -tearoff 0 +.mbar add cascade -label Repository -menu .mbar.repository +.mbar add cascade -label Edit -menu .mbar.edit +if {[is_enabled branch]} { + .mbar add cascade -label Branch -menu .mbar.branch +} +if {[is_enabled multicommit] || [is_enabled singlecommit]} { + .mbar add cascade -label Commit -menu .mbar.commit +} +if {[is_enabled transport]} { + .mbar add cascade -label Merge -menu .mbar.merge + .mbar add cascade -label Fetch -menu .mbar.fetch + .mbar add cascade -label Push -menu .mbar.push +} +. configure -menu .mbar + +# -- Repository Menu +# +menu .mbar.repository + +.mbar.repository add command \ + -label {Browse Current Branch} \ - -command {new_browser $current_branch} ++ -command {browser::new $current_branch} +trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Browse \$current_branch\" ;#" +.mbar.repository add separator + +.mbar.repository add command \ + -label {Visualize Current Branch} \ + -command {do_gitk $current_branch} +trace add variable current_branch write ".mbar.repository entryconf [.mbar.repository index last] -label \"Visualize \$current_branch\" ;#" +.mbar.repository add command \ + -label {Visualize All Branches} \ + -command {do_gitk --all} +.mbar.repository add separator + +if {[is_enabled multicommit]} { + .mbar.repository add command -label {Database Statistics} \ + -command do_stats + + .mbar.repository add command -label {Compress Database} \ + -command do_gc + + .mbar.repository add command -label {Verify Database} \ + -command do_fsck_objects + + .mbar.repository add separator + + if {[is_Cygwin]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ + -command do_cygwin_shortcut + } elseif {[is_Windows]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ + -command do_windows_shortcut + } elseif {[is_MacOSX]} { + .mbar.repository add command \ + -label {Create Desktop Icon} \ + -command do_macosx_app + } +} + +.mbar.repository add command -label Quit \ + -command do_quit \ + -accelerator $M1T-Q + +# -- Edit Menu +# +menu .mbar.edit +.mbar.edit add command -label Undo \ + -command {catch {[focus] edit undo}} \ + -accelerator $M1T-Z +.mbar.edit add command -label Redo \ + -command {catch {[focus] edit redo}} \ + -accelerator $M1T-Y +.mbar.edit add separator +.mbar.edit add command -label Cut \ + -command {catch {tk_textCut [focus]}} \ + -accelerator $M1T-X +.mbar.edit add command -label Copy \ + -command {catch {tk_textCopy [focus]}} \ + -accelerator $M1T-C +.mbar.edit add command -label Paste \ + -command {catch {tk_textPaste [focus]; [focus] see insert}} \ + -accelerator $M1T-V +.mbar.edit add command -label Delete \ + -command {catch {[focus] delete sel.first sel.last}} \ + -accelerator Del +.mbar.edit add separator +.mbar.edit add command -label {Select All} \ + -command {catch {[focus] tag add sel 0.0 end}} \ + -accelerator $M1T-A + +# -- Branch Menu +# +if {[is_enabled branch]} { + menu .mbar.branch + + .mbar.branch add command -label {Create...} \ + -command do_create_branch \ + -accelerator $M1T-N + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + + .mbar.branch add command -label {Delete...} \ + -command do_delete_branch + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] + + .mbar.branch add command -label {Reset...} \ + -command merge::reset_hard + lappend disable_on_lock [list .mbar.branch entryconf \ + [.mbar.branch index last] -state] +} + +# -- Commit Menu +# +if {[is_enabled multicommit] || [is_enabled singlecommit]} { + menu .mbar.commit + + .mbar.commit add radiobutton \ + -label {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value new + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add radiobutton \ + -label {Amend Last Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value amend + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add separator + + .mbar.commit add command -label Rescan \ + -command do_rescan \ + -accelerator F5 + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Add 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} \ + -command do_add_all \ + -accelerator $M1T-I + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Unstage From Commit} \ + -command do_unstage_selection + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add command -label {Revert Changes} \ + -command do_revert_selection + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] + + .mbar.commit add separator + + .mbar.commit add command -label {Sign Off} \ + -command do_signoff \ + -accelerator $M1T-S + + .mbar.commit add command -label Commit \ + -command do_commit \ + -accelerator $M1T-Return + lappend disable_on_lock \ + [list .mbar.commit entryconf [.mbar.commit index last] -state] +} + +# -- Merge Menu +# +if {[is_enabled branch]} { + menu .mbar.merge + .mbar.merge add command -label {Local Merge...} \ + -command merge::dialog + 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 +# +if {[is_enabled transport]} { + menu .mbar.fetch + + menu .mbar.push + .mbar.push add command -label {Push...} \ + -command do_push_anywhere +} + +if {[is_MacOSX]} { + # -- Apple Menu (Mac OS X only) + # + .mbar add cascade -label Apple -menu .mbar.apple + menu .mbar.apple + + .mbar.apple add command -label "About [appname]" \ + -command do_about + .mbar.apple add command -label "Options..." \ + -command do_options +} else { + # -- Edit Menu + # + .mbar.edit add separator + .mbar.edit add command -label {Options...} \ + -command do_options + + # -- Tools Menu + # + if {[file exists /usr/local/miga/lib/gui-miga] + && [file exists .pvcsrc]} { + 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 +# +.mbar add cascade -label Help -menu .mbar.help +menu .mbar.help + +if {![is_MacOSX]} { + .mbar.help add command -label "About [appname]" \ + -command do_about +} + +set browser {} +catch {set browser $repo_config(instaweb.browser)} +set doc_path [file dirname [gitexec]] +set doc_path [file join $doc_path Documentation index.html] + +if {[is_Cygwin]} { + set doc_path [exec cygpath --mixed $doc_path] +} + +if {$browser eq {}} { + if {[is_MacOSX]} { + set browser open + } elseif {[is_Cygwin]} { + set program_files [file dirname [exec cygpath --windir]] + set program_files [file join $program_files {Program Files}] + set firefox [file join $program_files {Mozilla Firefox} firefox.exe] + set ie [file join $program_files {Internet Explorer} IEXPLORE.EXE] + if {[file exists $firefox]} { + set browser $firefox + } elseif {[file exists $ie]} { + set browser $ie + } + unset program_files firefox ie + } +} + +if {[file isfile $doc_path]} { + set doc_url "file:$doc_path" +} else { + set doc_url {http://www.kernel.org/pub/software/scm/git/docs/} +} + +if {$browser ne {}} { + .mbar.help add command -label {Online Documentation} \ + -command [list exec $browser $doc_url &] +} +unset browser doc_path doc_url + +# -- Standard bindings +# +bind . do_quit +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]} + ++set subcommand_args {} ++proc usage {} { ++ puts stderr "usage: $::argv0 $::subcommand $::subcommand_args" ++ exit 1 ++} ++ +# -- Not a normal commit type invocation? Do that instead! +# +switch -- $subcommand { +browser { - if {[llength $argv] != 1} { - puts stderr "usage: $argv0 browser commit" - exit 1 ++ 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] + } - set current_branch [lindex $argv 0] - new_browser $current_branch ++ default usage ++ } ++ browser::new $current_branch + return +} +blame { - if {[llength $argv] != 2} { - puts stderr "usage: $argv0 blame commit path" - exit 1 ++ set subcommand_args {rev? path?} ++ set head {} ++ set path {} ++ set is_path 0 ++ foreach a $argv { ++ if {$is_path || [file exists $_prefix$a]} { ++ if {$path ne {}} usage ++ set path $_prefix$a ++ break ++ } elseif {$a eq {--}} { ++ if {$path ne {}} { ++ if {$head ne {}} usage ++ set head $path ++ set path {} ++ } ++ set is_path 1 ++ } elseif {$head eq {}} { ++ if {$head ne {}} usage ++ set head $a ++ } else { ++ usage ++ } ++ } ++ unset is_path ++ ++ if {$head eq {}} { ++ set current_branch [git symbolic-ref HEAD] ++ regsub ^refs/((heads|tags|remotes)/)? \ ++ $current_branch {} current_branch ++ } else { ++ set current_branch $head + } - set current_branch [lindex $argv 0] - show_blame $current_branch [lindex $argv 1] ++ ++ if {$path eq {}} usage ++ blame::new $head $path + return +} +citool - +gui { + if {[llength $argv] != 0} { + puts -nonewline stderr "usage: $argv0" + if {$subcommand ne {gui} && [appname] ne "git-$subcommand"} { + puts -nonewline stderr " $subcommand" + } + puts stderr {} + exit 1 + } + # fall through to setup UI for commits +} +default { + puts stderr "usage: $argv0 \[{blame|browser|citool}\]" + exit 1 +} +} + +# -- Branch Control +# +frame .branch \ + -borderwidth 1 \ + -relief sunken +label .branch.l1 \ + -text {Current Branch:} \ + -anchor w \ + -justify left +label .branch.cb \ + -textvariable current_branch \ + -anchor w \ + -justify left +pack .branch.l1 -side left +pack .branch.cb -side left -fill x +pack .branch -side top -fill x + +# -- Main Window Layout +# +panedwindow .vpane -orient vertical +panedwindow .vpane.files -orient horizontal +.vpane add .vpane.files -sticky nsew -height 100 -width 200 +pack .vpane -anchor n -side top -fill both -expand 1 + +# -- Index File List +# +frame .vpane.files.index -height 100 -width 200 - label .vpane.files.index.title -text {Changes To Be Committed} \ ++label .vpane.files.index.title -text {Staged Changes (Will Be Committed)} \ + -background green +text $ui_index -background white -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.index.sx set} \ + -yscrollcommand {.vpane.files.index.sy set} \ + -state disabled +scrollbar .vpane.files.index.sx -orient h -command [list $ui_index xview] +scrollbar .vpane.files.index.sy -orient v -command [list $ui_index yview] +pack .vpane.files.index.title -side top -fill x +pack .vpane.files.index.sx -side bottom -fill x +pack .vpane.files.index.sy -side right -fill y +pack $ui_index -side left -fill both -expand 1 +.vpane.files add .vpane.files.index -sticky nsew + +# -- Working Directory File List +# +frame .vpane.files.workdir -height 100 -width 200 - label .vpane.files.workdir.title -text {Changed But Not Updated} \ ++label .vpane.files.workdir.title -text {Unstaged Changes (Will Not Be Committed)} \ + -background red +text $ui_workdir -background white -borderwidth 0 \ + -width 20 -height 10 \ + -wrap none \ + -cursor $cursor_ptr \ + -xscrollcommand {.vpane.files.workdir.sx set} \ + -yscrollcommand {.vpane.files.workdir.sy set} \ + -state disabled +scrollbar .vpane.files.workdir.sx -orient h -command [list $ui_workdir xview] +scrollbar .vpane.files.workdir.sy -orient v -command [list $ui_workdir yview] +pack .vpane.files.workdir.title -side top -fill x +pack .vpane.files.workdir.sx -side bottom -fill x +pack .vpane.files.workdir.sy -side right -fill y +pack $ui_workdir -side left -fill both -expand 1 +.vpane.files add .vpane.files.workdir -sticky nsew + +foreach i [list $ui_index $ui_workdir] { + $i tag conf in_diff -font font_uibold + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] +} +unset i + +# -- Diff and Commit Area +# +frame .vpane.lower -height 300 -width 400 +frame .vpane.lower.commarea +frame .vpane.lower.diff -relief sunken -borderwidth 1 +pack .vpane.lower.commarea -side top -fill x +pack .vpane.lower.diff -side bottom -fill both -expand 1 +.vpane add .vpane.lower -sticky nsew + +# -- Commit Area Buttons +# +frame .vpane.lower.commarea.buttons +label .vpane.lower.commarea.buttons.l -text {} \ + -anchor w \ + -justify left +pack .vpane.lower.commarea.buttons.l -side top -fill x +pack .vpane.lower.commarea.buttons -side left -fill y + +button .vpane.lower.commarea.buttons.rescan -text {Rescan} \ + -command do_rescan +pack .vpane.lower.commarea.buttons.rescan -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.rescan conf -state} + +button .vpane.lower.commarea.buttons.incall -text {Add Existing} \ + -command do_add_all +pack .vpane.lower.commarea.buttons.incall -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.incall conf -state} + +button .vpane.lower.commarea.buttons.signoff -text {Sign Off} \ + -command do_signoff +pack .vpane.lower.commarea.buttons.signoff -side top -fill x + +button .vpane.lower.commarea.buttons.commit -text {Commit} \ + -command do_commit +pack .vpane.lower.commarea.buttons.commit -side top -fill x +lappend disable_on_lock \ + {.vpane.lower.commarea.buttons.commit conf -state} + +# -- Commit Message Buffer +# +frame .vpane.lower.commarea.buffer +frame .vpane.lower.commarea.buffer.header +set ui_comm .vpane.lower.commarea.buffer.t +set ui_coml .vpane.lower.commarea.buffer.header.l +radiobutton .vpane.lower.commarea.buffer.header.new \ + -text {New Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value new +lappend disable_on_lock \ + [list .vpane.lower.commarea.buffer.header.new conf -state] +radiobutton .vpane.lower.commarea.buffer.header.amend \ + -text {Amend Last Commit} \ + -command do_select_commit_type \ + -variable selected_commit_type \ + -value amend +lappend disable_on_lock \ + [list .vpane.lower.commarea.buffer.header.amend conf -state] +label $ui_coml \ + -anchor w \ + -justify left +proc trace_commit_type {varname args} { + global ui_coml commit_type + switch -glob -- $commit_type { + initial {set txt {Initial Commit Message:}} + amend {set txt {Amended Commit Message:}} + amend-initial {set txt {Amended Initial Commit Message:}} + amend-merge {set txt {Amended Merge Commit Message:}} + merge {set txt {Merge Commit Message:}} + * {set txt {Commit Message:}} + } + $ui_coml conf -text $txt +} +trace add variable commit_type write trace_commit_type +pack $ui_coml -side left -fill x +pack .vpane.lower.commarea.buffer.header.amend -side right +pack .vpane.lower.commarea.buffer.header.new -side right + +text $ui_comm -background white -borderwidth 1 \ + -undo true \ + -maxundo 20 \ + -autoseparators true \ + -relief sunken \ + -width 75 -height 9 -wrap none \ + -font font_diff \ + -yscrollcommand {.vpane.lower.commarea.buffer.sby set} +scrollbar .vpane.lower.commarea.buffer.sby \ + -command [list $ui_comm yview] +pack .vpane.lower.commarea.buffer.header -side top -fill x +pack .vpane.lower.commarea.buffer.sby -side right -fill y +pack $ui_comm -side left -fill y +pack .vpane.lower.commarea.buffer -side left -fill y + +# -- Commit Message Buffer Context Menu +# +set ctxm .vpane.lower.commarea.buffer.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Cut} \ + -command {tk_textCut $ui_comm} +$ctxm add command \ + -label {Copy} \ + -command {tk_textCopy $ui_comm} +$ctxm add command \ + -label {Paste} \ + -command {tk_textPaste $ui_comm} +$ctxm add command \ + -label {Delete} \ + -command {$ui_comm delete sel.first sel.last} +$ctxm add separator +$ctxm add command \ + -label {Select All} \ + -command {focus $ui_comm;$ui_comm tag add sel 0.0 end} +$ctxm add command \ + -label {Copy All} \ + -command { + $ui_comm tag add sel 0.0 end + tk_textCopy $ui_comm + $ui_comm tag remove sel 0.0 end + } +$ctxm add separator +$ctxm add command \ + -label {Sign Off} \ + -command do_signoff +bind_button3 $ui_comm "tk_popup $ctxm %X %Y" + +# -- Diff Header +# +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_path + set s [mapdesc [lindex $file_states($p) 0] $p] + set f {File:} + set p [escape_path $p] + set o normal + } + + .vpane.lower.diff.header.status configure -text $s + .vpane.lower.diff.header.file configure -text $f + .vpane.lower.diff.header.path configure -text $p + foreach w $diff_actions { + uplevel #0 $w $o + } +} +trace add variable current_diff_path write trace_current_diff_path + +frame .vpane.lower.diff.header -background orange +label .vpane.lower.diff.header.status \ + -background orange \ + -width $max_status_desc \ + -anchor w \ + -justify left +label .vpane.lower.diff.header.file \ + -background orange \ + -anchor w \ + -justify left +label .vpane.lower.diff.header.path \ + -background orange \ + -anchor w \ + -justify left +pack .vpane.lower.diff.header.status -side left +pack .vpane.lower.diff.header.file -side left +pack .vpane.lower.diff.header.path -fill x +set ctxm .vpane.lower.diff.header.ctxm +menu $ctxm -tearoff 0 +$ctxm add command \ + -label {Copy} \ + -command { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $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" + +# -- Diff Body +# +frame .vpane.lower.diff.body +set ui_diff .vpane.lower.diff.body.t +text $ui_diff -background white -borderwidth 0 \ + -width 80 -height 15 -wrap none \ + -font font_diff \ + -xscrollcommand {.vpane.lower.diff.body.sbx set} \ + -yscrollcommand {.vpane.lower.diff.body.sby set} \ + -state disabled +scrollbar .vpane.lower.diff.body.sbx -orient horizontal \ + -command [list $ui_diff xview] +scrollbar .vpane.lower.diff.body.sby -orient vertical \ + -command [list $ui_diff yview] +pack .vpane.lower.diff.body.sbx -side bottom -fill x +pack .vpane.lower.diff.body.sby -side right -fill y +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_cr -elide true +$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 red +$ui_diff tag conf d_+s \ + -foreground {#00a000} \ + -background {#e2effa} +$ui_diff tag conf d_-s \ + -foreground red \ + -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} \ + -command reshow_diff +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Copy} \ + -command {tk_textCopy $ui_diff} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Select All} \ + -command {focus $ui_diff;$ui_diff tag add sel 0.0 end} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Copy All} \ + -command { + $ui_diff tag add sel 0.0 end + tk_textCopy $ui_diff + $ui_diff tag remove sel 0.0 end + } +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command \ + -label {Apply/Reverse Hunk} \ + -command {apply_hunk $cursorX $cursorY} +set ui_diff_applyhunk [$ctxm index last] +lappend diff_actions [list $ctxm entryconf $ui_diff_applyhunk -state] +$ctxm add separator +$ctxm add command \ + -label {Decrease Font Size} \ + -command {incr_font_size font_diff -1} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Increase Font Size} \ + -command {incr_font_size font_diff 1} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add separator +$ctxm add command \ + -label {Show Less Context} \ + -command {if {$repo_config(gui.diffcontext) >= 2} { + incr repo_config(gui.diffcontext) -1 + reshow_diff + }} +lappend diff_actions [list $ctxm entryconf [$ctxm index last] -state] +$ctxm add command \ + -label {Show More Context} \ + -command { + incr repo_config(gui.diffcontext) + reshow_diff + } +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} + } else { + $ctxm entryconf $ui_diff_applyhunk -label {Stage Hunk For Commit} + } + tk_popup $ctxm %X %Y +" +unset ui_diff_applyhunk + +# -- Status Bar +# +label .status -textvariable ui_status_value \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken +pack .status -anchor w -side bottom -fill x + +# -- Load geometry +# +catch { +set gm $repo_config(gui.geometry) +wm geometry . [lindex $gm 0] +.vpane sash place 0 \ + [lindex [.vpane sash coord 0] 0] \ + [lindex $gm 1] +.vpane.files sash place 0 \ + [lindex $gm 2] \ + [lindex [.vpane.files sash coord 0] 1] +unset gm +} + +# -- Key Bindings +# +bind $ui_comm <$M1B-Key-Return> {do_commit;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} +bind $ui_comm <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_comm <$M1B-Key-v> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-V> {tk_textPaste %W; %W see insert; break} +bind $ui_comm <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_comm <$M1B-Key-A> {%W tag add sel 0.0 end;break} + +bind $ui_diff <$M1B-Key-x> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-X> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-c> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-C> {tk_textCopy %W;break} +bind $ui_diff <$M1B-Key-v> {break} +bind $ui_diff <$M1B-Key-V> {break} +bind $ui_diff <$M1B-Key-a> {%W tag add sel 0.0 end;break} +bind $ui_diff <$M1B-Key-A> {%W tag add sel 0.0 end;break} +bind $ui_diff {catch {%W yview scroll -1 units};break} +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} +bind $ui_diff {catch {%W yview scroll -1 units};break} +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} +bind $ui_diff {catch {%W yview scroll -1 pages};break} +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 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_add_all +bind . <$M1B-Key-I> do_add_all +bind . <$M1B-Key-Return> do_commit +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" +} +unset i + +set file_lists($ui_index) [list] +set file_lists($ui_workdir) [list] + +wm title . "[appname] ([reponame]) [file normalize [file dirname [gitdir]]]" +focus -force $ui_comm + +# -- Warn the user about environmental problems. Cygwin's Tcl +# does *not* pass its env array onto any processes it spawns. +# This means that git processes get none of our environment. +# +if {[is_Cygwin]} { + set ignored_env 0 + set suggest_user {} + set msg "Possible environment issues exist. + +The following environment variables are probably +going to be ignored by any Git subprocess run +by [appname]: + +" + foreach name [array names env] { + switch -regexp -- $name { + {^GIT_INDEX_FILE$} - + {^GIT_OBJECT_DIRECTORY$} - + {^GIT_ALTERNATE_OBJECT_DIRECTORIES$} - + {^GIT_DIFF_OPTS$} - + {^GIT_EXTERNAL_DIFF$} - + {^GIT_PAGER$} - + {^GIT_TRACE$} - + {^GIT_CONFIG$} - + {^GIT_CONFIG_LOCAL$} - + {^GIT_(AUTHOR|COMMITTER)_DATE$} { + append msg " - $name\n" + incr ignored_env + } + {^GIT_(AUTHOR|COMMITTER)_(NAME|EMAIL)$} { + append msg " - $name\n" + incr ignored_env + set suggest_user $name + } + } + } + if {$ignored_env > 0} { + append msg " +This is due to a known issue with the +Tcl binary distributed by Cygwin." + + if {$suggest_user ne {}} { + append msg " + +A good replacement for $suggest_user +is placing values for the user.name and +user.email settings into your personal +~/.gitconfig file. +" + } + warn_popup $msg + } + unset ignored_env msg suggest_user name +} + +# -- Only initialize complex UI if we are going to stay running. +# +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. + +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 diff --cc git-gui/lib/blame.tcl index 6d894e52d,000000000..8b032d959 mode 100644,000000..100644 --- a/git-gui/lib/blame.tcl +++ b/git-gui/lib/blame.tcl @@@ -1,407 -1,0 +1,416 @@@ +# git-gui blame viewer +# Copyright (C) 2006, 2007 Shawn Pearce + - proc show_blame {commit path} { - global next_browser_id blame_status blame_data - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set blame_status($w) {Loading current file content...} ++class blame { ++ ++field commit ; # input commit to blame ++field path ; # input filename to view in $commit ++ ++field w ++field w_line ++field w_load ++field w_file ++field w_cmit ++field status ++ ++field highlight_line -1 ; # current line selected ++field highlight_commit {} ; # sha1 of commit selected ++ ++field total_lines 0 ; # total length of file ++field blame_lines 0 ; # number of lines computed ++field commit_count 0 ; # number of commits in $commit_list ++field commit_list {} ; # list of commit sha1 in receipt order ++field order ; # array commit -> receipt order ++field header ; # array commit,key -> header field ++field line_commit ; # array line -> sha1 commit ++field line_file ; # array line -> file name ++ ++field r_commit ; # commit currently being parsed ++field r_orig_line ; # original line number ++field r_final_line ; # final line number ++field r_line_count ; # lines in this region ++ ++constructor new {i_commit i_path} { ++ set commit $i_commit ++ set path $i_path ++ ++ make_toplevel top w ++ wm title $top "[appname] ([reponame]): File Viewer" ++ set status "Loading $commit:$path..." + + label $w.path -text "$commit:$path" \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -side top -fill x + + frame $w.out + text $w.out.loaded_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 1 \ + -font font_diff + $w.out.loaded_t tag conf annotated -background grey + + text $w.out.linenumber_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 5 \ + -font font_diff + $w.out.linenumber_t tag conf linenumber -justify right + + text $w.out.file_t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 40 \ + -width 80 \ + -xscrollcommand [list $w.out.sbx set] \ + -font font_diff + + scrollbar $w.out.sbx -orient h -command [list $w.out.file_t xview] + scrollbar $w.out.sby -orient v \ + -command [list scrollbar2many [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview] + grid \ + $w.out.linenumber_t \ + $w.out.loaded_t \ + $w.out.file_t \ + $w.out.sby \ + -sticky nsew + grid conf $w.out.sbx -column 2 -sticky we + grid columnconfigure $w.out 2 -weight 1 + grid rowconfigure $w.out 0 -weight 1 + pack $w.out -fill both -expand 1 + - label $w.status -textvariable blame_status($w) \ ++ label $w.status \ ++ -textvariable @status \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -side bottom -fill x + + frame $w.cm + text $w.cm.t \ + -background white -borderwidth 0 \ + -state disabled \ + -wrap none \ + -height 10 \ + -width 80 \ + -xscrollcommand [list $w.cm.sbx set] \ + -yscrollcommand [list $w.cm.sby set] \ + -font font_diff + scrollbar $w.cm.sbx -orient h -command [list $w.cm.t xview] + scrollbar $w.cm.sby -orient v -command [list $w.cm.t yview] + pack $w.cm.sby -side right -fill y + pack $w.cm.sbx -side bottom -fill x + pack $w.cm.t -expand 1 -fill both + pack $w.cm -side bottom -fill x + + menu $w.ctxm -tearoff 0 - $w.ctxm add command -label "Copy Commit" \ - -command "blame_copycommit $w \$cursorW @\$cursorX,\$cursorY" ++ $w.ctxm add command \ ++ -label "Copy Commit" \ ++ -command [cb _copycommit] ++ ++ set w_line $w.out.linenumber_t ++ set w_load $w.out.loaded_t ++ set w_file $w.out.file_t ++ set w_cmit $w.cm.t + + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t] { + $i tag conf in_sel \ + -background [$i cget -foreground] \ + -foreground [$i cget -background] + $i conf -yscrollcommand \ + [list many2scrollbar [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + ] yview $w.out.sby] - bind $i " - blame_click {$w} \\ - $w.cm.t \\ - $w.out.linenumber_t \\ - $w.out.file_t \\ - $i @%x,%y - focus $i - " ++ bind $i "[cb _click $i @%x,%y]; focus $i" + bind_button3 $i " + set cursorX %x + set cursorY %y + set cursorW %W + tk_popup $w.ctxm %X %Y + " + } + + foreach i [list \ + $w.out.loaded_t \ + $w.out.linenumber_t \ + $w.out.file_t \ + $w.cm.t] { + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 units};break} + bind $i {catch {%W yview scroll 1 units};break} + bind $i {catch {%W xview scroll -1 units};break} + bind $i {catch {%W xview scroll 1 units};break} + bind $i {catch {%W yview scroll -1 pages};break} + bind $i {catch {%W yview scroll 1 pages};break} + } + - bind $w.cm.t "focus $w.cm.t" - bind $tl "focus $tl" - bind $tl " - array unset blame_status {$w} - array unset blame_data $w,* - " - wm title $tl "[appname] ([reponame]): File Viewer" - - set blame_data($w,commit_count) 0 - set blame_data($w,commit_list) {} - set blame_data($w,total_lines) 0 - set blame_data($w,blame_lines) 0 - set blame_data($w,highlight_commit) {} - set blame_data($w,highlight_line) -1 - - set cmd [list git cat-file blob "$commit:$path"] - set fd [open "| $cmd" r] - fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_catfile \ - $fd $w $commit $path \ - $w.cm.t $w.out.loaded_t $w.out.linenumber_t $w.out.file_t] - } - - proc read_blame_catfile {fd w commit path w_cmit w_load w_line w_file} { - global blame_status blame_data ++ bind $w.cm.t [list focus $w.cm.t] ++ bind $top [list focus $top] ++ bind $top [list delete_this $this] + - if {![winfo exists $w_file]} { - catch {close $fd} - return ++ if {$commit eq {}} { ++ set fd [open $path r] ++ } else { ++ set cmd [list git cat-file blob "$commit:$path"] ++ set fd [open "| $cmd" r] + } ++ fconfigure $fd -blocking 0 -translation lf -encoding binary ++ fileevent $fd readable [cb _read_file $fd] ++} + - set n $blame_data($w,total_lines) ++method _read_file {fd} { + $w_load conf -state normal + $w_line conf -state normal + $w_file conf -state normal + while {[gets $fd line] >= 0} { + regsub "\r\$" $line {} line - incr n ++ incr total_lines + $w_load insert end "\n" - $w_line insert end "$n\n" linenumber ++ $w_line insert end "$total_lines\n" linenumber + $w_file insert end "$line\n" + } + $w_load conf -state disabled + $w_line conf -state disabled + $w_file conf -state disabled - set blame_data($w,total_lines) $n + + if {[eof $fd]} { + close $fd - blame_incremental_status $w ++ _status $this + set cmd [list git blame -M -C --incremental] - lappend cmd $commit -- $path ++ if {$commit eq {}} { ++ lappend cmd --contents $path ++ } else { ++ lappend cmd $commit ++ } ++ lappend cmd -- $path + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation lf -encoding binary - fileevent $fd readable [list read_blame_incremental $fd $w \ - $w_load $w_cmit $w_line $w_file] - } - } - - proc read_blame_incremental {fd w w_load w_cmit w_line w_file} { - global blame_status blame_data - - if {![winfo exists $w_file]} { - catch {close $fd} - return ++ fileevent $fd readable [cb _read_blame $fd] + } ++} ifdeleted { catch {close $fd} } + ++method _read_blame {fd} { + while {[gets $fd line] >= 0} { + if {[regexp {^([a-z0-9]{40}) (\d+) (\d+) (\d+)$} $line line \ + cmit original_line final_line line_count]} { - set blame_data($w,commit) $cmit - set blame_data($w,original_line) $original_line - set blame_data($w,final_line) $final_line - set blame_data($w,line_count) $line_count ++ set r_commit $cmit ++ set r_orig_line $original_line ++ set r_final_line $final_line ++ set r_line_count $line_count + - if {[catch {set g $blame_data($w,$cmit,order)}]} { ++ if {[catch {set g $order($cmit)}]} { + $w_line tag conf g$cmit + $w_file tag conf g$cmit + $w_line tag raise in_sel + $w_file tag raise in_sel + $w_file tag raise sel - set blame_data($w,$cmit,order) $blame_data($w,commit_count) - incr blame_data($w,commit_count) - lappend blame_data($w,commit_list) $cmit ++ set order($cmit) $commit_count ++ incr commit_count ++ lappend commit_list $cmit + } + } elseif {[string match {filename *} $line]} { + set file [string range $line 9 end] - set n $blame_data($w,line_count) - set lno $blame_data($w,final_line) - set cmit $blame_data($w,commit) ++ set n $r_line_count ++ set lno $r_final_line ++ set cmit $r_commit + + while {$n > 0} { - if {[catch {set g g$blame_data($w,line$lno,commit)}]} { - $w_load tag add annotated $lno.0 "$lno.0 lineend + 1c" ++ set lno_e "$lno.0 lineend + 1c" ++ if {[catch {set g g$line_commit($lno)}]} { ++ $w_load tag add annotated $lno.0 $lno_e + } else { - $w_line tag remove g$g $lno.0 "$lno.0 lineend + 1c" - $w_file tag remove g$g $lno.0 "$lno.0 lineend + 1c" ++ $w_line tag remove g$g $lno.0 $lno_e ++ $w_file tag remove g$g $lno.0 $lno_e + } + - set blame_data($w,line$lno,commit) $cmit - set blame_data($w,line$lno,file) $file - $w_line tag add g$cmit $lno.0 "$lno.0 lineend + 1c" - $w_file tag add g$cmit $lno.0 "$lno.0 lineend + 1c" ++ set line_commit($lno) $cmit ++ set line_file($lno) $file ++ $w_line tag add g$cmit $lno.0 $lno_e ++ $w_file tag add g$cmit $lno.0 $lno_e + - if {$blame_data($w,highlight_line) == -1} { ++ if {$highlight_line == -1} { + if {[lindex [$w_file yview] 0] == 0} { + $w_file see $lno.0 - blame_showcommit $w $w_cmit $w_line $w_file $lno ++ _showcommit $this $lno + } - } elseif {$blame_data($w,highlight_line) == $lno} { - blame_showcommit $w $w_cmit $w_line $w_file $lno ++ } elseif {$highlight_line == $lno} { ++ _showcommit $this $lno + } + + incr n -1 + incr lno - incr blame_data($w,blame_lines) ++ incr blame_lines + } + - set hc $blame_data($w,highlight_commit) ++ set hc $highlight_commit + if {$hc ne {} - && [expr {$blame_data($w,$hc,order) + 1}] - == $blame_data($w,$cmit,order)} { - blame_showcommit $w $w_cmit $w_line $w_file \ - $blame_data($w,highlight_line) ++ && [expr {$order($hc) + 1}] == $order($cmit)} { ++ _showcommit $this $highlight_line + } - } elseif {[regexp {^([a-z-]+) (.*)$} $line line header data]} { - set blame_data($w,$blame_data($w,commit),$header) $data ++ } elseif {[regexp {^([a-z-]+) (.*)$} $line line key data]} { ++ set header($r_commit,$key) $data + } + } + + if {[eof $fd]} { + close $fd - set blame_status($w) {Annotation complete.} ++ set status {Annotation complete.} + } else { - blame_incremental_status $w ++ _status $this + } - } ++} ifdeleted { catch {close $fd} } + - proc blame_incremental_status {w} { - global blame_status blame_data - - set have $blame_data($w,blame_lines) - set total $blame_data($w,total_lines) ++method _status {} { ++ set have $blame_lines ++ set total $total_lines + set pdone 0 + if {$total} {set pdone [expr {100 * $have / $total}]} + - set blame_status($w) [format \ ++ set status [format \ + "Loading annotations... %i of %i lines annotated (%2i%%)" \ + $have $total $pdone] +} + - proc blame_click {w w_cmit w_line w_file cur_w pos} { ++method _click {cur_w pos} { + set lno [lindex [split [$cur_w index $pos] .] 0] + if {$lno eq {}} return + ++ set lno_e "$lno.0 + 1 line" + $w_line tag remove in_sel 0.0 end + $w_file tag remove in_sel 0.0 end - $w_line tag add in_sel $lno.0 "$lno.0 + 1 line" - $w_file tag add in_sel $lno.0 "$lno.0 + 1 line" ++ $w_line tag add in_sel $lno.0 $lno_e ++ $w_file tag add in_sel $lno.0 $lno_e + - blame_showcommit $w $w_cmit $w_line $w_file $lno ++ _showcommit $this $lno +} + - set blame_colors { ++variable blame_colors { + #ff4040 + #ff40ff + #4040ff +} + - proc blame_showcommit {w w_cmit w_line w_file lno} { - global blame_colors blame_data repo_config ++method _showcommit {lno} { ++ global repo_config ++ variable blame_colors + - set cmit $blame_data($w,highlight_commit) - if {$cmit ne {}} { - set idx $blame_data($w,$cmit,order) ++ if {$highlight_commit ne {}} { ++ set idx $order($highlight_commit) + set i 0 + foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] ++ set h [lindex $commit_list [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background white + $w_file tag conf g$h -background white + incr i + } + } + + $w_cmit conf -state normal + $w_cmit delete 0.0 end - if {[catch {set cmit $blame_data($w,line$lno,commit)}]} { ++ if {[catch {set cmit $line_commit($lno)}]} { + set cmit {} + $w_cmit insert end "Loading annotation..." + } else { - set idx $blame_data($w,$cmit,order) ++ set idx $order($cmit) + set i 0 + foreach c $blame_colors { - set h [lindex $blame_data($w,commit_list) [expr {$idx - 1 + $i}]] ++ set h [lindex $commit_list [expr {$idx - 1 + $i}]] + $w_line tag conf g$h -background $c + $w_file tag conf g$h -background $c + incr i + } + + set author_name {} + set author_email {} + set author_time {} - catch {set author_name $blame_data($w,$cmit,author)} - catch {set author_email $blame_data($w,$cmit,author-mail)} - catch {set author_time [clock format $blame_data($w,$cmit,author-time)]} ++ catch {set author_name $header($cmit,author)} ++ catch {set author_email $header($cmit,author-mail)} ++ catch {set author_time [clock format \ ++ $header($cmit,author-time) \ ++ -format {%Y-%m-%d %H:%M:%S} ++ ]} + + set committer_name {} + set committer_email {} + set committer_time {} - catch {set committer_name $blame_data($w,$cmit,committer)} - catch {set committer_email $blame_data($w,$cmit,committer-mail)} - catch {set committer_time [clock format $blame_data($w,$cmit,committer-time)]} - - if {[catch {set msg $blame_data($w,$cmit,message)}]} { ++ catch {set committer_name $header($cmit,committer)} ++ catch {set committer_email $header($cmit,committer-mail)} ++ catch {set committer_time [clock format \ ++ $header($cmit,committer-time) \ ++ -format {%Y-%m-%d %H:%M:%S} ++ ]} ++ ++ if {[catch {set msg $header($cmit,message)}]} { + set msg {} + catch { + set fd [open "| git cat-file commit $cmit" r] + fconfigure $fd -encoding binary -translation lf + if {[catch {set enc $repo_config(i18n.commitencoding)}]} { + set enc utf-8 + } + while {[gets $fd line] > 0} { + if {[string match {encoding *} $line]} { + set enc [string tolower [string range $line 9 end]] + } + } + set msg [encoding convertfrom $enc [read $fd]] + set msg [string trim $msg] + close $fd + + set author_name [encoding convertfrom $enc $author_name] + set committer_name [encoding convertfrom $enc $committer_name] + - set blame_data($w,$cmit,author) $author_name - set blame_data($w,$cmit,committer) $committer_name ++ set header($cmit,author) $author_name ++ set header($cmit,committer) $committer_name + } - set blame_data($w,$cmit,message) $msg ++ set header($cmit,message) $msg + } + - $w_cmit insert end "commit $cmit\n" - $w_cmit insert end "Author: $author_name $author_email $author_time\n" - $w_cmit insert end "Committer: $committer_name $committer_email $committer_time\n" - $w_cmit insert end "Original File: [escape_path $blame_data($w,line$lno,file)]\n" - $w_cmit insert end "\n" - $w_cmit insert end $msg ++ $w_cmit insert end "commit $cmit ++Author: $author_name $author_email $author_time ++Committer: $committer_name $committer_email $committer_time ++Original File: [escape_path $line_file($lno)] ++ ++$msg" + } + $w_cmit conf -state disabled + - set blame_data($w,highlight_line) $lno - set blame_data($w,highlight_commit) $cmit ++ set highlight_line $lno ++ set highlight_commit $cmit +} + - proc blame_copycommit {w i pos} { - global blame_data - set lno [lindex [split [$i index $pos] .] 0] - if {![catch {set commit $blame_data($w,line$lno,commit)}]} { ++method _copycommit {} { ++ set pos @$::cursorX,$::cursorY ++ set lno [lindex [split [$::cursorW index $pos] .] 0] ++ if {![catch {set commit $line_commit($lno)}]} { + clipboard clear + clipboard append \ + -format STRING \ + -type STRING \ + -- $commit + } +} ++ ++} diff --cc git-gui/lib/browser.tcl index 631859ae7,000000000..fd86b1121 mode 100644,000000..100644 --- a/git-gui/lib/browser.tcl +++ b/git-gui/lib/browser.tcl @@@ -1,263 -1,0 +1,239 @@@ +# git-gui tree browser +# Copyright (C) 2006, 2007 Shawn Pearce + - set next_browser_id 0 - - proc new_browser {commit} { - global next_browser_id cursor_ptr M1B - global browser_commit browser_status browser_stack browser_path browser_busy - - if {[winfo ismapped .]} { - set w .browser[incr next_browser_id] - set tl $w - toplevel $w - } else { - set w {} - set tl . - } - set w_list $w.list.l - set browser_commit($w_list) $commit - set browser_status($w_list) {Starting...} - set browser_stack($w_list) {} - set browser_path($w_list) $browser_commit($w_list): - set browser_busy($w_list) 1 - - label $w.path -textvariable browser_path($w_list) \ ++class browser { ++ ++field w ++field browser_commit ++field browser_path ++field browser_files {} ++field browser_status {Starting...} ++field browser_stack {} ++field browser_busy 1 ++ ++constructor new {commit} { ++ global cursor_ptr M1B ++ make_toplevel top w ++ wm title $top "[appname] ([reponame]): File Browser" ++ ++ set browser_commit $commit ++ set browser_path $browser_commit: ++ ++ label $w.path \ ++ -textvariable @browser_path \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken \ + -font font_uibold + pack $w.path -anchor w -side top -fill x + + frame $w.list ++ set w_list $w.list.l + text $w_list -background white -borderwidth 0 \ + -cursor $cursor_ptr \ + -state disabled \ + -wrap none \ + -height 20 \ + -width 70 \ + -xscrollcommand [list $w.list.sbx set] \ + -yscrollcommand [list $w.list.sby set] + $w_list tag conf in_sel \ + -background [$w_list cget -foreground] \ + -foreground [$w_list cget -background] + scrollbar $w.list.sbx -orient h -command [list $w_list xview] + scrollbar $w.list.sby -orient v -command [list $w_list yview] + pack $w.list.sbx -side bottom -fill x + pack $w.list.sby -side right -fill y + pack $w_list -side left -fill both -expand 1 + pack $w.list -side top -fill both -expand 1 + - label $w.status -textvariable browser_status($w_list) \ ++ label $w.status \ ++ -textvariable @browser_status \ + -anchor w \ + -justify left \ + -borderwidth 1 \ + -relief sunken + pack $w.status -anchor w -side bottom -fill x + - bind $w_list "browser_click 0 $w_list @%x,%y;break" - bind $w_list "browser_click 1 $w_list @%x,%y;break" - bind $w_list <$M1B-Up> "browser_parent $w_list;break" - bind $w_list <$M1B-Left> "browser_parent $w_list;break" - bind $w_list "browser_move -1 $w_list;break" - bind $w_list "browser_move 1 $w_list;break" - bind $w_list <$M1B-Right> "browser_enter $w_list;break" - bind $w_list "browser_enter $w_list;break" - bind $w_list "browser_page -1 $w_list;break" - bind $w_list "browser_page 1 $w_list;break" ++ bind $w_list "[cb _click 0 @%x,%y];break" ++ bind $w_list "[cb _click 1 @%x,%y];break" ++ bind $w_list <$M1B-Up> "[cb _parent] ;break" ++ bind $w_list <$M1B-Left> "[cb _parent] ;break" ++ bind $w_list "[cb _move -1] ;break" ++ bind $w_list "[cb _move 1] ;break" ++ bind $w_list <$M1B-Right> "[cb _enter] ;break" ++ bind $w_list "[cb _enter] ;break" ++ bind $w_list "[cb _page -1] ;break" ++ bind $w_list "[cb _page 1] ;break" + bind $w_list break + bind $w_list break + - bind $tl "focus $w" - bind $tl " - array unset browser_buffer $w_list - array unset browser_files $w_list - array unset browser_status $w_list - array unset browser_stack $w_list - array unset browser_path $w_list - array unset browser_commit $w_list - array unset browser_busy $w_list - " - wm title $tl "[appname] ([reponame]): File Browser" - ls_tree $w_list $browser_commit($w_list) {} ++ bind $w_list [list focus $w_list] ++ bind $w_list [list delete_this $this] ++ set w $w_list ++ _ls $this $browser_commit ++ return $this +} + - proc browser_move {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return ++method _move {dir} { ++ if {$browser_busy} return + set lno [lindex [split [$w index in_sel.first] .] 0] + incr lno $dir - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + - proc browser_page {dir w} { - global browser_files browser_busy - - if {$browser_busy($w)} return ++method _page {dir} { ++ if {$browser_busy} return + $w yview scroll $dir pages + set lno [expr {int( + [lindex [$w yview] 0] - * [llength $browser_files($w)] ++ * [llength $browser_files] + + 1)}] - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + $w see $lno.0 + } +} + - proc browser_parent {w} { - global browser_files browser_status browser_path - global browser_stack browser_busy - - if {$browser_busy($w)} return - set info [lindex $browser_files($w) 0] ++method _parent {} { ++ if {$browser_busy} return ++ set info [lindex $browser_files 0] + if {[lindex $info 0] eq {parent}} { - set parent [lindex $browser_stack($w) end-1] - set browser_stack($w) [lrange $browser_stack($w) 0 end-2] - if {$browser_stack($w) eq {}} { - regsub {:.*$} $browser_path($w) {:} browser_path($w) ++ set parent [lindex $browser_stack end-1] ++ set browser_stack [lrange $browser_stack 0 end-2] ++ if {$browser_stack eq {}} { ++ regsub {:.*$} $browser_path {:} browser_path + } else { - regsub {/[^/]+$} $browser_path($w) {} browser_path($w) ++ regsub {/[^/]+$} $browser_path {} browser_path + } - set browser_status($w) "Loading $browser_path($w)..." - ls_tree $w [lindex $parent 0] [lindex $parent 1] ++ set browser_status "Loading $browser_path..." ++ _ls $this [lindex $parent 0] [lindex $parent 1] + } +} + - proc browser_enter {w} { - global browser_files browser_status browser_path - global browser_commit browser_stack browser_busy - - if {$browser_busy($w)} return ++method _enter {} { ++ if {$browser_busy} return + set lno [lindex [split [$w index in_sel.first] .] 0] - set info [lindex $browser_files($w) [expr {$lno - 1}]] ++ set info [lindex $browser_files [expr {$lno - 1}]] + if {$info ne {}} { + switch -- [lindex $info 0] { + parent { - browser_parent $w ++ _parent $this + } + tree { + set name [lindex $info 2] + set escn [escape_path $name] - set browser_status($w) "Loading $escn..." - append browser_path($w) $escn - ls_tree $w [lindex $info 1] $name ++ set browser_status "Loading $escn..." ++ append browser_path $escn ++ _ls $this [lindex $info 1] $name + } + blob { + set name [lindex $info 2] + set p {} - foreach n $browser_stack($w) { ++ foreach n $browser_stack { + append p [lindex $n 1] + } + append p $name - show_blame $browser_commit($w) $p ++ blame::new $browser_commit $p + } + } + } +} + - proc browser_click {was_double_click w pos} { - global browser_files browser_busy - - if {$browser_busy($w)} return ++method _click {was_double_click pos} { ++ if {$browser_busy} return + set lno [lindex [split [$w index $pos] .] 0] + focus $w + - if {[lindex $browser_files($w) [expr {$lno - 1}]] ne {}} { ++ if {[lindex $browser_files [expr {$lno - 1}]] ne {}} { + $w tag remove in_sel 0.0 end + $w tag add in_sel $lno.0 [expr {$lno + 1}].0 + if {$was_double_click} { - browser_enter $w ++ _enter $this + } + } +} + - proc ls_tree {w tree_id name} { - global browser_buffer browser_files browser_stack browser_busy - - set browser_buffer($w) {} - set browser_files($w) {} - set browser_busy($w) 1 ++method _ls {tree_id {name {}}} { ++ set browser_buffer {} ++ set browser_files {} ++ set browser_busy 1 + + $w conf -state normal + $w tag remove in_sel 0.0 end + $w delete 0.0 end - if {$browser_stack($w) ne {}} { ++ if {$browser_stack ne {}} { + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon0 \ + -image file_uplevel + $w insert end {[Up To Parent]} - lappend browser_files($w) parent ++ lappend browser_files parent + } - lappend browser_stack($w) [list $tree_id $name] ++ lappend browser_stack [list $tree_id $name] + $w conf -state disabled + + set cmd [list git ls-tree -z $tree_id] + set fd [open "| $cmd" r] + fconfigure $fd -blocking 0 -translation binary -encoding binary - fileevent $fd readable [list read_ls_tree $fd $w] ++ fileevent $fd readable [cb _read $fd] +} + - proc read_ls_tree {fd w} { - global browser_buffer browser_files browser_status browser_busy - - if {![winfo exists $w]} { - catch {close $fd} - return - } - - append browser_buffer($w) [read $fd] - set pck [split $browser_buffer($w) "\0"] - set browser_buffer($w) [lindex $pck end] ++method _read {fd} { ++ append browser_buffer [read $fd] ++ set pck [split $browser_buffer "\0"] ++ set browser_buffer [lindex $pck end] + - set n [llength $browser_files($w)] ++ set n [llength $browser_files] + $w conf -state normal + foreach p [lrange $pck 0 end-1] { + set info [split $p "\t"] + set path [lindex $info 1] + set info [split [lindex $info 0] { }] + set type [lindex $info 1] + set object [lindex $info 2] + + switch -- $type { + blob { + set image file_mod + } + tree { + set image file_dir + append path / + } + default { + set image file_question + } + } + + if {$n > 0} {$w insert end "\n"} + $w image create end \ + -align center -padx 5 -pady 1 \ + -name icon[incr n] \ + -image $image + $w insert end [escape_path $path] - lappend browser_files($w) [list $type $object $path] ++ lappend browser_files [list $type $object $path] + } + $w conf -state disabled + + if {[eof $fd]} { + close $fd - set browser_status($w) Ready. - set browser_busy($w) 0 - array unset browser_buffer $w ++ set browser_status Ready. ++ set browser_busy 0 ++ unset browser_buffer + if {$n > 0} { + $w tag add in_sel 1.0 2.0 + focus -force $w + } + } ++} ifdeleted { ++ catch {close $fd} ++} ++ +} diff --cc git-gui/lib/class.tcl index 000000000,000000000..88b056522 new file mode 100644 --- /dev/null +++ b/git-gui/lib/class.tcl @@@ -1,0 -1,0 +1,154 @@@ ++# git-gui simple class/object fake-alike ++# Copyright (C) 2007 Shawn Pearce ++ ++proc class {class body} { ++ if {[namespace exists $class]} { ++ error "class $class already declared" ++ } ++ namespace eval $class { ++ variable __nextid 0 ++ variable __sealed 0 ++ variable __field_list {} ++ variable __field_array ++ ++ proc cb {name args} { ++ upvar this this ++ set args [linsert $args 0 $name $this] ++ return [uplevel [list namespace code $args]] ++ } ++ } ++ namespace eval $class $body ++} ++ ++proc field {name args} { ++ set class [uplevel {namespace current}] ++ variable ${class}::__sealed ++ variable ${class}::__field_array ++ ++ switch [llength $args] { ++ 0 { set new [list $name] } ++ 1 { set new [list $name [lindex $args 0]] } ++ default { error "wrong # args: field name value?" } ++ } ++ ++ if {$__sealed} { ++ error "class $class is sealed (cannot add new fields)" ++ } ++ ++ if {[catch {set old $__field_array($name)}]} { ++ variable ${class}::__field_list ++ lappend __field_list $new ++ set __field_array($name) 1 ++ } else { ++ error "field $name already declared" ++ } ++} ++ ++proc constructor {name params body} { ++ set class [uplevel {namespace current}] ++ set ${class}::__sealed 1 ++ variable ${class}::__field_list ++ set mbodyc {} ++ ++ append mbodyc {set this } $class ++ append mbodyc {::__o[incr } $class {::__nextid]} \; ++ append mbodyc {namespace eval $this {}} \; ++ ++ if {$__field_list ne {}} { ++ append mbodyc {upvar #0} ++ foreach n $__field_list { ++ set n [lindex $n 0] ++ append mbodyc { ${this}::} $n { } $n ++ regsub -all @$n\\M $body "\${this}::$n" body ++ } ++ append mbodyc \; ++ foreach n $__field_list { ++ if {[llength $n] == 2} { ++ append mbodyc \ ++ {set } [lindex $n 0] { } [list [lindex $n 1]] \; ++ } ++ } ++ } ++ append mbodyc $body ++ namespace eval $class [list proc $name $params $mbodyc] ++} ++ ++proc method {name params body {deleted {}} {del_body {}}} { ++ set class [uplevel {namespace current}] ++ set ${class}::__sealed 1 ++ variable ${class}::__field_list ++ set params [linsert $params 0 this] ++ set mbodyc {} ++ ++ switch $deleted { ++ {} {} ++ ifdeleted { ++ append mbodyc {if {![namespace exists $this]} } ++ append mbodyc \{ $del_body \; return \} \; ++ } ++ default { ++ error "wrong # args: method name args body (ifdeleted body)?" ++ } ++ } ++ ++ set decl {} ++ foreach n $__field_list { ++ set n [lindex $n 0] ++ if {[regexp -- $n\\M $body]} { ++ if { [regexp -all -- $n\\M $body] == 1 ++ && [regexp -all -- \\\$$n\\M $body] == 1 ++ && [regexp -all -- \\\$$n\\( $body] == 0} { ++ regsub -all \\\$$n\\M $body "\[set \${this}::$n\]" body ++ } else { ++ append decl { ${this}::} $n { } $n ++ regsub -all @$n\\M $body "\${this}::$n" body ++ } ++ } ++ } ++ if {$decl ne {}} { ++ append mbodyc {upvar #0} $decl \; ++ } ++ append mbodyc $body ++ namespace eval $class [list proc $name $params $mbodyc] ++} ++ ++proc delete_this {{t {}}} { ++ if {$t eq {}} { ++ upvar this this ++ set t $this ++ } ++ if {[namespace exists $t]} {namespace delete $t} ++} ++ ++proc make_toplevel {t w} { ++ upvar $t top $w pfx ++ if {[winfo ismapped .]} { ++ upvar this this ++ regsub -all {::} $this {__} w ++ set top .$w ++ set pfx $top ++ toplevel $top ++ } else { ++ set top . ++ set pfx {} ++ } ++} ++ ++ ++## auto_mkindex support for class/constructor/method ++## ++auto_mkindex_parser::command class {name body} { ++ variable parser ++ variable contextStack ++ set contextStack [linsert $contextStack 0 $name] ++ $parser eval [list _%@namespace eval $name] $body ++ set contextStack [lrange $contextStack 1 end] ++} ++auto_mkindex_parser::command constructor {name args} { ++ variable index ++ variable scriptFile ++ append index [list set auto_index([fullname $name])] \ ++ [format { [list source [file join $dir %s]]} \ ++ [file split $scriptFile]] "\n" ++} ++ diff --cc git-gui/lib/console.tcl index 75f3e0463,000000000..8c112f3a8 mode 100644,000000..100644 --- a/git-gui/lib/console.tcl +++ b/git-gui/lib/console.tcl @@@ -1,201 -1,0 +1,176 @@@ +# git-gui console support +# Copyright (C) 2006, 2007 Shawn Pearce + - namespace eval console { - - variable next_console_id 0 - variable console_data - variable console_cr - - proc new {short_title long_title} { - variable next_console_id - variable console_data - - set w .console[incr next_console_id] - set console_data($w) [list $short_title $long_title] - return [_init $w] ++class console { ++ ++field t_short ++field t_long ++field w ++field console_cr ++ ++constructor new {short_title long_title} { ++ set t_short $short_title ++ set t_long $long_title ++ _init $this ++ return $this +} + - proc _init {w} { ++method _init {} { + global M1B - variable console_cr - variable console_data ++ make_toplevel top w ++ wm title $top "[appname] ([reponame]): $t_short" ++ set console_cr 1.0 + - set console_cr($w) 1.0 - toplevel $w + frame $w.m - label $w.m.l1 -text "[lindex $console_data($w) 1]:" \ ++ label $w.m.l1 \ ++ -textvariable @t_long \ + -anchor w \ + -justify left \ + -font font_uibold + text $w.m.t \ + -background white -borderwidth 1 \ + -relief sunken \ + -width 80 -height 10 \ + -font font_diff \ + -state disabled \ + -yscrollcommand [list $w.m.sby set] + label $w.m.s -text {Working... please wait...} \ + -anchor w \ + -justify left \ + -font font_uibold + scrollbar $w.m.sby -command [list $w.m.t yview] + pack $w.m.l1 -side top -fill x + pack $w.m.s -side bottom -fill x + pack $w.m.sby -side right -fill y + pack $w.m.t -side left -fill both -expand 1 + pack $w.m -side top -fill both -expand 1 -padx 5 -pady 10 + + menu $w.ctxm -tearoff 0 + $w.ctxm add command -label "Copy" \ + -command "tk_textCopy $w.m.t" + $w.ctxm add command -label "Select All" \ + -command "focus $w.m.t;$w.m.t tag add sel 0.0 end" + $w.ctxm add command -label "Copy All" \ + -command " + $w.m.t tag add sel 0.0 end + tk_textCopy $w.m.t + $w.m.t tag remove sel 0.0 end + " + + button $w.ok -text {Close} \ + -state disabled \ + -command "destroy $w" + pack $w.ok -side bottom -anchor e -pady 10 -padx 10 + + bind_button3 $w.m.t "tk_popup $w.ctxm %X %Y" + 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] ([reponame]): [lindex $console_data($w) 0]" - return $w +} + - proc exec {w cmd {after {}}} { ++method exec {cmd {after {}}} { + # -- Cygwin's Tcl tosses the enviroment when we exec our child. + # But most users need that so we have to relogin. :-( + # + if {[is_Cygwin]} { + set cmd [list sh --login -c "cd \"[pwd]\" && [join $cmd { }]"] + } + + # -- Tcl won't let us redirect both stdout and stderr to + # the same pipe. So pass it through cat... + # + set cmd [concat | $cmd |& cat] + + set fd_f [open $cmd r] + fconfigure $fd_f -blocking 0 -translation binary - fileevent $fd_f readable \ - [namespace code [list _read $w $fd_f $after]] ++ fileevent $fd_f readable [cb _read $fd_f $after] +} + - proc _read {w fd after} { - variable console_cr - ++method _read {fd after} { + set buf [read $fd] + if {$buf ne {}} { - if {![winfo exists $w]} {_init $w} ++ if {![winfo exists $w.m.t]} {_init $this} + $w.m.t conf -state normal + set c 0 + set n [string length $buf] + while {$c < $n} { + set cr [string first "\r" $buf $c] + set lf [string first "\n" $buf $c] + if {$cr < 0} {set cr [expr {$n + 1}]} + if {$lf < 0} {set lf [expr {$n + 1}]} + + if {$lf < $cr} { + $w.m.t insert end [string range $buf $c $lf] - set console_cr($w) [$w.m.t index {end -1c}] ++ set console_cr [$w.m.t index {end -1c}] + set c $lf + incr c + } else { - $w.m.t delete $console_cr($w) end ++ $w.m.t delete $console_cr end + $w.m.t insert end "\n" + $w.m.t insert end [string range $buf $c $cr] + set c $cr + incr c + } + } + $w.m.t conf -state disabled + $w.m.t see end + } + + fconfigure $fd -blocking 1 + if {[eof $fd]} { + if {[catch {close $fd}]} { + set ok 0 + } else { + set ok 1 + } + if {$after ne {}} { - uplevel #0 $after $w $ok ++ uplevel #0 $after $ok + } else { - done $w $ok ++ done $this $ok + } + return + } + fconfigure $fd -blocking 0 +} + - proc chain {cmdlist w {ok 1}} { ++method chain {cmdlist {ok 1}} { + if {$ok} { + if {[llength $cmdlist] == 0} { - done $w $ok ++ done $this $ok + return + } + + set cmd [lindex $cmdlist 0] + set cmdlist [lrange $cmdlist 1 end] + + if {[lindex $cmd 0] eq {exec}} { - exec $w \ - [lindex $cmd 1] \ - [namespace code [list chain $cmdlist]] ++ exec $this \ ++ [lrange $cmd 1 end] \ ++ [cb chain $cmdlist] + } else { - uplevel #0 $cmd $cmdlist $w $ok ++ uplevel #0 $cmd [cb chain $cmdlist] + } + } else { - done $w $ok ++ done $this $ok + } +} + - proc done {args} { - variable console_cr - variable console_data - - switch -- [llength $args] { - 2 { - set w [lindex $args 0] - set ok [lindex $args 1] - } - 3 { - set w [lindex $args 1] - set ok [lindex $args 2] - } - default { - error "wrong number of args: done ?ignored? w ok" - } - } - ++method done {ok} { + if {$ok} { - if {[winfo exists $w]} { ++ if {[winfo exists $w.m.s]} { + $w.m.s conf -background green -text {Success} + $w.ok conf -state normal + focus $w.ok + } + } else { - if {![winfo exists $w]} { - _init $w ++ if {![winfo exists $w.m.s]} { ++ _init $this + } + $w.m.s conf -background red -text {Error: Command Failed} + $w.ok conf -state normal + focus $w.ok + } - - array unset console_cr $w - array unset console_data $w ++ delete_this +} + +} diff --cc git-gui/lib/database.tcl index 73058a826,000000000..43e4a289b mode 100644,000000..100644 --- a/git-gui/lib/database.tcl +++ b/git-gui/lib/database.tcl @@@ -1,89 -1,0 +1,89 @@@ +# git-gui object database management support +# Copyright (C) 2006, 2007 Shawn Pearce + +proc do_stats {} { + set fd [open "| git count-objects -v" r] + while {[gets $fd line] > 0} { + if {[regexp {^([^:]+): (\d+)$} $line _ name value]} { + set stats($name) $value + } + } + close $fd + + set packed_sz 0 + foreach p [glob -directory [gitdir objects pack] \ + -type f \ + -nocomplain -- *] { + incr packed_sz [file size $p] + } + if {$packed_sz > 0} { + set stats(size-pack) [expr {$packed_sz / 1024}] + } + + set w .stats_view + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + label $w.header -text {Database Statistics} + pack $w.header -side top -fill x + + frame $w.buttons -border 1 + button $w.buttons.close -text Close \ + -default active \ + -command [list destroy $w] + button $w.buttons.gc -text {Compress Database} \ + -default normal \ + -command "destroy $w;do_gc" + pack $w.buttons.close -side right + pack $w.buttons.gc -side left + pack $w.buttons -side bottom -fill x -pady 10 -padx 10 + + frame $w.stat -borderwidth 1 -relief solid + foreach s { + {count {Number of loose objects}} + {size {Disk space used by loose objects} { KiB}} + {in-pack {Number of packed objects}} + {packs {Number of packs}} + {size-pack {Disk space used by packed objects} { KiB}} + {prune-packable {Packed objects waiting for pruning}} + {garbage {Garbage files}} + } { + set name [lindex $s 0] + set label [lindex $s 1] + if {[catch {set value $stats($name)}]} continue + if {[llength $s] > 2} { + set value "$value[lindex $s 2]" + } + + label $w.stat.l_$name -text "$label:" -anchor w + label $w.stat.v_$name -text $value -anchor w + grid $w.stat.l_$name $w.stat.v_$name -sticky we -padx {0 5} + } + pack $w.stat -pady 10 -padx 10 + + bind $w "grab $w; focus $w.buttons.close" + bind $w [list destroy $w] + bind $w [list destroy $w] + wm title $w "[appname] ([reponame]): Database Statistics" + tkwait window $w +} + +proc do_gc {} { + set w [console::new {gc} {Compressing the object database}] - console::chain { - {exec {git pack-refs --prune}} - {exec {git reflog expire --all}} - {exec {git repack -a -d -l}} - {exec {git rerere gc}} - } $w ++ console::chain $w { ++ {exec git pack-refs --prune} ++ {exec git reflog expire --all} ++ {exec git repack -a -d -l} ++ {exec git rerere gc} ++ } +} + +proc do_fsck_objects {} { + set w [console::new {fsck-objects} \ + {Verifying the object database with fsck-objects}] + set cmd [list git fsck-objects] + lappend cmd --full + lappend cmd --cache + lappend cmd --strict + console::exec $w $cmd +} diff --cc git-gui/lib/merge.tcl index 3dce856e5,000000000..24ed24b3d mode 100644,000000..100644 --- a/git-gui/lib/merge.tcl +++ b/git-gui/lib/merge.tcl @@@ -1,309 -1,0 +1,312 @@@ +# git-gui branch merge support +# Copyright (C) 2006, 2007 Shawn Pearce + +namespace eval merge { + +proc _can_merge {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot merge while amending. + +You must finish amending this commit before starting any type of merge. +} + return 0 + } + + if {[committer_ident] eq {}} {return 0} + if {![lock_index merge]} {return 0} + + # -- Our in memory state should match the repository. + # + repository_state curType curHEAD curMERGE_HEAD + if {$commit_type ne $curType || $HEAD ne $curHEAD} { + info_popup {Last scanned state does not match repository state. + +Another Git program has modified this repository since the last scan. A rescan must be performed before a merge can be performed. + +The rescan will be automatically started now. +} + unlock_index + rescan {set ui_status_value {Ready.}} + return 0 + } + + foreach path [array names file_states] { + switch -glob -- [lindex $file_states($path) 0] { + _O { + continue; # and pray it works! + } + U? { + error_popup "You are in the middle of a conflicted merge. + +File [short_path $path] has merge conflicts. + +You must resolve them, add the file, and commit to complete the current merge. Only then can you begin another merge. +" + unlock_index + return 0 + } + ?? { + error_popup "You are in the middle of a change. + +File [short_path $path] is modified. + +You should complete the current commit before starting a merge. Doing so will help you abort a failed merge, should the need arise. +" + unlock_index + return 0 + } + } + } + + return 1 +} + +proc _refs {w list} { + set r {} + foreach i [$w.source.l curselection] { + lappend r [lindex [lindex $list $i] 0] + } + return $r +} + +proc _visualize {w list} { + set revs [_refs $w $list] + if {$revs eq {}} return + lappend revs --not HEAD + do_gitk $revs +} + +proc _start {w list} { + global HEAD ui_status_value current_branch + + set cmd [list git merge] + set names [_refs $w $list] + set revcnt [llength $names] + append cmd { } $names + + if {$revcnt == 0} { + return + } elseif {$revcnt == 1} { + set unit branch + } elseif {$revcnt <= 15} { + set unit branches + + if {[tk_dialog \ + $w.confirm_octopus \ + [wm title $w] \ + "Use octopus merge strategy? + +You are merging $revcnt branches at once. This requires using the octopus merge driver, which may not succeed if there are file-level conflicts. +" \ + question \ + 0 \ + {Cancel} \ + {Use octopus} \ + ] != 1} return + } else { + tk_messageBox \ + -icon error \ + -type ok \ + -title [wm title $w] \ + -parent $w \ + -message "Too many branches selected. + +You have requested to merge $revcnt branches in an octopus merge. This exceeds Git's internal limit of 15 branches per merge. + +Please select fewer branches. To merge more than 15 branches, merge the branches in batches. +" + return + } + + set msg "Merging $current_branch, [join $names {, }]" + set ui_status_value "$msg..." + set cons [console::new "Merge" $msg] - console::exec $cons $cmd [namespace code [list _finish $revcnt]] ++ console::exec $cons $cmd \ ++ [namespace code [list _finish $revcnt $cons]] + bind $w {} + destroy $w +} + +proc _finish {revcnt w ok} { + console::done $w $ok + if {$ok} { + set msg {Merge completed successfully.} + } else { + if {$revcnt != 1} { + info_popup "Octopus merge failed. + +Your merge of $revcnt branches has failed. + +There are file-level conflicts between the branches which must be resolved manually. + +The working directory will now be reset. + +You can attempt this merge again by merging only one branch at a time." $w + + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable \ + [namespace code [list _reset_wait $fd]] + set ui_status_value {Aborting... please wait...} + return + } + + set msg {Merge failed. Conflict resolution is required.} + } + unlock_index + rescan [list set ui_status_value $msg] +} + +proc dialog {} { + global current_branch + global M1B + + if {![_can_merge]} return + + set fmt {list %(objectname) %(*objectname) %(refname) %(subject)} + set cmd [list git for-each-ref --tcl --format=$fmt] + lappend cmd refs/heads + lappend cmd refs/remotes + lappend cmd refs/tags + set fr_fd [open "| $cmd" r] + fconfigure $fr_fd -translation binary + while {[gets $fr_fd line] > 0} { + set line [eval $line] + set ref [lindex $line 2] + regsub ^refs/(heads|remotes|tags)/ $ref {} ref + set subj($ref) [lindex $line 3] + lappend sha1([lindex $line 0]) $ref + if {[lindex $line 1] ne {}} { + lappend sha1([lindex $line 1]) $ref + } + } + close $fr_fd + + set to_show {} + set fr_fd [open "| git rev-list --all --not HEAD"] + while {[gets $fr_fd line] > 0} { + if {[catch {set ref $sha1($line)}]} continue + foreach n $ref { + lappend to_show [list $n $line] + } + } + close $fr_fd + set to_show [lsort -unique $to_show] + + set w .merge_setup + toplevel $w + wm geometry $w "+[winfo rootx .]+[winfo rooty .]" + + set _visualize [namespace code [list _visualize $w $to_show]] + set _start [namespace code [list _start $w $to_show]] + + label $w.header \ + -text "Merge Into $current_branch" \ + -font font_uibold + pack $w.header -side top -fill x + + frame $w.buttons + button $w.buttons.visualize -text Visualize -command $_visualize + pack $w.buttons.visualize -side left + button $w.buttons.create -text Merge -command $_start + pack $w.buttons.create -side right + button $w.buttons.cancel -text {Cancel} -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.source -text {Source Branches} + listbox $w.source.l \ + -height 10 \ + -width 70 \ + -font font_diff \ + -selectmode extended \ + -yscrollcommand [list $w.source.sby set] + scrollbar $w.source.sby -command [list $w.source.l yview] + pack $w.source.sby -side right -fill y + pack $w.source.l -side left -fill both -expand 1 + pack $w.source -fill both -expand 1 -pady 5 -padx 5 + + foreach ref $to_show { + set n [lindex $ref 0] + if {[string length $n] > 20} { + set n "[string range $n 0 16]..." + } + $w.source.l insert end [format {%s %-20s %s} \ + [string range [lindex $ref 1] 0 5] \ + $n \ + $subj([lindex $ref 0])] + } + ++ bind $w.source.l [list event generate %W ] ++ bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l [list event generate %W ] + bind $w.source.l $_visualize + + bind $w <$M1B-Key-Return> $_start + bind $w "grab $w; focus $w.source.l" + bind $w "unlock_index;destroy $w" + bind $w unlock_index + wm title $w "[appname] ([reponame]): Merge" + tkwait window $w +} + +proc reset_hard {} { + global HEAD commit_type file_states + + if {[string match amend* $commit_type]} { + info_popup {Cannot abort while amending. + +You must finish amending this commit. +} + return + } + + if {![lock_index abort]} return + + if {[string match *merge* $commit_type]} { + set op merge + } else { + set op commit + } + + if {[ask_popup "Abort $op? + +Aborting the current $op will cause *ALL* uncommitted changes to be lost. + +Continue with aborting the current $op?"] eq {yes}} { + set fd [open "| git read-tree --reset -u HEAD" r] + fconfigure $fd -blocking 0 -translation binary + fileevent $fd readable [namespace code [list _reset_wait $fd]] + set ui_status_value {Aborting... please wait...} + } else { + unlock_index + } +} + +proc _reset_wait {fd} { + global ui_comm + + read $fd + if {[eof $fd]} { + close $fd + unlock_index + + $ui_comm delete 0.0 end + $ui_comm edit modified false + + catch {file delete [gitdir MERGE_HEAD]} + catch {file delete [gitdir rr-cache MERGE_RR]} + catch {file delete [gitdir SQUASH_MSG]} + catch {file delete [gitdir MERGE_MSG]} + catch {file delete [gitdir GITGUI_MSG]} + + rescan {set ui_status_value {Abort completed. Ready.}} + } +} + +}