【UE4】【UE5】Unreal Engineで使うSVNのバージョンが1.9.5縛りになってしまっている理由

[okazu]: https://qiita.com/EGJ-Kaz_Okada/items/ca3ff9150bfabf05e1e6
[polyCube1]: https://note.com/polycube1/n/n917902484eca

:::note info
確認したバージョンはUE5.3.1です。
ただし、UE4も含め他のバージョンのUEも同じ状況だと思われます。
また、Win64環境でしか確認していません。
:::

# 参考サイト

- [[UE4]UE4がサポートしている Subversion (SVN)のバージョンを確認する方法について | Qiita@EGJ-Kaz_Okada][okazu]
- [[UE5]ソースコントロール(SVN)を連携する | polyCube1][polyCube1]

# はじめに

最新バージョンの SVNをインストールしている環境では、Unreal Engineのソースコントロールを使おうとしても、リポジトリのフォーマットエラーが発生してしまう。
下の画像でハイライトされている ```svn: E160043: Expected FS format between '1' and '7'; found format '8'``` がそのエラーメッセージである。
![20231012_RevisionControlLogin.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/8f201c89-af98-4cef-76ed-625a04b24aaa.jpeg)
これを回避するには、例えば[２つ目の参考サイト][polyCube1]などを参照されたい。
この記事では、回避策ではなく、この問題が起きてしまっている原因についてを書いている。

# Unreal Engineには svnが同梱されている

UEをインストールするとコマンドライン版の svn一式もインストールされる。そのディレクトリは UEインストールディレクトリの下の```Engine/Binaries/ThirdParty/svn/Win64/```である。
[１つ目の参考サイト][okazu]にも書かれているように、このディレクトリのsvnはUE4の時代からずっとver 1.9.5である。
下の画像は、コマンドプロンプトでこのディレクトリのsvn.exeのバージョンを表示させたものである。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/471c7d87-0fe2-ba2b-ffd3-be28bf462a9d.png)
version 1.9.5と表示された。
現状、Unreal Engineエディタ上で使うSVNは、PCにインストールしたSVNを使わず、このディレクトリに入っているSVNを使ってしまっている。それゆえに、バージョン1.9.5に縛られているのである。

これは、エンジン起動時のログからも知ることができる。
UEエディタを起動させたら、Output Logを開いて「LogSourceControl」で検索すると以下のようになる。
![20231012_LogSourceControl.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/7a32b735-636f-c2c5-39d7-43b46e0d7507.jpeg)
上の画像でハイライトされた部分を抜粋すると：
```Unable to detect system-level svn binary```
　（システムレベルのSVNが見つからなかった）
```Using path D:/Program Files/Epic Games/UE_5.3/Engine/Binaries/ThirdParty/svn/Win64/svn.exe```
　（Unreal Engine同梱のsvn.exeを使います）

つまり、PCにインストールされたSVNを探したものの**見つからず**、代替策としてUE同梱のSVNを使っていることになる。

# PCにインストールされたSVNがみつからない原因

上記のLogSourceControlに書かれている文字列でエンジンソースを検索すると、当該箇所が見つかる。
- **ソースファイル**：```SubersionSourceControlUtils.cpp```
- **ソースのディレクトリ**： ```/Engine/Plugins/Developer/SubversionSourceControl/Source/SubversionSourceControl/Private/```
- **関数名**： ```DetectSubversionPath()```

そこで行われている処理を簡略化したソースコードで説明する。

```c++
namespace SubversionSourceControlUtils
{
    static FString DetectSubversionPath()
    {
        // ...もろもろ省略

        FString SVNPath;

        {
            // Attempt to detect a system wide version of the svn command line tools
            void* ReadPipe = nullptr, *WritePipe = nullptr;
            FPlatformProcess::CreatePipe(ReadPipe, WritePipe);

            FProcHandle ProcessHandle = FPlatformProcess::CreateProc(TEXT("where"), TEXT("svn.exe"), /*以下略*/);
            if (ProcessHandle.IsValid())
            {
                FPlatformProcess::WaitForProc(ProcessHandle);
                SVNPath = FPlatformProcess::ReadPipe(ReadPipe);

                // Trim at the first \n
                int32 NewLine = INDEX_NONE;
                if (SVNPath.FindChar('\n', NewLine))
                {
                    SVN.LeftInline(NewLine);
                }
            }

            FPlatformProcess::ClosePipe(ReadPipe, WritePipe);
            FPlatformProcess::CloseProc(ProcessHandle);
        }

        bool bPathIsValid = !SVNPath.IsEmpty() && FPaths::FileExists(SVNPath);
        if (!bPathIsValid)
        {
            UE_LOG(LogSourceControl, Log, TEXT("Unable to detect system-level svn binary."));
            SVNPath = DefaultPath;
        }

        UE_LOG(LogSourceControl, Log, TEXT("Using path %s for svn operations"), *FPaths::ConvertRelativePathToFull(SVNPath));
        return SVNPath;
    }
}
```

以下に、問題となる箇所を順に書く。

## ① where svn.exeを実行する

まず、where svn.exeを実行して、svn.exeを探している。

```c++
FProcHandle ProcessHandle = FPlatformProcess::CreateProc(TEXT("where"), TEXT("svn.exe"), /*以下略*/);
```

ちなみにコマンドプロンプトで同じことをすると、次のようになり、インストールされたsvn.exeのパスがちゃんと取れていることが分かる。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/358170/e51121cc-b255-8748-5d3d-0ae17954bbfb.png)

## ② where svn.exeの結果を文字列で受け取る

次に、where svn.exeの結果を文字列に受け取る。

```c++
SVNPath = FPlatformProcess::ReadPipe(ReadPipe);
```

このSVNPathをデバッガで見ると、次のような文字列になっていた。
```"C:\\Program Files\\TortoiseSVN\\bin\\svn.exe\r\n"```
なんと、この時点では、ちゃんとPCにインストールされたsvnを見つけているのである。

## ③末尾の改行文字を取り除く

SVNPathの末尾にくっついている改行文字```"\r\n"```を取り除く、はずなのだが…

```c++
int32 NewLine = INDEX_NONE;
if (SVNPath.FindChar('\n', NewLine))
{
    SVN.LeftInline(NewLine);
}
```

この書き方では、```'\n'```しか取り除かれない。この処理を通った後のSVNPathは
```"C:\\Program Files\\TortoiseSVN\\bin\\svn.exe\r"```となっていて、案の定、末尾の```'\r'```が残ってしまっていた。

## ④SVNPathに書かれたファイルが存在するかを確認する

```c++
bool bPathIsValid = !SVNPath.IsEmpty() && FPaths::FileExists(SVNPath);
```

FPaths::FileExists()は、残念ながら、末尾に```'\r'```が残っているパス文字列 ```"C:\\Program Files\\TortoiseSVN\\bin\\svn.exe\r"```に対しては falseを返す。つまり、svn.exeは見つからないことになる。

## ⑤見つからなかったので、デフォルトのsvnを使う

```c++
if (!bPathIsValid)
{
    UE_LOG(LogSourceControl, Log, TEXT("Unable to detect system-level svn binary."));
    SVNPath = DefaultPath; // DeafultPathは UEに同梱されたSVN (Engine/Binaries/ThirdParty/svn/Win64/svn.exe)
}
```

結局、SVNPathは UEに同梱されたSVNのパスで上書きされてしまう。
もし最新バージョンのSVNでリポジトリを作っていた場合、そのリポジトリをUEに同梱されたver1.9.5のSVNで操作しようとしてしまい、「リポジトリのフォーマットが違う」というエラーが発生してしまう。

# 修正案

自分のPCにインストールするSVNをver1.9.5にダウングレードしてリポジトリを作れば、UEエディタ上のソースコントロールでもエラーなく使えるのだが、どうにも気持ちが悪い。
出来れば Epic Gamesさんに当該ソースを修正してもらいたい！

おそらく、修正方法は簡単で、もともとのコードが
```c++
// Trim at the first \n
int32 NewLine = INDEX_NONE;
if (SVNPath.FindChar('\n', NewLine))
{
    SVN.LeftInline(NewLine);
}
```
のところを、
```c++
// Trim whitespace characters from the end
SVNPath.TrimEndInline();
```
と書き直すとうまく動くと思われる。

:::note warn
実際に書きなおしての動作確認はやっていないのでご注意ください。
:::