mirror of
https://github.com/mountain-loop/yaak.git
synced 2026-04-11 03:26:58 +02:00
Handle remote branches
This commit is contained in:
2
src-tauri/gen/schemas/acl-manifests.json
generated
2
src-tauri/gen/schemas/acl-manifests.json
generated
File diff suppressed because one or more lines are too long
20
src-tauri/gen/schemas/desktop-schema.json
generated
20
src-tauri/gen/schemas/desktop-schema.json
generated
@@ -5432,6 +5432,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:allow-checkout"
|
"const": "yaak-git:allow-checkout"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:allow-checkout-remote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the commit command without any pre-configured scope.",
|
"description": "Enables the commit command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5442,6 +5447,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:allow-delete-branch"
|
"const": "yaak-git:allow-delete-branch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:allow-fetch-all"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the initialize command without any pre-configured scope.",
|
"description": "Enables the initialize command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5492,6 +5502,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:deny-checkout"
|
"const": "yaak-git:deny-checkout"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:deny-checkout-remote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the commit command without any pre-configured scope.",
|
"description": "Denies the commit command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5502,6 +5517,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:deny-delete-branch"
|
"const": "yaak-git:deny-delete-branch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:deny-fetch-all"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the initialize command without any pre-configured scope.",
|
"description": "Denies the initialize command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
20
src-tauri/gen/schemas/macOS-schema.json
generated
20
src-tauri/gen/schemas/macOS-schema.json
generated
@@ -5432,6 +5432,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:allow-checkout"
|
"const": "yaak-git:allow-checkout"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:allow-checkout-remote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the commit command without any pre-configured scope.",
|
"description": "Enables the commit command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5442,6 +5447,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:allow-delete-branch"
|
"const": "yaak-git:allow-delete-branch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:allow-fetch-all"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the initialize command without any pre-configured scope.",
|
"description": "Enables the initialize command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5492,6 +5502,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:deny-checkout"
|
"const": "yaak-git:deny-checkout"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:deny-checkout-remote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the commit command without any pre-configured scope.",
|
"description": "Denies the commit command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -5502,6 +5517,11 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "yaak-git:deny-delete-branch"
|
"const": "yaak-git:deny-delete-branch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "yaak-git:deny-fetch-all"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Denies the initialize command without any pre-configured scope.",
|
"description": "Denies the initialize command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export type GitStatus = "added" | "conflict" | "current" | "modified" | "removed
|
|||||||
|
|
||||||
export type GitStatusEntry = { relaPath: string, status: GitStatus, staged: boolean, prev: SyncModel | null, next: SyncModel | null, };
|
export type GitStatusEntry = { relaPath: string, status: GitStatus, staged: boolean, prev: SyncModel | null, next: SyncModel | null, };
|
||||||
|
|
||||||
export type GitStatusSummary = { path: string, headRef: string | null, headRefShorthand: string | null, entries: Array<GitStatusEntry>, origins: Array<string>, branches: Array<string>, };
|
export type GitStatusSummary = { path: string, headRef: string | null, headRefShorthand: string | null, entries: Array<GitStatusEntry>, origins: Array<string>, localBranches: Array<string>, remoteBranches: Array<string>, };
|
||||||
|
|
||||||
export type PullResult = { receivedBytes: number, receivedObjects: number, };
|
export type PullResult = { receivedBytes: number, receivedObjects: number, };
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ const COMMANDS: &[&str] = &[
|
|||||||
"checkout",
|
"checkout",
|
||||||
"commit",
|
"commit",
|
||||||
"delete_branch",
|
"delete_branch",
|
||||||
|
"fetch_all",
|
||||||
"initialize",
|
"initialize",
|
||||||
"log",
|
"log",
|
||||||
"merge_branch",
|
"merge_branch",
|
||||||
|
|||||||
@@ -41,7 +41,7 @@ export function useGit(dir: string) {
|
|||||||
mutationFn: (args) => invoke('plugin:yaak-git|delete_branch', { dir, ...args }),
|
mutationFn: (args) => invoke('plugin:yaak-git|delete_branch', { dir, ...args }),
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}),
|
}),
|
||||||
checkout: useMutation<void, string, { branch: string; force: boolean }>({
|
checkout: useMutation<string, string, { branch: string; force: boolean }>({
|
||||||
mutationKey: ['git', 'checkout', dir],
|
mutationKey: ['git', 'checkout', dir],
|
||||||
mutationFn: (args) => invoke('plugin:yaak-git|checkout', { dir, ...args }),
|
mutationFn: (args) => invoke('plugin:yaak-git|checkout', { dir, ...args }),
|
||||||
onSuccess,
|
onSuccess,
|
||||||
@@ -51,6 +51,11 @@ export function useGit(dir: string) {
|
|||||||
mutationFn: (args) => invoke('plugin:yaak-git|commit', { dir, ...args }),
|
mutationFn: (args) => invoke('plugin:yaak-git|commit', { dir, ...args }),
|
||||||
onSuccess,
|
onSuccess,
|
||||||
}),
|
}),
|
||||||
|
fetchAll: useMutation<string, string, void>({
|
||||||
|
mutationKey: ['git', 'checkout', dir],
|
||||||
|
mutationFn: () => invoke('plugin:yaak-git|fetch_all', { dir }),
|
||||||
|
onSuccess,
|
||||||
|
}),
|
||||||
push: useMutation<PushResult, string, void>({
|
push: useMutation<PushResult, string, void>({
|
||||||
mutationKey: ['git', 'push', dir],
|
mutationKey: ['git', 'push', dir],
|
||||||
mutationFn: () => invoke('plugin:yaak-git|push', { dir }),
|
mutationFn: () => invoke('plugin:yaak-git|push', { dir }),
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-checkout-remote"
|
||||||
|
description = "Enables the checkout_remote command without any pre-configured scope."
|
||||||
|
commands.allow = ["checkout_remote"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-checkout-remote"
|
||||||
|
description = "Denies the checkout_remote command without any pre-configured scope."
|
||||||
|
commands.deny = ["checkout_remote"]
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Automatically generated - DO NOT EDIT!
|
||||||
|
|
||||||
|
"$schema" = "../../schemas/schema.json"
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "allow-fetch-all"
|
||||||
|
description = "Enables the fetch_all command without any pre-configured scope."
|
||||||
|
commands.allow = ["fetch_all"]
|
||||||
|
|
||||||
|
[[permission]]
|
||||||
|
identifier = "deny-fetch-all"
|
||||||
|
description = "Denies the fetch_all command without any pre-configured scope."
|
||||||
|
commands.deny = ["fetch_all"]
|
||||||
@@ -7,6 +7,7 @@ Default permissions for the plugin
|
|||||||
- `allow-checkout`
|
- `allow-checkout`
|
||||||
- `allow-commit`
|
- `allow-commit`
|
||||||
- `allow-delete-branch`
|
- `allow-delete-branch`
|
||||||
|
- `allow-fetch-all`
|
||||||
- `allow-initialize`
|
- `allow-initialize`
|
||||||
- `allow-log`
|
- `allow-log`
|
||||||
- `allow-merge-branch`
|
- `allow-merge-branch`
|
||||||
@@ -105,6 +106,32 @@ Denies the checkout command without any pre-configured scope.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
`yaak-git:allow-checkout-remote`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the checkout_remote command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-git:deny-checkout-remote`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the checkout_remote command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
`yaak-git:allow-commit`
|
`yaak-git:allow-commit`
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
@@ -157,6 +184,32 @@ Denies the delete_branch command without any pre-configured scope.
|
|||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
|
|
||||||
|
`yaak-git:allow-fetch-all`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Enables the fetch_all command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
`yaak-git:deny-fetch-all`
|
||||||
|
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
|
||||||
|
Denies the fetch_all command without any pre-configured scope.
|
||||||
|
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
|
||||||
|
<tr>
|
||||||
|
<td>
|
||||||
|
|
||||||
`yaak-git:allow-initialize`
|
`yaak-git:allow-initialize`
|
||||||
|
|
||||||
</td>
|
</td>
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ permissions = [
|
|||||||
"allow-checkout",
|
"allow-checkout",
|
||||||
"allow-commit",
|
"allow-commit",
|
||||||
"allow-delete-branch",
|
"allow-delete-branch",
|
||||||
|
"allow-fetch-all",
|
||||||
"allow-initialize",
|
"allow-initialize",
|
||||||
"allow-log",
|
"allow-log",
|
||||||
"allow-merge-branch",
|
"allow-merge-branch",
|
||||||
|
|||||||
@@ -324,6 +324,16 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "deny-checkout"
|
"const": "deny-checkout"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-checkout-remote"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the checkout_remote command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-checkout-remote"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the commit command without any pre-configured scope.",
|
"description": "Enables the commit command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
@@ -344,6 +354,16 @@
|
|||||||
"type": "string",
|
"type": "string",
|
||||||
"const": "deny-delete-branch"
|
"const": "deny-delete-branch"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"description": "Enables the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "allow-fetch-all"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"description": "Denies the fetch_all command without any pre-configured scope.",
|
||||||
|
"type": "string",
|
||||||
|
"const": "deny-fetch-all"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"description": "Enables the initialize command without any pre-configured scope.",
|
"description": "Enables the initialize command without any pre-configured scope.",
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
|||||||
@@ -22,9 +22,13 @@ pub(crate) fn branch_set_upstream_after_push(repo: &Repository, branch_name: &st
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn git_checkout_branch(dir: &Path, branch: &str, force: bool) -> Result<()> {
|
pub(crate) fn git_checkout_branch(dir: &Path, branch_name: &str, force: bool) -> Result<String> {
|
||||||
|
if branch_name.starts_with("origin/") {
|
||||||
|
return git_checkout_remote_branch(dir, branch_name, force);
|
||||||
|
}
|
||||||
|
|
||||||
let repo = open_repo(dir)?;
|
let repo = open_repo(dir)?;
|
||||||
let branch = get_branch_by_name(&repo, branch)?;
|
let branch = get_branch_by_name(&repo, branch_name)?;
|
||||||
let branch_ref = branch.into_reference();
|
let branch_ref = branch.into_reference();
|
||||||
let branch_tree = branch_ref.peel_to_tree()?;
|
let branch_tree = branch_ref.peel_to_tree()?;
|
||||||
|
|
||||||
@@ -36,7 +40,22 @@ pub(crate) fn git_checkout_branch(dir: &Path, branch: &str, force: bool) -> Resu
|
|||||||
repo.checkout_tree(branch_tree.as_object(), Some(&mut options))?;
|
repo.checkout_tree(branch_tree.as_object(), Some(&mut options))?;
|
||||||
repo.set_head(branch_ref.name().unwrap())?;
|
repo.set_head(branch_ref.name().unwrap())?;
|
||||||
|
|
||||||
Ok(())
|
Ok(branch_name.to_string())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn git_checkout_remote_branch(dir: &Path, branch_name: &str, force: bool) -> Result<String> {
|
||||||
|
let branch_name = branch_name.trim_start_matches("origin/");
|
||||||
|
let repo = open_repo(dir)?;
|
||||||
|
|
||||||
|
let refname = format!("refs/remotes/origin/{}", branch_name);
|
||||||
|
let remote_ref = repo.find_reference(&refname)?;
|
||||||
|
let commit = remote_ref.peel_to_commit()?;
|
||||||
|
|
||||||
|
let mut new_branch = repo.branch(branch_name, &commit, false)?;
|
||||||
|
let upstream_name = format!("origin/{}", branch_name);
|
||||||
|
new_branch.set_upstream(Some(&upstream_name))?;
|
||||||
|
|
||||||
|
return git_checkout_branch(dir, branch_name, force)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn git_create_branch(dir: &Path, name: &str) -> Result<()> {
|
pub(crate) fn git_create_branch(dir: &Path, name: &str) -> Result<()> {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
use crate::branch::{git_checkout_branch, git_create_branch, git_delete_branch, git_merge_branch};
|
use crate::branch::{git_checkout_branch, git_create_branch, git_delete_branch, git_merge_branch};
|
||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
|
use crate::fetch::git_fetch_all;
|
||||||
use crate::git::{
|
use crate::git::{
|
||||||
git_add, git_commit, git_init, git_log, git_status, git_unstage, GitCommit, GitStatusSummary,
|
git_add, git_commit, git_init, git_log, git_status, git_unstage, GitCommit, GitStatusSummary,
|
||||||
};
|
};
|
||||||
@@ -10,7 +11,7 @@ use tauri::command;
|
|||||||
// NOTE: All of these commands are async to prevent blocking work from locking up the UI
|
// NOTE: All of these commands are async to prevent blocking work from locking up the UI
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn checkout(dir: &Path, branch: &str, force: bool) -> Result<()> {
|
pub async fn checkout(dir: &Path, branch: &str, force: bool) -> Result<String> {
|
||||||
git_checkout_branch(dir, branch, force)
|
git_checkout_branch(dir, branch, force)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -49,6 +50,11 @@ pub async fn commit(dir: &Path, message: &str) -> Result<()> {
|
|||||||
git_commit(dir, message)
|
git_commit(dir, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[command]
|
||||||
|
pub async fn fetch_all(dir: &Path) -> Result<()> {
|
||||||
|
git_fetch_all(dir)
|
||||||
|
}
|
||||||
|
|
||||||
#[command]
|
#[command]
|
||||||
pub async fn push(dir: &Path) -> Result<PushResult> {
|
pub async fn push(dir: &Path) -> Result<PushResult> {
|
||||||
git_push(dir)
|
git_push(dir)
|
||||||
|
|||||||
37
src-tauri/yaak-git/src/fetch.rs
Normal file
37
src-tauri/yaak-git/src/fetch.rs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
use crate::callbacks::default_callbacks;
|
||||||
|
use crate::error::Result;
|
||||||
|
use crate::repository::open_repo;
|
||||||
|
use git2::{FetchOptions, ProxyOptions, Repository};
|
||||||
|
use std::path::Path;
|
||||||
|
|
||||||
|
pub(crate) fn git_fetch_all(dir: &Path) -> Result<()> {
|
||||||
|
let repo = open_repo(dir)?;
|
||||||
|
let remotes = repo.remotes()?.iter().flatten().map(String::from).collect::<Vec<_>>();
|
||||||
|
|
||||||
|
for (_idx, remote) in remotes.into_iter().enumerate() {
|
||||||
|
fetch_from_remote(&repo, &remote)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn fetch_from_remote(repo: &Repository, remote: &str) -> Result<()> {
|
||||||
|
let mut remote = repo.find_remote(remote)?;
|
||||||
|
|
||||||
|
let mut options = FetchOptions::new();
|
||||||
|
let callbacks = default_callbacks();
|
||||||
|
|
||||||
|
options.prune(git2::FetchPrune::On);
|
||||||
|
let mut proxy = ProxyOptions::new();
|
||||||
|
proxy.auto();
|
||||||
|
|
||||||
|
options.proxy_options(proxy);
|
||||||
|
options.download_tags(git2::AutotagOption::All);
|
||||||
|
options.remote_callbacks(callbacks);
|
||||||
|
|
||||||
|
remote.fetch(&[] as &[&str], Some(&mut options), None)?;
|
||||||
|
// fetch tags (also removing remotely deleted ones)
|
||||||
|
remote.fetch(&["refs/tags/*:refs/tags/*"], Some(&mut options), None)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::error::Result;
|
use crate::error::Result;
|
||||||
use crate::repository::open_repo;
|
use crate::repository::open_repo;
|
||||||
use crate::util::list_branch_names;
|
use crate::util::{local_branch_names, remote_branch_names};
|
||||||
use chrono::{DateTime, Utc};
|
use chrono::{DateTime, Utc};
|
||||||
use git2::IndexAddOption;
|
use git2::IndexAddOption;
|
||||||
use log::{info, warn};
|
use log::{info, warn};
|
||||||
@@ -19,7 +19,8 @@ pub struct GitStatusSummary {
|
|||||||
pub head_ref_shorthand: Option<String>,
|
pub head_ref_shorthand: Option<String>,
|
||||||
pub entries: Vec<GitStatusEntry>,
|
pub entries: Vec<GitStatusEntry>,
|
||||||
pub origins: Vec<String>,
|
pub origins: Vec<String>,
|
||||||
pub branches: Vec<String>,
|
pub local_branches: Vec<String>,
|
||||||
|
pub remote_branches: Vec<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize, TS)]
|
||||||
@@ -288,7 +289,8 @@ pub fn git_status(dir: &Path) -> Result<GitStatusSummary> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let origins = repo.remotes()?.into_iter().filter_map(|o| Some(o?.to_string())).collect();
|
let origins = repo.remotes()?.into_iter().filter_map(|o| Some(o?.to_string())).collect();
|
||||||
let branches = list_branch_names(&repo)?;
|
let local_branches = local_branch_names(&repo)?;
|
||||||
|
let remote_branches = remote_branch_names(&repo)?;
|
||||||
|
|
||||||
Ok(GitStatusSummary {
|
Ok(GitStatusSummary {
|
||||||
entries,
|
entries,
|
||||||
@@ -296,7 +298,8 @@ pub fn git_status(dir: &Path) -> Result<GitStatusSummary> {
|
|||||||
path: dir.to_string_lossy().to_string(),
|
path: dir.to_string_lossy().to_string(),
|
||||||
head_ref,
|
head_ref,
|
||||||
head_ref_shorthand,
|
head_ref_shorthand,
|
||||||
branches,
|
local_branches,
|
||||||
|
remote_branches,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::commands::{add, branch, checkout, commit, delete_branch, initialize, log, merge_branch, pull, push, status, unstage};
|
use crate::commands::{add, branch, checkout, commit, delete_branch, fetch_all, initialize, log, merge_branch, pull, push, status, unstage};
|
||||||
use tauri::{
|
use tauri::{
|
||||||
generate_handler,
|
generate_handler,
|
||||||
plugin::{Builder, TauriPlugin},
|
plugin::{Builder, TauriPlugin},
|
||||||
@@ -9,6 +9,7 @@ mod branch;
|
|||||||
mod callbacks;
|
mod callbacks;
|
||||||
mod commands;
|
mod commands;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod fetch;
|
||||||
mod git;
|
mod git;
|
||||||
mod merge;
|
mod merge;
|
||||||
mod pull;
|
mod pull;
|
||||||
@@ -24,6 +25,7 @@ pub fn init<R: Runtime>() -> TauriPlugin<R> {
|
|||||||
checkout,
|
checkout,
|
||||||
commit,
|
commit,
|
||||||
delete_branch,
|
delete_branch,
|
||||||
|
fetch_all,
|
||||||
initialize,
|
initialize,
|
||||||
log,
|
log,
|
||||||
merge_branch,
|
merge_branch,
|
||||||
|
|||||||
@@ -34,7 +34,7 @@ pub(crate) fn get_current_branch(repo: &Repository) -> Result<Option<Branch>> {
|
|||||||
Ok(None)
|
Ok(None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn list_branch_names(repo: &Repository) -> Result<Vec<String>> {
|
pub(crate) fn local_branch_names(repo: &Repository) -> Result<Vec<String>> {
|
||||||
let mut branches = Vec::new();
|
let mut branches = Vec::new();
|
||||||
for branch in repo.branches(Some(BranchType::Local))? {
|
for branch in repo.branches(Some(BranchType::Local))? {
|
||||||
let branch = branch?.0;
|
let branch = branch?.0;
|
||||||
@@ -45,6 +45,17 @@ pub(crate) fn list_branch_names(repo: &Repository) -> Result<Vec<String>> {
|
|||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn remote_branch_names(repo: &Repository) -> Result<Vec<String>> {
|
||||||
|
let mut branches = Vec::new();
|
||||||
|
for branch in repo.branches(Some(BranchType::Remote))? {
|
||||||
|
let branch = branch?.0;
|
||||||
|
let name = branch.name_bytes()?;
|
||||||
|
let name = bytes_to_string(name)?;
|
||||||
|
branches.push(name);
|
||||||
|
}
|
||||||
|
Ok(branches)
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn get_branch_by_name<'s>(repo: &'s Repository, name: &str) -> Result<Branch<'s>> {
|
pub(crate) fn get_branch_by_name<'s>(repo: &'s Repository, name: &str) -> Result<Branch<'s>> {
|
||||||
Ok(repo.find_branch(name, BranchType::Local)?)
|
Ok(repo.find_branch(name, BranchType::Local)?)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import classNames from 'classnames';
|
|||||||
|
|
||||||
import { useMemo, useState } from 'react';
|
import { useMemo, useState } from 'react';
|
||||||
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
import { fallbackRequestName } from '../lib/fallbackRequestName';
|
||||||
|
import { showToast } from '../lib/toast';
|
||||||
import { Banner } from './core/Banner';
|
import { Banner } from './core/Banner';
|
||||||
import { Button } from './core/Button';
|
import { Button } from './core/Button';
|
||||||
import type { CheckboxProps } from './core/Checkbox';
|
import type { CheckboxProps } from './core/Checkbox';
|
||||||
@@ -46,8 +47,9 @@ export function GitCommitDialog({ syncDir, onDone, workspace }: Props) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const handleCreateCommitAndPush = async () => {
|
const handleCreateCommitAndPush = async () => {
|
||||||
await handleCreateCommit();
|
await commit.mutateAsync({ message });
|
||||||
await push.mutateAsync();
|
await push.mutateAsync();
|
||||||
|
showToast({ id: 'git-push-success', message: 'Pushed changes', color: 'success' });
|
||||||
onDone();
|
onDone();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -34,9 +34,16 @@ export function GitDropdown() {
|
|||||||
|
|
||||||
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
||||||
const workspace = useActiveWorkspace();
|
const workspace = useActiveWorkspace();
|
||||||
const [{ status, log }, { branch, deleteBranch, mergeBranch, push, pull, checkout }] =
|
const [{ status, log }, { branch, deleteBranch, fetchAll, mergeBranch, push, pull, checkout }] =
|
||||||
useGit(syncDir);
|
useGit(syncDir);
|
||||||
|
|
||||||
|
const localBranches = status.data?.localBranches ?? [];
|
||||||
|
const remoteBranches = status.data?.remoteBranches ?? [];
|
||||||
|
const remoteOnlyBranches = remoteBranches.filter(
|
||||||
|
(b) => !localBranches.includes(b.replace(/^origin\//, '')),
|
||||||
|
);
|
||||||
|
const currentBranch = status.data?.headRefShorthand ?? 'UNKNOWN';
|
||||||
|
|
||||||
if (workspace == null) {
|
if (workspace == null) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -69,12 +76,12 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
showErrorToast('git-checkout-error', String(err));
|
showErrorToast('git-checkout-error', String(err));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
async onSuccess() {
|
async onSuccess(branchName) {
|
||||||
showToast({
|
showToast({
|
||||||
id: 'git-checkout-success',
|
id: 'git-checkout-success',
|
||||||
message: (
|
message: (
|
||||||
<>
|
<>
|
||||||
Switched branch <InlineCode>{branch}</InlineCode>
|
Switched branch <InlineCode>{branchName}</InlineCode>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
color: 'success',
|
color: 'success',
|
||||||
@@ -124,7 +131,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
{
|
{
|
||||||
label: 'Merge Branch',
|
label: 'Merge Branch',
|
||||||
leftSlot: <Icon icon="merge" />,
|
leftSlot: <Icon icon="merge" />,
|
||||||
hidden: (status.data?.branches ?? []).length <= 1,
|
hidden: localBranches.length <= 1,
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
showDialog({
|
showDialog({
|
||||||
id: 'git-merge',
|
id: 'git-merge',
|
||||||
@@ -132,15 +139,13 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
size: 'sm',
|
size: 'sm',
|
||||||
description: (
|
description: (
|
||||||
<>
|
<>
|
||||||
Select a branch to merge into <InlineCode>{status.data?.headRefShorthand}</InlineCode>
|
Select a branch to merge into <InlineCode>{currentBranch}</InlineCode>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
render: ({ hide }) => (
|
render: ({ hide }) => (
|
||||||
<BranchSelectionDialog
|
<BranchSelectionDialog
|
||||||
selectText="Merge"
|
selectText="Merge"
|
||||||
branches={(status.data?.branches ?? []).filter(
|
branches={localBranches.filter((b) => b !== currentBranch)}
|
||||||
(b) => b !== status.data?.headRefShorthand,
|
|
||||||
)}
|
|
||||||
onCancel={hide}
|
onCancel={hide}
|
||||||
onSelect={async (branch) => {
|
onSelect={async (branch) => {
|
||||||
await mergeBranch.mutateAsync(
|
await mergeBranch.mutateAsync(
|
||||||
@@ -153,7 +158,7 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
message: (
|
message: (
|
||||||
<>
|
<>
|
||||||
Merged <InlineCode>{branch}</InlineCode> into{' '}
|
Merged <InlineCode>{branch}</InlineCode> into{' '}
|
||||||
<InlineCode>{status.data?.headRefShorthand}</InlineCode>
|
<InlineCode>{currentBranch}</InlineCode>
|
||||||
</>
|
</>
|
||||||
),
|
),
|
||||||
});
|
});
|
||||||
@@ -173,10 +178,9 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
{
|
{
|
||||||
label: 'Delete Branch',
|
label: 'Delete Branch',
|
||||||
leftSlot: <Icon icon="trash" />,
|
leftSlot: <Icon icon="trash" />,
|
||||||
hidden: (status.data?.branches ?? []).length <= 1,
|
hidden: localBranches.length <= 1,
|
||||||
color: 'danger',
|
color: 'danger',
|
||||||
async onSelect() {
|
async onSelect() {
|
||||||
const currentBranch = status.data?.headRefShorthand;
|
|
||||||
if (currentBranch == null) return;
|
if (currentBranch == null) return;
|
||||||
|
|
||||||
const confirmed = await showConfirmDelete({
|
const confirmed = await showConfirmDelete({
|
||||||
@@ -256,9 +260,17 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{ type: 'separator', label: 'Branches', hidden: (status.data?.branches ?? []).length < 1 },
|
{ type: 'separator', label: 'Branches', hidden: localBranches.length < 1 },
|
||||||
...(status.data?.branches ?? []).map((branch) => {
|
...localBranches.map((branch) => {
|
||||||
const isCurrent = status.data?.headRefShorthand === branch;
|
const isCurrent = currentBranch === branch;
|
||||||
|
return {
|
||||||
|
label: branch,
|
||||||
|
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
|
||||||
|
onSelect: isCurrent ? undefined : () => tryCheckout(branch, false),
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
...remoteOnlyBranches.map((branch) => {
|
||||||
|
const isCurrent = currentBranch === branch;
|
||||||
return {
|
return {
|
||||||
label: branch,
|
label: branch,
|
||||||
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
|
leftSlot: <Icon icon={isCurrent ? 'check' : 'empty'} />,
|
||||||
@@ -268,9 +280,9 @@ function SyncDropdownWithSyncDir({ syncDir }: { syncDir: string }) {
|
|||||||
];
|
];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown fullWidth items={items}>
|
<Dropdown fullWidth items={items} onOpen={fetchAll.mutate}>
|
||||||
<GitMenuButton>
|
<GitMenuButton>
|
||||||
{noRepo ? 'Configure Git' : <InlineCode>{status.data?.headRefShorthand}</InlineCode>}
|
<InlineCode>{currentBranch}</InlineCode>
|
||||||
<Icon icon="git_branch" size="sm" />
|
<Icon icon="git_branch" size="sm" />
|
||||||
</GitMenuButton>
|
</GitMenuButton>
|
||||||
</Dropdown>
|
</Dropdown>
|
||||||
|
|||||||
@@ -71,6 +71,7 @@ export interface DropdownProps {
|
|||||||
items: DropdownItem[];
|
items: DropdownItem[];
|
||||||
fullWidth?: boolean;
|
fullWidth?: boolean;
|
||||||
hotKeyAction?: HotkeyAction;
|
hotKeyAction?: HotkeyAction;
|
||||||
|
onOpen?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DropdownRef {
|
export interface DropdownRef {
|
||||||
@@ -89,7 +90,7 @@ export interface DropdownRef {
|
|||||||
const openAtom = atom<string | null>(null);
|
const openAtom = atom<string | null>(null);
|
||||||
|
|
||||||
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
|
export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown(
|
||||||
{ children, items, hotKeyAction, fullWidth }: DropdownProps,
|
{ children, items, hotKeyAction, fullWidth, onOpen }: DropdownProps,
|
||||||
ref,
|
ref,
|
||||||
) {
|
) {
|
||||||
const id = useRef(generateId());
|
const id = useRef(generateId());
|
||||||
@@ -116,13 +117,14 @@ export const Dropdown = forwardRef<DropdownRef, DropdownProps>(function Dropdown
|
|||||||
const newIsOpen = typeof o === 'function' ? o(prevIsOpen) : o;
|
const newIsOpen = typeof o === 'function' ? o(prevIsOpen) : o;
|
||||||
// Persist background color of button until we close the dropdown
|
// Persist background color of button until we close the dropdown
|
||||||
if (newIsOpen) {
|
if (newIsOpen) {
|
||||||
|
onOpen?.();
|
||||||
buttonRef.current!.style.backgroundColor = window
|
buttonRef.current!.style.backgroundColor = window
|
||||||
.getComputedStyle(buttonRef.current!)
|
.getComputedStyle(buttonRef.current!)
|
||||||
.getPropertyValue('background-color');
|
.getPropertyValue('background-color');
|
||||||
}
|
}
|
||||||
return newIsOpen ? id.current : null; // Set global atom to current ID to signify open state
|
return newIsOpen ? id.current : null; // Set global atom to current ID to signify open state
|
||||||
});
|
});
|
||||||
}, []);
|
}, [onOpen]);
|
||||||
|
|
||||||
// Because a different dropdown can cause ours to close, a useEffect([isOpen]) is the only method
|
// Because a different dropdown can cause ours to close, a useEffect([isOpen]) is the only method
|
||||||
// we have of detecting the dropdown closed, to do cleanup.
|
// we have of detecting the dropdown closed, to do cleanup.
|
||||||
|
|||||||
Reference in New Issue
Block a user