Volume XXII, number 279Tuesday, October 6, 2026Latest message 33 minutes ago

The Git List

News and archive of git@vger.kernel.org, since April 2005

patchgitk: add user-defined custom commands

9 messages between Aug 4, 2026 and Aug 11, 2026, from Tim Wiederhake via GitGitGadget, Johannes Sixt, Tim Wiederhake, Junio C Hamano.

Plain Markdown or JSON for tools and agents. Diffs are folded; open one to read it.

Tim Wiederhake via GitGitGadgetAug 4, 2026, 21:43 UTC on lore
From: Tim Wiederhake <twied@gmx.net>

Allow users to define up to three custom commands each for the commit list and the diff display area. Commands are configured in a new "Commands" tab in the preferences dialog, with a name and a command template per slot. Non-empty slots appear in the right-click context menu of the respective area.

Command templates support placeholder substitution (commit id, commit title, author name, author date, etc.) and are executed via "sh -c" to allow for background execution by appending "&", and pipeing. If a command terminates with exit code 42, its output is displayed; otherwise only non-zero exit codes are reported.

Signed-off-by: Tim Wiederhake <twied@gmx.net>
Assisted-by: Claude Opus 4.6
---
    gitk: add user-defined custom commands
Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2371%2Ftwied%2Fcustom_commands-v1
Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2371/twied/custom_commands-v1
Pull-Request: https://github.com/git/git/pull/2371
 gitk-git/gitk | 220 +++++++++++++++++++++++++++++++++++++++++++++++++-
 1 file changed, 217 insertions(+), 3 deletions(-)
Show changes to gitk-git/gitk +217 −3
diff --git a/gitk-git/gitk b/gitk-git/gitk
index 0f3571050b..af9cfd9eec 100755
--- a/gitk-git/gitk
+++ b/gitk-git/gitk
@@ -3696,9 +3696,11 @@ proc find_ctext_fileinfo {line} {
 }
 
 proc pop_diff_menu {w X Y x y} {
-    global ctext diff_menu flist_menu_file
-    global diff_menu_txtpos diff_menu_line
-    global diff_menu_filebase
+    global ctext diff_menu flist_menu_file currentid
+    global diff_menu_txtpos diff_menu_line diff_menu_filebase
+    global usercmd_dd_name1 usercmd_dd_body1
+    global usercmd_dd_name2 usercmd_dd_body2
+    global usercmd_dd_name3 usercmd_dd_body3
 
     set diff_menu_txtpos [split [$w index "@$x,$y"] "."]
     set diff_menu_line [lindex $diff_menu_txtpos 0]
@@ -3711,6 +3713,10 @@ proc pop_diff_menu {w X Y x y} {
     if {$f eq {}} return
     set flist_menu_file [lindex $f 0]
     set diff_menu_filebase [lindex $f 1]
+    update_menu_custom_cmds $diff_menu $currentid \
+        [list $usercmd_dd_name1 $usercmd_dd_body1 \
+              $usercmd_dd_name2 $usercmd_dd_body2 \
+              $usercmd_dd_name3 $usercmd_dd_body3]
     tk_popup $diff_menu $X $Y
 }
 
@@ -9148,9 +9154,134 @@ proc mstime {} {
     return [format "%.3f" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]
 }
 
+proc update_menu_custom_cmds {menu id cmds} {
+    if {![info exists ::usercmd_menu_count($menu)]} {
+        set ::usercmd_menu_count($menu) 0
+    }
+
+    for {set j 0} {$j < $::usercmd_menu_count($menu)} {incr j} {
+        $menu delete end
+    }
+
+    set ::usercmd_menu_count($menu) 0
+    foreach {name cmd_template} $cmds {
+        if {$name ne "" && $cmd_template ne ""} {
+            if {$::usercmd_menu_count($menu) == 0} {
+                $menu add separator
+                incr ::usercmd_menu_count($menu)
+            }
+            $menu add command -label $name \
+                -command [list exec_custom_cmd $cmd_template $id]
+            incr ::usercmd_menu_count($menu)
+        }
+    }
+}
+
+proc get_blame_origin {} {
+    global diff_menu_filebase diff_menu_line flist_menu_file
+    global currentid cmitmode parents curview cdup
+
+    set blame_id ""
+    set blame_line ""
+    catch {
+        if {$cmitmode eq "tree"} {
+            set line [expr {$diff_menu_line - $diff_menu_filebase}]
+            set blamefile [file join $cdup $flist_menu_file]
+            set blame_out [exec git blame -p -L$line,+1 $currentid -- $blamefile]
+            set first [lindex [split $blame_out "\n"] 0]
+            set blame_id [lindex $first 0]
+            set blame_line [lindex $first 1]
+        } else {
+            set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]
+            if {$h ne {}} {
+                set pi [lindex $h 0]
+                if {$pi > 0} {
+                    incr pi -1
+                    set blame_parent [lindex $parents($curview,$currentid) $pi]
+                    set line [lindex $h 1]
+                    set blamefile [file join $cdup $flist_menu_file]
+                    set blame_out [exec git blame -p -L$line,+1 $blame_parent -- $blamefile]
+                    set first [lindex [split $blame_out "\n"] 0]
+                    set blame_id [lindex $first 0]
+                    set blame_line [lindex $first 1]
+                }
+            }
+        }
+    }
+    return [list $blame_id $blame_line]
+}
+
+proc get_diff_file {} {
+    global flist_menu_file
+    if {[info exists flist_menu_file]} {
+        return $flist_menu_file
+    }
+    return ""
+}
+
+proc exec_custom_cmd {cmd_template id} {
+    global commitinfo markedid
+
+    getcommit $id
+
+    set blame_computed 0
+    set blame_id ""
+    set blame_line ""
+
+    set cmd ""
+    set len [string length $cmd_template]
+    for {set i 0} {$i < $len} {incr i} {
+        if {[string index $cmd_template $i] eq "%" && $i + 1 < $len} {
+            set next [string index $cmd_template [expr {$i + 1}]]
+            if {!$blame_computed && ($next eq "b" || $next eq "l")} {
+                set blame [get_blame_origin]
+                set blame_id [lindex $blame 0]
+                set blame_line [lindex $blame 1]
+                set blame_computed 1
+            }
+            switch -- $next {
+                "%" { append cmd "%" }
+                "i" { append cmd $id }
+                "t" { append cmd [lindex $commitinfo($id) 0] }
+                "a" { append cmd [lindex $commitinfo($id) 1] }
+                "d" { append cmd [lindex $commitinfo($id) 2] }
+                "c" { append cmd [lindex $commitinfo($id) 3] }
+                "D" { append cmd [lindex $commitinfo($id) 4] }
+                "m" { append cmd [lindex $commitinfo($id) 5] }
+                "M" { if {[info exists markedid]} { append cmd $markedid } }
+                "b" { append cmd $blame_id }
+                "f" { append cmd [get_diff_file] }
+                "l" { append cmd $blame_line }
+                default { append cmd "%" $next }
+            }
+            incr i
+        } else {
+            append cmd [string index $cmd_template $i]
+        }
+    }
+
+    if {[catch {exec sh -c $cmd 2>@1} output]} {
+        set exitcode 1
+        if {[lindex $::errorCode 0] eq "CHILDSTATUS"} {
+            set exitcode [lindex $::errorCode 2]
+        }
+        if {$exitcode == 42} {
+            tk_messageBox -type ok -icon info \
+                -title [mc "Command output"] -message $output
+        } else {
+            tk_messageBox -type ok -icon error \
+                -title [mc "Command failed"] \
+                -message [mc "Exit code %d:\n%s" $exitcode $output]
+        }
+    }
+}
+
 proc rowmenu {x y id} {
     global rowctxmenu selectedline rowmenuid curview
     global nullid nullid2 fakerowmenu mainhead markedid
+    global usercmd_cl_name1 usercmd_cl_body1
+    global usercmd_cl_name2 usercmd_cl_body2
+    global usercmd_cl_name3 usercmd_cl_body3
 
     stopfinding
     set rowmenuid $id
@@ -9182,6 +9313,10 @@ proc rowmenu {x y id} {
     $menu entryconfigure [mca "Make patch"] -state $state
     $menu entryconfigure [mca "Diff this -> marked commit"] -state $mstate
     $menu entryconfigure [mca "Diff marked commit -> this"] -state $mstate
+    update_menu_custom_cmds $menu $id \
+        [list $usercmd_cl_name1 $usercmd_cl_body1 \
+              $usercmd_cl_name2 $usercmd_cl_body2 \
+              $usercmd_cl_name3 $usercmd_cl_body3]
     tk_popup $menu $x $y
 }
 
@@ -11916,6 +12051,60 @@ proc prefspage_fonts {notebook} {
     return $page
 }
 
+proc prefspage_commands {notebook} {
+    global {*}$::config_variables
+
+    set page [create_prefs_page $notebook.commands]
+
+    ttk::label $page.cl_header -text [mc "Commit list"] -font mainfontbold
+    grid $page.cl_header - - -sticky w -pady 10
+
+    ttk::label $page.cl_namelbl -text [mc "Name"]
+    ttk::label $page.cl_cmdlbl -text [mc "Command"]
+    grid x $page.cl_namelbl $page.cl_cmdlbl -sticky w
+
+    foreach i {1 2 3} {
+        ttk::label $page.cl_row${i}lbl -text "${i}."
+        ttk::entry $page.cl_name${i} -textvariable usercmd_cl_name${i} -width 20
+        ttk::entry $page.cl_body${i} -textvariable usercmd_cl_body${i} -width 40
+        grid $page.cl_row${i}lbl $page.cl_name${i} $page.cl_body${i} -sticky ew -padx 2
+    }
+
+    ttk::label $page.dd_header -text [mc "Diff display"] -font mainfontbold
+    grid $page.dd_header - - -sticky w -pady 10
+
+    ttk::label $page.dd_namelbl -text [mc "Name"]
+    ttk::label $page.dd_cmdlbl -text [mc "Command"]
+    grid x $page.dd_namelbl $page.dd_cmdlbl -sticky w
+
+    foreach i {1 2 3} {
+        ttk::label $page.dd_row${i}lbl -text "${i}."
+        ttk::entry $page.dd_name${i} -textvariable usercmd_dd_name${i} -width 20
+        ttk::entry $page.dd_body${i} -textvariable usercmd_dd_body${i} -width 40
+        grid $page.dd_row${i}lbl $page.dd_name${i} $page.dd_body${i} -sticky ew -padx 2
+    }
+
+    set explain "Commands with both name and command filled in will "
+    append explain "appear in the context menu (right-click) of the "
+    append explain "respective area. "
+    append explain "Substitution: %% = literal %, %i = commit id, "
+    append explain "%t = title, %m = message, %a = author, "
+    append explain "%d = author date, %c = committer, "
+    append explain "%D = committer date, %M = marked commit id, "
+    append explain "%f = file path (diff only), "
+    append explain "%b = blame origin id (diff only), "
+    append explain "%l = blame origin line number (diff only). "
+    append explain "Exit code 0 = silent; 42 = show output; "
+    append explain "other = show error. "
+    append explain "Append \"&\" to run asynchronously."
+    ttk::label $page.explain -text $explain -wraplength 500 -justify left
+    grid $page.explain - - -sticky w -pady 10 -padx 5
+
+    grid columnconfigure $page 2 -weight 1
+
+    return $page
+}
+
 proc doprefs {} {
     global oldprefs prefstop
     global {*}$::config_variables
@@ -11938,6 +12127,7 @@ proc doprefs {} {
     lappend pages [prefspage_general $notebook] [mc "General"]
     lappend pages [prefspage_colors $notebook] [mc "Colors"]
     lappend pages [prefspage_fonts $notebook] [mc "Fonts"]
+    lappend pages [prefspage_commands $notebook] [mc "Commands"]
     set col 0
     foreach {page title} $pages {
         $notebook add $page -text $title
@@ -12659,6 +12849,18 @@ set autocopy 0
 set autoselect 1
 set autosellen $hashlength
 set perfile_attrs 0
+set usercmd_cl_name1 ""
+set usercmd_cl_body1 ""
+set usercmd_cl_name2 ""
+set usercmd_cl_body2 ""
+set usercmd_cl_name3 ""
+set usercmd_cl_body3 ""
+set usercmd_dd_name1 ""
+set usercmd_dd_body1 ""
+set usercmd_dd_name2 ""
+set usercmd_dd_body2 ""
+set usercmd_dd_name3 ""
+set usercmd_dd_body3 ""
 
 if {[tk windowingsystem] eq "aqua"} {
     set extdifftool "opendiff"
@@ -12807,6 +13009,18 @@ set config_variables {
     uifgcolor
     uifgdisabledcolor
     uifont
+    usercmd_cl_body1
+    usercmd_cl_body2
+    usercmd_cl_body3
+    usercmd_cl_name1
+    usercmd_cl_name2
+    usercmd_cl_name3
+    usercmd_dd_body1
+    usercmd_dd_body2
+    usercmd_dd_body3
+    usercmd_dd_name1
+    usercmd_dd_name2
+    usercmd_dd_name3
     visiblerefs
     web_browser
     workingfilescirclecolor

base-commit: 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a
-- 
gitgitgadget
Johannes SixtAug 5, 2026, 06:59 UTC in reply to Tim Wiederhake via GitGitGadget on lore

Re: [PATCH] gitk: add user-defined custom commands

Am 04.08.26 um 23:43 schrieb Tim Wiederhake via GitGitGadget:
Show 11 quoted lines
> Allow users to define up to three custom commands each for the commit
> list and the diff display area.  Commands are configured in a new
> "Commands" tab in the preferences dialog, with a name and a command
> template per slot.  Non-empty slots appear in the right-click context
> menu of the respective area.
> 
> Command templates support placeholder substitution (commit id, commit
> title, author name, author date, etc.) and are executed via "sh -c"
> to allow for background execution by appending "&", and pipeing.  If
> a command terminates with exit code 42, its output is displayed;
> otherwise only non-zero exit codes are reported.

Thanks, but this commit message is a bit lacking: it does not explain why it is a good idea to have this feature, what purpose it servers. For this reason, it is not possible to tell whether the design is sound and whether the implementation follows the design.

> Signed-off-by: Tim Wiederhake <twied@gmx.net>
> Assisted-by: Claude Opus 4.6

Thank you for being explicit about AI assistance. Note that this code is going to be downstreamed to the Git project. Therefore, their AI rules[*] must be obeyed.

Equally important is that I want to be sure that you have checked and carefully reviewed and understood everything the AI produced. I am not going to look at the code until there is sufficient insurance that you did. (Blatantly put, I don't want to review code produced by someone else with AI.)

[*] https://git-scm.com/docs/SubmittingPatches#ai
-- Hannes
Tim WiederhakeAug 7, 2026, 21:39 UTC in reply to Johannes Sixt on lore

Re: [PATCH] gitk: add user-defined custom commands

On Wed, 2026-08-05 at 08:59 +0200, Johannes Sixt wrote:
Show 42 quoted lines
> Am 04.08.26 um 23:43 schrieb Tim Wiederhake via GitGitGadget:
> > Allow users to define up to three custom commands each for the
> > commit
> > list and the diff display area.  Commands are configured in a new
> > "Commands" tab in the preferences dialog, with a name and a command
> > template per slot.  Non-empty slots appear in the right-click
> > context
> > menu of the respective area.
> > 
> > Command templates support placeholder substitution (commit id,
> > commit
> > title, author name, author date, etc.) and are executed via "sh -c"
> > to allow for background execution by appending "&", and pipeing. 
> > If
> > a command terminates with exit code 42, its output is displayed;
> > otherwise only non-zero exit codes are reported.
> 
> Thanks, but this commit message is a bit lacking: it does not explain
> why it is a good idea to have this feature, what purpose it servers.
> For
> this reason, it is not possible to tell whether the design is sound
> and
> whether the implementation follows the design.
> 
> > Signed-off-by: Tim Wiederhake <twied@gmx.net>
> > Assisted-by: Claude Opus 4.6
> Thank you for being explicit about AI assistance. Note that this code
> is
> going to be downstreamed to the Git project. Therefore, their AI
> rules[*] must be obeyed.
> 
> Equally important is that I want to be sure that you have checked and
> carefully reviewed and understood everything the AI produced. I am
> not
> going to look at the code until there is sufficient insurance that
> you
> did. (Blatantly put, I don't want to review code produced by someone
> else with AI.)
> 
> [*] https://git-scm.com/docs/SubmittingPatches#ai
> 
> -- Hannes
Hi Hannes,
thanks for the feedback!

I agree that the commit message is a bit light on the "why" side of things. Personally, I wrote this patch because i wanted gitk to have the ability to let me select a line in any commit, rebase to that commit, start an editor with that file and line selected, and after quitting the editor amend the commit and continue the rebase. An "edit this line at that point in history" function of sorts, because I tend to spot typos only twenty commits later.

If I had proposed a patch to this effect, I am sure it would have been nack'd as too niche, wrong tool, or any other reason. But having the possibility to define custom commands allows users to adapt gitk to their workflow instead of the other way round. I am very open to suggestions on how to put this in the commit message without resorting to (contrieved) examples.

Regarding the use about AI: I used Claude to produce the initial implementation. I do not write Tcl, and frankly, this patch has not changed that. I have reworked the code - using Claude - until it was effectively the Tcl version of code I would have written myself in Python or C or any other language that I actually understand. Does that pass the bar?

Regards, Tim

Junio C HamanoAug 7, 2026, 22:16 UTC in reply to Tim Wiederhake via GitGitGadget on lore

Re: [PATCH] gitk: add user-defined custom commands

"Tim Wiederhake via GitGitGadget" <gitgitgadget@gmail.com> writes:
Show 32 quoted lines
> +    set len [string length $cmd_template]
> +    for {set i 0} {$i < $len} {incr i} {
> +        if {[string index $cmd_template $i] eq "%" && $i + 1 < $len} {
> +            set next [string index $cmd_template [expr {$i + 1}]]
> +            if {!$blame_computed && ($next eq "b" || $next eq "l")} {
> +                set blame [get_blame_origin]
> +                set blame_id [lindex $blame 0]
> +                set blame_line [lindex $blame 1]
> +                set blame_computed 1
> +            }
> +            switch -- $next {
> +                "%" { append cmd "%" }
> +                "i" { append cmd $id }
> +                "t" { append cmd [lindex $commitinfo($id) 0] }
> +                "a" { append cmd [lindex $commitinfo($id) 1] }
> +                "d" { append cmd [lindex $commitinfo($id) 2] }
> +                "c" { append cmd [lindex $commitinfo($id) 3] }
> +                "D" { append cmd [lindex $commitinfo($id) 4] }
> +                "m" { append cmd [lindex $commitinfo($id) 5] }
> +                "M" { if {[info exists markedid]} { append cmd $markedid } }
> +                "b" { append cmd $blame_id }
> +                "f" { append cmd [get_diff_file] }
> +                "l" { append cmd $blame_line }
> +                default { append cmd "%" $next }
> +            }
> +            incr i
> +        } else {
> +            append cmd [string index $cmd_template $i]
> +        }
> +    }
> +
> +    if {[catch {exec sh -c $cmd 2>@1} output]} {

What do various members of $commitinfo field have? I presume that title and message are pretty much free text under control of anybody who can write to the repository and entice you to run this command, so running with "sh -c $cmd" would require $cmd to be quoting the payload properly, or you'd be opening yourself to be an arbitrary command execution, no? With template "echo '%t'" you thought you are just printing the title but if the title has "title?'; echo no'" in it, wouldn't cmd end up being

	echo 'title?'; echo no''

and a more creative type can use something other than "echo no", to have a process run under your name and do more interesting things, right?

Note that I no longer speak Tcl (even though I admit I used to), so if there is some "magic" that makes use of $cmd in {exec sh -c $cmd} safe, the above may be missing the mark by a mile.

Johannes SixtAug 9, 2026, 08:33 UTC in reply to Tim Wiederhake on lore

Re: [PATCH] gitk: add user-defined custom commands

Am 07.08.26 um 23:39 schrieb Tim Wiederhake:
Show 6 quoted lines
> If I had proposed a patch to this effect, I am sure it would have been
> nack'd as too niche, wrong tool, or any other reason. But having the
> possibility to define custom commands allows users to adapt gitk to
> their workflow instead of the other way round. I am very open to
> suggestions on how to put this in the commit message without resorting
> to (contrieved) examples.

The reason why you were unable to sell the change better is that your change is a large set of features in a single commit. If you started small, it would be much easier to get off the ground.

For example, start with "I notice in the diff that a change is not quite right. Let me start an editor with the file loaded." That is a feature I can understand is useful.

Next, let the editor start with the cursor at a particular line! That's quite useful, too, but need not be part of the first step.

Then proceed to a use-case that needs to call `git rebase`.

But when it comes to author, committer, dates, or blame information as potential substitutions, you will have a much harder time to argue that they are useful. Move these features in their own patch. If you do have a use-case, mention it.

The gist of it is: make this a patch series that starts small and works its way forward with additional features in new commits. Don't add features just "because we can".

Show 6 quoted lines
> Regarding the use about AI: I used Claude to produce the initial
> implementation. I do not write Tcl, and frankly, this patch has not
> changed that. I have reworked the code - using Claude - until it was
> effectively the Tcl version of code I would have written myself in
> Python or C or any other language that I actually understand. Does that
> pass the bar?

I am not 100% sure. I take it that you understand what the added Tcl code does (that should not bee too difficult even if this is your first time doing Tcl). However, the Git project's guideline says:

> It’s not yet clear that this can be legally satisfied when 
> submitting significant amount of content that has been generated by
> AI tools.
So,... Any advice from the Git community would be appreciated.
-- Hannes
Johannes SixtAug 9, 2026, 08:47 UTC in reply to Junio C Hamano on lore

Re: [PATCH] gitk: add user-defined custom commands

Am 08.08.26 um 00:16 schrieb Junio C Hamano:
Show 9 quoted lines
> With template "echo '%t'" you thought you
> are just printing the title but if the title has "title?'; echo no'" in
> it, wouldn't cmd end up being 
> 
> 	echo 'title?'; echo no''
> 
> and a more creative type can use something other than "echo no", to
> have a process run under your name and do more interesting things,
> right?
A very important observation!
-- Hannes
Tim WiederhakeAug 10, 2026, 19:28 UTC in reply to Junio C Hamano on lore

Re: [PATCH] gitk: add user-defined custom commands

On Fri, 2026-08-07 at 15:16 -0700, Junio C Hamano wrote:
Show 57 quoted lines
> "Tim Wiederhake via GitGitGadget" <gitgitgadget@gmail.com> writes:
> 
> > +    set len [string length $cmd_template]
> > +    for {set i 0} {$i < $len} {incr i} {
> > +        if {[string index $cmd_template $i] eq "%" && $i + 1 <
> > $len} {
> > +            set next [string index $cmd_template [expr {$i + 1}]]
> > +            if {!$blame_computed && ($next eq "b" || $next eq
> > "l")} {
> > +                set blame [get_blame_origin]
> > +                set blame_id [lindex $blame 0]
> > +                set blame_line [lindex $blame 1]
> > +                set blame_computed 1
> > +            }
> > +            switch -- $next {
> > +                "%" { append cmd "%" }
> > +                "i" { append cmd $id }
> > +                "t" { append cmd [lindex $commitinfo($id) 0] }
> > +                "a" { append cmd [lindex $commitinfo($id) 1] }
> > +                "d" { append cmd [lindex $commitinfo($id) 2] }
> > +                "c" { append cmd [lindex $commitinfo($id) 3] }
> > +                "D" { append cmd [lindex $commitinfo($id) 4] }
> > +                "m" { append cmd [lindex $commitinfo($id) 5] }
> > +                "M" { if {[info exists markedid]} { append cmd
> > $markedid } }
> > +                "b" { append cmd $blame_id }
> > +                "f" { append cmd [get_diff_file] }
> > +                "l" { append cmd $blame_line }
> > +                default { append cmd "%" $next }
> > +            }
> > +            incr i
> > +        } else {
> > +            append cmd [string index $cmd_template $i]
> > +        }
> > +    }
> > +
> > +    if {[catch {exec sh -c $cmd 2>@1} output]} {
> 
> What do various members of $commitinfo field have?  I presume that
> title and message are pretty much free text under control of anybody
> who can write to the repository and entice you to run this command,
> so running with "sh -c $cmd" would require $cmd to be quoting the
> payload properly, or you'd be opening yourself to be an arbitrary
> command execution, no?  With template "echo '%t'" you thought you
> are just printing the title but if the title has "title?'; echo no'"
> in
> it, wouldn't cmd end up being 
> 
> 	echo 'title?'; echo no''
> 
> and a more creative type can use something other than "echo no", to
> have a process run under your name and do more interesting things,
> right?
> 
> Note that I no longer speak Tcl (even though I admit I used to), so
> if there is some "magic" that makes use of $cmd in {exec sh -c $cmd}
> safe, the above may be missing the mark by a mile.

You are right, the code is vulnerable to shell injection. The first version was calling the command directly, so no escaping was necessary.

I added the "sh -c" to facilitate process forking ("&") and simple exit code manipulation ("command && exit 42"). But by now I honestly am not sure anymore on what the best solution is: calling the command directly and have the user write a wrapper script if necessary; or add code to properly escape all data read from commits and retain the convenience.

Opinions?
Tim
Tim WiederhakeAug 10, 2026, 19:38 UTC in reply to Johannes Sixt on lore

Re: [PATCH] gitk: add user-defined custom commands

On Sun, 2026-08-09 at 10:33 +0200, Johannes Sixt wrote:
Show 60 quoted lines
> Am 07.08.26 um 23:39 schrieb Tim Wiederhake:
> > If I had proposed a patch to this effect, I am sure it would have
> > been
> > nack'd as too niche, wrong tool, or any other reason. But having
> > the
> > possibility to define custom commands allows users to adapt gitk to
> > their workflow instead of the other way round. I am very open to
> > suggestions on how to put this in the commit message without
> > resorting
> > to (contrieved) examples.
> 
> The reason why you were unable to sell the change better is that your
> change is a large set of features in a single commit. If you started
> small, it would be much easier to get off the ground.
> 
> For example, start with "I notice in the diff that a change is not
> quite
> right. Let me start an editor with the file loaded." That is a
> feature I
> can understand is useful.
> 
> Next, let the editor start with the cursor at a particular line!
> That's
> quite useful, too, but need not be part of the first step.
> 
> Then proceed to a use-case that needs to call `git rebase`.
> 
> But when it comes to author, committer, dates, or blame information
> as
> potential substitutions, you will have a much harder time to argue
> that
> they are useful. Move these features in their own patch. If you do
> have
> a use-case, mention it.
> 
> The gist of it is: make this a patch series that starts small and
> works
> its way forward with additional features in new commits. Don't add
> features just "because we can".
> 
> > Regarding the use about AI: I used Claude to produce the initial
> > implementation. I do not write Tcl, and frankly, this patch has not
> > changed that. I have reworked the code - using Claude - until it
> > was
> > effectively the Tcl version of code I would have written myself in
> > Python or C or any other language that I actually understand. Does
> > that
> > pass the bar?
> 
> I am not 100% sure. I take it that you understand what the added Tcl
> code does (that should not bee too difficult even if this is your
> first
> time doing Tcl). However, the Git project's guideline says:
> 
> > It’s not yet clear that this can be legally satisfied when 
> > submitting significant amount of content that has been generated by
> > AI tools.
> So,... Any advice from the Git community would be appreciated.
> 
> -- Hannes
Thanks for the continued feedback.

I think there may be a misunderstanding about what this patch does. I am not proposing "add an edit-file button to gitk." I am proposing "let users add their own commands to the context menus." The rebase+edit workflow was an example of what becomes possible. It is not the feature itself.

The incremental approach you suggest (first add an editor launch, then line numbers, then rebase support) would make sense if I were proposing a specific built-in workflow. But I am proposing a generic mechanism. Shipping it with only %f but not %i or %t would produce a half-useful extension system that nobody can actually use, existing only to satisfy the review process. The individual substitutions are not independent features; they are parameters of a single feature.

I did give a rationale: enabling users to adapt gitk to their workflow instead of the other way around. And I did give a concrete example. "Don't add features just because we can" does not apply here. The feature has a stated purpose and a demonstrated use case. If the rationale is unconvincing, I am happy to hear what would be convincing, but I would rather not have to justify each placeholder individually.

That said, if splitting the patch into smaller pieces makes review easier, I can do that -- as long as we agree that the goal is the complete mechanism, not a series of standalone features that each need to justify their own existence.

Regarding the use of AI: I designed the feature. The UI layout, the "data model", the substitution mechanism, the execution semantics... and described it in English in form of a prompt. An AI translated that description to Tcl. I then reworked the output through multiple iterations until the code matched what I would have written myself in a language I'm proficient in. I believe this is a valid use of AI. The algorithm and design are mine; the language-specific syntax is not, and I don't think it needs to be. AI guidelines exist to guard against unreviewed, ununderstood code being dumped into the project (and I very much agree with that). But that is not what happened here.

Regards, Tim

Johannes SixtAug 11, 2026, 18:43 UTC in reply to Tim Wiederhake on lore

Re: [PATCH] gitk: add user-defined custom commands

Am 10.08.26 um 21:38 schrieb Tim Wiederhake:
Show 5 quoted lines
> I think there may be a misunderstanding about what this patch does. I
> am not proposing "add an edit-file button to gitk." I am proposing "let
> users add their own commands to the context menus." The rebase+edit
> workflow was an example of what becomes possible. It is not the feature
> itself.

I totally understand that you are proposing a way to supply generic commands, and I do not ask for something else, but I was a bit too terse in what I said. I meant to say that you can use "invoke an editor" as the justification for the generic command that is called from the diff panel. And "git rebase" can be a justification for a generic command called from the commit list. (These two kinds of commands should really be added in separate steps, BTW.)

> Shipping it with only %f but not %i or %t would produce a half-useful
> extension system that nobody can actually use, existing only to satisfy
> the review process.

You already get something very useful with only the %f (filename) substitution, because it can invoke an editor with a suitable file.

Do not underestimate the review process. Presenting the features in digestible pieces is absolutely beneficial. The substitutions lend themselves to be their own commits each (or in small groups per commit).

-- Hannes

Back to recent threads