Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
Help us understand the problem. What is going on with this article?

Git GUIでパッチの一部をコミットする機能の日本語対応

More than 1 year has passed since last update.

パッチの一部をコミットする機能とは

Git にはgit add -pで変更の一部だけをステージ→コミットする機能がある。
そして、これをGit GUIで行うと直感的に操作することができてとても良い。

Git GUIでの操作例

以下の例では2箇所の変更がファイル「ふうばあ.md」にあり、その上の部分だけをステージしている。

  1. ステージしたいファイルを選び、右ペインの変更箇所を選んで右クリック
  2. 「Stage Hunk For Commit」を選ぶか、図の例のように変更を反映したい行を選択した状態で「Stage Lines For Commit」を選ぶ

    step1.png

  3. 以下の例のように選択部分だけがインデックスに追加され、選択されなかった部分はUnstage Changesに残る

    step2.png

問題

Gitの日本語に関する問題はだいぶ緩和したがGit for Windowsではまだ問題がある。

操作対象のファイルにマルチバイト文字を含んでいるとこの機能が失敗する。実際、上の例は対処をしなければ以下のように失敗する。メッセージ中のファイル名がバケバケなのがわかる。

失敗の例

error.png

対象バージョン

Git for Windows version 2.18.0で確認

$ git --version
git version 2.18.0.windows.1

対処

以下の2種類のパッチを考えた。このどちらかを

"C:\Program Files (x86)\Git\mingw64\share\git-gui\lib\diff.tcl"

このファイルに適用することで問題を解消することができる。

パッチ1

--- diff.tcl.orig   2018-06-22 11:12:04.000000000 +0900
+++ diff.tcl    2018-07-18 22:49:38.130462100 +0900
@@ -603,11 +603,14 @@
        set e_lno end
    }

+   set current_diff_header [replace_diff_header $current_diff_header $current_diff_path]
+
    if {[catch {
        set enc [get_path_encoding $current_diff_path]
        set p [eval git_write $apply_cmd]
-       fconfigure $p -translation binary -encoding $enc
+       fconfigure $p -translation binary -encoding utf-8
        puts -nonewline $p $current_diff_header
+       fconfigure $p -translation binary -encoding $enc
        puts -nonewline $p [$ui_diff get $s_lno $e_lno]
        close $p} err]} {
        error_popup "$failed_msg\n\n$err"
@@ -640,6 +643,21 @@
    }
 }

+proc replace_diff_header {diff_header path_name} {
+
+    set index_line {}
+
+   if {[regexp -line {^(index.*)$} $diff_header - index_line]} {
+       return "diff --git a/$path_name b/$path_name
+$index_line
+--- a/$path_name
++++ b/$path_name
+"
+   } else {
+       return $diff_header
+   }
+}
+
 proc apply_range_or_line {x y} {
    global current_diff_path current_diff_header current_diff_side
    global ui_diff ui_index file_states
@@ -822,11 +840,14 @@
        set first_l [$ui_diff index "$next_l + 1 lines"]
    }

+   set current_diff_header [replace_diff_header $current_diff_header $current_diff_path]
+
    if {[catch {
        set enc [get_path_encoding $current_diff_path]
        set p [eval git_write $apply_cmd]
-       fconfigure $p -translation binary -encoding $enc
+       fconfigure $p -translation binary -encoding utf-8
        puts -nonewline $p $current_diff_header
+       fconfigure $p -translation binary -encoding $enc
        puts -nonewline $p $wholepatch
        close $p} err]} {
        error_popup "$failed_msg\n\n$err"

パッチ2

--- diff.tcl.orig   2018-06-22 11:12:04.000000000 +0900
+++ diff.tcl    2018-07-18 22:51:35.600088600 +0900
@@ -362,9 +362,9 @@
    set ::current_diff_inheader 1
    fconfigure $fd \
        -blocking 0 \
-       -encoding [get_path_encoding $path] \
+       -encoding utf-8 \
        -translation lf
-   fileevent $fd readable [list read_diff $fd $conflict_size $cont_info]
+   fileevent $fd readable [list read_diff $fd $conflict_size $cont_info [get_path_encoding $path]]
 }

 proc parse_color_line {line} {
@@ -392,7 +392,7 @@
    return [list $result $markup]
 }

-proc read_diff {fd conflict_size cont_info} {
+proc read_diff {fd conflict_size cont_info diff_enc} {
    global ui_diff diff_active is_submodule_diff
    global is_3way_diff is_conflict_diff current_diff_header
    global current_diff_queue
@@ -410,11 +410,15 @@
            || [string match {diff --cc *}       $line]
            || [string match {diff --combined *} $line]} {
            set ::current_diff_inheader 1
+           fconfigure $fd -encoding utf-8
        }

        # -- Check for end of diff header (any hunk line will do this).
        #
-       if {[regexp {^@@+ } $line]} {set ::current_diff_inheader 0}
+       if {[regexp {^@@+ } $line]} {
+           set ::current_diff_inheader 0
+           fconfigure $fd -encoding $diff_enc
+       }

        # -- Automatically detect if this is a 3 way diff.
        #
@@ -429,6 +433,7 @@
            if {   [string match {Binary files * and * differ} $line]
                || [regexp {^\* Unmerged path }                $line]} {
                set ::current_diff_inheader 0
+               fconfigure $fd -encoding $diff_enc
            } else {
                append current_diff_header $line "\n"
            }
@@ -606,8 +611,9 @@
    if {[catch {
        set enc [get_path_encoding $current_diff_path]
        set p [eval git_write $apply_cmd]
-       fconfigure $p -translation binary -encoding $enc
+       fconfigure $p -translation binary -encoding utf-8
        puts -nonewline $p $current_diff_header
+       fconfigure $p -translation binary -encoding $enc
        puts -nonewline $p [$ui_diff get $s_lno $e_lno]
        close $p} err]} {
        error_popup "$failed_msg\n\n$err"
@@ -825,8 +831,9 @@
    if {[catch {
        set enc [get_path_encoding $current_diff_path]
        set p [eval git_write $apply_cmd]
-       fconfigure $p -translation binary -encoding $enc
+       fconfigure $p -translation binary -encoding utf-8
        puts -nonewline $p $current_diff_header
+       fconfigure $p -translation binary -encoding $enc
        puts -nonewline $p $wholepatch
        close $p} err]} {
        error_popup "$failed_msg\n\n$err"

解説

Git GUIではファイルのエンコーディングを.gitattributesで指定したencodingパラメータの値でデコードしようとする。このデフォルト値は日本版Windowsではcp932でとなっているので何も指定しなければファイルの内容についてはcp932とみなされる。

一方、git diff の出力は

diff --git a/パス名 b/パス名
index xxxx yyyy 06000
--- a/パス名
+++ b/パス名
 foo
 bar
-baz
+foo
 foo
 bar

という形式であるが、最初の4行のヘッダ部分をGit for WindowsのGitはUTF-8で出力する。

そして、Git GUIがパッチの内容をステージするときこのdiffの出力から対象のファイルを決定しようとするが、UTF-8の出力をcp932とみなしてデコードするために文字化けを起こす。

対処に示したパッチ1では、ヘッダ部分を自分で作成し直すことで対処している。パッチ2ではdiffの出力を読むとき最初の4行はUTF-8として読み込み、残りはファイルのエンコーディングで読み込むことで文字化けを起こさない対処をしている。

この解析結果から、ファイルを常にUTF-8で扱うようにすれば本記事のパッチがなくても正しく動作することがわかる。したがって、

.gitattributes
* encoding=utf-8

などとファイルのエンコーディングを常にUTF-8であるとして、実際に作成するファイルもUTF-8にすればパッチは必要なくなると思われる。しかし、Windows環境でそのようなことが可能なのか?は疑問。

もうひとつ、後で気づいたのだが

git config core.quotepath true

としてても問題は発生しないようだ。しかし、これを行うとgit statusの出力が見づらくなるのでGit GUIの中だけこのような設定が適用できれば一番いいかもしれない。

その他

  • Git GUIでパッチの一部をコミットする機能の使い方と問題点を示した。しかし、今流行のVisual Studio Codeでも同じ操作はできるし、日本語に関する問題も発生しない。

  • Git GUIの差分表示に git diff -w(空白の差異を表示しない)を使っているとこの記事の対処の有無に関わらず、パッチの一部をステージするときに失敗する。

git diff -w を使えないことの対策に、Ignore whitespaceのトグルボタンを追加してみました。(以下は、gitに対するパッチになってます)

diff --git a/git-gui/git-gui.sh b/git-gui/git-gui.sh
index 6de74ce63..3f1300db7 100755
--- a/git-gui/git-gui.sh
+++ b/git-gui/git-gui.sh
@@ -3458,4 +3458,9 @@ trace add variable current_diff_path write trace_current_diff_path

 gold_frame .vpane.lower.diff.header
+set is_ignore_whitespace 0
+${NS}::checkbutton .vpane.lower.diff.header.chk \
+   -text [mc "Ignore whitespace"] \
+   -variable is_ignore_whitespace \
+   -command reshow_diff
 tlabel .vpane.lower.diff.header.status \
    -background gold \
@@ -3474,4 +3479,5 @@ tlabel .vpane.lower.diff.header.path \
    -anchor w \
    -justify left
+pack .vpane.lower.diff.header.chk -side left
 pack .vpane.lower.diff.header.status -side left
 pack .vpane.lower.diff.header.file -side left
diff --git a/git-gui/lib/diff.tcl b/git-gui/lib/diff.tcl
index 68c4a6c73..d86f1d4c6 100644
--- a/git-gui/lib/diff.tcl
+++ b/git-gui/lib/diff.tcl
@@ -289,4 +289,5 @@ proc start_show_diff {cont_info {add_opts {}}} {
    global ui_diff ui_index ui_workdir
    global current_diff_path current_diff_side current_diff_header
+   global is_ignore_whitespace

    set path $current_diff_path
@@ -330,4 +331,7 @@ proc start_show_diff {cont_info {add_opts {}}} {
    lappend cmd -p
    lappend cmd --color
+   if {$is_ignore_whitespace} {
+       lappend cmd -w
+   }
    set cmd [concat $cmd $repo_config(gui.diffopts)]
    if {$repo_config(gui.diffcontext) >= 1} {

スクリーンショット 2018-08-08 21.51.18.png

jca02266
Family BASIC→N88-BASIC→Z80 マシン語→C,C++→EmacsLisp→sh,awk→Perl→Ruby→TclTk→vb6, vba→SQL→Java (ほぼ学習のみ -> Pascal,Prolog,C#,Python,Haskell,Scala,Groovy,JavaScript,PowerShell,Go,Rust,Kotlin,TypeScript)
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away