Browse Source

git-gui: Add a Tools menu for arbitrary commands.

Due to the emphasis on scriptability in the git
design, it is impossible to provide 100% complete
GUI. Currently unaccounted areas include git-svn
and other source control system interfaces, TopGit,
all custom scripts.

This problem can be mitigated by providing basic
customization capabilities in Git Gui. This commit
adds a new Tools menu, which can be configured
to contain items invoking arbitrary shell commands.

The interface is powerful enough to allow calling
both batch text programs like git-svn, and GUI editors.
To support the latter use, the commands have access
to the name of the currently selected file through
the environment.

Signed-off-by: Alexander Gavrilov <angavrilov@gmail.com>
Signed-off-by: Shawn O. Pearce <spearce@spearce.org>
maint
Alexander Gavrilov 16 years ago committed by Shawn O. Pearce
parent
commit
0ce76ded1b
  1. 17
      git-gui.sh
  2. 108
      lib/tools.tcl
  3. 234
      lib/tools_dlg.tcl

17
git-gui.sh

@ -2289,6 +2289,9 @@ if {[is_enabled transport]} { @@ -2289,6 +2289,9 @@ if {[is_enabled transport]} {
.mbar add cascade -label [mc Merge] -menu .mbar.merge
.mbar add cascade -label [mc Remote] -menu .mbar.remote
}
if {[is_enabled multicommit] || [is_enabled singlecommit]} {
.mbar add cascade -label [mc Tools] -menu .mbar.tools
}
. configure -menu .mbar

# -- Repository Menu
@ -2563,6 +2566,20 @@ if {[is_MacOSX]} { @@ -2563,6 +2566,20 @@ if {[is_MacOSX]} {
-command do_options
}

# -- Tools Menu
#
if {[is_enabled multicommit] || [is_enabled singlecommit]} {
set tools_menubar .mbar.tools
menu $tools_menubar
$tools_menubar add separator
$tools_menubar add command -label [mc "Add..."] -command tools_add::dialog
$tools_menubar add command -label [mc "Remove..."] -command tools_remove::dialog
set tools_tailcnt 3
if {[array names repo_config guitool.*.cmd] ne {}} {
tools_populate_all
}
}

# -- Help Menu
#
.mbar add cascade -label [mc Help] -menu .mbar.help

108
lib/tools.tcl

@ -0,0 +1,108 @@ @@ -0,0 +1,108 @@
# git-gui Tools menu implementation

proc tools_list {} {
global repo_config

set names {}
foreach item [array names repo_config guitool.*.cmd] {
lappend names [string range $item 8 end-4]
}
return [lsort $names]
}

proc tools_populate_all {} {
global tools_menubar tools_menutbl
global tools_tailcnt

set mbar_end [$tools_menubar index end]
set mbar_base [expr {$mbar_end - $tools_tailcnt}]
if {$mbar_base >= 0} {
$tools_menubar delete 0 $mbar_base
}

array unset tools_menutbl

foreach fullname [tools_list] {
tools_populate_one $fullname
}
}

proc tools_create_item {parent args} {
global tools_menubar tools_tailcnt
if {$parent eq $tools_menubar} {
set pos [expr {[$parent index end]-$tools_tailcnt+1}]
eval [list $parent insert $pos] $args
} else {
eval [list $parent add] $args
}
}

proc tools_populate_one {fullname} {
global tools_menubar tools_menutbl tools_id

if {![info exists tools_id]} {
set tools_id 0
}

set names [split $fullname '/']
set parent $tools_menubar
for {set i 0} {$i < [llength $names]-1} {incr i} {
set subname [join [lrange $names 0 $i] '/']
if {[info exists tools_menutbl($subname)]} {
set parent $tools_menutbl($subname)
} else {
set subid $parent.t$tools_id
tools_create_item $parent cascade \
-label [lindex $names $i] -menu $subid
menu $subid
set tools_menutbl($subname) $subid
set parent $subid
incr tools_id
}
}

tools_create_item $parent command \
-label [lindex $names end] \
-command [list tools_exec $fullname]
}

proc tools_exec {fullname} {
global repo_config env current_diff_path
global current_branch is_detached

if {[is_config_true "guitool.$fullname.needsfile"]} {
if {$current_diff_path eq {}} {
error_popup [mc "Running %s requires a selected file." $fullname]
return
}
}

if {[is_config_true "guitool.$fullname.confirm"]} {
if {[ask_popup [mc "Are you sure you want to run %s?" $fullname]] ne {yes}} {
return
}
}

set env(GIT_GUITOOL) $fullname
set env(FILENAME) $current_diff_path
if {$is_detached} {
set env(CUR_BRANCH) ""
} else {
set env(CUR_BRANCH) $current_branch
}

set cmdline $repo_config(guitool.$fullname.cmd)
if {[is_config_true "guitool.$fullname.noconsole"]} {
exec sh -c $cmdline &
} else {
regsub {/} $fullname { / } title
set w [console::new \
[mc "Tool: %s" $title] \
[mc "Running: %s" $cmdline]]
console::exec $w [list sh -c $cmdline]
}

unset env(GIT_GUITOOL)
unset env(FILENAME)
unset env(CUR_BRANCH)
}

234
lib/tools_dlg.tcl

@ -0,0 +1,234 @@ @@ -0,0 +1,234 @@
# git-gui Tools menu dialogs

class tools_add {

field w ; # widget path
field w_name ; # new remote name widget
field w_cmd ; # new remote location widget

field name {}; # name of the tool
field command {}; # command to execute
field add_global 0; # add to the --global config
field no_console 0; # disable using the console
field needs_file 0; # ensure filename is set
field confirm 0; # ask for confirmation

constructor dialog {} {
global repo_config

make_toplevel top w
wm title $top [append "[appname] ([reponame]): " [mc "Add Tool"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
wm transient $top .
}

label $w.header -text [mc "Add New Tool Command"] -font font_uibold
pack $w.header -side top -fill x

frame $w.buttons
checkbutton $w.buttons.global \
-text [mc "Add globally"] \
-variable @add_global
pack $w.buttons.global -side left -padx 5
button $w.buttons.create -text [mc Add] \
-default active \
-command [cb _add]
pack $w.buttons.create -side right
button $w.buttons.cancel -text [mc 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.desc -text [mc "Tool Details"]

label $w.desc.name_cmnt -anchor w\
-text [mc "Use '/' separators to create a submenu tree:"]
grid x $w.desc.name_cmnt -sticky we -padx {0 5} -pady {0 2}
label $w.desc.name_l -text [mc "Name:"]
set w_name $w.desc.name_t
entry $w_name \
-borderwidth 1 \
-relief sunken \
-width 40 \
-textvariable @name \
-validate key \
-validatecommand [cb _validate_name %d %S]
grid $w.desc.name_l $w_name -sticky we -padx {0 5}

label $w.desc.cmd_l -text [mc "Command:"]
set w_cmd $w.desc.cmd_t
entry $w_cmd \
-borderwidth 1 \
-relief sunken \
-width 40 \
-textvariable @command
grid $w.desc.cmd_l $w_cmd -sticky we -padx {0 5} -pady {0 3}

grid columnconfigure $w.desc 1 -weight 1
pack $w.desc -anchor nw -fill x -pady 5 -padx 5

checkbutton $w.confirm \
-text [mc "Ask for confirmation before running"] \
-variable @confirm
pack $w.confirm -anchor w -pady {5 0} -padx 5

checkbutton $w.noconsole \
-text [mc "Don't show the command output window"] \
-variable @no_console
pack $w.noconsole -anchor w -padx 5

checkbutton $w.needsfile \
-text [mc "Run only if a diff is selected (\$FILENAME not empty)"] \
-variable @needs_file
pack $w.needsfile -anchor w -padx 5

bind $w <Visibility> [cb _visible]
bind $w <Key-Escape> [list destroy $w]
bind $w <Key-Return> [cb _add]\;break
tkwait window $w
}

method _add {} {
global repo_config

if {$name eq {}} {
error_popup [mc "Please supply a name for the tool."]
focus $w_name
return
}

set item "guitool.$name.cmd"

if {[info exists repo_config($item)]} {
error_popup [mc "Tool '%s' already exists." $name]
focus $w_name
return
}

set cmd [list git config]
if {$add_global} { lappend cmd --global }
set items {}
if {$no_console} { lappend items "guitool.$name.noconsole" }
if {$confirm} { lappend items "guitool.$name.confirm" }
if {$needs_file} { lappend items "guitool.$name.needsfile" }

if {[catch {
eval $cmd [list $item $command]
foreach citem $items { eval $cmd [list $citem yes] }
} err]} {
error_popup [mc "Could not add tool:\n%s" $err]
} else {
set repo_config($item) $command
foreach citem $items { set repo_config($citem) yes }

tools_populate_all
}

destroy $w
}

method _validate_name {d S} {
if {$d == 1} {
if {[regexp {[~?*&\[\0\"\\\{]} $S]} {
return 0
}
}
return 1
}

method _visible {} {
grab $w
$w_name icursor end
focus $w_name
}

}

class tools_remove {

field w ; # widget path
field w_names ; # name list

constructor dialog {} {
global repo_config global_config system_config

load_config 1

make_toplevel top w
wm title $top [append "[appname] ([reponame]): " [mc "Remove Tool"]]
if {$top ne {.}} {
wm geometry $top "+[winfo rootx .]+[winfo rooty .]"
wm transient $top .
}

label $w.header -text [mc "Remove Tool Commands"] -font font_uibold
pack $w.header -side top -fill x

frame $w.buttons
button $w.buttons.create -text [mc Remove] \
-default active \
-command [cb _remove]
pack $w.buttons.create -side right
button $w.buttons.cancel -text [mc Cancel] \
-command [list destroy $w]
pack $w.buttons.cancel -side right -padx 5
pack $w.buttons -side bottom -fill x -pady 10 -padx 10

frame $w.list
set w_names $w.list.l
listbox $w_names \
-height 10 \
-width 30 \
-selectmode extended \
-exportselection false \
-yscrollcommand [list $w.list.sby set]
scrollbar $w.list.sby -command [list $w.list.l yview]
pack $w.list.sby -side right -fill y
pack $w.list.l -side left -fill both -expand 1
pack $w.list -fill both -expand 1 -pady 5 -padx 5

set local_cnt 0
foreach fullname [tools_list] {
# Cannot delete system tools
if {[info exists system_config(guitool.$fullname.cmd)]} continue

$w_names insert end $fullname
if {![info exists global_config(guitool.$fullname.cmd)]} {
$w_names itemconfigure end -foreground blue
incr local_cnt
}
}

if {$local_cnt > 0} {
label $w.colorlbl -foreground blue \
-text [mc "(Blue denotes repository-local tools)"]
pack $w.colorlbl -fill x -pady 5 -padx 5
}

bind $w <Visibility> [cb _visible]
bind $w <Key-Escape> [list destroy $w]
bind $w <Key-Return> [cb _remove]\;break
tkwait window $w
}

method _remove {} {
foreach i [$w_names curselection] {
set name [$w_names get $i]

catch { git config --remove-section guitool.$name }
catch { git config --global --remove-section guitool.$name }
}

load_config 0
tools_populate_all

destroy $w
}

method _visible {} {
grab $w
focus $w_names
}

}
Loading…
Cancel
Save