{"thread":{"id":"66116","subject":"[PATCH] gitk: add user-defined custom commands","startedAt":"2026-08-04T21:44:01Z","lastAt":"2026-08-11T18:43:34Z","messageCount":9,"participants":["Tim Wiederhake via GitGitGadget","Johannes Sixt","Tim Wiederhake","Junio C Hamano"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"549625","messageId":"pull.2371.git.git.1785879839766.gitgitgadget@gmail.com","threadId":"66116","inReplyTo":null,"subject":"[PATCH] gitk: add user-defined custom commands","fromName":"Tim Wiederhake via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2026-08-04T21:43:59Z","receivedAt":"2026-08-04T21:44:01Z","isPatch":true,"body":"From: Tim Wiederhake <twied@gmx.net>\n\nAllow users to define up to three custom commands each for the commit\nlist and the diff display area.  Commands are configured in a new\n\"Commands\" tab in the preferences dialog, with a name and a command\ntemplate per slot.  Non-empty slots appear in the right-click context\nmenu of the respective area.\n\nCommand templates support placeholder substitution (commit id, commit\ntitle, author name, author date, etc.) and are executed via \"sh -c\"\nto allow for background execution by appending \"&\", and pipeing.  If\na command terminates with exit code 42, its output is displayed;\notherwise only non-zero exit codes are reported.\n\nSigned-off-by: Tim Wiederhake <twied@gmx.net>\nAssisted-by: Claude Opus 4.6\n---\n    gitk: add user-defined custom commands\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-git-2371%2Ftwied%2Fcustom_commands-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-git-2371/twied/custom_commands-v1\nPull-Request: https://github.com/git/git/pull/2371\n\n gitk-git/gitk | 220 +++++++++++++++++++++++++++++++++++++++++++++++++-\n 1 file changed, 217 insertions(+), 3 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 0f3571050b..af9cfd9eec 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3696,9 +3696,11 @@ proc find_ctext_fileinfo {line} {\n }\n \n proc pop_diff_menu {w X Y x y} {\n-    global ctext diff_menu flist_menu_file\n-    global diff_menu_txtpos diff_menu_line\n-    global diff_menu_filebase\n+    global ctext diff_menu flist_menu_file currentid\n+    global diff_menu_txtpos diff_menu_line diff_menu_filebase\n+    global usercmd_dd_name1 usercmd_dd_body1\n+    global usercmd_dd_name2 usercmd_dd_body2\n+    global usercmd_dd_name3 usercmd_dd_body3\n \n     set diff_menu_txtpos [split [$w index \"@$x,$y\"] \".\"]\n     set diff_menu_line [lindex $diff_menu_txtpos 0]\n@@ -3711,6 +3713,10 @@ proc pop_diff_menu {w X Y x y} {\n     if {$f eq {}} return\n     set flist_menu_file [lindex $f 0]\n     set diff_menu_filebase [lindex $f 1]\n+    update_menu_custom_cmds $diff_menu $currentid \\\n+        [list $usercmd_dd_name1 $usercmd_dd_body1 \\\n+              $usercmd_dd_name2 $usercmd_dd_body2 \\\n+              $usercmd_dd_name3 $usercmd_dd_body3]\n     tk_popup $diff_menu $X $Y\n }\n \n@@ -9148,9 +9154,134 @@ proc mstime {} {\n     return [format \"%.3f\" [expr {([clock click -milliseconds] - $startmstime) / 1000.0}]]\n }\n \n+proc update_menu_custom_cmds {menu id cmds} {\n+    if {![info exists ::usercmd_menu_count($menu)]} {\n+        set ::usercmd_menu_count($menu) 0\n+    }\n+\n+    for {set j 0} {$j < $::usercmd_menu_count($menu)} {incr j} {\n+        $menu delete end\n+    }\n+\n+    set ::usercmd_menu_count($menu) 0\n+    foreach {name cmd_template} $cmds {\n+        if {$name ne \"\" && $cmd_template ne \"\"} {\n+            if {$::usercmd_menu_count($menu) == 0} {\n+                $menu add separator\n+                incr ::usercmd_menu_count($menu)\n+            }\n+            $menu add command -label $name \\\n+                -command [list exec_custom_cmd $cmd_template $id]\n+            incr ::usercmd_menu_count($menu)\n+        }\n+    }\n+}\n+\n+proc get_blame_origin {} {\n+    global diff_menu_filebase diff_menu_line flist_menu_file\n+    global currentid cmitmode parents curview cdup\n+\n+    set blame_id \"\"\n+    set blame_line \"\"\n+    catch {\n+        if {$cmitmode eq \"tree\"} {\n+            set line [expr {$diff_menu_line - $diff_menu_filebase}]\n+            set blamefile [file join $cdup $flist_menu_file]\n+            set blame_out [exec git blame -p -L$line,+1 $currentid -- $blamefile]\n+            set first [lindex [split $blame_out \"\\n\"] 0]\n+            set blame_id [lindex $first 0]\n+            set blame_line [lindex $first 1]\n+        } else {\n+            set h [find_hunk_blamespec $diff_menu_filebase $diff_menu_line]\n+            if {$h ne {}} {\n+                set pi [lindex $h 0]\n+                if {$pi > 0} {\n+                    incr pi -1\n+                    set blame_parent [lindex $parents($curview,$currentid) $pi]\n+                    set line [lindex $h 1]\n+                    set blamefile [file join $cdup $flist_menu_file]\n+                    set blame_out [exec git blame -p -L$line,+1 $blame_parent -- $blamefile]\n+                    set first [lindex [split $blame_out \"\\n\"] 0]\n+                    set blame_id [lindex $first 0]\n+                    set blame_line [lindex $first 1]\n+                }\n+            }\n+        }\n+    }\n+    return [list $blame_id $blame_line]\n+}\n+\n+proc get_diff_file {} {\n+    global flist_menu_file\n+    if {[info exists flist_menu_file]} {\n+        return $flist_menu_file\n+    }\n+    return \"\"\n+}\n+\n+proc exec_custom_cmd {cmd_template id} {\n+    global commitinfo markedid\n+\n+    getcommit $id\n+\n+    set blame_computed 0\n+    set blame_id \"\"\n+    set blame_line \"\"\n+\n+    set cmd \"\"\n+    set len [string length $cmd_template]\n+    for {set i 0} {$i < $len} {incr i} {\n+        if {[string index $cmd_template $i] eq \"%\" && $i + 1 < $len} {\n+            set next [string index $cmd_template [expr {$i + 1}]]\n+            if {!$blame_computed && ($next eq \"b\" || $next eq \"l\")} {\n+                set blame [get_blame_origin]\n+                set blame_id [lindex $blame 0]\n+                set blame_line [lindex $blame 1]\n+                set blame_computed 1\n+            }\n+            switch -- $next {\n+                \"%\" { append cmd \"%\" }\n+                \"i\" { append cmd $id }\n+                \"t\" { append cmd [lindex $commitinfo($id) 0] }\n+                \"a\" { append cmd [lindex $commitinfo($id) 1] }\n+                \"d\" { append cmd [lindex $commitinfo($id) 2] }\n+                \"c\" { append cmd [lindex $commitinfo($id) 3] }\n+                \"D\" { append cmd [lindex $commitinfo($id) 4] }\n+                \"m\" { append cmd [lindex $commitinfo($id) 5] }\n+                \"M\" { if {[info exists markedid]} { append cmd $markedid } }\n+                \"b\" { append cmd $blame_id }\n+                \"f\" { append cmd [get_diff_file] }\n+                \"l\" { append cmd $blame_line }\n+                default { append cmd \"%\" $next }\n+            }\n+            incr i\n+        } else {\n+            append cmd [string index $cmd_template $i]\n+        }\n+    }\n+\n+    if {[catch {exec sh -c $cmd 2>@1} output]} {\n+        set exitcode 1\n+        if {[lindex $::errorCode 0] eq \"CHILDSTATUS\"} {\n+            set exitcode [lindex $::errorCode 2]\n+        }\n+        if {$exitcode == 42} {\n+            tk_messageBox -type ok -icon info \\\n+                -title [mc \"Command output\"] -message $output\n+        } else {\n+            tk_messageBox -type ok -icon error \\\n+                -title [mc \"Command failed\"] \\\n+                -message [mc \"Exit code %d:\\n%s\" $exitcode $output]\n+        }\n+    }\n+}\n+\n proc rowmenu {x y id} {\n     global rowctxmenu selectedline rowmenuid curview\n     global nullid nullid2 fakerowmenu mainhead markedid\n+    global usercmd_cl_name1 usercmd_cl_body1\n+    global usercmd_cl_name2 usercmd_cl_body2\n+    global usercmd_cl_name3 usercmd_cl_body3\n \n     stopfinding\n     set rowmenuid $id\n@@ -9182,6 +9313,10 @@ proc rowmenu {x y id} {\n     $menu entryconfigure [mca \"Make patch\"] -state $state\n     $menu entryconfigure [mca \"Diff this -> marked commit\"] -state $mstate\n     $menu entryconfigure [mca \"Diff marked commit -> this\"] -state $mstate\n+    update_menu_custom_cmds $menu $id \\\n+        [list $usercmd_cl_name1 $usercmd_cl_body1 \\\n+              $usercmd_cl_name2 $usercmd_cl_body2 \\\n+              $usercmd_cl_name3 $usercmd_cl_body3]\n     tk_popup $menu $x $y\n }\n \n@@ -11916,6 +12051,60 @@ proc prefspage_fonts {notebook} {\n     return $page\n }\n \n+proc prefspage_commands {notebook} {\n+    global {*}$::config_variables\n+\n+    set page [create_prefs_page $notebook.commands]\n+\n+    ttk::label $page.cl_header -text [mc \"Commit list\"] -font mainfontbold\n+    grid $page.cl_header - - -sticky w -pady 10\n+\n+    ttk::label $page.cl_namelbl -text [mc \"Name\"]\n+    ttk::label $page.cl_cmdlbl -text [mc \"Command\"]\n+    grid x $page.cl_namelbl $page.cl_cmdlbl -sticky w\n+\n+    foreach i {1 2 3} {\n+        ttk::label $page.cl_row${i}lbl -text \"${i}.\"\n+        ttk::entry $page.cl_name${i} -textvariable usercmd_cl_name${i} -width 20\n+        ttk::entry $page.cl_body${i} -textvariable usercmd_cl_body${i} -width 40\n+        grid $page.cl_row${i}lbl $page.cl_name${i} $page.cl_body${i} -sticky ew -padx 2\n+    }\n+\n+    ttk::label $page.dd_header -text [mc \"Diff display\"] -font mainfontbold\n+    grid $page.dd_header - - -sticky w -pady 10\n+\n+    ttk::label $page.dd_namelbl -text [mc \"Name\"]\n+    ttk::label $page.dd_cmdlbl -text [mc \"Command\"]\n+    grid x $page.dd_namelbl $page.dd_cmdlbl -sticky w\n+\n+    foreach i {1 2 3} {\n+        ttk::label $page.dd_row${i}lbl -text \"${i}.\"\n+        ttk::entry $page.dd_name${i} -textvariable usercmd_dd_name${i} -width 20\n+        ttk::entry $page.dd_body${i} -textvariable usercmd_dd_body${i} -width 40\n+        grid $page.dd_row${i}lbl $page.dd_name${i} $page.dd_body${i} -sticky ew -padx 2\n+    }\n+\n+    set explain \"Commands with both name and command filled in will \"\n+    append explain \"appear in the context menu (right-click) of the \"\n+    append explain \"respective area. \"\n+    append explain \"Substitution: %% = literal %, %i = commit id, \"\n+    append explain \"%t = title, %m = message, %a = author, \"\n+    append explain \"%d = author date, %c = committer, \"\n+    append explain \"%D = committer date, %M = marked commit id, \"\n+    append explain \"%f = file path (diff only), \"\n+    append explain \"%b = blame origin id (diff only), \"\n+    append explain \"%l = blame origin line number (diff only). \"\n+    append explain \"Exit code 0 = silent; 42 = show output; \"\n+    append explain \"other = show error. \"\n+    append explain \"Append \\\"&\\\" to run asynchronously.\"\n+    ttk::label $page.explain -text $explain -wraplength 500 -justify left\n+    grid $page.explain - - -sticky w -pady 10 -padx 5\n+\n+    grid columnconfigure $page 2 -weight 1\n+\n+    return $page\n+}\n+\n proc doprefs {} {\n     global oldprefs prefstop\n     global {*}$::config_variables\n@@ -11938,6 +12127,7 @@ proc doprefs {} {\n     lappend pages [prefspage_general $notebook] [mc \"General\"]\n     lappend pages [prefspage_colors $notebook] [mc \"Colors\"]\n     lappend pages [prefspage_fonts $notebook] [mc \"Fonts\"]\n+    lappend pages [prefspage_commands $notebook] [mc \"Commands\"]\n     set col 0\n     foreach {page title} $pages {\n         $notebook add $page -text $title\n@@ -12659,6 +12849,18 @@ set autocopy 0\n set autoselect 1\n set autosellen $hashlength\n set perfile_attrs 0\n+set usercmd_cl_name1 \"\"\n+set usercmd_cl_body1 \"\"\n+set usercmd_cl_name2 \"\"\n+set usercmd_cl_body2 \"\"\n+set usercmd_cl_name3 \"\"\n+set usercmd_cl_body3 \"\"\n+set usercmd_dd_name1 \"\"\n+set usercmd_dd_body1 \"\"\n+set usercmd_dd_name2 \"\"\n+set usercmd_dd_body2 \"\"\n+set usercmd_dd_name3 \"\"\n+set usercmd_dd_body3 \"\"\n \n if {[tk windowingsystem] eq \"aqua\"} {\n     set extdifftool \"opendiff\"\n@@ -12807,6 +13009,18 @@ set config_variables {\n     uifgcolor\n     uifgdisabledcolor\n     uifont\n+    usercmd_cl_body1\n+    usercmd_cl_body2\n+    usercmd_cl_body3\n+    usercmd_cl_name1\n+    usercmd_cl_name2\n+    usercmd_cl_name3\n+    usercmd_dd_body1\n+    usercmd_dd_body2\n+    usercmd_dd_body3\n+    usercmd_dd_name1\n+    usercmd_dd_name2\n+    usercmd_dd_name3\n     visiblerefs\n     web_browser\n     workingfilescirclecolor\n\nbase-commit: 5b2471720c93ee30e5764a19f3d3b3ae9ec9712a\n-- \ngitgitgadget\n"},{"id":"549644","messageId":"82e59e71-5cb0-4a7f-9fc1-e66b367670f0@kdbg.org","threadId":"66116","inReplyTo":"pull.2371.git.git.1785879839766.gitgitgadget@gmail.com","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-08-05T06:59:14Z","receivedAt":"2026-08-05T06:59:37Z","isPatch":true,"body":"Am 04.08.26 um 23:43 schrieb Tim Wiederhake via GitGitGadget:\n> Allow users to define up to three custom commands each for the commit\n> list and the diff display area.  Commands are configured in a new\n> \"Commands\" tab in the preferences dialog, with a name and a command\n> template per slot.  Non-empty slots appear in the right-click context\n> menu of the respective area.\n> \n> Command templates support placeholder substitution (commit id, commit\n> title, author name, author date, etc.) and are executed via \"sh -c\"\n> to allow for background execution by appending \"&\", and pipeing.  If\n> a command terminates with exit code 42, its output is displayed;\n> otherwise only non-zero exit codes are reported.\n\nThanks, but this commit message is a bit lacking: it does not explain\nwhy it is a good idea to have this feature, what purpose it servers. For\nthis reason, it is not possible to tell whether the design is sound and\nwhether the implementation follows the design.\n\n> Signed-off-by: Tim Wiederhake <twied@gmx.net>\n> Assisted-by: Claude Opus 4.6\nThank you for being explicit about AI assistance. Note that this code is\ngoing to be downstreamed to the Git project. Therefore, their AI\nrules[*] must be obeyed.\n\nEqually important is that I want to be sure that you have checked and\ncarefully reviewed and understood everything the AI produced. I am not\ngoing to look at the code until there is sufficient insurance that you\ndid. (Blatantly put, I don't want to review code produced by someone\nelse with AI.)\n\n[*] https://git-scm.com/docs/SubmittingPatches#ai\n\n-- Hannes\n\n"},{"id":"550054","messageId":"76636876b815ac4aaac77eb7b772e2e55234e11a.camel@gmx.net","threadId":"66116","inReplyTo":"82e59e71-5cb0-4a7f-9fc1-e66b367670f0@kdbg.org","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Tim Wiederhake","fromEmail":"twied@gmx.net","sentAt":"2026-08-07T21:39:22Z","receivedAt":"2026-08-07T21:39:24Z","isPatch":true,"body":"On Wed, 2026-08-05 at 08:59 +0200, Johannes Sixt wrote:\n> Am 04.08.26 um 23:43 schrieb Tim Wiederhake via GitGitGadget:\n> > Allow users to define up to three custom commands each for the\n> > commit\n> > list and the diff display area.  Commands are configured in a new\n> > \"Commands\" tab in the preferences dialog, with a name and a command\n> > template per slot.  Non-empty slots appear in the right-click\n> > context\n> > menu of the respective area.\n> > \n> > Command templates support placeholder substitution (commit id,\n> > commit\n> > title, author name, author date, etc.) and are executed via \"sh -c\"\n> > to allow for background execution by appending \"&\", and pipeing. \n> > If\n> > a command terminates with exit code 42, its output is displayed;\n> > otherwise only non-zero exit codes are reported.\n> \n> Thanks, but this commit message is a bit lacking: it does not explain\n> why it is a good idea to have this feature, what purpose it servers.\n> For\n> this reason, it is not possible to tell whether the design is sound\n> and\n> whether the implementation follows the design.\n> \n> > Signed-off-by: Tim Wiederhake <twied@gmx.net>\n> > Assisted-by: Claude Opus 4.6\n> Thank you for being explicit about AI assistance. Note that this code\n> is\n> going to be downstreamed to the Git project. Therefore, their AI\n> rules[*] must be obeyed.\n> \n> Equally important is that I want to be sure that you have checked and\n> carefully reviewed and understood everything the AI produced. I am\n> not\n> going to look at the code until there is sufficient insurance that\n> you\n> did. (Blatantly put, I don't want to review code produced by someone\n> else with AI.)\n> \n> [*] https://git-scm.com/docs/SubmittingPatches#ai\n> \n> -- Hannes\n\nHi Hannes,\n\nthanks for the feedback!\n\nI agree that the commit message is a bit light on the \"why\" side of\nthings. Personally, I wrote this patch because i wanted gitk to have\nthe ability to let me select a line in any commit, rebase to that\ncommit, start an editor with that file and line selected, and after\nquitting the editor amend the commit and continue the rebase. An \"edit\nthis line at that point in history\" function of sorts, because I tend\nto spot typos only twenty commits later.\n\nIf I had proposed a patch to this effect, I am sure it would have been\nnack'd as too niche, wrong tool, or any other reason. But having the\npossibility to define custom commands allows users to adapt gitk to\ntheir workflow instead of the other way round. I am very open to\nsuggestions on how to put this in the commit message without resorting\nto (contrieved) examples.\n\nRegarding the use about AI: I used Claude to produce the initial\nimplementation. I do not write Tcl, and frankly, this patch has not\nchanged that. I have reworked the code - using Claude - until it was\neffectively the Tcl version of code I would have written myself in\nPython or C or any other language that I actually understand. Does that\npass the bar?\n\nRegards,\nTim\n"},{"id":"550068","messageId":"xmqq7bm1d1au.fsf@gitster.g","threadId":"66116","inReplyTo":"pull.2371.git.git.1785879839766.gitgitgadget@gmail.com","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2026-08-07T22:16:09Z","receivedAt":"2026-08-07T22:16:12Z","isPatch":true,"body":"\"Tim Wiederhake via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n\n> +    set len [string length $cmd_template]\n> +    for {set i 0} {$i < $len} {incr i} {\n> +        if {[string index $cmd_template $i] eq \"%\" && $i + 1 < $len} {\n> +            set next [string index $cmd_template [expr {$i + 1}]]\n> +            if {!$blame_computed && ($next eq \"b\" || $next eq \"l\")} {\n> +                set blame [get_blame_origin]\n> +                set blame_id [lindex $blame 0]\n> +                set blame_line [lindex $blame 1]\n> +                set blame_computed 1\n> +            }\n> +            switch -- $next {\n> +                \"%\" { append cmd \"%\" }\n> +                \"i\" { append cmd $id }\n> +                \"t\" { append cmd [lindex $commitinfo($id) 0] }\n> +                \"a\" { append cmd [lindex $commitinfo($id) 1] }\n> +                \"d\" { append cmd [lindex $commitinfo($id) 2] }\n> +                \"c\" { append cmd [lindex $commitinfo($id) 3] }\n> +                \"D\" { append cmd [lindex $commitinfo($id) 4] }\n> +                \"m\" { append cmd [lindex $commitinfo($id) 5] }\n> +                \"M\" { if {[info exists markedid]} { append cmd $markedid } }\n> +                \"b\" { append cmd $blame_id }\n> +                \"f\" { append cmd [get_diff_file] }\n> +                \"l\" { append cmd $blame_line }\n> +                default { append cmd \"%\" $next }\n> +            }\n> +            incr i\n> +        } else {\n> +            append cmd [string index $cmd_template $i]\n> +        }\n> +    }\n> +\n> +    if {[catch {exec sh -c $cmd 2>@1} output]} {\n\nWhat do various members of $commitinfo field have?  I presume that\ntitle and message are pretty much free text under control of anybody\nwho can write to the repository and entice you to run this command,\nso running with \"sh -c $cmd\" would require $cmd to be quoting the\npayload properly, or you'd be opening yourself to be an arbitrary\ncommand execution, no?  With template \"echo '%t'\" you thought you\nare just printing the title but if the title has \"title?'; echo no'\" in\nit, wouldn't cmd end up being \n\n\techo 'title?'; echo no''\n\nand a more creative type can use something other than \"echo no\", to\nhave a process run under your name and do more interesting things,\nright?\n\nNote that I no longer speak Tcl (even though I admit I used to), so\nif there is some \"magic\" that makes use of $cmd in {exec sh -c $cmd}\nsafe, the above may be missing the mark by a mile.\n"},{"id":"550116","messageId":"00ed1592-b2cf-4844-8174-fb087fe0b0fe@kdbg.org","threadId":"66116","inReplyTo":"76636876b815ac4aaac77eb7b772e2e55234e11a.camel@gmx.net","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-08-09T08:33:26Z","receivedAt":"2026-08-09T08:33:30Z","isPatch":true,"body":"Am 07.08.26 um 23:39 schrieb Tim Wiederhake:\n> If I had proposed a patch to this effect, I am sure it would have been\n> nack'd as too niche, wrong tool, or any other reason. But having the\n> possibility to define custom commands allows users to adapt gitk to\n> their workflow instead of the other way round. I am very open to\n> suggestions on how to put this in the commit message without resorting\n> to (contrieved) examples.\n\nThe reason why you were unable to sell the change better is that your\nchange is a large set of features in a single commit. If you started\nsmall, it would be much easier to get off the ground.\n\nFor example, start with \"I notice in the diff that a change is not quite\nright. Let me start an editor with the file loaded.\" That is a feature I\ncan understand is useful.\n\nNext, let the editor start with the cursor at a particular line! That's\nquite useful, too, but need not be part of the first step.\n\nThen proceed to a use-case that needs to call `git rebase`.\n\nBut when it comes to author, committer, dates, or blame information as\npotential substitutions, you will have a much harder time to argue that\nthey are useful. Move these features in their own patch. If you do have\na use-case, mention it.\n\nThe gist of it is: make this a patch series that starts small and works\nits way forward with additional features in new commits. Don't add\nfeatures just \"because we can\".\n\n> Regarding the use about AI: I used Claude to produce the initial\n> implementation. I do not write Tcl, and frankly, this patch has not\n> changed that. I have reworked the code - using Claude - until it was\n> effectively the Tcl version of code I would have written myself in\n> Python or C or any other language that I actually understand. Does that\n> pass the bar?\n\nI am not 100% sure. I take it that you understand what the added Tcl\ncode does (that should not bee too difficult even if this is your first\ntime doing Tcl). However, the Git project's guideline says:\n\n> It’s not yet clear that this can be legally satisfied when \n> submitting significant amount of content that has been generated by\n> AI tools.\nSo,... Any advice from the Git community would be appreciated.\n\n-- Hannes\n\n"},{"id":"550117","messageId":"ac611c48-4f95-4bf1-addd-bdc4344e80f3@kdbg.org","threadId":"66116","inReplyTo":"xmqq7bm1d1au.fsf@gitster.g","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-08-09T08:47:59Z","receivedAt":"2026-08-09T08:48:19Z","isPatch":true,"body":"Am 08.08.26 um 00:16 schrieb Junio C Hamano:\n> With template \"echo '%t'\" you thought you\n> are just printing the title but if the title has \"title?'; echo no'\" in\n> it, wouldn't cmd end up being \n> \n> \techo 'title?'; echo no''\n> \n> and a more creative type can use something other than \"echo no\", to\n> have a process run under your name and do more interesting things,\n> right?\n\nA very important observation!\n\n-- Hannes\n\n"},{"id":"550218","messageId":"f3b73531581a6f94410d863339c5683ae8d63e0d.camel@gmx.net","threadId":"66116","inReplyTo":"xmqq7bm1d1au.fsf@gitster.g","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Tim Wiederhake","fromEmail":"twied@gmx.net","sentAt":"2026-08-10T19:28:05Z","receivedAt":"2026-08-10T19:28:10Z","isPatch":true,"body":"On Fri, 2026-08-07 at 15:16 -0700, Junio C Hamano wrote:\n> \"Tim Wiederhake via GitGitGadget\" <gitgitgadget@gmail.com> writes:\n> \n> > +    set len [string length $cmd_template]\n> > +    for {set i 0} {$i < $len} {incr i} {\n> > +        if {[string index $cmd_template $i] eq \"%\" && $i + 1 <\n> > $len} {\n> > +            set next [string index $cmd_template [expr {$i + 1}]]\n> > +            if {!$blame_computed && ($next eq \"b\" || $next eq\n> > \"l\")} {\n> > +                set blame [get_blame_origin]\n> > +                set blame_id [lindex $blame 0]\n> > +                set blame_line [lindex $blame 1]\n> > +                set blame_computed 1\n> > +            }\n> > +            switch -- $next {\n> > +                \"%\" { append cmd \"%\" }\n> > +                \"i\" { append cmd $id }\n> > +                \"t\" { append cmd [lindex $commitinfo($id) 0] }\n> > +                \"a\" { append cmd [lindex $commitinfo($id) 1] }\n> > +                \"d\" { append cmd [lindex $commitinfo($id) 2] }\n> > +                \"c\" { append cmd [lindex $commitinfo($id) 3] }\n> > +                \"D\" { append cmd [lindex $commitinfo($id) 4] }\n> > +                \"m\" { append cmd [lindex $commitinfo($id) 5] }\n> > +                \"M\" { if {[info exists markedid]} { append cmd\n> > $markedid } }\n> > +                \"b\" { append cmd $blame_id }\n> > +                \"f\" { append cmd [get_diff_file] }\n> > +                \"l\" { append cmd $blame_line }\n> > +                default { append cmd \"%\" $next }\n> > +            }\n> > +            incr i\n> > +        } else {\n> > +            append cmd [string index $cmd_template $i]\n> > +        }\n> > +    }\n> > +\n> > +    if {[catch {exec sh -c $cmd 2>@1} output]} {\n> \n> What do various members of $commitinfo field have?  I presume that\n> title and message are pretty much free text under control of anybody\n> who can write to the repository and entice you to run this command,\n> so running with \"sh -c $cmd\" would require $cmd to be quoting the\n> payload properly, or you'd be opening yourself to be an arbitrary\n> command execution, no?  With template \"echo '%t'\" you thought you\n> are just printing the title but if the title has \"title?'; echo no'\"\n> in\n> it, wouldn't cmd end up being \n> \n> \techo 'title?'; echo no''\n> \n> and a more creative type can use something other than \"echo no\", to\n> have a process run under your name and do more interesting things,\n> right?\n> \n> Note that I no longer speak Tcl (even though I admit I used to), so\n> if there is some \"magic\" that makes use of $cmd in {exec sh -c $cmd}\n> safe, the above may be missing the mark by a mile.\n\nYou are right, the code is vulnerable to shell injection. The first\nversion was calling the command directly, so no escaping was necessary.\n\nI added the \"sh -c\" to facilitate process forking (\"&\") and simple exit\ncode manipulation (\"command && exit 42\"). But by now I honestly am not\nsure anymore on what the best solution is: calling the command directly\nand have the user write a wrapper script if necessary; or add code to\nproperly escape all data read from commits and retain the convenience.\n\nOpinions?\n\nTim\n"},{"id":"550220","messageId":"ba558d0c5e08190b4a130efe43b5644c8c0bd571.camel@gmx.net","threadId":"66116","inReplyTo":"00ed1592-b2cf-4844-8174-fb087fe0b0fe@kdbg.org","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Tim Wiederhake","fromEmail":"twied@gmx.net","sentAt":"2026-08-10T19:38:12Z","receivedAt":"2026-08-10T19:38:15Z","isPatch":true,"body":"On Sun, 2026-08-09 at 10:33 +0200, Johannes Sixt wrote:\n> Am 07.08.26 um 23:39 schrieb Tim Wiederhake:\n> > If I had proposed a patch to this effect, I am sure it would have\n> > been\n> > nack'd as too niche, wrong tool, or any other reason. But having\n> > the\n> > possibility to define custom commands allows users to adapt gitk to\n> > their workflow instead of the other way round. I am very open to\n> > suggestions on how to put this in the commit message without\n> > resorting\n> > to (contrieved) examples.\n> \n> The reason why you were unable to sell the change better is that your\n> change is a large set of features in a single commit. If you started\n> small, it would be much easier to get off the ground.\n> \n> For example, start with \"I notice in the diff that a change is not\n> quite\n> right. Let me start an editor with the file loaded.\" That is a\n> feature I\n> can understand is useful.\n> \n> Next, let the editor start with the cursor at a particular line!\n> That's\n> quite useful, too, but need not be part of the first step.\n> \n> Then proceed to a use-case that needs to call `git rebase`.\n> \n> But when it comes to author, committer, dates, or blame information\n> as\n> potential substitutions, you will have a much harder time to argue\n> that\n> they are useful. Move these features in their own patch. If you do\n> have\n> a use-case, mention it.\n> \n> The gist of it is: make this a patch series that starts small and\n> works\n> its way forward with additional features in new commits. Don't add\n> features just \"because we can\".\n> \n> > Regarding the use about AI: I used Claude to produce the initial\n> > implementation. I do not write Tcl, and frankly, this patch has not\n> > changed that. I have reworked the code - using Claude - until it\n> > was\n> > effectively the Tcl version of code I would have written myself in\n> > Python or C or any other language that I actually understand. Does\n> > that\n> > pass the bar?\n> \n> I am not 100% sure. I take it that you understand what the added Tcl\n> code does (that should not bee too difficult even if this is your\n> first\n> time doing Tcl). However, the Git project's guideline says:\n> \n> > It’s not yet clear that this can be legally satisfied when \n> > submitting significant amount of content that has been generated by\n> > AI tools.\n> So,... Any advice from the Git community would be appreciated.\n> \n> -- Hannes\n\nThanks for the continued feedback.\n\nI think there may be a misunderstanding about what this patch does. I\nam not proposing \"add an edit-file button to gitk.\" I am proposing \"let\nusers add their own commands to the context menus.\" The rebase+edit\nworkflow was an example of what becomes possible. It is not the feature\nitself.\n\nThe incremental approach you suggest (first add an editor launch, then\nline numbers, then rebase support) would make sense if I were proposing\na specific built-in workflow. But I am proposing a generic mechanism.\nShipping it with only %f but not %i or %t would produce a half-useful\nextension system that nobody can actually use, existing only to satisfy\nthe review process. The individual substitutions are not independent\nfeatures; they are parameters of a single feature.\n\nI did give a rationale: enabling users to adapt gitk to their workflow\ninstead of the other way around. And I did give a concrete example.\n\"Don't add features just because we can\" does not apply here. The\nfeature has a stated purpose and a demonstrated use case. If the\nrationale is unconvincing, I am happy to hear what would be convincing,\nbut I would rather not have to justify each placeholder individually.\n\nThat said, if splitting the patch into smaller pieces makes review\neasier, I can do that -- as long as we agree that the goal is the\ncomplete mechanism, not a series of standalone features that each need\nto justify their own existence.\n\nRegarding the use of AI: I designed the feature. The UI layout, the\n\"data model\", the substitution mechanism, the execution semantics...\nand described it in English in form of a prompt. An AI translated that\ndescription to Tcl. I then reworked the output through multiple\niterations until the code matched what I would have written myself in a\nlanguage I'm proficient in. I believe this is a valid use of AI. The\nalgorithm and design are mine; the language-specific syntax is not, and\nI don't think it needs to be. AI guidelines exist to guard against\nunreviewed, ununderstood code being dumped into the project (and I very\nmuch agree with that). But that is not what happened here.\n\nRegards,\nTim\n"},{"id":"550321","messageId":"84dd36eb-bc7c-4a35-b0c3-238e6d3c08ed@kdbg.org","threadId":"66116","inReplyTo":"ba558d0c5e08190b4a130efe43b5644c8c0bd571.camel@gmx.net","subject":"Re: [PATCH] gitk: add user-defined custom commands","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2026-08-11T18:43:24Z","receivedAt":"2026-08-11T18:43:34Z","isPatch":true,"body":"Am 10.08.26 um 21:38 schrieb Tim Wiederhake:\n> I think there may be a misunderstanding about what this patch does. I\n> am not proposing \"add an edit-file button to gitk.\" I am proposing \"let\n> users add their own commands to the context menus.\" The rebase+edit\n> workflow was an example of what becomes possible. It is not the feature\n> itself.\n\nI totally understand that you are proposing a way to supply generic\ncommands, and I do not ask for something else, but I was a bit too terse\nin what I said. I meant to say that you can use \"invoke an editor\" as\nthe justification for the generic command that is called from the diff\npanel. And \"git rebase\" can be a justification for a generic command\ncalled from the commit list. (These two kinds of commands should really\nbe added in separate steps, BTW.)\n\n> Shipping it with only %f but not %i or %t would produce a half-useful\n> extension system that nobody can actually use, existing only to satisfy\n> the review process.\n\nYou already get something very useful with only the %f (filename)\nsubstitution, because it can invoke an editor with a suitable file.\n\nDo not underestimate the review process. Presenting the features in\ndigestible pieces is absolutely beneficial. The substitutions lend\nthemselves to be their own commits each (or in small groups per commit).\n\n-- Hannes\n\n"}]}