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

Archives Details

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

Rust

2025.04.14

この記事は最終更新日から1年以上が経過しています。

どもです。

今回は前回の「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

ゲーム開発に必要な基本数学入門

2025.08.15

Godot 4 & Rust 始め方

2025.06.19

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

CATEGORY LIST

LATEST NEWS

ラズパイが高騰しているのでストレス解消のためAliExpressで購入した電子パーツ達

電子工作

2026.08.02

【2026】【VMware Fusion】MacでWindowsを無料で扱う。

windows

2026.07.19

Raspberry Pi 5 でマインクラフトサーバーを立てる(Java版 × 統合版クロスプレイ対応)

RaspberryPi

2026.06.24

ラズパイが高い。

RaspberryPi

2026.05.26

【Claude Code】フル稼働。ToDo Appを様々なGUIフレームワーク用いて作らせる。

AI・Bot・algorithm

2026.05.24

Macで歩く「たのしいバイナリの歩き方」うさみみハリケーンの代わりに、Cheat Engine / Bit slicerを使用する

アセンブラ

2026.04.12

Macで歩く「たのしいバイナリの歩き方」

アセンブラ

2026.04.10

【Railway】ひたすら安く個人開発サービスを運用する計画

サーバー

2026.04.06

たびのきろく

イベント

2026.02.23

【Railway】MySQLサービスをコスト抑えて運用する

運用

2026.01.19

あけましておめでとうございますmm DjangoアプリをRailwayに移行する。

運用

2026.01.06

効率の良い AI駆動開発について考える

AI・Bot・algorithm

2025.11.09

RANKING

Follow

SPONSOR

現在、掲載募集中です。



Links

About Us

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

Entry Profile

Graphical FrontEnd Engineer
- Daisuke Takayama

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

FOLLOW US