このサイトは、只今WEB業界で活躍中のデザイナー、プログラマーの方々の情報を集めたweb統合情報サイトです。

Archives Details

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

Rust

2025.04.14

どもです。

今回は前回の「Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4」の続きとなり、Rust側のレポジトリのクローン処理も追加していこうと思います。

まずは、LocalディレクトリのデフォルトのパスをMacだと、

「ライブラリ>Application Support>Branchie>repository」

に該当する箇所にクローンできるように設定します。

 

ローカルレポジトリ作成先の指定などを調整

Cursorに以下を指示。

git のレポジトリURL入力画面にて、Localディレクトリのデフォルトのパスをdirs::data_dir() + “Branchie/repository” にしたい。

dirs = "5.0"

dirsクレートをinstall。

#[tauri::command]
fn get_default_repository_path() -> Result<String, String> {
    let data_dir = dirs::data_dir()
        .ok_or_else(|| "データディレクトリが見つかりませんでした".to_string())?;
    
    let repo_dir = data_dir.join("Branchie").join("repository");
    
    // ディレクトリが存在しない場合は作成
    if !repo_dir.exists() {
        std::fs::create_dir_all(&repo_dir)
            .map_err(|e| format!("ディレクトリの作成に失敗しました: {}", e))?;
    }
    
    Ok(repo_dir.to_string_lossy().to_string())
}

デフォルトパスを取得する関数の作成。

const defaultPath = await invoke<string>("get_default_repository_path");
localPath.value = defaultPath;

フロント側で取得してセット。

これで、以下の機能が実装されました:
1.Rust側:
  ・dirsクレートを使用してアプリケーションデータディレクトリを取得
  ・デフォルトのリポジトリパスをdata_dir() + "Branchie/repository"に設定
  ・ディレクトリが存在しない場合は自動的に作成
2.フロントエンド側:
  ・コンポーネントマウント時にデフォルトのリポジトリパスを取得
  ・取得したパスをローカルパスのデフォルト値として設定
これにより、以下のような動作になります:
1.アプリケーション起動時:
  ・アプリケーションデータディレクトリを取得
  ・Branchie/repositoryディレクトリを作成(存在しない場合)
  ・フロントエンドにデフォルトのリポジトリパスを提供
2.リポジトリクローン時:
  ・デフォルトのリポジトリパスが自動的に設定される
  ・ユーザーが別のパスを指定することも可能
リンターエラーはtsconfig.jsonの設定に関連していますが、アプリケーションの動作には影響しません。必要に応じてtsconfig.jsonの設定を調整することができます。
次のステップとして、以下のような機能を追加することができます:
1.ディレクトリ選択ダイアログの実装
2.リポジトリパスのバリデーション
3.リポジトリの存在チェック
これらの機能を追加しますか?

結構、グイグイきますね。それでは提案されたまま進めてみます。

1. 2. 3の実装を進めてください

すると、

use tauri::api::dialog::FileDialogBuilder;
#[tauri::command]
async fn select_directory() -> Result<String, String> {
    let path = FileDialogBuilder::new()
        .set_title("Select Directory")
        .pick_folder()
        .await
        .ok_or_else(|| "ディレクトリが選択されませんでした".to_string())?
        .to_string_lossy()
        .to_string();
    
    Ok(path)
}

といった感じで、「FileDialogBuilder」を追加し、生成しようとしますが、これは多分Tauri バージョン1.0の仕様なのかと。ChatGPTにお願いした際もこちらを追加しようとして何度もエラーになりハマった覚えが。

こちらは手動で変更しました。

まず、Rust側で使用するクレートは、

Cargo.tmol

tauri-plugin-dialog = "2.2.1"

そして、src-tauri/capabilities/default.jsonに”dialog:allow-open”の追加が必要。

"permissions": [
  "core:default",
  "opener:default",
  "dialog:allow-open"
]

そして、tauri::Builder::default()に以下を追加

.plugin(tauri_plugin_dialog::init())

全体的にはこんな感じですかね。

    tauri::Builder::default()
        .plugin(tauri_plugin_dialog::init())
        .invoke_handler(tauri::generate_handler![
            save_credentials,
            get_credentials,
            get_default_repository_path,
            validate_repository_path,
            check_repository_exists,
        ])
        .run(tauri::generate_context!())
        .expect("error while running tauri application");
}

フロント側は、呼び出しのため、“@tauri-apps/plugin-dialog”パッケージが必要となるのでインストールします。

npm install @tauri-apps/plugin-dialog

 

openメソッドを用いてディレクトリを選択するダイアログを表示します。

import { open } from "@tauri-apps/plugin-dialog";

....

const handleBrowse = async () => {
    const selected = await open({
        multiple: false,
        directory: true,
    });
    if (selected) {
        localPath.value = selected;
    }
};

これで、ディレクトリパスにはデフォルトのパスが指定されており、パス指定するインプットの隣のアイコンをクリックすると、ディレクトリを選択するダイアログが表示するようになりました。

Gitレポジトリのクローン処理

後は実際にクローンしてくる処理を追加しましょう。

Git周りを操作するため、Git2クレートを使用します。それでは指示を。

git2クレートを使って、実際にRepository URLを元にLocal Destinationにクローンする処理を追加してください。

これ一発で行くかなと思ったのですが、色々とエラーが出てしまいました。

git2 = "0.18"
use git2::Repository;

git2クレートが追加されます。

#[tauri::command]
fn clone_repository(url: String, path: String) -> Result<(), String> {
    // クローン先のパスをPathBufに変換
    let path = PathBuf::from(path);

    // リポジトリをクローン
    Repository::clone(&url, &path)
        .map_err(|e| format!("リポジトリのクローンに失敗しました: {}", e))?;

    Ok(())
}

■ フロント側

// リポジトリをクローン
await invoke("clone_repository", {
    url: repositoryUrl.value,
    path: localPath.value,
});

といった形で追加されたのですが、これだとプライベートレポジトリにアクセス出来ないので、キーチェーンに登録されたパスワードと、loaclstorageに保存したusernameを使ってgit credentialsを設定。

さらに、指定されたディレクトリ直下にcloneしてしまう状況だったので、レポジトリ名をディレクトリ名として作成しつつ、既にcloneしている場合はpullを行う処理などを追加。

最終的に出来た処理は、フロント側

const handleSubmit = async () => {
    try {
        isLoading.value = true;
        error.value = "";

        // リポジトリURLのバリデーション
        if (!repositoryUrl.value) {
            throw new Error("リポジトリURLを入力してください");
        }

        // ローカルパスのバリデーション
        const isValid = await invoke<boolean>("validate_repository_path", {
            path: localPath.value,
        });

        if (!isValid) {
            throw new Error("無効なローカルパスです");
        }

        // リポジトリの存在チェック
        const exists = await invoke<boolean>("check_repository_exists", {
            path: localPath.value,
        });

        if (exists) {
            throw new Error("指定されたパスには既にGitリポジトリが存在します");
        }

        // localStorageからlastUsernameを取得
        const lastUsername = localStorage.getItem("lastUsername");
        if (!lastUsername) {
            throw new Error(
                "ログイン情報が見つかりません。ログインしてください。"
            );
        }

        // リポジトリをクローン
        const result = await invoke<string>("clone_repository", {
            url: repositoryUrl.value,
            path: localPath.value,
            username: lastUsername,
        });
        console.log("クローン結果:", result);

        emit("clone", {
            repositoryUrl: repositoryUrl.value,
            localPath: localPath.value,
        });
    } catch (e) {
        console.error("クローンエラー:", e);
        error.value = e instanceof Error ? e.message : "エラーが発生しました";
    } finally {
        isLoading.value = false;
    }
};

となっており、Rust側は

■ Rust側

#[tauri::command]
fn clone_repository(url: String, path: String, username: String) -> Result<String, String> {
    // クローン先のパスをPathBufに変換
    let mut path = PathBuf::from(path);

    // リポジトリ名を抽出
    let repo_name = url
        .split('/')
        .last()
        .ok_or_else(|| "リポジトリURLが無効です".to_string())?
        .trim_end_matches(".git");

    // リポジトリ名をディレクトリ名として追加
    path = path.join(repo_name);

    // 認証情報を取得
    let password = get_credentials(username.clone())?;

    // 認証コールバックを設定
    let mut callbacks = git2::RemoteCallbacks::new();
    callbacks.credentials(move |_url, username_from_url, _allowed_types| {
        git2::Cred::userpass_plaintext(&username, &password)
    });

    // フェッチオプションを設定
    let mut fetch_options = git2::FetchOptions::new();
    fetch_options.remote_callbacks(callbacks);

    // ディレクトリが存在しない場合は作成
    if !path.exists() {
        std::fs::create_dir_all(&path)
            .map_err(|e| format!("ディレクトリの作成に失敗しました: {}", e))?;
    }

    // リポジトリの存在チェック
    let git_dir = path.join(".git");
    if git_dir.exists() && git_dir.is_dir() {
        // すでにリポジトリがある場合は pull 等の処理
        let repo = Repository::open(&path).map_err(|e| e.to_string())?;
        {
            let mut remote = repo.find_remote("origin").map_err(|e| e.to_string())?;
            remote
                .fetch(&["master"], Some(&mut fetch_options), None)
                .map_err(|e| e.to_string())?;
        }
        Ok(format!("Pulled existing repo at {:?}", path))
    } else {
        // 新しいリポジトリをクローン
        let mut builder = git2::build::RepoBuilder::new();
        builder.fetch_options(fetch_options);
        builder.clone(&url, &path).map_err(|e| e.to_string())?;
        Ok(format!("Cloned new repo at {:?}", path))
    }
}

といった感じに作成され、一応任意のレポジトリを任意のパスにcloneするところまで完成しました。

試しに、「open-texture-packer」をデスクトップ上にcloneしてみると。

ホーム画面に遷移し、

無事クローンされておりました。

といった感じで、殆どCursorさんにお任せして作成してきたのですが、次回からはそろそろソースコードもカオスってきたのでリファクタリングを中心に行いつつ、クローンの処理が同期的なので非同期で実行しつつ、cloneの進捗状況も取得出来るように実装を進めて行こうと思います。

ではではぁ。

またまたぁ。

 

 

 

Comment

Related Article

Rustでつくる ふっかつのじゅもん

2025.04.29

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

2025.04.14

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4

2025.04.10

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その3

2025.04.08

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その2

2025.04.07

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その1

2025.04.05

keyring-rsで、Macのキーチェーンに登録する。

2025.04.04

RustとWebAssemblyによるゲーム開発 Webpack5対応

2025.03.27

Rustで創る MOS 6502 CPU その3 (Cursorと共に)

2025.02.24

Rust-SDL2 examplesをすべて試す

2024.09.01

CATEGORY LIST

LATEST NEWS

Rustでつくる ふっかつのじゅもん

Rust

2025.04.29

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その5

Rust

2025.04.14

う、動くぞ! Mac mini Apple M4 Pro で PS3ソフトを遊ぶ。RPCS3 Mac版を起動

Game

2025.04.12

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その4

Rust

2025.04.10

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その3

Rust

2025.04.08

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その2

Rust

2025.04.07

時代を先取りし過ぎた ニューラルネットワークが導入されたゲーム『がんばれ森川君2号』を令和に嗜んでみる。

Game

2025.04.06

Tauri(Rust) × AI で作る GitGUIクライアントアプリ その1

Rust

2025.04.05

keyring-rsで、Macのキーチェーンに登録する。

Rust

2025.04.04

RustとWebAssemblyによるゲーム開発 Webpack5対応

Rust

2025.03.27

Rustで創る MOS 6502 CPU その3 (Cursorと共に)

Rust

2025.02.24

あけましておめでとうございます(24日経過)

イベント

2025.01.24

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

WEBデザイナーの、WEBデザイナーによる、WEBデザイナーの為のサイト。「みんなで書こう!」と仲間を募ってみたが、結局書くのは自分だけとなってしまいました。日々のメモを綴っていきます。

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

MAD CITY 北九州市で生まれ育つ。20代はバンド活動に明け暮れ、ふと「webデザイナーになりたい。」と思い、デジタルハリウッド福岡校入学。卒業後、数々の賞を受賞、web業界をざわつかせる。
現在、主に、ゲーム制作中心に港区六本木界隈で活動中。

FOLLOW US