Skip to content

Commit

Permalink
git-gui: Add a Tools menu for arbitrary commands.
Browse files Browse the repository at this point in the history
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>
  • Loading branch information
Alexander Gavrilov authored and Shawn O. Pearce committed Nov 16, 2008
1 parent 7cf4566 commit 0ce76de
Show file tree
Hide file tree
Showing 3 changed files with 359 additions and 0 deletions.
17 changes: 17 additions & 0 deletions git-gui.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
108 changes: 108 additions & 0 deletions lib/tools.tcl
Original file line number Diff line number Diff line change
@@ -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 changes: 234 additions & 0 deletions lib/tools_dlg.tcl
Original file line number Diff line number Diff line change
@@ -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
}

}

0 comments on commit 0ce76de

Please sign in to comment.