{"thread":{"id":"61991","subject":"[PATCH] gitk: added external diff file rename detection","startedAt":"2024-08-22T09:27:59Z","lastAt":"2025-11-06T18:16:49Z","messageCount":17,"participants":["ToBoMi via GitGitGadget","tobias.boesch@miele.com","Johannes Sixt"],"isPatch":true,"patchVersion":1,"patchTotal":null},"messages":[{"id":"501526","messageId":"pull.1774.git.1724318874608.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":null,"subject":"[PATCH] gitk: added external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-08-22T09:27:54Z","receivedAt":"2024-08-22T09:27:59Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: deboeto <tobias.boesch@miele.com>\n\n* If a file was renamed between commits and\n    an external diff is started through gitk\n    on the THE ORIGINAL FILE NAME (not the\n    renamed one), gitk was unable to open\n    the renamed file in the external diff\n    editor.\n    It failed to fetch the renamed file from\n    git, because it fetched it with the original\n    path in contrast to using the renamed path\n*   gitk now detects the rename and opens the\n    external diff with the original and the RENAMED\n    file instead of no file (it is able to fetch\n    the renamed file now from git with the renamed\n    path/filename)\n* Since git doesn't destinguish between move or\n    rename this also works for moved files\n* External diff detection and usage is optional\n    and has to be enabled in gitk settings\n* External rename detection ist marked\n    EXPERIMENTAL in the settings and disabled\n    by default\n* Showing the renamed file doesn't work when THE\n    RENAMED FILE is selected in gitk and an\n    external diff ist started on that file,\n    because the selected file is not renamed in\n    that commit. It already IS the renamed file.\n\nSigned-off-by: deboeto <tobias.boesch@miele.com>\n---\n    gitk: added external diff file rename detection\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v1\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v1\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\n gitk-git/gitk | 54 ++++++++++++++++++++++++++++++++++++++++++++-------\n 1 file changed, 47 insertions(+), 7 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 7a087f123d7..f7427f6d3f2 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3662,11 +3662,33 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} {\n+    global ctext\n+\n+    set renamed_filenames [list {}]\n+    set filename [file tail $filepath]\n+    set rename_from_text_length 12\n+    set rename_to_text_length 10\n+    set reg_expr_rename_from {^rename from (.*$filename)}\n+    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n+    set reg_expr_rename_to {^rename to (.*)}\n+    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n+    if { ($rename_from_text_index != {})} {\n+        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n+        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+    }\n+    return $renamed_filenames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n     global diffids\n     global extdifftool\n+    global file_rename_detection\n \n     if {[llength $diffids] == 1} {\n         # no reference commit given\n@@ -3692,8 +3714,21 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    if {$file_rename_detection} {\n+        set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n+        set rename_from_filename [lindex $renamed_filenames 1]\n+        set rename_to_filename [lindex $renamed_filenames 2]\n+        if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+            set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+            set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+        } else {\n+            set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+            set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+        }\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n@@ -11577,7 +11612,7 @@ proc create_prefs_page {w} {\n proc prefspage_general {notebook} {\n     global NS maxwidth maxgraphpct showneartags showlocalchanges\n     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n-    global hideremotes want_ttk have_ttk maxrefs web_browser\n+    global hideremotes want_ttk have_ttk maxrefs web_browser file_rename_detection\n \n     set page [create_prefs_page $notebook.general]\n \n@@ -11639,12 +11674,16 @@ proc prefspage_general {notebook} {\n     grid $page.lgen - -sticky w -pady 10\n     ${NS}::checkbutton $page.want_ttk -variable want_ttk \\\n         -text [mc \"Use themed widgets\"]\n+    ${NS}::checkbutton $page.file_rename_detection -variable file_rename_detection \\\n+        -text [mc \"Use ext diff file rename detection\"]\n+    ${NS}::label $page.file_rename_detection_note -text [mc \"(EXPERIMENTAL\\nTries to find the file path of a\\nrenamed file in external diff)\"]\n     if {$have_ttk} {\n         ${NS}::label $page.ttk_note -text [mc \"(change requires restart)\"]\n     } else {\n         ${NS}::label $page.ttk_note -text [mc \"(currently unavailable)\"]\n     }\n     grid x $page.want_ttk $page.ttk_note -sticky w\n+    grid x $page.file_rename_detection $page.file_rename_detection_note -sticky w\n     return $page\n }\n \n@@ -11725,7 +11764,7 @@ proc doprefs {} {\n     global oldprefs prefstop showneartags showlocalchanges\n     global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor\n     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n-    global hideremotes want_ttk have_ttk\n+    global hideremotes want_ttk have_ttk file_rename_detection\n \n     set top .gitkprefs\n     set prefstop $top\n@@ -11734,7 +11773,7 @@ proc doprefs {} {\n         return\n     }\n     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n-                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n         set oldprefs($v) [set $v]\n     }\n     ttk_toplevel $top\n@@ -11860,7 +11899,7 @@ proc prefscan {} {\n     global oldprefs prefstop\n \n     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n-                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n         global $v\n         set $v $oldprefs($v)\n     }\n@@ -12404,6 +12443,7 @@ set autoselect 1\n set autosellen 40\n set perfile_attrs 0\n set want_ttk 1\n+set file_rename_detection 0\n \n if {[tk windowingsystem] eq \"aqua\"} {\n     set extdifftool \"opendiff\"\n@@ -12498,7 +12538,7 @@ config_check_tmp_exists 50\n set config_variables {\n     mainfont textfont uifont tabstop findmergefiles maxgraphpct maxwidth\n     cmitmode wrapcomment autoselect autosellen showneartags maxrefs visiblerefs\n-    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk\n+    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk file_rename_detection\n     bgcolor fgcolor uifgcolor uifgdisabledcolor colors diffcolors mergecolors\n     markbgcolor diffcontext selectbgcolor foundbgcolor currentsearchhitbgcolor\n     extdifftool perfile_attrs headbgcolor headfgcolor headoutlinecolor\n\nbase-commit: b9849e4f7631d80f146d159bf7b60263b3205632\n-- \ngitgitgadget\n"},{"id":"502310","messageId":"pull.1774.v2.git.1725607698680.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.git.1724318874608.gitgitgadget@gmail.com","subject":"[PATCH v2] gitk: added external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2024-09-06T07:28:18Z","receivedAt":"2024-09-06T07:28:22Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\n* If a file was renamed between commits and an external diff is started\n  through gitk on the THE ORIGINAL FILE NAME (not the renamed one),\n  gitk was unable to open the renamed file in the external diff editor.\n  It failed to fetch the renamed file from git, because it fetched it\n  with the original path in contrast to using the renamed path\n* gitk now detects the rename and opens the external diff with the\n  original and the RENAMED file instead of no file (it is able to\n  fetch the renamed file now from git with the renamed path/filename)\n* Since git doesn't destinguish between move or rename this also works\n  for moved files\n* External diff detection and usage is optional and has to be enabled in\n  gitk settings\n* External rename detection ist marked EXPERIMENTAL in the settings\n  and disabled by default\n* Showing the renamed file doesn't work when THE RENAMED FILE is selected\n  in gitk and an external diff ist started on that file, because the\n  selected file is not renamed in that commit. It already IS the renamed\n  file.\n\nSigned-off-by: Tobias Boeesch <tobias.boesch@miele.com>\n---\n    gitk: added external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v2\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v2\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v1:\n\n 1:  4ff4aec82fe ! 1:  6209080cad6 gitk: added external diff file rename detection\n     @@\n       ## Metadata ##\n     -Author: deboeto <tobias.boesch@miele.com>\n     +Author: Tobias Boesch <tobias.boesch@miele.com>\n      \n       ## Commit message ##\n          gitk: added external diff file rename detection\n      \n     -    * If a file was renamed between commits and\n     -        an external diff is started through gitk\n     -        on the THE ORIGINAL FILE NAME (not the\n     -        renamed one), gitk was unable to open\n     -        the renamed file in the external diff\n     -        editor.\n     -        It failed to fetch the renamed file from\n     -        git, because it fetched it with the original\n     -        path in contrast to using the renamed path\n     -    *   gitk now detects the rename and opens the\n     -        external diff with the original and the RENAMED\n     -        file instead of no file (it is able to fetch\n     -        the renamed file now from git with the renamed\n     -        path/filename)\n     -    * Since git doesn't destinguish between move or\n     -        rename this also works for moved files\n     -    * External diff detection and usage is optional\n     -        and has to be enabled in gitk settings\n     -    * External rename detection ist marked\n     -        EXPERIMENTAL in the settings and disabled\n     -        by default\n     -    * Showing the renamed file doesn't work when THE\n     -        RENAMED FILE is selected in gitk and an\n     -        external diff ist started on that file,\n     -        because the selected file is not renamed in\n     -        that commit. It already IS the renamed file.\n     +    * If a file was renamed between commits and an external diff is started\n     +      through gitk on the THE ORIGINAL FILE NAME (not the renamed one),\n     +      gitk was unable to open the renamed file in the external diff editor.\n     +      It failed to fetch the renamed file from git, because it fetched it\n     +      with the original path in contrast to using the renamed path\n     +    * gitk now detects the rename and opens the external diff with the\n     +      original and the RENAMED file instead of no file (it is able to\n     +      fetch the renamed file now from git with the renamed path/filename)\n     +    * Since git doesn't destinguish between move or rename this also works\n     +      for moved files\n     +    * External diff detection and usage is optional and has to be enabled in\n     +      gitk settings\n     +    * External rename detection ist marked EXPERIMENTAL in the settings\n     +      and disabled by default\n     +    * Showing the renamed file doesn't work when THE RENAMED FILE is selected\n     +      in gitk and an external diff ist started on that file, because the\n     +      selected file is not renamed in that commit. It already IS the renamed\n     +      file.\n      \n     -    Signed-off-by: deboeto <tobias.boesch@miele.com>\n     +    Signed-off-by: Tobias Boeesch <tobias.boesch@miele.com>\n      \n       ## gitk-git/gitk ##\n      @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n\n\n gitk-git/gitk | 54 ++++++++++++++++++++++++++++++++++++++++++++-------\n 1 file changed, 47 insertions(+), 7 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 7a087f123d7..f7427f6d3f2 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3662,11 +3662,33 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} {\n+    global ctext\n+\n+    set renamed_filenames [list {}]\n+    set filename [file tail $filepath]\n+    set rename_from_text_length 12\n+    set rename_to_text_length 10\n+    set reg_expr_rename_from {^rename from (.*$filename)}\n+    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n+    set reg_expr_rename_to {^rename to (.*)}\n+    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n+    if { ($rename_from_text_index != {})} {\n+        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n+        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+    }\n+    return $renamed_filenames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n     global diffids\n     global extdifftool\n+    global file_rename_detection\n \n     if {[llength $diffids] == 1} {\n         # no reference commit given\n@@ -3692,8 +3714,21 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    if {$file_rename_detection} {\n+        set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n+        set rename_from_filename [lindex $renamed_filenames 1]\n+        set rename_to_filename [lindex $renamed_filenames 2]\n+        if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+            set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+            set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+        } else {\n+            set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+            set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+        }\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n@@ -11577,7 +11612,7 @@ proc create_prefs_page {w} {\n proc prefspage_general {notebook} {\n     global NS maxwidth maxgraphpct showneartags showlocalchanges\n     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n-    global hideremotes want_ttk have_ttk maxrefs web_browser\n+    global hideremotes want_ttk have_ttk maxrefs web_browser file_rename_detection\n \n     set page [create_prefs_page $notebook.general]\n \n@@ -11639,12 +11674,16 @@ proc prefspage_general {notebook} {\n     grid $page.lgen - -sticky w -pady 10\n     ${NS}::checkbutton $page.want_ttk -variable want_ttk \\\n         -text [mc \"Use themed widgets\"]\n+    ${NS}::checkbutton $page.file_rename_detection -variable file_rename_detection \\\n+        -text [mc \"Use ext diff file rename detection\"]\n+    ${NS}::label $page.file_rename_detection_note -text [mc \"(EXPERIMENTAL\\nTries to find the file path of a\\nrenamed file in external diff)\"]\n     if {$have_ttk} {\n         ${NS}::label $page.ttk_note -text [mc \"(change requires restart)\"]\n     } else {\n         ${NS}::label $page.ttk_note -text [mc \"(currently unavailable)\"]\n     }\n     grid x $page.want_ttk $page.ttk_note -sticky w\n+    grid x $page.file_rename_detection $page.file_rename_detection_note -sticky w\n     return $page\n }\n \n@@ -11725,7 +11764,7 @@ proc doprefs {} {\n     global oldprefs prefstop showneartags showlocalchanges\n     global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor\n     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n-    global hideremotes want_ttk have_ttk\n+    global hideremotes want_ttk have_ttk file_rename_detection\n \n     set top .gitkprefs\n     set prefstop $top\n@@ -11734,7 +11773,7 @@ proc doprefs {} {\n         return\n     }\n     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n-                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n         set oldprefs($v) [set $v]\n     }\n     ttk_toplevel $top\n@@ -11860,7 +11899,7 @@ proc prefscan {} {\n     global oldprefs prefstop\n \n     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n-                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n         global $v\n         set $v $oldprefs($v)\n     }\n@@ -12404,6 +12443,7 @@ set autoselect 1\n set autosellen 40\n set perfile_attrs 0\n set want_ttk 1\n+set file_rename_detection 0\n \n if {[tk windowingsystem] eq \"aqua\"} {\n     set extdifftool \"opendiff\"\n@@ -12498,7 +12538,7 @@ config_check_tmp_exists 50\n set config_variables {\n     mainfont textfont uifont tabstop findmergefiles maxgraphpct maxwidth\n     cmitmode wrapcomment autoselect autosellen showneartags maxrefs visiblerefs\n-    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk\n+    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk file_rename_detection\n     bgcolor fgcolor uifgcolor uifgdisabledcolor colors diffcolors mergecolors\n     markbgcolor diffcontext selectbgcolor foundbgcolor currentsearchhitbgcolor\n     extdifftool perfile_attrs headbgcolor headfgcolor headoutlinecolor\n\nbase-commit: 2e7b89e038c0c888acf61f1b4ee5a43d4dd5e94c\n-- \ngitgitgadget\n"},{"id":"503897","messageId":"AS2PR08MB8288F13F795F7B6E1DD1F354E1702@AS2PR08MB8288.eurprd08.prod.outlook.com","threadId":"61991","inReplyTo":"pull.1774.v2.git.1725607698680.gitgitgadget@gmail.com","subject":"AW: [PATCH v2] gitk: added external diff file rename detection","fromName":"tobias.boesch@miele.com","fromEmail":"tobias.boesch@miele.com","sentAt":"2024-10-02T09:47:16Z","receivedAt":"2024-10-02T09:48:03Z","isPatch":true,"sender":{"key":"tobias.boesch@miele.com","avatar":"https://avatars.githubusercontent.com/u/64197724?v=4"},"body":"> -----Ursprüngliche Nachricht-----\n> Von: ToBoMi via GitGitGadget <gitgitgadget@gmail.com>\n> Gesendet: Freitag, 6. September 2024 09:28\n> An: git@vger.kernel.org\n> Cc: Boesch, Tobias <tobias.boesch@miele.com>; Boesch, Tobias\n> <tobias.boesch@miele.com>\n> Betreff: [PATCH v2] gitk: added external diff file rename detection\n>\n> From: Tobias Boesch <tobias.boesch@miele.com>\n>\n> * If a file was renamed between commits and an external diff is started\n>   through gitk on the THE ORIGINAL FILE NAME (not the renamed one),\n>   gitk was unable to open the renamed file in the external diff editor.\n>   It failed to fetch the renamed file from git, because it fetched it\n>   with the original path in contrast to using the renamed path\n> * gitk now detects the rename and opens the external diff with the\n>   original and the RENAMED file instead of no file (it is able to\n>   fetch the renamed file now from git with the renamed path/filename)\n> * Since git doesn't destinguish between move or rename this also works\n>   for moved files\n> * External diff detection and usage is optional and has to be enabled in\n>   gitk settings\n> * External rename detection ist marked EXPERIMENTAL in the settings\n>   and disabled by default\n\nIt would be nice to have this without an additional setting.\nJust enable the feature and remove the option.\n\n> * Showing the renamed file doesn't work when THE RENAMED FILE is selected\n>   in gitk and an external diff ist started on that file, because the\n>   selected file is not renamed in that commit. It already IS the renamed\n>   file.\n\ngitk already shows the exact same diff for both file entries, the file that is renamed and the renamed file.\nThis is why it would be okay to show the same external diff for both file entries.\nIt should be possible from the current code change to also gather the file names the other way round, that is to gather the file path of the file to be renamed from the renamed file path.\nSince gitk doesn't change the diff depending on the file that is selected, the external diff should not switch sides depending on the file being selected.\n\n>\n> Signed-off-by: Tobias Boeesch <tobias.boesch@miele.com>\n> ---\n>     gitk: added external diff file rename detection\n>\n>     Changes since v1:\n>\n>      * Commit message ident\n>      * Commit message line length\n>\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-\n> 1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v2\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-\n> 1774/ToBoMi/detect_renamed_files_when_opening_diff-v2\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1774\n>\n> Range-diff vs v1:\n>\n>  1:  4ff4aec82fe ! 1:  6209080cad6 gitk: added external diff file rename\n> detection\n>      @@\n>        ## Metadata ##\n>      -Author: deboeto <tobias.boesch@miele.com>\n>      +Author: Tobias Boesch <tobias.boesch@miele.com>\n>\n>        ## Commit message ##\n>           gitk: added external diff file rename detection\n>\n>      -    * If a file was renamed between commits and\n>      -        an external diff is started through gitk\n>      -        on the THE ORIGINAL FILE NAME (not the\n>      -        renamed one), gitk was unable to open\n>      -        the renamed file in the external diff\n>      -        editor.\n>      -        It failed to fetch the renamed file from\n>      -        git, because it fetched it with the original\n>      -        path in contrast to using the renamed path\n>      -    *   gitk now detects the rename and opens the\n>      -        external diff with the original and the RENAMED\n>      -        file instead of no file (it is able to fetch\n>      -        the renamed file now from git with the renamed\n>      -        path/filename)\n>      -    * Since git doesn't destinguish between move or\n>      -        rename this also works for moved files\n>      -    * External diff detection and usage is optional\n>      -        and has to be enabled in gitk settings\n>      -    * External rename detection ist marked\n>      -        EXPERIMENTAL in the settings and disabled\n>      -        by default\n>      -    * Showing the renamed file doesn't work when THE\n>      -        RENAMED FILE is selected in gitk and an\n>      -        external diff ist started on that file,\n>      -        because the selected file is not renamed in\n>      -        that commit. It already IS the renamed file.\n>      +    * If a file was renamed between commits and an external diff is started\n>      +      through gitk on the THE ORIGINAL FILE NAME (not the renamed one),\n>      +      gitk was unable to open the renamed file in the external diff editor.\n>      +      It failed to fetch the renamed file from git, because it fetched it\n>      +      with the original path in contrast to using the renamed path\n>      +    * gitk now detects the rename and opens the external diff with the\n>      +      original and the RENAMED file instead of no file (it is able to\n>      +      fetch the renamed file now from git with the renamed path/filename)\n>      +    * Since git doesn't destinguish between move or rename this also works\n>      +      for moved files\n>      +    * External diff detection and usage is optional and has to be enabled in\n>      +      gitk settings\n>      +    * External rename detection ist marked EXPERIMENTAL in the settings\n>      +      and disabled by default\n>      +    * Showing the renamed file doesn't work when THE RENAMED FILE is\n> selected\n>      +      in gitk and an external diff ist started on that file, because the\n>      +      selected file is not renamed in that commit. It already IS the renamed\n>      +      file.\n>\n>      -    Signed-off-by: deboeto <tobias.boesch@miele.com>\n>      +    Signed-off-by: Tobias Boeesch <tobias.boesch@miele.com>\n>\n>        ## gitk-git/gitk ##\n>       @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n>\n>\n>  gitk-git/gitk | 54 ++++++++++++++++++++++++++++++++++++++++++++--\n> -----\n>  1 file changed, 47 insertions(+), 7 deletions(-)\n>\n> diff --git a/gitk-git/gitk b/gitk-git/gitk index 7a087f123d7..f7427f6d3f2\n> 100755\n> --- a/gitk-git/gitk\n> +++ b/gitk-git/gitk\n> @@ -3662,11 +3662,33 @@ proc external_diff_get_one_file {diffid filename\n> diffdir} {\n>                 \"revision $diffid\"]\n>  }\n>\n> +proc check_for_renames_in_diff {filepath} {\n> +    global ctext\n> +\n> +    set renamed_filenames [list {}]\n> +    set filename [file tail $filepath]\n> +    set rename_from_text_length 12\n> +    set rename_to_text_length 10\n> +    set reg_expr_rename_from {^rename from (.*$filename)}\n> +    set reg_expr_rename_from [subst -nobackslashes -nocommands\n> $reg_expr_rename_from]\n> +    set reg_expr_rename_to {^rename to (.*)}\n> +    set rename_from_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_from 0.0]\n> +    if { ($rename_from_text_index != {})} {\n> +        set rename_to_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_to $rename_from_text_index]\n> +        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) }\n> {\n> +            lappend renamed_filenames [$ctext get \"$rename_from_text_index +\n> $rename_from_text_length chars\" \"$rename_from_text_index lineend\"]\n> +            lappend renamed_filenames [$ctext get \"$rename_to_text_index +\n> $rename_to_text_length chars\" \"$rename_to_text_index lineend\"]\n> +        }\n> +    }\n> +    return $renamed_filenames\n> +}\n> +\n>  proc external_diff {} {\n>      global nullid nullid2\n>      global flist_menu_file\n>      global diffids\n>      global extdifftool\n> +    global file_rename_detection\n>\n>      if {[llength $diffids] == 1} {\n>          # no reference commit given\n> @@ -3692,8 +3714,21 @@ proc external_diff {} {\n>      if {$diffdir eq {}} return\n>\n>      # gather files to diff\n> -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    if {$file_rename_detection} {\n> +        set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n> +        set rename_from_filename [lindex $renamed_filenames 1]\n> +        set rename_to_filename [lindex $renamed_filenames 2]\n> +        if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n> +            set difffromfile [external_diff_get_one_file $diffidfrom\n> $rename_from_filename $diffdir]\n> +            set difftofile [external_diff_get_one_file $diffidto $rename_to_filename\n> $diffdir]\n> +        } else {\n> +            set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> +            set difftofile [external_diff_get_one_file $diffidto $flist_menu_file\n> $diffdir]\n> +        }\n> +    } else {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file\n> $diffdir]\n> +    }\n>\n>      if {$difffromfile ne {} && $difftofile ne {}} {\n>          set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile] @@ -\n> 11577,7 +11612,7 @@ proc create_prefs_page {w} {  proc prefspage_general\n> {notebook} {\n>      global NS maxwidth maxgraphpct showneartags showlocalchanges\n>      global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n> -    global hideremotes want_ttk have_ttk maxrefs web_browser\n> +    global hideremotes want_ttk have_ttk maxrefs web_browser\n> + file_rename_detection\n>\n>      set page [create_prefs_page $notebook.general]\n>\n> @@ -11639,12 +11674,16 @@ proc prefspage_general {notebook} {\n>      grid $page.lgen - -sticky w -pady 10\n>      ${NS}::checkbutton $page.want_ttk -variable want_ttk \\\n>          -text [mc \"Use themed widgets\"]\n> +    ${NS}::checkbutton $page.file_rename_detection -variable\n> file_rename_detection \\\n> +        -text [mc \"Use ext diff file rename detection\"]\n> +    ${NS}::label $page.file_rename_detection_note -text [mc\n> + \"(EXPERIMENTAL\\nTries to find the file path of a\\nrenamed file in\n> + external diff)\"]\n>      if {$have_ttk} {\n>          ${NS}::label $page.ttk_note -text [mc \"(change requires restart)\"]\n>      } else {\n>          ${NS}::label $page.ttk_note -text [mc \"(currently unavailable)\"]\n>      }\n>      grid x $page.want_ttk $page.ttk_note -sticky w\n> +    grid x $page.file_rename_detection $page.file_rename_detection_note\n> + -sticky w\n>      return $page\n>  }\n>\n> @@ -11725,7 +11764,7 @@ proc doprefs {} {\n>      global oldprefs prefstop showneartags showlocalchanges\n>      global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor\n>      global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n> -    global hideremotes want_ttk have_ttk\n> +    global hideremotes want_ttk have_ttk file_rename_detection\n>\n>      set top .gitkprefs\n>      set prefstop $top\n> @@ -11734,7 +11773,7 @@ proc doprefs {} {\n>          return\n>      }\n>      foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n> -                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n> +                   limitdiffs tabstop perfile_attrs hideremotes\n> + want_ttk file_rename_detection} {\n>          set oldprefs($v) [set $v]\n>      }\n>      ttk_toplevel $top\n> @@ -11860,7 +11899,7 @@ proc prefscan {} {\n>      global oldprefs prefstop\n>\n>      foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n> -                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n> +                   limitdiffs tabstop perfile_attrs hideremotes\n> + want_ttk file_rename_detection} {\n>          global $v\n>          set $v $oldprefs($v)\n>      }\n> @@ -12404,6 +12443,7 @@ set autoselect 1  set autosellen 40  set\n> perfile_attrs 0  set want_ttk 1\n> +set file_rename_detection 0\n>\n>  if {[tk windowingsystem] eq \"aqua\"} {\n>      set extdifftool \"opendiff\"\n> @@ -12498,7 +12538,7 @@ config_check_tmp_exists 50  set\n> config_variables {\n>      mainfont textfont uifont tabstop findmergefiles maxgraphpct maxwidth\n>      cmitmode wrapcomment autoselect autosellen showneartags maxrefs\n> visiblerefs\n> -    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk\n> +    hideremotes showlocalchanges datetimeformat limitdiffs uicolor\n> + want_ttk file_rename_detection\n>      bgcolor fgcolor uifgcolor uifgdisabledcolor colors diffcolors mergecolors\n>      markbgcolor diffcontext selectbgcolor foundbgcolor\n> currentsearchhitbgcolor\n>      extdifftool perfile_attrs headbgcolor headfgcolor headoutlinecolor\n>\n> base-commit: 2e7b89e038c0c888acf61f1b4ee5a43d4dd5e94c\n> --\n> gitgitgadget\n\n\n-------------------------------------------------------------------------------------------------\nimperial-Werke oHG, Sitz Bünde, Registergericht Bad Oeynhausen - HRA 4825\n"},{"id":"513532","messageId":"pull.1774.v3.git.1741093275742.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v2.git.1725607698680.gitgitgadget@gmail.com","subject":"[PATCH v3] gitk: added external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-03-04T13:01:15Z","receivedAt":"2025-03-04T13:01:20Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\n* If a file was renamed between commits and an external diff is started\n  through gitk on the original or the renamed file name,\n  gitk was unable to open the renamed file in the external diff editor.\n  It failed to fetch the renamed file from git, because it fetched it\n  using its original path in contrast to using the renamed path of the\n  file.\n* With this change gitk detects the rename and opens the external diff\n  with the original and the renamed file instead of no file (it is able\n  to fetch the renamed file path and name now from git).\n* Since git doesn't destinguish between move or rename this also works\n  for moved files.\n* Showing the external diff with the original and the renamed file\n  works when either of the files is selected in gitk.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: added external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v3\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v3\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v2:\n\n 1:  6209080cad6 ! 1:  1a64e989713 gitk: added external diff file rename detection\n     @@ Commit message\n          gitk: added external diff file rename detection\n      \n          * If a file was renamed between commits and an external diff is started\n     -      through gitk on the THE ORIGINAL FILE NAME (not the renamed one),\n     +      through gitk on the original or the renamed file name,\n            gitk was unable to open the renamed file in the external diff editor.\n            It failed to fetch the renamed file from git, because it fetched it\n     -      with the original path in contrast to using the renamed path\n     -    * gitk now detects the rename and opens the external diff with the\n     -      original and the RENAMED file instead of no file (it is able to\n     -      fetch the renamed file now from git with the renamed path/filename)\n     -    * Since git doesn't destinguish between move or rename this also works\n     -      for moved files\n     -    * External diff detection and usage is optional and has to be enabled in\n     -      gitk settings\n     -    * External rename detection ist marked EXPERIMENTAL in the settings\n     -      and disabled by default\n     -    * Showing the renamed file doesn't work when THE RENAMED FILE is selected\n     -      in gitk and an external diff ist started on that file, because the\n     -      selected file is not renamed in that commit. It already IS the renamed\n     +      using its original path in contrast to using the renamed path of the\n            file.\n     +    * With this change gitk detects the rename and opens the external diff\n     +      with the original and the renamed file instead of no file (it is able\n     +      to fetch the renamed file path and name now from git).\n     +    * Since git doesn't destinguish between move or rename this also works\n     +      for moved files.\n     +    * Showing the external diff with the original and the renamed file\n     +      works when either of the files is selected in gitk.\n      \n     -    Signed-off-by: Tobias Boeesch <tobias.boesch@miele.com>\n     +    Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n      \n       ## gitk-git/gitk ##\n      @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n     @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n      +\n      +    set renamed_filenames [list {}]\n      +    set filename [file tail $filepath]\n     -+    set rename_from_text_length 12\n     -+    set rename_to_text_length 10\n     ++    set rename_from_text_identifier_length 12\n     ++    set rename_to_text_identifier_length 10\n      +    set reg_expr_rename_from {^rename from (.*$filename)}\n      +    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n     -+    set reg_expr_rename_to {^rename to (.*)}\n      +    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n      +    if { ($rename_from_text_index != {})} {\n     ++        set reg_expr_rename_to {^rename to (.*)}\n      +        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n      +        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n     -+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_length chars\" \"$rename_from_text_index lineend\"]\n     -+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_length chars\" \"$rename_to_text_index lineend\"]\n     ++            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n     ++            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n     ++        }\n     ++        return $renamed_filenames\n     ++    }\n     ++    set reg_expr_rename_to {^rename to (.*$filename)}\n     ++    set reg_expr_rename_to [subst -nobackslashes -nocommands $reg_expr_rename_to]\n     ++    set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to 0.0]\n     ++    if { ($rename_to_text_index != {})} {\n     ++        set reg_expr_rename_from {^rename from (.*)}\n     ++        set rename_from_text_index [$ctext search -backwards -elide -regexp -- $reg_expr_rename_from $rename_to_text_index]\n     ++        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {}) } {\n     ++            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n     ++            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n      +        }\n     ++        return $renamed_filenames\n      +    }\n     -+    return $renamed_filenames\n      +}\n      +\n       proc external_diff {} {\n           global nullid nullid2\n           global flist_menu_file\n     -     global diffids\n     -     global extdifftool\n     -+    global file_rename_detection\n     - \n     -     if {[llength $diffids] == 1} {\n     -         # no reference commit given\n      @@ gitk-git/gitk: proc external_diff {} {\n           if {$diffdir eq {}} return\n       \n           # gather files to diff\n      -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n      -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n     -+    if {$file_rename_detection} {\n     -+        set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n     -+        set rename_from_filename [lindex $renamed_filenames 1]\n     -+        set rename_to_filename [lindex $renamed_filenames 2]\n     -+        if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n     -+            set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n     -+            set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n     -+        } else {\n     -+            set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n     -+            set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n     -+        }\n     ++    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n     ++    set rename_from_filename [lindex $renamed_filenames 1]\n     ++    set rename_to_filename [lindex $renamed_filenames 2]\n     ++    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n     ++        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n     ++        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n      +    } else {\n      +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n      +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n     @@ gitk-git/gitk: proc external_diff {} {\n       \n           if {$difffromfile ne {} && $difftofile ne {}} {\n               set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n     -@@ gitk-git/gitk: proc create_prefs_page {w} {\n     - proc prefspage_general {notebook} {\n     -     global NS maxwidth maxgraphpct showneartags showlocalchanges\n     -     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n     --    global hideremotes want_ttk have_ttk maxrefs web_browser\n     -+    global hideremotes want_ttk have_ttk maxrefs web_browser file_rename_detection\n     - \n     -     set page [create_prefs_page $notebook.general]\n     - \n     -@@ gitk-git/gitk: proc prefspage_general {notebook} {\n     -     grid $page.lgen - -sticky w -pady 10\n     -     ${NS}::checkbutton $page.want_ttk -variable want_ttk \\\n     -         -text [mc \"Use themed widgets\"]\n     -+    ${NS}::checkbutton $page.file_rename_detection -variable file_rename_detection \\\n     -+        -text [mc \"Use ext diff file rename detection\"]\n     -+    ${NS}::label $page.file_rename_detection_note -text [mc \"(EXPERIMENTAL\\nTries to find the file path of a\\nrenamed file in external diff)\"]\n     -     if {$have_ttk} {\n     -         ${NS}::label $page.ttk_note -text [mc \"(change requires restart)\"]\n     -     } else {\n     -         ${NS}::label $page.ttk_note -text [mc \"(currently unavailable)\"]\n     -     }\n     -     grid x $page.want_ttk $page.ttk_note -sticky w\n     -+    grid x $page.file_rename_detection $page.file_rename_detection_note -sticky w\n     -     return $page\n     - }\n     - \n     -@@ gitk-git/gitk: proc doprefs {} {\n     -     global oldprefs prefstop showneartags showlocalchanges\n     -     global uicolor bgcolor fgcolor ctext diffcolors selectbgcolor markbgcolor\n     -     global tabstop limitdiffs autoselect autosellen extdifftool perfile_attrs\n     --    global hideremotes want_ttk have_ttk\n     -+    global hideremotes want_ttk have_ttk file_rename_detection\n     - \n     -     set top .gitkprefs\n     -     set prefstop $top\n     -@@ gitk-git/gitk: proc doprefs {} {\n     -         return\n     -     }\n     -     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n     --                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n     -+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n     -         set oldprefs($v) [set $v]\n     -     }\n     -     ttk_toplevel $top\n     -@@ gitk-git/gitk: proc prefscan {} {\n     -     global oldprefs prefstop\n     - \n     -     foreach v {maxwidth maxgraphpct showneartags showlocalchanges \\\n     --                   limitdiffs tabstop perfile_attrs hideremotes want_ttk} {\n     -+                   limitdiffs tabstop perfile_attrs hideremotes want_ttk file_rename_detection} {\n     -         global $v\n     -         set $v $oldprefs($v)\n     -     }\n     -@@ gitk-git/gitk: set autoselect 1\n     - set autosellen 40\n     - set perfile_attrs 0\n     - set want_ttk 1\n     -+set file_rename_detection 0\n     - \n     - if {[tk windowingsystem] eq \"aqua\"} {\n     -     set extdifftool \"opendiff\"\n     -@@ gitk-git/gitk: config_check_tmp_exists 50\n     - set config_variables {\n     -     mainfont textfont uifont tabstop findmergefiles maxgraphpct maxwidth\n     -     cmitmode wrapcomment autoselect autosellen showneartags maxrefs visiblerefs\n     --    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk\n     -+    hideremotes showlocalchanges datetimeformat limitdiffs uicolor want_ttk file_rename_detection\n     -     bgcolor fgcolor uifgcolor uifgdisabledcolor colors diffcolors mergecolors\n     -     markbgcolor diffcontext selectbgcolor foundbgcolor currentsearchhitbgcolor\n     -     extdifftool perfile_attrs headbgcolor headfgcolor headoutlinecolor\n\n\n gitk-git/gitk | 45 +++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 43 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex bc9efa18566..ddbe60398f2 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3806,6 +3806,39 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} {\n+    global ctext\n+\n+    set renamed_filenames [list {}]\n+    set filename [file tail $filepath]\n+    set rename_from_text_identifier_length 12\n+    set rename_to_text_identifier_length 10\n+    set reg_expr_rename_from {^rename from (.*$filename)}\n+    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n+    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n+    if { ($rename_from_text_index != {})} {\n+        set reg_expr_rename_to {^rename to (.*)}\n+        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n+        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+        return $renamed_filenames\n+    }\n+    set reg_expr_rename_to {^rename to (.*$filename)}\n+    set reg_expr_rename_to [subst -nobackslashes -nocommands $reg_expr_rename_to]\n+    set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to 0.0]\n+    if { ($rename_to_text_index != {})} {\n+        set reg_expr_rename_from {^rename from (.*)}\n+        set rename_from_text_index [$ctext search -backwards -elide -regexp -- $reg_expr_rename_from $rename_to_text_index]\n+        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+        return $renamed_filenames\n+    }\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3836,8 +3869,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n+    set rename_from_filename [lindex $renamed_filenames 1]\n+    set rename_to_filename [lindex $renamed_filenames 2]\n+    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: db91954e18654eeebc54c900f44c704002e1866d\n-- \ngitgitgadget\n"},{"id":"514396","messageId":"53c5aed4-ce4f-4ad7-8e90-a97fd3c973cd@kdbg.org","threadId":"61991","inReplyTo":"pull.1774.v3.git.1741093275742.gitgitgadget@gmail.com","subject":"Re: [PATCH v3] gitk: added external diff file rename detection","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-03-16T16:21:44Z","receivedAt":"2025-03-16T16:21:47Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 04.03.25 um 14:01 schrieb ToBoMi via GitGitGadget:\n> From: Tobias Boesch <tobias.boesch@miele.com>\n> \n> * If a file was renamed between commits and an external diff is started\n>   through gitk on the original or the renamed file name,\n>   gitk was unable to open the renamed file in the external diff editor.\n>   It failed to fetch the renamed file from git, because it fetched it\n>   using its original path in contrast to using the renamed path of the\n>   file.\n> * With this change gitk detects the rename and opens the external diff\n>   with the original and the renamed file instead of no file (it is able\n>   to fetch the renamed file path and name now from git).\n> * Since git doesn't destinguish between move or rename this also works\n>   for moved files.\n> * Showing the external diff with the original and the renamed file\n>   works when either of the files is selected in gitk.\n> \n> Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n> ---\n\nI've picked up this patch, but haven't found time to look at it in\ndetail. It will take some time. Please bear with me.\n\nThe commit message would need to be rewritten to match our usual style:\n\n- We do not use bullet points for normal text paragraphs.\n- We describe the status quo in present tense,\n- and then the changes in imperative mood. (Like, \"code, become so!\")\n- The subject is not in past tense, but usually also imperative.\n\n-- Hannes\n\n"},{"id":"516883","messageId":"pull.1774.v4.git.1745830037917.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v3.git.1741093275742.gitgitgadget@gmail.com","subject":"[PATCH v4] gitk: add external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-04-28T08:47:17Z","receivedAt":"2025-04-28T08:47:21Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\nIf a file is renamed between commits and an external diff is started\nthrough gitk on the original or the renamed file name,\ngitk is unable to open the renamed file in the external diff editor.\nIt fails to fetch the renamed file from git, because it fetches it\nusing its original path in contrast to using the renamed path of the\nfile.\nDetect the rename and open the external diff with the original and\nthe renamed file instead of no file (fetch the renamed file path and\nname from git) no matter if the original or the renamed file is\nselected in gitk.\nSince moved or renamed file are handled the same way do this also\nfor moved files.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: add external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n    \n    Changes since v3:\n    \n     * Changed message to use present tense, removed bullet points and\n       described changes in imperative mood\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v4\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v4\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v3:\n\n 1:  1a64e989713 ! 1:  948b94bef5c gitk: added external diff file rename detection\n     @@ Metadata\n      Author: Tobias Boesch <tobias.boesch@miele.com>\n      \n       ## Commit message ##\n     -    gitk: added external diff file rename detection\n     +    gitk: add external diff file rename detection\n      \n     -    * If a file was renamed between commits and an external diff is started\n     -      through gitk on the original or the renamed file name,\n     -      gitk was unable to open the renamed file in the external diff editor.\n     -      It failed to fetch the renamed file from git, because it fetched it\n     -      using its original path in contrast to using the renamed path of the\n     -      file.\n     -    * With this change gitk detects the rename and opens the external diff\n     -      with the original and the renamed file instead of no file (it is able\n     -      to fetch the renamed file path and name now from git).\n     -    * Since git doesn't destinguish between move or rename this also works\n     -      for moved files.\n     -    * Showing the external diff with the original and the renamed file\n     -      works when either of the files is selected in gitk.\n     +    If a file is renamed between commits and an external diff is started\n     +    through gitk on the original or the renamed file name,\n     +    gitk is unable to open the renamed file in the external diff editor.\n     +    It fails to fetch the renamed file from git, because it fetches it\n     +    using its original path in contrast to using the renamed path of the\n     +    file.\n     +    Detect the rename and open the external diff with the original and\n     +    the renamed file instead of no file (fetch the renamed file path and\n     +    name from git) no matter if the original or the renamed file is\n     +    selected in gitk.\n     +    Since moved or renamed file are handled the same way do this also\n     +    for moved files.\n      \n          Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n      \n\n\n gitk-git/gitk | 45 +++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 43 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex bc9efa18566..ddbe60398f2 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3806,6 +3806,39 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} {\n+    global ctext\n+\n+    set renamed_filenames [list {}]\n+    set filename [file tail $filepath]\n+    set rename_from_text_identifier_length 12\n+    set rename_to_text_identifier_length 10\n+    set reg_expr_rename_from {^rename from (.*$filename)}\n+    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n+    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n+    if { ($rename_from_text_index != {})} {\n+        set reg_expr_rename_to {^rename to (.*)}\n+        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n+        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+        return $renamed_filenames\n+    }\n+    set reg_expr_rename_to {^rename to (.*$filename)}\n+    set reg_expr_rename_to [subst -nobackslashes -nocommands $reg_expr_rename_to]\n+    set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to 0.0]\n+    if { ($rename_to_text_index != {})} {\n+        set reg_expr_rename_from {^rename from (.*)}\n+        set rename_from_text_index [$ctext search -backwards -elide -regexp -- $reg_expr_rename_from $rename_to_text_index]\n+        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {}) } {\n+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n+        }\n+        return $renamed_filenames\n+    }\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3836,8 +3869,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n+    set rename_from_filename [lindex $renamed_filenames 1]\n+    set rename_to_filename [lindex $renamed_filenames 2]\n+    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: 5b97a56fa0e7d580dc8865b73107407c9b3f0eff\n-- \ngitgitgadget\n"},{"id":"516884","messageId":"DU0PR08MB82799639DED41134E20C3255E1812@DU0PR08MB8279.eurprd08.prod.outlook.com","threadId":"61991","inReplyTo":"53c5aed4-ce4f-4ad7-8e90-a97fd3c973cd@kdbg.org","subject":"AW: [PATCH v3] gitk: added external diff file rename detection","fromName":"tobias.boesch@miele.com","fromEmail":"tobias.boesch@miele.com","sentAt":"2025-04-28T08:52:36Z","receivedAt":"2025-04-28T08:53:03Z","isPatch":true,"sender":{"key":"tobias.boesch@miele.com","avatar":"https://avatars.githubusercontent.com/u/64197724?v=4"},"body":"Thanks for the update.\nTake the time you need.\nChanges to the message are following.\n\n> -----Ursprüngliche Nachricht-----\n> Von: Johannes Sixt <j6t@kdbg.org>\n> Gesendet: Sonntag, 16. März 2025 17:22\n> An: Boesch, Tobias <tobias.boesch@miele.com>\n> Cc: git@vger.kernel.org; ToBoMi via GitGitGadget <gitgitgadget@gmail.com>\n> Betreff: Re: [PATCH v3] gitk: added external diff file rename detection\n>\n> Am 04.03.25 um 14:01 schrieb ToBoMi via GitGitGadget:\n> > From: Tobias Boesch <tobias.boesch@miele.com>\n> >\n> > * If a file was renamed between commits and an external diff is started\n> >   through gitk on the original or the renamed file name,\n> >   gitk was unable to open the renamed file in the external diff editor.\n> >   It failed to fetch the renamed file from git, because it fetched it\n> >   using its original path in contrast to using the renamed path of the\n> >   file.\n> > * With this change gitk detects the rename and opens the external diff\n> >   with the original and the renamed file instead of no file (it is able\n> >   to fetch the renamed file path and name now from git).\n> > * Since git doesn't destinguish between move or rename this also works\n> >   for moved files.\n> > * Showing the external diff with the original and the renamed file\n> >   works when either of the files is selected in gitk.\n> >\n> > Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n> > ---\n>\n> I've picked up this patch, but haven't found time to look at it in detail. It will\n> take some time. Please bear with me.\n>\n> The commit message would need to be rewritten to match our usual style:\n>\n> - We do not use bullet points for normal text paragraphs.\n> - We describe the status quo in present tense,\n> - and then the changes in imperative mood. (Like, \"code, become so!\")\n> - The subject is not in past tense, but usually also imperative.\n>\n> -- Hannes\n\n\n\n-------------------------------------------------------------------------------------------------\nimperial-Werke oHG, Sitz Bünde, Registergericht Bad Oeynhausen - HRA 4825\n"},{"id":"517381","messageId":"39a90178-5989-403d-8e4e-290827eb44a3@kdbg.org","threadId":"61991","inReplyTo":"pull.1774.v4.git.1745830037917.gitgitgadget@gmail.com","subject":"Re: [PATCH v4] gitk: add external diff file rename detection","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-05-06T19:39:12Z","receivedAt":"2025-05-06T19:39:14Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 28.04.25 um 10:47 schrieb ToBoMi via GitGitGadget:\n> From: Tobias Boesch <tobias.boesch@miele.com>\n> \n> If a file is renamed between commits and an external diff is started\n> through gitk on the original or the renamed file name,\n> gitk is unable to open the renamed file in the external diff editor.\n> It fails to fetch the renamed file from git, because it fetches it\n> using its original path in contrast to using the renamed path of the\n> file.\n> Detect the rename and open the external diff with the original and\n> the renamed file instead of no file (fetch the renamed file path and\n> name from git) no matter if the original or the renamed file is\n> selected in gitk.\n> Since moved or renamed file are handled the same way do this also\n> for moved files.\n> \n> Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n\nThank you. Sorry for taking so long to respond.\n\nIn general, I like the goal of this patch.\n\nI am not familar, yet, how renamed files are represented in Gitk.\n\nI wonder whether it is necessary to parse diff text to find renamed file\nnames. When you click on a renamed file in the file list, the diff panel\njumps to the corresponding text for both the original file name and the\nrenamed file name. Is the information about those two names not already\navailable?\n\nWould it make sense to support also copied files?\n\n>  gitk-git/gitk | 45 +++++++++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 43 insertions(+), 2 deletions(-)\n> \n> diff --git a/gitk-git/gitk b/gitk-git/gitk\n> index bc9efa18566..ddbe60398f2 100755\n> --- a/gitk-git/gitk\n> +++ b/gitk-git/gitk\n> @@ -3806,6 +3806,39 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n>                 \"revision $diffid\"]\n>  }\n>  \n> +proc check_for_renames_in_diff {filepath} {\n> +    global ctext\n> +\n> +    set renamed_filenames [list {}]\n> +    set filename [file tail $filepath]\n> +    set rename_from_text_identifier_length 12\n> +    set rename_to_text_identifier_length 10\n> +    set reg_expr_rename_from {^rename from (.*$filename)}\n\n$filename can certainly have characters that are special for a regular\nexpression, such as the fullstop, right? They need to be escaped or this\nwill find the wrong file if one at all.\n\nIf this search wants to find one side of the rename, why does it ignore\nthe directories?\n\n> +    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n> +    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n> +    if { ($rename_from_text_index != {})} {\n\nHere and elsewhere in this patch we have a string comparison that uses\n'!='. It should use 'ne'.\n\nPlease avoid the extra set of parentheses, even around && (below). Also,\nin this code base, we do not have spaces around the condition inside {}.\n\n> +        set reg_expr_rename_to {^rename to (.*)}\n> +        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n> +        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n> +            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n> +            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n> +        }\n> +        return $renamed_filenames\n> +    }\n> +    set reg_expr_rename_to {^rename to (.*$filename)}\n> +    set reg_expr_rename_to [subst -nobackslashes -nocommands $reg_expr_rename_to]\n> +    set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to 0.0]\n> +    if { ($rename_to_text_index != {})} {\n> +        set reg_expr_rename_from {^rename from (.*)}\n> +        set rename_from_text_index [$ctext search -backwards -elide -regexp -- $reg_expr_rename_from $rename_to_text_index]\n> +        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {}) } {\n> +            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n> +            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n> +        }\n> +        return $renamed_filenames\n> +    }\n> +}\n> +\n\nCan we please have shorter variable names? They are all local variables.\nI have to spend so mucht time to find the end of the variable names\nbefore I can understand what the lines do...\n\n>  proc external_diff {} {\n>      global nullid nullid2\n>      global flist_menu_file\n> @@ -3836,8 +3869,16 @@ proc external_diff {} {\n>      if {$diffdir eq {}} return\n>  \n>      # gather files to diff\n> -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n> +    set rename_from_filename [lindex $renamed_filenames 1]\n> +    set rename_to_filename [lindex $renamed_filenames 2]\n> +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n> +    } else {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    }\n>  \n>      if {$difffromfile ne {} && $difftofile ne {}} {\n>          set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n> \n> base-commit: 5b97a56fa0e7d580dc8865b73107407c9b3f0eff\n\n-- Hannes\n\n"},{"id":"520027","messageId":"AS2PR08MB82885AB4E7F05F5566620593E16AA@AS2PR08MB8288.eurprd08.prod.outlook.com","threadId":"61991","inReplyTo":"39a90178-5989-403d-8e4e-290827eb44a3@kdbg.org","subject":"AW: [PATCH v4] gitk: add external diff file rename detection","fromName":"tobias.boesch@miele.com","fromEmail":"tobias.boesch@miele.com","sentAt":"2025-06-10T08:28:32Z","receivedAt":"2025-06-10T08:29:03Z","isPatch":true,"sender":{"key":"tobias.boesch@miele.com","avatar":"https://avatars.githubusercontent.com/u/64197724?v=4"},"body":"> -----Ursprüngliche Nachricht-----\n> Von: Johannes Sixt <j6t@kdbg.org>\n> Gesendet: Dienstag, 6. Mai 2025 21:39\n> An: ToBoMi via GitGitGadget <gitgitgadget@gmail.com>; git@vger.kernel.org\n> Cc: Boesch, Tobias <tobias.boesch@miele.com>\n> Betreff: Re: [PATCH v4] gitk: add external diff file rename detection\n>\n> Am 28.04.25 um 10:47 schrieb ToBoMi via GitGitGadget:\n> > From: Tobias Boesch <tobias.boesch@miele.com>\n> >\n> > If a file is renamed between commits and an external diff is started\n> > through gitk on the original or the renamed file name, gitk is unable\n> > to open the renamed file in the external diff editor.\n> > It fails to fetch the renamed file from git, because it fetches it\n> > using its original path in contrast to using the renamed path of the\n> > file.\n> > Detect the rename and open the external diff with the original and the\n> > renamed file instead of no file (fetch the renamed file path and name\n> > from git) no matter if the original or the renamed file is selected in\n> > gitk.\n> > Since moved or renamed file are handled the same way do this also for\n> > moved files.\n> >\n> > Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n>\n> Thank you. Sorry for taking so long to respond.\n>\n> In general, I like the goal of this patch.\n>\n> I am not familar, yet, how renamed files are represented in Gitk.\n>\n> I wonder whether it is necessary to parse diff text to find renamed file names.\n> When you click on a renamed file in the file list, the diff panel jumps to the\n> corresponding text for both the original file name and the renamed file name.\n> Is the information about those two names not already available?\n\nI believe this could go wrong, because I think one can scroll after selecting\nthe file and then right click on it or even another file to execute the external diff.\nThen the filenames are no longer at the top top of the text field.\n\nI got around this by finding a git command that delivers the full paths of the original\nand the renamed file. Since git commands are executed in the codebase already I\nhope this is okay and maybe this is even more efficient than parsing the ctext.\n\n>\n> Would it make sense to support also copied files?\n\nI don't know how why this is a benefit and I believe that git currently does not handle\ncopied files the way it does with renamed or moved files. That's why I would like to skip this.\n\n>\n> >  gitk-git/gitk | 45 +++++++++++++++++++++++++++++++++++++++++++--\n> >  1 file changed, 43 insertions(+), 2 deletions(-)\n> >\n> > diff --git a/gitk-git/gitk b/gitk-git/gitk index\n> > bc9efa18566..ddbe60398f2 100755\n> > --- a/gitk-git/gitk\n> > +++ b/gitk-git/gitk\n> > @@ -3806,6 +3806,39 @@ proc external_diff_get_one_file {diffid filename\n> diffdir} {\n> >                 \"revision $diffid\"]\n> >  }\n> >\n> > +proc check_for_renames_in_diff {filepath} {\n> > +    global ctext\n> > +\n> > +    set renamed_filenames [list {}]\n> > +    set filename [file tail $filepath]\n> > +    set rename_from_text_identifier_length 12\n> > +    set rename_to_text_identifier_length 10\n> > +    set reg_expr_rename_from {^rename from (.*$filename)}\n>\n> $filename can certainly have characters that are special for a regular\n> expression, such as the fullstop, right? They need to be escaped or this will\n> find the wrong file if one at all.\n>\n> If this search wants to find one side of the rename, why does it ignore the\n> directories?\n\nTrue. Now that a git command is used that emits the full path of the files\nThis regex is no longer necessary and is removed.\n\n>\n> > +    set reg_expr_rename_from [subst -nobackslashes -nocommands\n> $reg_expr_rename_from]\n> > +    set rename_from_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_from 0.0]\n> > +    if { ($rename_from_text_index != {})} {\n>\n> Here and elsewhere in this patch we have a string comparison that uses '!='. It\n> should use 'ne'.\n\ndone\n\n>\n> Please avoid the extra set of parentheses, even around && (below). Also, in\n> this code base, we do not have spaces around the condition inside {}.\n\ndone\n\n>\n> > +        set reg_expr_rename_to {^rename to (.*)}\n> > +        set rename_to_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_to $rename_from_text_index]\n> > +        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {})\n> } {\n> > +            lappend renamed_filenames [$ctext get \"$rename_from_text_index\n> + $rename_from_text_identifier_length chars\" \"$rename_from_text_index\n> lineend\"]\n> > +            lappend renamed_filenames [$ctext get \"$rename_to_text_index +\n> $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n> > +        }\n> > +        return $renamed_filenames\n> > +    }\n> > +    set reg_expr_rename_to {^rename to (.*$filename)}\n> > +    set reg_expr_rename_to [subst -nobackslashes -nocommands\n> $reg_expr_rename_to]\n> > +    set rename_to_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_to 0.0]\n> > +    if { ($rename_to_text_index != {})} {\n> > +        set reg_expr_rename_from {^rename from (.*)}\n> > +        set rename_from_text_index [$ctext search -backwards -elide -regexp -\n> - $reg_expr_rename_from $rename_to_text_index]\n> > +        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {})\n> } {\n> > +            lappend renamed_filenames [$ctext get \"$rename_from_text_index\n> + $rename_from_text_identifier_length chars\" \"$rename_from_text_index\n> lineend\"]\n> > +            lappend renamed_filenames [$ctext get \"$rename_to_text_index +\n> $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n> > +        }\n> > +        return $renamed_filenames\n> > +    }\n> > +}\n> > +\n>\n> Can we please have shorter variable names? They are all local variables.\n> I have to spend so mucht time to find the end of the variable names before I\n> can understand what the lines do...\n\ndone\n\n>\n> >  proc external_diff {} {\n> >      global nullid nullid2\n> >      global flist_menu_file\n> > @@ -3836,8 +3869,16 @@ proc external_diff {} {\n> >      if {$diffdir eq {}} return\n> >\n> >      # gather files to diff\n> > -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> > -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> > +    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n> > +    set rename_from_filename [lindex $renamed_filenames 1]\n> > +    set rename_to_filename [lindex $renamed_filenames 2]\n> > +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n> > +        set difffromfile [external_diff_get_one_file $diffidfrom\n> $rename_from_filename $diffdir]\n> > +        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename\n> $diffdir]\n> > +    } else {\n> > +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> > +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file\n> $diffdir]\n> > +    }\n> >\n> >      if {$difffromfile ne {} && $difftofile ne {}} {\n> >          set cmd [list [shellsplit $extdifftool] $difffromfile\n> > $difftofile]\n> >\n> > base-commit: 5b97a56fa0e7d580dc8865b73107407c9b3f0eff\n>\n> -- Hannes\n\n\n\n-------------------------------------------------------------------------------------------------\nimperial-Werke oHG, Sitz Bünde, Registergericht Bad Oeynhausen - HRA 4825\n"},{"id":"520028","messageId":"pull.1774.v5.git.1749544174590.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v4.git.1745830037917.gitgitgadget@gmail.com","subject":"[PATCH v5] gitk: add external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-10T08:29:34Z","receivedAt":"2025-06-10T08:29:38Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\nIf a file is renamed between commits and an external diff is started\nthrough gitk on the original or the renamed file name,\ngitk is unable to open the renamed file in the external diff editor.\nIt fails to fetch the renamed file from git, because it fetches it\nusing its original path in contrast to using the renamed path of the\nfile.\nDetect the rename and open the external diff with the original and\nthe renamed file instead of no file (fetch the renamed file path and\nname from git) no matter if the original or the renamed file is\nselected in gitk.\nSince moved or renamed file are handled the same way do this also\nfor moved files.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: add external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n    \n    Changes since v3:\n    \n     * Changed message to use present tense, removed bullet points and\n       described changes in imperative mood\n    \n    Changes sine v4:\n    \n     * Use a git command to gather the changed file paths rather than\n       parsing the text from the diff window panel for efficiency and to\n       avoid regex containing the filename as a variable.\n     * Change != to ne in string comparison\n     * removed extra set of parentheses around &&\n     * shorter variable names\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v5\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v5\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v4:\n\n 1:  948b94bef5c ! 1:  0d28f189dc3 gitk: add external diff file rename detection\n     @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n                      \"revision $diffid\"]\n       }\n       \n     -+proc check_for_renames_in_diff {filepath} {\n     -+    global ctext\n     ++proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n     ++    global nullid nullid2\n      +\n     -+    set renamed_filenames [list {}]\n     -+    set filename [file tail $filepath]\n     -+    set rename_from_text_identifier_length 12\n     -+    set rename_to_text_identifier_length 10\n     -+    set reg_expr_rename_from {^rename from (.*$filename)}\n     -+    set reg_expr_rename_from [subst -nobackslashes -nocommands $reg_expr_rename_from]\n     -+    set rename_from_text_index [$ctext search -elide -regexp -- $reg_expr_rename_from 0.0]\n     -+    if { ($rename_from_text_index != {})} {\n     -+        set reg_expr_rename_to {^rename to (.*)}\n     -+        set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to $rename_from_text_index]\n     -+        if { ($rename_from_text_index != {}) && ($rename_to_text_index != {}) } {\n     -+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n     -+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n     -+        }\n     -+        return $renamed_filenames\n     ++    if {$diffidfrom eq $nullid} {\n     ++        set rev [list $diffidto -R]\n     ++    } elseif {$diffidfrom eq $nullid2} {\n     ++        set rev [list $diffidto --cached -R]\n     ++    } elseif {$diffidto eq $nullid} {\n     ++        set rev [list $diffidfrom]\n     ++    } elseif {$diffidto eq $nullid2} {\n     ++        set rev [list $diffidfrom --cached]\n     ++    } else {\n     ++        set rev [list $diffidfrom..$diffidto]\n      +    }\n     -+    set reg_expr_rename_to {^rename to (.*$filename)}\n     -+    set reg_expr_rename_to [subst -nobackslashes -nocommands $reg_expr_rename_to]\n     -+    set rename_to_text_index [$ctext search -elide -regexp -- $reg_expr_rename_to 0.0]\n     -+    if { ($rename_to_text_index != {})} {\n     -+        set reg_expr_rename_from {^rename from (.*)}\n     -+        set rename_from_text_index [$ctext search -backwards -elide -regexp -- $reg_expr_rename_from $rename_to_text_index]\n     -+        if { ($rename_to_text_index != {}) && ($rename_from_text_index != {}) } {\n     -+            lappend renamed_filenames [$ctext get \"$rename_from_text_index + $rename_from_text_identifier_length chars\" \"$rename_from_text_index lineend\"]\n     -+            lappend renamed_filenames [$ctext get \"$rename_to_text_index + $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n     ++\n     ++    set renames [list {}]\n     ++    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R} cmd_result]} {\n     ++        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from commit %s to %s.\" \\\n     ++                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n     ++    }\n     ++    set filename [file tail $filepath]\n     ++    set regex_ren {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+(\\S+)\\s+(\\S+)}\n     ++    set regex_ren [subst -nobackslashes -nocommands $regex_ren]\n     ++    if {[regexp -line -- $regex_ren $cmd_result whole_match ren_from ren_to]} {\n     ++        if {$ren_from ne {} && $ren_to ne {}} {\n     ++            lappend renames $ren_from\n     ++            lappend renames $ren_to\n      +        }\n     -+        return $renamed_filenames\n      +    }\n     ++    return $renames\n      +}\n      +\n       proc external_diff {} {\n     @@ gitk-git/gitk: proc external_diff {} {\n           # gather files to diff\n      -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n      -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n     -+    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n     ++    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto $flist_menu_file]\n      +    set rename_from_filename [lindex $renamed_filenames 1]\n      +    set rename_to_filename [lindex $renamed_filenames 2]\n      +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n\n\n gitk-git/gitk | 44 ++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 42 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 19689765cde..f97904f5fa2 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3775,6 +3775,38 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n+    global nullid nullid2\n+\n+    if {$diffidfrom eq $nullid} {\n+        set rev [list $diffidto -R]\n+    } elseif {$diffidfrom eq $nullid2} {\n+        set rev [list $diffidto --cached -R]\n+    } elseif {$diffidto eq $nullid} {\n+        set rev [list $diffidfrom]\n+    } elseif {$diffidto eq $nullid2} {\n+        set rev [list $diffidfrom --cached]\n+    } else {\n+        set rev [list $diffidfrom..$diffidto]\n+    }\n+\n+    set renames [list {}]\n+    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R} cmd_result]} {\n+        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from commit %s to %s.\" \\\n+                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n+    }\n+    set filename [file tail $filepath]\n+    set regex_ren {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+(\\S+)\\s+(\\S+)}\n+    set regex_ren [subst -nobackslashes -nocommands $regex_ren]\n+    if {[regexp -line -- $regex_ren $cmd_result whole_match ren_from ren_to]} {\n+        if {$ren_from ne {} && $ren_to ne {}} {\n+            lappend renames $ren_from\n+            lappend renames $ren_to\n+        }\n+    }\n+    return $renames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3805,8 +3837,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto $flist_menu_file]\n+    set rename_from_filename [lindex $renamed_filenames 1]\n+    set rename_to_filename [lindex $renamed_filenames 2]\n+    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: 14de3eb34435db79c6e7edc8082c302a26a8330a\n-- \ngitgitgadget\n"},{"id":"520213","messageId":"AS2PR08MB8288190B1A6A04CAF89E1228E177A@AS2PR08MB8288.eurprd08.prod.outlook.com","threadId":"61991","inReplyTo":"pull.1774.v5.git.1749544174590.gitgitgadget@gmail.com","subject":"AW: [PATCH v5] gitk: add external diff file rename detection","fromName":"tobias.boesch@miele.com","fromEmail":"tobias.boesch@miele.com","sentAt":"2025-06-13T08:18:42Z","receivedAt":"2025-06-13T08:20:05Z","isPatch":true,"sender":{"key":"tobias.boesch@miele.com","avatar":"https://avatars.githubusercontent.com/u/64197724?v=4"},"body":"Hi Hannes,\n\nplease ignore the V5 patch.\nI totally missed to check for the file of interest when checking for renames.\nI'll correct that and send an update.\n\nBest wishes\nTobias\n\n> -----Ursprüngliche Nachricht-----\n> Von: ToBoMi via GitGitGadget <gitgitgadget@gmail.com>\n> Gesendet: Dienstag, 10. Juni 2025 10:30\n> An: git@vger.kernel.org\n> Cc: Johannes Sixt <j6t@kdbg.org>; Boesch, Tobias\n> <tobias.boesch@miele.com>; Boesch, Tobias <tobias.boesch@miele.com>;\n> Boesch, Tobias <tobias.boesch@miele.com>\n> Betreff: [PATCH v5] gitk: add external diff file rename detection\n>\n> From: Tobias Boesch <tobias.boesch@miele.com>\n>\n> If a file is renamed between commits and an external diff is started through\n> gitk on the original or the renamed file name, gitk is unable to open the\n> renamed file in the external diff editor.\n> It fails to fetch the renamed file from git, because it fetches it using its original\n> path in contrast to using the renamed path of the file.\n> Detect the rename and open the external diff with the original and the\n> renamed file instead of no file (fetch the renamed file path and name from git)\n> no matter if the original or the renamed file is selected in gitk.\n> Since moved or renamed file are handled the same way do this also for moved\n> files.\n>\n> Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n> ---\n>     gitk: add external diff file rename detection\n>\n>     Changes since v1:\n>\n>      * Commit message ident\n>      * Commit message line length\n>\n>     Changes since v2:\n>\n>      * Removed option for rename detection (Adding GUI options seems to be\n>        not desired - which is understandable)\n>      * Rebased on current master of git-for-windows\n>      * Renamed variables for a better understanding\n>      * Made rename detection also work when the renamed file is selected in\n>        gitk\n>\n>     Changes since v3:\n>\n>      * Changed message to use present tense, removed bullet points and\n>        described changes in imperative mood\n>\n>     Changes sine v4:\n>\n>      * Use a git command to gather the changed file paths rather than\n>        parsing the text from the diff window panel for efficiency and to\n>        avoid regex containing the filename as a variable.\n>      * Change != to ne in string comparison\n>      * removed extra set of parentheses around &&\n>      * shorter variable names\n>\n> Published-As: https://github.com/gitgitgadget/git/releases/tag/pr-\n> 1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v5\n> Fetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-\n> 1774/ToBoMi/detect_renamed_files_when_opening_diff-v5\n> Pull-Request: https://github.com/gitgitgadget/git/pull/1774\n>\n> Range-diff vs v4:\n>\n>  1:  948b94bef5c ! 1:  0d28f189dc3 gitk: add external diff file rename\n> detection\n>      @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n>                       \"revision $diffid\"]\n>        }\n>\n>      -+proc check_for_renames_in_diff {filepath} {\n>      -+    global ctext\n>      ++proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n>      ++    global nullid nullid2\n>       +\n>      -+    set renamed_filenames [list {}]\n>      -+    set filename [file tail $filepath]\n>      -+    set rename_from_text_identifier_length 12\n>      -+    set rename_to_text_identifier_length 10\n>      -+    set reg_expr_rename_from {^rename from (.*$filename)}\n>      -+    set reg_expr_rename_from [subst -nobackslashes -nocommands\n> $reg_expr_rename_from]\n>      -+    set rename_from_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_from 0.0]\n>      -+    if { ($rename_from_text_index != {})} {\n>      -+        set reg_expr_rename_to {^rename to (.*)}\n>      -+        set rename_to_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_to $rename_from_text_index]\n>      -+        if { ($rename_from_text_index != {}) && ($rename_to_text_index !=\n> {}) } {\n>      -+            lappend renamed_filenames [$ctext get\n> \"$rename_from_text_index + $rename_from_text_identifier_length chars\"\n> \"$rename_from_text_index lineend\"]\n>      -+            lappend renamed_filenames [$ctext get \"$rename_to_text_index +\n> $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n>      -+        }\n>      -+        return $renamed_filenames\n>      ++    if {$diffidfrom eq $nullid} {\n>      ++        set rev [list $diffidto -R]\n>      ++    } elseif {$diffidfrom eq $nullid2} {\n>      ++        set rev [list $diffidto --cached -R]\n>      ++    } elseif {$diffidto eq $nullid} {\n>      ++        set rev [list $diffidfrom]\n>      ++    } elseif {$diffidto eq $nullid2} {\n>      ++        set rev [list $diffidfrom --cached]\n>      ++    } else {\n>      ++        set rev [list $diffidfrom..$diffidto]\n>       +    }\n>      -+    set reg_expr_rename_to {^rename to (.*$filename)}\n>      -+    set reg_expr_rename_to [subst -nobackslashes -nocommands\n> $reg_expr_rename_to]\n>      -+    set rename_to_text_index [$ctext search -elide -regexp --\n> $reg_expr_rename_to 0.0]\n>      -+    if { ($rename_to_text_index != {})} {\n>      -+        set reg_expr_rename_from {^rename from (.*)}\n>      -+        set rename_from_text_index [$ctext search -backwards -elide -regexp\n> -- $reg_expr_rename_from $rename_to_text_index]\n>      -+        if { ($rename_to_text_index != {}) && ($rename_from_text_index !=\n> {}) } {\n>      -+            lappend renamed_filenames [$ctext get\n> \"$rename_from_text_index + $rename_from_text_identifier_length chars\"\n> \"$rename_from_text_index lineend\"]\n>      -+            lappend renamed_filenames [$ctext get \"$rename_to_text_index +\n> $rename_to_text_identifier_length chars\" \"$rename_to_text_index lineend\"]\n>      ++\n>      ++    set renames [list {}]\n>      ++    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-\n> filter=R} cmd_result]} {\n>      ++        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from\n> commit %s to %s.\" \\\n>      ++                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n>      ++    }\n>      ++    set filename [file tail $filepath]\n>      ++    set regex_ren {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+(\\S+)\\s+(\\S+)}\n>      ++    set regex_ren [subst -nobackslashes -nocommands $regex_ren]\n>      ++    if {[regexp -line -- $regex_ren $cmd_result whole_match ren_from\n> ren_to]} {\n>      ++        if {$ren_from ne {} && $ren_to ne {}} {\n>      ++            lappend renames $ren_from\n>      ++            lappend renames $ren_to\n>       +        }\n>      -+        return $renamed_filenames\n>       +    }\n>      ++    return $renames\n>       +}\n>       +\n>        proc external_diff {} {\n>      @@ gitk-git/gitk: proc external_diff {} {\n>            # gather files to diff\n>       -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n>       -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file\n> $diffdir]\n>      -+    set renamed_filenames [check_for_renames_in_diff $flist_menu_file]\n>      ++    set renamed_filenames [check_for_renames_in_diff $diffidfrom\n> $diffidto $flist_menu_file]\n>       +    set rename_from_filename [lindex $renamed_filenames 1]\n>       +    set rename_to_filename [lindex $renamed_filenames 2]\n>       +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n>\n>\n>  gitk-git/gitk | 44 ++++++++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 42 insertions(+), 2 deletions(-)\n>\n> diff --git a/gitk-git/gitk b/gitk-git/gitk index 19689765cde..f97904f5fa2\n> 100755\n> --- a/gitk-git/gitk\n> +++ b/gitk-git/gitk\n> @@ -3775,6 +3775,38 @@ proc external_diff_get_one_file {diffid filename\n> diffdir} {\n>                 \"revision $diffid\"]\n>  }\n>\n> +proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n> +    global nullid nullid2\n> +\n> +    if {$diffidfrom eq $nullid} {\n> +        set rev [list $diffidto -R]\n> +    } elseif {$diffidfrom eq $nullid2} {\n> +        set rev [list $diffidto --cached -R]\n> +    } elseif {$diffidto eq $nullid} {\n> +        set rev [list $diffidfrom]\n> +    } elseif {$diffidto eq $nullid2} {\n> +        set rev [list $diffidfrom --cached]\n> +    } else {\n> +        set rev [list $diffidfrom..$diffidto]\n> +    }\n> +\n> +    set renames [list {}]\n> +    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R}\n> cmd_result]} {\n> +        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from\n> commit %s to %s.\" \\\n> +                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n> +    }\n> +    set filename [file tail $filepath]\n> +    set regex_ren {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+(\\S+)\\s+(\\S+)}\n> +    set regex_ren [subst -nobackslashes -nocommands $regex_ren]\n> +    if {[regexp -line -- $regex_ren $cmd_result whole_match ren_from ren_to]}\n> {\n> +        if {$ren_from ne {} && $ren_to ne {}} {\n> +            lappend renames $ren_from\n> +            lappend renames $ren_to\n> +        }\n> +    }\n> +    return $renames\n> +}\n> +\n>  proc external_diff {} {\n>      global nullid nullid2\n>      global flist_menu_file\n> @@ -3805,8 +3837,16 @@ proc external_diff {} {\n>      if {$diffdir eq {}} return\n>\n>      # gather files to diff\n> -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto\n> $flist_menu_file]\n> +    set rename_from_filename [lindex $renamed_filenames 1]\n> +    set rename_to_filename [lindex $renamed_filenames 2]\n> +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom\n> $rename_from_filename $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename\n> $diffdir]\n> +    } else {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file\n> $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file\n> $diffdir]\n> +    }\n>\n>      if {$difffromfile ne {} && $difftofile ne {}} {\n>          set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n>\n> base-commit: 14de3eb34435db79c6e7edc8082c302a26a8330a\n> --\n> gitgitgadget\n\n\n-------------------------------------------------------------------------------------------------\nimperial-Werke oHG, Sitz Bünde, Registergericht Bad Oeynhausen - HRA 4825\n"},{"id":"520628","messageId":"pull.1774.v6.git.1750755954011.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v5.git.1749544174590.gitgitgadget@gmail.com","subject":"[PATCH v6] gitk: add external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-06-24T09:05:53Z","receivedAt":"2025-06-24T09:05:58Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\nIf a file is renamed between commits and an external diff is started\nthrough gitk on the original or the renamed file name,\ngitk is unable to open the renamed file in the external diff editor.\nIt fails to fetch the renamed file from git, because it fetches it\nusing its original path in contrast to using the renamed path of the\nfile.\nDetect the rename and open the external diff with the original and\nthe renamed file instead of no file (fetch the renamed file path and\nname from git) no matter if the original or the renamed file is\nselected in gitk.\nSince moved or renamed file are handled the same way do this also\nfor moved files.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: add external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n    \n    Changes since v3:\n    \n     * Changed message to use present tense, removed bullet points and\n       described changes in imperative mood\n    \n    Changes sine v4:\n    \n     * Use a git command to gather the changed file paths rather than\n       parsing the text from the diff window panel for efficiency and to\n       avoid regex containing the filename as a variable.\n     * Change != to ne in string comparison\n     * removed extra set of parentheses around &&\n     * shorter variable names\n    \n    Changes sine v5:\n    \n     * Include filename in rename check. Find only the file and its renamed\n       version that is selected in the GUI.\n     * Escape special characters in the filename to prevent that they are\n       intepreted as part of a regular expression\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v6\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v6\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v5:\n\n 1:  0d28f189dc3 ! 1:  f1c71e56324 gitk: add external diff file rename detection\n     @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n      +                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n      +    }\n      +    set filename [file tail $filepath]\n     -+    set regex_ren {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+(\\S+)\\s+(\\S+)}\n     -+    set regex_ren [subst -nobackslashes -nocommands $regex_ren]\n     -+    if {[regexp -line -- $regex_ren $cmd_result whole_match ren_from ren_to]} {\n     ++    set esc_chars {\\\\ | ? ^ * . $ \\[ \\] + \\( \\) \\{ \\}}\n     ++    foreach char $esc_chars {\n     ++        set filename [string map [list $char \\\\$char] $filename]\n     ++    }\n     ++    set regex_base {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+}\n     ++    set regex_ren_from $regex_base[subst -nobackslashes -nocommands {(\\S+$filename)\\s+(\\S+)}]\n     ++    set regex_ren_to $regex_base[subst -nobackslashes -nocommands {(\\S+)\\s+(\\S+$filename)}]\n     ++    if {[regexp -line -- $regex_ren_from $cmd_result whole_match ren_from ren_to]} {\n     ++        if {$ren_from ne {} && $ren_to ne {}} {\n     ++            lappend renames $ren_from\n     ++            lappend renames $ren_to\n     ++        }\n     ++    } elseif {[regexp -line -- $regex_ren_to $cmd_result whole_match ren_from ren_to]} {\n      +        if {$ren_from ne {} && $ren_to ne {}} {\n      +            lappend renames $ren_from\n      +            lappend renames $ren_to\n\n\n gitk-git/gitk | 54 +++++++++++++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 52 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex 19689765cde..5d4b0fd3a68 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3775,6 +3775,48 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n+    global nullid nullid2\n+\n+    if {$diffidfrom eq $nullid} {\n+        set rev [list $diffidto -R]\n+    } elseif {$diffidfrom eq $nullid2} {\n+        set rev [list $diffidto --cached -R]\n+    } elseif {$diffidto eq $nullid} {\n+        set rev [list $diffidfrom]\n+    } elseif {$diffidto eq $nullid2} {\n+        set rev [list $diffidfrom --cached]\n+    } else {\n+        set rev [list $diffidfrom..$diffidto]\n+    }\n+\n+    set renames [list {}]\n+    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R} cmd_result]} {\n+        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from commit %s to %s.\" \\\n+                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n+    }\n+    set filename [file tail $filepath]\n+    set esc_chars {\\\\ | ? ^ * . $ \\[ \\] + \\( \\) \\{ \\}}\n+    foreach char $esc_chars {\n+        set filename [string map [list $char \\\\$char] $filename]\n+    }\n+    set regex_base {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+}\n+    set regex_ren_from $regex_base[subst -nobackslashes -nocommands {(\\S+$filename)\\s+(\\S+)}]\n+    set regex_ren_to $regex_base[subst -nobackslashes -nocommands {(\\S+)\\s+(\\S+$filename)}]\n+    if {[regexp -line -- $regex_ren_from $cmd_result whole_match ren_from ren_to]} {\n+        if {$ren_from ne {} && $ren_to ne {}} {\n+            lappend renames $ren_from\n+            lappend renames $ren_to\n+        }\n+    } elseif {[regexp -line -- $regex_ren_to $cmd_result whole_match ren_from ren_to]} {\n+        if {$ren_from ne {} && $ren_to ne {}} {\n+            lappend renames $ren_from\n+            lappend renames $ren_to\n+        }\n+    }\n+    return $renames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3805,8 +3847,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto $flist_menu_file]\n+    set rename_from_filename [lindex $renamed_filenames 1]\n+    set rename_to_filename [lindex $renamed_filenames 2]\n+    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: 14de3eb34435db79c6e7edc8082c302a26a8330a\n-- \ngitgitgadget\n"},{"id":"520671","messageId":"be10d14f-d6f6-487a-b520-0bd56e5550e8@kdbg.org","threadId":"61991","inReplyTo":"pull.1774.v6.git.1750755954011.gitgitgadget@gmail.com","subject":"Re: [PATCH v6] gitk: add external diff file rename detection","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-06-25T06:23:23Z","receivedAt":"2025-06-25T06:23:39Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 24.06.25 um 11:05 schrieb ToBoMi via GitGitGadget:\n> From: Tobias Boesch <tobias.boesch@miele.com>\n> \n> If a file is renamed between commits and an external diff is started\n> through gitk on the original or the renamed file name,\n> gitk is unable to open the renamed file in the external diff editor.\n> It fails to fetch the renamed file from git, because it fetches it\n> using its original path in contrast to using the renamed path of the\n> file.\n> Detect the rename and open the external diff with the original and\n> the renamed file instead of no file (fetch the renamed file path and\n> name from git) no matter if the original or the renamed file is\n> selected in gitk.\n> Since moved or renamed file are handled the same way do this also\n> for moved files.\n\nIn Git parlance, when we talk about \"renamed\" files, we always mean\nfiles that have any part of the path name changed (and not just the last\npath component). Therefore, this last sentence is redundant.\n\n>     Changes sine v4:\n>     \n>      * Use a git command to gather the changed file paths rather than\n>        parsing the text from the diff window panel for efficiency and to\n>        avoid regex containing the filename as a variable.\n\nAn earlier round parsed the rename information from the patch text\npanel. I argued that it should not be necessary, because the file list\nalready knows how to scroll the diff text panel to the correct section\nand should already what was renamed. It turns out it's not that simple.\n\nBut I still think that this information can be leveraged for this new\npurpose and that it is not necessary to invoke an external process. At a\nminimum, it should be possible to parse off the rename information from\na smaller section of the patch text, because we know where the section\npertaining to the file of interest starts. Look for uses of the variable\n'difffilestart'.\n\nI think that the goal of this patch can be achieved easier by parsing\nthe patch text panel rather than parsing the output of another `git`\ncommand. I'll still comment on the presented solution just in case it\nturns out we must invoke `git` anyway.\n\n> +    set renames [list {}]\n\nThis constructs a list with one element that is the empty string. I\nassume you meant one of these:\n\n    set renames [list]\n    set renames {}\n\n> +    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R} cmd_result]} {\n\nA few things I have to note here:\n\n- Don't use porcelain commands, use plumbing commands, i.e., `git\ndiff-tree`, `git diff-index`, and `git diff-files` instead of `git diff`.\n- Place non-option arguments after all options.\n- Why use --stat?\n- --numstat may be easier to parse than --raw, but see below.\n\n> +        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from commit %s to %s.\" \\\n> +                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n> +    }\n> +    set filename [file tail $filepath]\n> +    set esc_chars {\\\\ | ? ^ * . $ \\[ \\] + \\( \\) \\{ \\}}\n> +    foreach char $esc_chars {\n> +        set filename [string map [list $char \\\\$char] $filename]\n> +    }\n> +    set regex_base {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+}\n> +    set regex_ren_from $regex_base[subst -nobackslashes -nocommands {(\\S+$filename)\\s+(\\S+)}]\n\nThis regular expression wants to parse the first of the two file names\nthat are on the output line. But it assumes that the second of the two\nnames cannot contain spaces: '(\\S+)'. This is not a valid assumption.\n\nNote that the output format of --raw is very restricted. In particular,\nthe file names are separated from the rest not by space of any kind, but\nby TAB. A much stricter regular expression can be used. But still, TAB\nis a character that is permitted in file names, and the regular\nexpression could match still match incorrectly. You can use -z to\nseparate the parts with a zero byte in an unambiguous way and split the\nparts without a regular expression.\n\n> +    set regex_ren_to $regex_base[subst -nobackslashes -nocommands {(\\S+)\\s+(\\S+$filename)}]\n\nI don't understand the purpose of 'subst' here. Is this not just\n\n    set regex_ren_to $regex_base{(\\S+)\\s+(\\S+}$filename{)}\n\n> @@ -3805,8 +3847,16 @@ proc external_diff {} {\n>      if {$diffdir eq {}} return\n>  \n>      # gather files to diff\n> -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto $flist_menu_file]\n> +    set rename_from_filename [lindex $renamed_filenames 1]\n> +    set rename_to_filename [lindex $renamed_filenames 2]\n> +    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n\nThis expression doesn't follow the pattern that we see elsewhere, e.g.,\nin the context below.\n\nPlease lose the redundant \"_filename\" in the variable names. In this\ncontext, it's clear that these are names, not indices or somehthing\nelse. Also, when you look around, you notice that we normally don't use\nthe underscore in variable names.\n\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n> +    } else {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    }\n>  \n>      if {$difffromfile ne {} && $difftofile ne {}} {\n>          set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\n-- Hannes\n\n"},{"id":"530023","messageId":"pull.1774.v7.git.1761905371163.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v6.git.1750755954011.gitgitgadget@gmail.com","subject":"[PATCH v7] gitk: add external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-10-31T10:09:31Z","receivedAt":"2025-10-31T10:09:34Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\nIf a file is renamed between commits and an external diff is started\nthrough gitk on the original or the renamed file name,\ngitk is unable to open the renamed file in the external diff editor.\nIt fails to fetch the renamed file from git, because it fetches it\nusing its original path in contrast to using the renamed path of the\nfile.\nDetect the rename and open the external diff with the original and\nthe renamed file instead of no file (fetch the renamed file path and\nname from git) no matter if the original or the renamed file is\nselected in gitk.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: add external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n    \n    Changes since v3:\n    \n     * Changed message to use present tense, removed bullet points and\n       described changes in imperative mood\n    \n    Changes sine v4:\n    \n     * Use a git command to gather the changed file paths rather than\n       parsing the text from the diff window panel for efficiency and to\n       avoid regex containing the filename as a variable.\n     * Change != to ne in string comparison\n     * removed extra set of parentheses around &&\n     * shorter variable names\n    \n    Changes sine v5:\n    \n     * Include filename in rename check. Find only the file and its renamed\n       version that is selected in the GUI.\n     * Escape special characters in the filename to prevent that they are\n       intepreted as part of a regular expression\n    \n    Changes since v6:\n    \n     * Don't extra mention moved files in commit message\n     * Create empty list properly (avoid creting a list containing an empty\n       string)\n     * Avoid snake case in variable names\n     * Change logic that checks the renames and gathers the file paths to\n       use the difffilestart variable for efficient file path extraction\n     * Removed underscores in file names\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v7\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v7\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v6:\n\n 1:  f1c71e5632 ! 1:  7c09a28951 gitk: add external diff file rename detection\n     @@ Commit message\n          the renamed file instead of no file (fetch the renamed file path and\n          name from git) no matter if the original or the renamed file is\n          selected in gitk.\n     -    Since moved or renamed file are handled the same way do this also\n     -    for moved files.\n      \n          Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n      \n     @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n                      \"revision $diffid\"]\n       }\n       \n     -+proc check_for_renames_in_diff {diffidfrom diffidto filepath} {\n     -+    global nullid nullid2\n     ++proc check_for_renames_in_diff {filepath} { # renames\n     ++    global difffilestart ctext\n      +\n     -+    if {$diffidfrom eq $nullid} {\n     -+        set rev [list $diffidto -R]\n     -+    } elseif {$diffidfrom eq $nullid2} {\n     -+        set rev [list $diffidto --cached -R]\n     -+    } elseif {$diffidto eq $nullid} {\n     -+        set rev [list $diffidfrom]\n     -+    } elseif {$diffidto eq $nullid2} {\n     -+        set rev [list $diffidfrom --cached]\n     -+    } else {\n     -+        set rev [list $diffidfrom..$diffidto]\n     -+    }\n     -+\n     -+    set renames [list {}]\n     -+    if {[catch {eval exec git diff $rev --find-renames --stat --raw --diff-filter=R} cmd_result]} {\n     -+        error_popup \"[mc \"Error getting file rename info for file \\\"%s\\\" from commit %s to %s.\" \\\n     -+                            $filepath $diffidfrom $diffidto] $cmd_result.\\n\\n\"\n     -+    }\n      +    set filename [file tail $filepath]\n     -+    set esc_chars {\\\\ | ? ^ * . $ \\[ \\] + \\( \\) \\{ \\}}\n     -+    foreach char $esc_chars {\n     -+        set filename [string map [list $char \\\\$char] $filename]\n     -+    }\n     -+    set regex_base {\\d+\\s\\d+\\s\\S+\\s\\S+\\s\\S+\\s+}\n     -+    set regex_ren_from $regex_base[subst -nobackslashes -nocommands {(\\S+$filename)\\s+(\\S+)}]\n     -+    set regex_ren_to $regex_base[subst -nobackslashes -nocommands {(\\S+)\\s+(\\S+$filename)}]\n     -+    if {[regexp -line -- $regex_ren_from $cmd_result whole_match ren_from ren_to]} {\n     -+        if {$ren_from ne {} && $ren_to ne {}} {\n     -+            lappend renames $ren_from\n     -+            lappend renames $ren_to\n     -+        }\n     -+    } elseif {[regexp -line -- $regex_ren_to $cmd_result whole_match ren_from ren_to]} {\n     -+        if {$ren_from ne {} && $ren_to ne {}} {\n     -+            lappend renames $ren_from\n     -+            lappend renames $ren_to\n     ++    set renames {}\n     ++    \n     ++    foreach loc $difffilestart {\n     ++        set loclineend [string map {.0 .end} $loc]\n     ++        set ctextline [$ctext get $loc $loclineend]\n     ++        if {[string first $filename $ctextline] != -1} {\n     ++            set fromlineloc \"$loc + 2 lines\"\n     ++            set tolineloc \"$loc + 3 lines\"\n     ++            set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n     ++            set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n     ++            if {[string match \"rename from *\" $renfromline]\n     ++                && [string match \"rename to *\" $rentoline]} {\n     ++                set renfrom [string range $renfromline 12 end]\n     ++                set rento [string range $rentoline 10 end]\n     ++                lappend renames $renfrom\n     ++                lappend renames $rento\n     ++                break\n     ++            }\n      +        }\n      +    }\n     ++    \n      +    return $renames\n      +}\n      +\n     @@ gitk-git/gitk: proc external_diff {} {\n           # gather files to diff\n      -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n      -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n     -+    set renamed_filenames [check_for_renames_in_diff $diffidfrom $diffidto $flist_menu_file]\n     -+    set rename_from_filename [lindex $renamed_filenames 1]\n     -+    set rename_to_filename [lindex $renamed_filenames 2]\n     -+    if { ($rename_from_filename != {}) && ($rename_to_filename != {}) } {\n     -+        set difffromfile [external_diff_get_one_file $diffidfrom $rename_from_filename $diffdir]\n     -+        set difftofile [external_diff_get_one_file $diffidto $rename_to_filename $diffdir]\n     ++    set renames [check_for_renames_in_diff $flist_menu_file]\n     ++    set renamefrom [lindex $renames 0]\n     ++    set renameto [lindex $renames 1]\n     ++    if { ($renamefrom != {}) && ($renameto != {}) } {\n     ++        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]\n     ++        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]\n      +    } else {\n      +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n      +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n\n\n gitk-git/gitk | 40 ++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 38 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex c02db0194d..8f8f1cb9ab 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3788,6 +3788,34 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} { # renames\n+    global difffilestart ctext\n+\n+    set filename [file tail $filepath]\n+    set renames {}\n+    \n+    foreach loc $difffilestart {\n+        set loclineend [string map {.0 .end} $loc]\n+        set ctextline [$ctext get $loc $loclineend]\n+        if {[string first $filename $ctextline] != -1} {\n+            set fromlineloc \"$loc + 2 lines\"\n+            set tolineloc \"$loc + 3 lines\"\n+            set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n+            set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n+            if {[string match \"rename from *\" $renfromline]\n+                && [string match \"rename to *\" $rentoline]} {\n+                set renfrom [string range $renfromline 12 end]\n+                set rento [string range $rentoline 10 end]\n+                lappend renames $renfrom\n+                lappend renames $rento\n+                break\n+            }\n+        }\n+    }\n+    \n+    return $renames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3818,8 +3846,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renames [check_for_renames_in_diff $flist_menu_file]\n+    set renamefrom [lindex $renames 0]\n+    set renameto [lindex $renames 1]\n+    if { ($renamefrom != {}) && ($renameto != {}) } {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: 57da342c786f59eaeb436c18635cc1c7597733d9\n-- \ngitgitgadget\n"},{"id":"530213","messageId":"1a180754-bbb1-48ca-84d4-8915201d086f@kdbg.org","threadId":"61991","inReplyTo":"pull.1774.v7.git.1761905371163.gitgitgadget@gmail.com","subject":"Re: [PATCH v7] gitk: add external diff file rename detection","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-11-04T18:04:57Z","receivedAt":"2025-11-04T18:39:00Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 31.10.25 um 11:09 schrieb ToBoMi via GitGitGadget:\n> From: Tobias Boesch <tobias.boesch@miele.com>\n> \n> If a file is renamed between commits and an external diff is started\n> through gitk on the original or the renamed file name,\n> gitk is unable to open the renamed file in the external diff editor.\n> It fails to fetch the renamed file from git, because it fetches it\n> using its original path in contrast to using the renamed path of the\n> file.\n> Detect the rename and open the external diff with the original and\n> the renamed file instead of no file (fetch the renamed file path and\n> name from git) no matter if the original or the renamed file is\n> selected in gitk.\n\nMake a mental note of this sentence.\n\n> \n> Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n> ---\n\n>     Changes since v6:\n>      * Change logic that checks the renames and gathers the file paths to\n>        use the difffilestart variable for efficient file path extraction\n\nNice to see this change!\n\n>  gitk-git/gitk | 40 ++++++++++++++++++++++++++++++++++++++--\n>  1 file changed, 38 insertions(+), 2 deletions(-)\n> \n> diff --git a/gitk-git/gitk b/gitk-git/gitk\n> index c02db0194d..8f8f1cb9ab 100755\n> --- a/gitk-git/gitk\n> +++ b/gitk-git/gitk\n> @@ -3788,6 +3788,34 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n>                 \"revision $diffid\"]\n>  }\n>  \n> +proc check_for_renames_in_diff {filepath} { # renames\n> +    global difffilestart ctext\n> +\n> +    set filename [file tail $filepath]\n> +    set renames {}\n> +    \n> +    foreach loc $difffilestart {\n> +        set loclineend [string map {.0 .end} $loc]\n> +        set ctextline [$ctext get $loc $loclineend]\n> +        if {[string first $filename $ctextline] != -1} {\n\nAt this point, we are looking at the separator line, which contains the\nrenamed-to file name. This means that we skip the following if the\nrenamed-from file name was selected in the file list. The consequence is\nthat the external diff shows differences between renamed files only when\nthe renamed-to file name was selected, in contrast to what the commit\nmessage announces.\n\nI suggest to check the file names extracted from the \"renamed from\" and\n\"renamed to\" lines instead.\n\n> +            set fromlineloc \"$loc + 2 lines\"\n> +            set tolineloc \"$loc + 3 lines\"\n> +            set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n> +            set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n> +            if {[string match \"rename from *\" $renfromline]\n> +                && [string match \"rename to *\" $rentoline]} {\n\nThese could be [string equal ...] with -length 12 and 10, respectively.\n\n> +                set renfrom [string range $renfromline 12 end]\n> +                set rento [string range $rentoline 10 end]\n\nHere we now have a problem if the file names are not plain ASCII. Then\nwe see escaped file names here. I consider this an independent bug in\nGitk, and we can ignore the problem for now in this patch.\n\n> +                lappend renames $renfrom\n> +                lappend renames $rento\n> +                break\n> +            }\n> +        }\n> +    }\n> +    \n> +    return $renames\n> +}\n> +\n>  proc external_diff {} {\n>      global nullid nullid2\n>      global flist_menu_file\n> @@ -3818,8 +3846,16 @@ proc external_diff {} {\n>      if {$diffdir eq {}} return\n>  \n>      # gather files to diff\n> -    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> -    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    set renames [check_for_renames_in_diff $flist_menu_file]\n> +    set renamefrom [lindex $renames 0]\n> +    set renameto [lindex $renames 1]\n> +    if { ($renamefrom != {}) && ($renameto != {}) } {\n\nPlease let's write this as\n\n    if {$renamefrom ne {} && $renameto ne {}} {\n\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]\n> +    } else {\n> +        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n> +        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n> +    }\n>  \n>      if {$difffromfile ne {} && $difftofile ne {}} {\n>          set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n> \n> base-commit: 57da342c786f59eaeb436c18635cc1c7597733d9\n\n-- Hannes\n\n"},{"id":"530320","messageId":"pull.1774.v8.git.1762440131635.gitgitgadget@gmail.com","threadId":"61991","inReplyTo":"pull.1774.v7.git.1761905371163.gitgitgadget@gmail.com","subject":"[PATCH v8] gitk: add external diff file rename detection","fromName":"ToBoMi via GitGitGadget","fromEmail":"gitgitgadget@gmail.com","sentAt":"2025-11-06T14:42:11Z","receivedAt":"2025-11-06T14:42:15Z","isPatch":true,"sender":{"key":"name:ToBoMi","avatar":null},"body":"From: Tobias Boesch <tobias.boesch@miele.com>\n\nIf a file is renamed between commits and an external diff is started\nthrough gitk on the original or the renamed file name,\ngitk is unable to open the renamed file in the external diff editor.\nIt fails to fetch the renamed file from git, because it fetches it\nusing its original path in contrast to using the renamed path of the\nfile.\nDetect the rename and open the external diff with the original and\nthe renamed file instead of no file (fetch the renamed file path and\nname from git) no matter if the original or the renamed file is\nselected in gitk.\n\nSigned-off-by: Tobias Boesch <tobias.boesch@miele.com>\n---\n    gitk: add external diff file rename detection\n    \n    Changes since v1:\n    \n     * Commit message ident\n     * Commit message line length\n    \n    Changes since v2:\n    \n     * Removed option for rename detection (Adding GUI options seems to be\n       not desired - which is understandable)\n     * Rebased on current master of git-for-windows\n     * Renamed variables for a better understanding\n     * Made rename detection also work when the renamed file is selected in\n       gitk\n    \n    Changes since v3:\n    \n     * Changed message to use present tense, removed bullet points and\n       described changes in imperative mood\n    \n    Changes sine v4:\n    \n     * Use a git command to gather the changed file paths rather than\n       parsing the text from the diff window panel for efficiency and to\n       avoid regex containing the filename as a variable.\n     * Change != to ne in string comparison\n     * removed extra set of parentheses around &&\n     * shorter variable names\n    \n    Changes sine v5:\n    \n     * Include filename in rename check. Find only the file and its renamed\n       version that is selected in the GUI.\n     * Escape special characters in the filename to prevent that they are\n       intepreted as part of a regular expression\n    \n    Changes since v6:\n    \n     * Don't extra mention moved files in commit message\n     * Create empty list properly (avoid creting a list containing an empty\n       string)\n     * Avoid snake case in variable names\n     * Change logic that checks the renames and gathers the file paths to\n       use the difffilestart variable for efficient file path extraction\n     * Removed underscores in file names\n    \n    Changes since v7:\n    \n     * Find renames by checking the file names extracted from the \"renamed\n       from\" and \"renamed to\" diff lines instead of the separator line (make\n       rename detection also work when the original file was selected) (I\n       checked this, but only in a repo where a file move occurred - that\n       worked since the filename didn't change)\n     * Change \"string match\" and wildcard to \"string equal\" with length\n       parameter\n     * Change empty list detection to use \"ne\" instead of \"!=\" and reduce\n       parenthesis\n\nPublished-As: https://github.com/gitgitgadget/git/releases/tag/pr-1774%2FToBoMi%2Fdetect_renamed_files_when_opening_diff-v8\nFetch-It-Via: git fetch https://github.com/gitgitgadget/git pr-1774/ToBoMi/detect_renamed_files_when_opening_diff-v8\nPull-Request: https://github.com/gitgitgadget/git/pull/1774\n\nRange-diff vs v7:\n\n 1:  7c09a28951 ! 1:  6fe99eb5a3 gitk: add external diff file rename detection\n     @@ gitk-git/gitk: proc external_diff_get_one_file {diffid filename diffdir} {\n      +    \n      +    foreach loc $difffilestart {\n      +        set loclineend [string map {.0 .end} $loc]\n     -+        set ctextline [$ctext get $loc $loclineend]\n     -+        if {[string first $filename $ctextline] != -1} {\n     -+            set fromlineloc \"$loc + 2 lines\"\n     -+            set tolineloc \"$loc + 3 lines\"\n     -+            set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n     -+            set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n     -+            if {[string match \"rename from *\" $renfromline]\n     -+                && [string match \"rename to *\" $rentoline]} {\n     -+                set renfrom [string range $renfromline 12 end]\n     -+                set rento [string range $rentoline 10 end]\n     -+                lappend renames $renfrom\n     -+                lappend renames $rento\n     ++        set fromlineloc \"$loc + 2 lines\"\n     ++        set tolineloc \"$loc + 3 lines\"\n     ++        set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n     ++        set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n     ++        if {[string equal -length 12 \"rename from \" $renfromline]\n     ++            && [string equal -length 10 \"rename to \" $rentoline]} {\n     ++            set renfrom [string range $renfromline 12 end]\n     ++            set rento [string range $rentoline 10 end]\n     ++                if {[string first $filename $renfrom] != -1\n     ++                    || [string first $filename $rento] != -1} {\n     ++                    lappend renames $renfrom\n     ++                    lappend renames $rento\n      +                break\n      +            }\n      +        }\n     @@ gitk-git/gitk: proc external_diff {} {\n      +    set renames [check_for_renames_in_diff $flist_menu_file]\n      +    set renamefrom [lindex $renames 0]\n      +    set renameto [lindex $renames 1]\n     -+    if { ($renamefrom != {}) && ($renameto != {}) } {\n     ++    if {$renamefrom ne {} && $renameto ne {}} {\n      +        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]\n      +        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]\n      +    } else {\n\n\n gitk-git/gitk | 40 ++++++++++++++++++++++++++++++++++++++--\n 1 file changed, 38 insertions(+), 2 deletions(-)\n\ndiff --git a/gitk-git/gitk b/gitk-git/gitk\nindex c02db0194d..90cbfd4ec1 100755\n--- a/gitk-git/gitk\n+++ b/gitk-git/gitk\n@@ -3788,6 +3788,34 @@ proc external_diff_get_one_file {diffid filename diffdir} {\n                \"revision $diffid\"]\n }\n \n+proc check_for_renames_in_diff {filepath} { # renames\n+    global difffilestart ctext\n+\n+    set filename [file tail $filepath]\n+    set renames {}\n+    \n+    foreach loc $difffilestart {\n+        set loclineend [string map {.0 .end} $loc]\n+        set fromlineloc \"$loc + 2 lines\"\n+        set tolineloc \"$loc + 3 lines\"\n+        set renfromline [$ctext get $fromlineloc [string map {.0 .end} $fromlineloc]]\n+        set rentoline [$ctext get $tolineloc [string map {.0 .end} $tolineloc]]\n+        if {[string equal -length 12 \"rename from \" $renfromline]\n+            && [string equal -length 10 \"rename to \" $rentoline]} {\n+            set renfrom [string range $renfromline 12 end]\n+            set rento [string range $rentoline 10 end]\n+                if {[string first $filename $renfrom] != -1\n+                    || [string first $filename $rento] != -1} {\n+                    lappend renames $renfrom\n+                    lappend renames $rento\n+                break\n+            }\n+        }\n+    }\n+    \n+    return $renames\n+}\n+\n proc external_diff {} {\n     global nullid nullid2\n     global flist_menu_file\n@@ -3818,8 +3846,16 @@ proc external_diff {} {\n     if {$diffdir eq {}} return\n \n     # gather files to diff\n-    set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n-    set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    set renames [check_for_renames_in_diff $flist_menu_file]\n+    set renamefrom [lindex $renames 0]\n+    set renameto [lindex $renames 1]\n+    if {$renamefrom ne {} && $renameto ne {}} {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $renamefrom $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $renameto $diffdir]\n+    } else {\n+        set difffromfile [external_diff_get_one_file $diffidfrom $flist_menu_file $diffdir]\n+        set difftofile [external_diff_get_one_file $diffidto $flist_menu_file $diffdir]\n+    }\n \n     if {$difffromfile ne {} && $difftofile ne {}} {\n         set cmd [list [shellsplit $extdifftool] $difffromfile $difftofile]\n\nbase-commit: 57da342c786f59eaeb436c18635cc1c7597733d9\n-- \ngitgitgadget\n"},{"id":"530331","messageId":"93e6fffd-3d23-43ad-b9df-940894b459d9@kdbg.org","threadId":"61991","inReplyTo":"pull.1774.v8.git.1762440131635.gitgitgadget@gmail.com","subject":"Re: [PATCH v8] gitk: add external diff file rename detection","fromName":"Johannes Sixt","fromEmail":"j6t@kdbg.org","sentAt":"2025-11-06T18:16:46Z","receivedAt":"2025-11-06T18:16:49Z","isPatch":true,"sender":{"key":"j6t@kdbg.org","avatar":"https://avatars.githubusercontent.com/u/14810926?v=4"},"body":"Am 06.11.25 um 15:42 schrieb ToBoMi via GitGitGadget:\n> From: Tobias Boesch <tobias.boesch@miele.com>\n> \n> If a file is renamed between commits and an external diff is started\n> through gitk on the original or the renamed file name,\n> gitk is unable to open the renamed file in the external diff editor.\n> It fails to fetch the renamed file from git, because it fetches it\n> using its original path in contrast to using the renamed path of the\n> file.\n> Detect the rename and open the external diff with the original and\n> the renamed file instead of no file (fetch the renamed file path and\n> name from git) no matter if the original or the renamed file is\n> selected in gitk.\n> \n> Signed-off-by: Tobias Boesch <tobias.boesch@miele.com>\n> ---\n\nThanks, this is looking good now. I applied these whitespace fixes while\nqueuing:\n\ndiff --git a/gitk b/gitk\nindex caaee16..9659466 100755\n--- a/gitk\n+++ b/gitk\n@@ -3811,7 +3811,7 @@ proc check_for_renames_in_diff {filepath} { # renames\n \n     set filename [file tail $filepath]\n     set renames {}\n-    \n+\n     foreach loc $difffilestart {\n         set loclineend [string map {.0 .end} $loc]\n         set fromlineloc \"$loc + 2 lines\"\n@@ -3822,15 +3822,15 @@ proc check_for_renames_in_diff {filepath} { # renames\n             && [string equal -length 10 \"rename to \" $rentoline]} {\n             set renfrom [string range $renfromline 12 end]\n             set rento [string range $rentoline 10 end]\n-                if {[string first $filename $renfrom] != -1\n-                    || [string first $filename $rento] != -1} {\n-                    lappend renames $renfrom\n-                    lappend renames $rento\n+            if {[string first $filename $renfrom] != -1\n+                || [string first $filename $rento] != -1} {\n+                lappend renames $renfrom\n+                lappend renames $rento\n                 break\n             }\n         }\n     }\n-    \n+\n     return $renames\n }\n \n\n-- Hannes\n\n"}]}