Skip to content

Commit cdd8632

Browse files
committed
but-worktrees-crate
but-worktrees-database Stubbed CLI
1 parent 903cb2d commit cdd8632

File tree

14 files changed

+363
-0
lines changed

14 files changed

+363
-0
lines changed

Cargo.lock

Lines changed: 19 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
-- Drop worktrees table
2+
DROP TABLE worktrees;
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
-- Create worktrees table
2+
CREATE TABLE `worktrees`(
3+
`id` TEXT NOT NULL PRIMARY KEY,
4+
`base` TEXT NOT NULL,
5+
`path` TEXT NOT NULL,
6+
`source` TEXT NOT NULL
7+
);
8+
9+
CREATE INDEX index_worktrees_on_path ON worktrees (path);

crates/but-db/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@ mod file_write_locks;
1919
pub use file_write_locks::FileWriteLock;
2020
mod workspace_rules;
2121
pub use workspace_rules::WorkspaceRule;
22+
mod worktrees;
23+
pub use worktrees::{Worktree, WorktreeSource};
2224

2325
use diesel_migrations::{EmbeddedMigrations, MigrationHarness, embed_migrations};
2426
pub const MIGRATIONS: EmbeddedMigrations = embed_migrations!("./migrations");

crates/but-db/src/schema.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,3 +86,12 @@ diesel::table! {
8686
approved -> Nullable<Bool>,
8787
}
8888
}
89+
90+
diesel::table! {
91+
worktrees (id) {
92+
id -> Text,
93+
base -> Text,
94+
path -> Text,
95+
source -> Text,
96+
}
97+
}

crates/but-db/src/worktrees.rs

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
use diesel::{ExpressionMethods, Identifiable, OptionalExtension as _, QueryDsl, RunQueryDsl};
2+
3+
use crate::DbHandle;
4+
use crate::schema::worktrees::dsl::worktrees;
5+
6+
use diesel::prelude::{Insertable, Queryable, Selectable};
7+
use serde::{Deserialize, Serialize};
8+
9+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
10+
pub enum WorktreeSource {
11+
Branch(String),
12+
}
13+
14+
#[derive(
15+
Debug, Clone, PartialEq, Serialize, Deserialize, Queryable, Selectable, Insertable, Identifiable,
16+
)]
17+
#[diesel(table_name = crate::schema::worktrees)]
18+
#[diesel(check_for_backend(diesel::sqlite::Sqlite))]
19+
pub struct Worktree {
20+
pub id: String,
21+
pub base: String,
22+
pub path: String,
23+
pub source: String,
24+
}
25+
26+
impl DbHandle {
27+
pub fn worktrees(&mut self) -> WorktreesHandle<'_> {
28+
WorktreesHandle { db: self }
29+
}
30+
}
31+
32+
pub struct WorktreesHandle<'a> {
33+
db: &'a mut DbHandle,
34+
}
35+
36+
impl WorktreesHandle<'_> {
37+
pub fn insert(&mut self, worktree: Worktree) -> Result<(), diesel::result::Error> {
38+
diesel::insert_into(worktrees)
39+
.values(worktree)
40+
.execute(&mut self.db.conn)?;
41+
Ok(())
42+
}
43+
44+
pub fn get(&mut self, id: &str) -> Result<Option<Worktree>, diesel::result::Error> {
45+
let worktree = worktrees
46+
.filter(crate::schema::worktrees::id.eq(id))
47+
.first::<Worktree>(&mut self.db.conn)
48+
.optional()?;
49+
Ok(worktree)
50+
}
51+
52+
pub fn delete(&mut self, id: &str) -> Result<(), diesel::result::Error> {
53+
diesel::delete(worktrees.filter(crate::schema::worktrees::id.eq(id)))
54+
.execute(&mut self.db.conn)?;
55+
Ok(())
56+
}
57+
58+
pub fn list(&mut self) -> Result<Vec<Worktree>, diesel::result::Error> {
59+
let worktree_list = worktrees.load::<Worktree>(&mut self.db.conn)?;
60+
Ok(worktree_list)
61+
}
62+
}

crates/but-worktrees/Cargo.toml

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
[package]
2+
name = "but-worktrees"
3+
version = "0.0.0"
4+
edition = "2024"
5+
authors = ["GitButler <[email protected]>"]
6+
publish = false
7+
rust-version = "1.89"
8+
[lib]
9+
doctest = false
10+
11+
[dependencies]
12+
anyhow.workspace = true
13+
but-db.workspace = true
14+
gix.workspace = true
15+
gitbutler-command-context.workspace = true
16+
gitbutler-project.workspace = true
17+
gitbutler-stack.workspace = true
18+
serde.workspace = true
19+
serde_json.workspace = true
20+
uuid.workspace = true
21+
22+
[dev-dependencies]
23+
gix-testtools.workspace = true
24+
gitbutler-testsupport.workspace = true
25+
gitbutler-oxidize.workspace = true
26+
insta.workspace = true

crates/but-worktrees/src/db.rs

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//! Database operations for worktrees.
2+
3+
use anyhow::Result;
4+
use gitbutler_command_context::CommandContext;
5+
use uuid::Uuid;
6+
7+
use crate::{Worktree, WorktreeSource};
8+
9+
/// Save a new worktree to the database.
10+
pub fn save_worktree(ctx: &mut CommandContext, worktree: Worktree) -> Result<()> {
11+
ctx.db()?.worktrees().insert(worktree.try_into()?)?;
12+
Ok(())
13+
}
14+
15+
/// Retrieve a worktree by its ID.
16+
pub fn get_worktree(ctx: &mut CommandContext, id: Uuid) -> Result<Option<Worktree>> {
17+
let worktree = ctx.db()?.worktrees().get(&id.to_string())?;
18+
match worktree {
19+
Some(w) => Ok(Some(w.try_into()?)),
20+
None => Ok(None),
21+
}
22+
}
23+
24+
/// Delete a worktree from the database.
25+
pub fn delete_worktree(ctx: &mut CommandContext, id: Uuid) -> Result<()> {
26+
ctx.db()?.worktrees().delete(&id.to_string())?;
27+
Ok(())
28+
}
29+
30+
/// List all worktrees in the database.
31+
pub fn list_worktrees(ctx: &mut CommandContext) -> Result<Vec<Worktree>> {
32+
let worktrees = ctx.db()?.worktrees().list()?;
33+
worktrees
34+
.into_iter()
35+
.map(|w| w.try_into())
36+
.collect::<Result<_, _>>()
37+
}
38+
39+
impl TryFrom<but_db::Worktree> for Worktree {
40+
type Error = anyhow::Error;
41+
42+
fn try_from(value: but_db::Worktree) -> Result<Self, Self::Error> {
43+
let source: WorktreeSource = serde_json::from_str(&value.source)?;
44+
let base = gix::ObjectId::from_hex(value.base.as_bytes())?;
45+
let path = std::path::PathBuf::from(value.path);
46+
47+
Ok(Worktree {
48+
id: Uuid::parse_str(&value.id)?,
49+
base,
50+
path,
51+
source,
52+
})
53+
}
54+
}
55+
56+
impl TryFrom<Worktree> for but_db::Worktree {
57+
type Error = anyhow::Error;
58+
59+
fn try_from(value: Worktree) -> Result<Self, Self::Error> {
60+
let source = serde_json::to_string(&value.source)?;
61+
let base = value.base.to_hex().to_string();
62+
let path = value.path.to_string_lossy().to_string();
63+
64+
Ok(but_db::Worktree {
65+
id: value.id.to_string(),
66+
base,
67+
path,
68+
source,
69+
})
70+
}
71+
}

crates/but-worktrees/src/lib.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
//! Worktree management types and database operations.
2+
3+
use std::path::PathBuf;
4+
5+
use serde::{Deserialize, Serialize};
6+
use uuid::Uuid;
7+
8+
/// Database operations for worktrees.
9+
pub mod db;
10+
11+
/// The source from which a worktree was created.
12+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
13+
#[serde(tag = "type", content = "data", rename_all = "camelCase")]
14+
pub enum WorktreeSource {
15+
/// The worktree was created from a branch reference.
16+
Branch(String),
17+
}
18+
19+
/// A worktree entry representing a Git worktree.
20+
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
21+
pub struct Worktree {
22+
/// Unique identifier for this worktree.
23+
pub id: Uuid,
24+
/// The base commit (ObjectId) from which this worktree was created.
25+
pub base: gix::ObjectId,
26+
/// The filesystem path to the worktree.
27+
pub path: PathBuf,
28+
/// The source from which this worktree was created.
29+
pub source: WorktreeSource,
30+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
mod util {
2+
use gitbutler_command_context::CommandContext;
3+
use gitbutler_stack::VirtualBranchesHandle;
4+
use gix_testtools::tempfile::TempDir;
5+
6+
pub fn test_ctx(name: &str) -> anyhow::Result<TestContext> {
7+
let (ctx, tmpdir) = gitbutler_testsupport::writable::fixture("example.sh", name)?;
8+
let handle = VirtualBranchesHandle::new(ctx.project().gb_dir());
9+
10+
Ok(TestContext {
11+
ctx,
12+
handle,
13+
tmpdir,
14+
})
15+
}
16+
17+
#[allow(unused)]
18+
pub struct TestContext {
19+
pub ctx: CommandContext,
20+
pub handle: VirtualBranchesHandle,
21+
pub tmpdir: TempDir,
22+
}
23+
}
24+
25+
mod example_scenario {
26+
use super::*;
27+
use util::test_ctx;
28+
29+
#[test]
30+
fn test_example_scenario() -> anyhow::Result<()> {
31+
let test_ctx = test_ctx("example-scenario")?;
32+
let stacks = test_ctx.handle.list_stacks_in_workspace()?;
33+
assert!(!stacks.is_empty());
34+
Ok(())
35+
}
36+
}

0 commit comments

Comments
 (0)