Skip to content

Commit ed5d726

Browse files
Factor out duplicated initialisation code in cli
1 parent 26063b5 commit ed5d726

File tree

15 files changed

+109
-126
lines changed

15 files changed

+109
-126
lines changed

josh-cli/src/bin/josh.rs

Lines changed: 91 additions & 108 deletions
Original file line numberDiff line numberDiff line change
@@ -290,15 +290,7 @@ fn main() {
290290
env_logger::init();
291291
let cli = Cli::parse();
292292

293-
let result = match &cli.command {
294-
Command::Clone(args) => handle_clone(args),
295-
Command::Fetch(args) => handle_fetch(args),
296-
Command::Pull(args) => handle_pull(args),
297-
Command::Push(args) => handle_push(args),
298-
Command::Remote(args) => handle_remote(args),
299-
Command::Filter(args) => handle_filter(args),
300-
Command::Link(args) => handle_link(args),
301-
};
293+
let result = run_command(&cli);
302294

303295
if let Err(e) = result {
304296
eprintln!("Error: {e}");
@@ -311,16 +303,16 @@ fn main() {
311303
}
312304
}
313305

314-
/// Apply josh filtering to all remote refs and update local refs
315-
fn apply_josh_filtering(
316-
repo_path: &std::path::Path,
317-
filter: &str,
318-
remote_name: &str,
319-
) -> anyhow::Result<()> {
320-
// Use josh API directly instead of calling josh-filter binary
321-
let filterobj = josh_core::filter::parse(filter)
322-
.map_err(from_josh_err)
323-
.context("Failed to parse filter")?;
306+
fn run_command(cli: &Cli) -> anyhow::Result<()> {
307+
// For clone, do the initial repo setup before creating transaction
308+
let repo_path = if let Command::Clone(args) = &cli.command {
309+
// For clone, we're not in a git repo initially, so clone first and use that path
310+
clone_repo(args)?
311+
} else {
312+
// For other commands, we need to be in a git repo
313+
let repo = git2::Repository::open_from_env().context("Not in a git repository")?;
314+
repo.path().parent().unwrap().to_path_buf()
315+
};
324316

325317
josh_core::cache_sled::sled_load(&repo_path.join(".git"))
326318
.map_err(from_josh_err)
@@ -330,16 +322,40 @@ fn apply_josh_filtering(
330322
josh_core::cache_stack::CacheStack::new()
331323
.with_backend(josh_core::cache_sled::SledCacheBackend::default())
332324
.with_backend(
333-
josh_core::cache_notes::NotesCacheBackend::new(repo_path)
325+
josh_core::cache_notes::NotesCacheBackend::new(&repo_path)
334326
.map_err(from_josh_err)
335327
.context("Failed to create NotesCacheBackend")?,
336328
),
337329
);
338330

339-
// Open Josh transaction
340-
let transaction = josh_core::cache::TransactionContext::new(repo_path, cache.clone())
331+
// Create transaction using the known repo path
332+
let transaction = josh_core::cache::TransactionContext::new(&repo_path, cache.clone())
341333
.open(None)
342-
.map_err(from_josh_err)?;
334+
.map_err(from_josh_err)
335+
.context("Failed TransactionContext::open")?;
336+
337+
match &cli.command {
338+
Command::Clone(args) => handle_clone(args, &transaction),
339+
Command::Fetch(args) => handle_fetch(args, &transaction),
340+
Command::Pull(args) => handle_pull(args, &transaction),
341+
Command::Push(args) => handle_push(args, &transaction),
342+
Command::Remote(args) => handle_remote(args, &transaction),
343+
Command::Filter(args) => handle_filter(args, &transaction),
344+
Command::Link(args) => handle_link(args, &transaction),
345+
}
346+
}
347+
348+
/// Apply josh filtering to all remote refs and update local refs
349+
fn apply_josh_filtering(
350+
transaction: &josh_core::cache::Transaction,
351+
repo_path: &std::path::Path,
352+
filter: &str,
353+
remote_name: &str,
354+
) -> anyhow::Result<()> {
355+
// Use josh API directly instead of calling josh-filter binary
356+
let filterobj = josh_core::filter::parse(filter)
357+
.map_err(from_josh_err)
358+
.context("Failed to parse filter")?;
343359

344360
let repo = transaction.repo();
345361

@@ -419,7 +435,8 @@ fn to_absolute_remote_url(url: &str) -> anyhow::Result<String> {
419435
}
420436
}
421437

422-
fn handle_clone(args: &CloneArgs) -> anyhow::Result<()> {
438+
/// Initial clone setup: create directory, init repo, add remote (no transaction needed)
439+
fn clone_repo(args: &CloneArgs) -> anyhow::Result<std::path::PathBuf> {
423440
// Use the provided output directory
424441
let output_dir = args.out.clone();
425442

@@ -439,6 +456,16 @@ fn handle_clone(args: &CloneArgs) -> anyhow::Result<()> {
439456

440457
handle_remote_add_repo(&remote_add_args, &output_dir)?;
441458

459+
Ok(output_dir)
460+
}
461+
462+
fn handle_clone(
463+
args: &CloneArgs,
464+
transaction: &josh_core::cache::Transaction,
465+
) -> anyhow::Result<()> {
466+
// Get the repo path from the transaction
467+
let output_dir = transaction.repo().path().parent().unwrap();
468+
442469
// Create FetchArgs from CloneArgs
443470
let fetch_args = FetchArgs {
444471
remote: "origin".to_string(),
@@ -447,13 +474,13 @@ fn handle_clone(args: &CloneArgs) -> anyhow::Result<()> {
447474
};
448475

449476
// Use handle_fetch to do the actual fetching and filtering
450-
handle_fetch_repo(&fetch_args, &output_dir)?;
477+
handle_fetch_repo(&fetch_args, output_dir, transaction)?;
451478

452479
// Get the default branch name from the remote HEAD symref
453480
let default_branch = if args.branch == "HEAD" {
454481
// Read the remote HEAD symref to get the default branch
455482
let head_ref = "refs/remotes/origin/HEAD".to_string();
456-
let repo = git2::Repository::open(&output_dir).context("Failed to open repository")?;
483+
let repo = transaction.repo();
457484

458485
let head_reference = repo
459486
.find_reference(&head_ref)
@@ -508,7 +535,7 @@ fn handle_clone(args: &CloneArgs) -> anyhow::Result<()> {
508535
Ok(())
509536
}
510537

511-
fn handle_pull(args: &PullArgs) -> anyhow::Result<()> {
538+
fn handle_pull(args: &PullArgs, transaction: &josh_core::cache::Transaction) -> anyhow::Result<()> {
512539
// Check if we're in a git repository
513540
let repo = git2::Repository::open_from_env().context("Not in a git repository")?;
514541
let repo_path = repo.path().parent().unwrap().to_path_buf();
@@ -521,7 +548,7 @@ fn handle_pull(args: &PullArgs) -> anyhow::Result<()> {
521548
};
522549

523550
// Use handle_fetch to do the actual fetching and filtering
524-
handle_fetch_repo(&fetch_args, &repo_path)?;
551+
handle_fetch_repo(&fetch_args, &repo_path, transaction)?;
525552

526553
// Get current working directory for shell commands
527554
let current_dir = std::env::current_dir()?;
@@ -549,12 +576,15 @@ fn handle_pull(args: &PullArgs) -> anyhow::Result<()> {
549576
Ok(())
550577
}
551578

552-
fn handle_fetch(args: &FetchArgs) -> anyhow::Result<()> {
579+
fn handle_fetch(
580+
args: &FetchArgs,
581+
transaction: &josh_core::cache::Transaction,
582+
) -> anyhow::Result<()> {
553583
// Check if we're in a git repository
554584
let repo = git2::Repository::open_from_env().context("Not in a git repository")?;
555585
let repo_path = repo.path().parent().unwrap().to_path_buf();
556586

557-
handle_fetch_repo(args, &repo_path)
587+
handle_fetch_repo(args, &repo_path, transaction)
558588
}
559589

560590
fn try_parse_symref(remote: &str, output: &str) -> Option<(String, String)> {
@@ -567,7 +597,11 @@ fn try_parse_symref(remote: &str, output: &str) -> Option<(String, String)> {
567597
Some((default_branch.to_string(), default_branch_ref))
568598
}
569599

570-
fn handle_fetch_repo(args: &FetchArgs, repo_path: &std::path::Path) -> anyhow::Result<()> {
600+
fn handle_fetch_repo(
601+
args: &FetchArgs,
602+
repo_path: &std::path::Path,
603+
transaction: &josh_core::cache::Transaction,
604+
) -> anyhow::Result<()> {
571605
let repo = git2::Repository::open(repo_path).context("Failed to open repository")?;
572606

573607
// Read the remote URL from josh-remote config
@@ -618,15 +652,15 @@ fn handle_fetch_repo(args: &FetchArgs, repo_path: &std::path::Path) -> anyhow::R
618652
remote: args.remote.clone(),
619653
};
620654

621-
handle_filter_repo(&filter_args, repo_path, false)?;
655+
handle_filter_repo(&filter_args, repo_path, false, transaction)?;
622656

623657
// Note: fetch doesn't checkout, it just updates the refs
624658
eprintln!("Fetched from remote: {}", args.remote);
625659

626660
Ok(())
627661
}
628662

629-
fn handle_push(args: &PushArgs) -> anyhow::Result<()> {
663+
fn handle_push(args: &PushArgs, transaction: &josh_core::cache::Transaction) -> anyhow::Result<()> {
630664
// Read filter from git config for the specific remote
631665
let repo = git2::Repository::open_from_env().context("Not in a git repository")?;
632666
let repo_path = repo.path().parent().unwrap();
@@ -643,28 +677,6 @@ fn handle_push(args: &PushArgs) -> anyhow::Result<()> {
643677
.map_err(from_josh_err)
644678
.context("Failed to parse filter")?;
645679

646-
josh_core::cache_sled::sled_load(repo_path)
647-
.map_err(from_josh_err)
648-
.context("Failed to load sled cache")?;
649-
650-
let cache = std::sync::Arc::new(
651-
josh_core::cache_stack::CacheStack::new()
652-
.with_backend(josh_core::cache_sled::SledCacheBackend::default())
653-
.with_backend(
654-
josh_core::cache_notes::NotesCacheBackend::new(repo_path)
655-
.map_err(from_josh_err)
656-
.context("Failed to create NotesCacheBackend")?,
657-
),
658-
);
659-
660-
// Open Josh transaction
661-
let transaction = josh_core::cache::TransactionContext::from_env(cache.clone())
662-
.map_err(from_josh_err)
663-
.context("Failed TransactionContext::from_env")?
664-
.open(None)
665-
.map_err(from_josh_err)
666-
.context("Failed TransactionContext::open")?;
667-
668680
// Get the remote URL from josh-remote config
669681
let remote_url = config
670682
.get_string(&format!("josh-remote.{}.url", args.remote))
@@ -848,14 +860,17 @@ fn handle_push(args: &PushArgs) -> anyhow::Result<()> {
848860
Ok(())
849861
}
850862

851-
fn handle_link(args: &LinkArgs) -> anyhow::Result<()> {
863+
fn handle_link(args: &LinkArgs, transaction: &josh_core::cache::Transaction) -> anyhow::Result<()> {
852864
match &args.command {
853-
LinkCommand::Add(add_args) => handle_link_add(add_args),
854-
LinkCommand::Fetch(fetch_args) => handle_link_fetch(fetch_args),
865+
LinkCommand::Add(add_args) => handle_link_add(add_args, transaction),
866+
LinkCommand::Fetch(fetch_args) => handle_link_fetch(fetch_args, transaction),
855867
}
856868
}
857869

858-
fn handle_link_add(args: &LinkAddArgs) -> anyhow::Result<()> {
870+
fn handle_link_add(
871+
args: &LinkAddArgs,
872+
transaction: &josh_core::cache::Transaction,
873+
) -> anyhow::Result<()> {
859874
use josh_core::filter::tree;
860875
use josh_core::{JoshLinkFile, Oid};
861876

@@ -962,30 +977,8 @@ fn handle_link_add(args: &LinkAddArgs) -> anyhow::Result<()> {
962977
.map_err(from_josh_err)
963978
.context("Failed to parse :link filter")?;
964979

965-
// Load the cache and create transaction
966-
let repo_path = repo.path().parent().unwrap();
967-
968-
josh_core::cache_sled::sled_load(&repo_path).unwrap();
969-
let cache = std::sync::Arc::new(
970-
josh_core::cache_stack::CacheStack::new()
971-
.with_backend(josh_core::cache_sled::SledCacheBackend::default())
972-
.with_backend(
973-
josh_core::cache_notes::NotesCacheBackend::new(&repo_path)
974-
.map_err(from_josh_err)
975-
.context("Failed to create NotesCacheBackend")?,
976-
),
977-
);
978-
979-
// Open Josh transaction
980-
let transaction = josh_core::cache::TransactionContext::from_env(cache.clone())
981-
.map_err(from_josh_err)
982-
.context("Failed TransactionContext::from_env")?
983-
.open(None)
984-
.map_err(from_josh_err)
985-
.context("Failed TransactionContext::open")?;
986-
987980
let filtered_commit = josh_core::filter_commit(
988-
&transaction,
981+
transaction,
989982
link_filter,
990983
new_commit,
991984
josh_core::filter::empty(),
@@ -1009,7 +1002,10 @@ fn handle_link_add(args: &LinkAddArgs) -> anyhow::Result<()> {
10091002
Ok(())
10101003
}
10111004

1012-
fn handle_link_fetch(args: &LinkFetchArgs) -> anyhow::Result<()> {
1005+
fn handle_link_fetch(
1006+
args: &LinkFetchArgs,
1007+
transaction: &josh_core::cache::Transaction,
1008+
) -> anyhow::Result<()> {
10131009
use josh_core::filter::tree;
10141010
use josh_core::{JoshLinkFile, Oid};
10151011

@@ -1147,28 +1143,8 @@ fn handle_link_fetch(args: &LinkFetchArgs) -> anyhow::Result<()> {
11471143
.map_err(from_josh_err)
11481144
.context("Failed to parse :link filter")?;
11491145

1150-
// Load the cache and create transaction
1151-
let repo_path = repo.path().parent().unwrap();
1152-
josh_core::cache_sled::sled_load(&repo_path).unwrap();
1153-
let cache = std::sync::Arc::new(
1154-
josh_core::cache_stack::CacheStack::new()
1155-
.with_backend(josh_core::cache_sled::SledCacheBackend::default())
1156-
.with_backend(
1157-
josh_core::cache_notes::NotesCacheBackend::new(&repo_path)
1158-
.map_err(from_josh_err)
1159-
.context("Failed to create NotesCacheBackend")?,
1160-
),
1161-
);
1162-
1163-
// Open Josh transaction
1164-
let transaction = josh_core::cache::TransactionContext::from_env(cache.clone())
1165-
.map_err(from_josh_err)
1166-
.context("Failed TransactionContext::from_env")?
1167-
.open(None)
1168-
.map_err(from_josh_err)
1169-
.context("Failed TransactionContext::open")?;
11701146
let filtered_commit = josh_core::filter_commit(
1171-
&transaction,
1147+
transaction,
11721148
link_filter,
11731149
new_commit,
11741150
josh_core::filter::empty(),
@@ -1189,7 +1165,10 @@ fn handle_link_fetch(args: &LinkFetchArgs) -> anyhow::Result<()> {
11891165
Ok(())
11901166
}
11911167

1192-
fn handle_remote(args: &RemoteArgs) -> anyhow::Result<()> {
1168+
fn handle_remote(
1169+
args: &RemoteArgs,
1170+
_transaction: &josh_core::cache::Transaction,
1171+
) -> anyhow::Result<()> {
11931172
match &args.command {
11941173
RemoteCommand::Add(add_args) => handle_remote_add(add_args),
11951174
}
@@ -1268,18 +1247,22 @@ fn handle_remote_add_repo(args: &RemoteAddArgs, repo_path: &std::path::Path) ->
12681247
}
12691248

12701249
/// Handle the `josh filter` command - apply filtering to existing refs without fetching
1271-
fn handle_filter(args: &FilterArgs) -> anyhow::Result<()> {
1250+
fn handle_filter(
1251+
args: &FilterArgs,
1252+
transaction: &josh_core::cache::Transaction,
1253+
) -> anyhow::Result<()> {
12721254
let repo = git2::Repository::open_from_env().context("Not in a git repository")?;
12731255
let repo_path = repo.path().parent().unwrap().to_path_buf();
12741256

1275-
handle_filter_repo(args, &repo_path, true)
1257+
handle_filter_repo(args, &repo_path, true, transaction)
12761258
}
12771259

12781260
/// Internal filter function that can be called from other handlers
12791261
fn handle_filter_repo(
12801262
args: &FilterArgs,
12811263
repo_path: &std::path::Path,
12821264
print_messages: bool,
1265+
transaction: &josh_core::cache::Transaction,
12831266
) -> anyhow::Result<()> {
12841267
let repo = git2::Repository::open(repo_path).context("Failed to open repository")?;
12851268

@@ -1296,7 +1279,7 @@ fn handle_filter_repo(
12961279
}
12971280

12981281
// Apply josh filtering (this is the same as in handle_fetch but without the git fetch step)
1299-
apply_josh_filtering(repo_path, &filter, &args.remote)?;
1282+
apply_josh_filtering(transaction, repo_path, &filter, &args.remote)?;
13001283

13011284
if print_messages {
13021285
println!("Applied filter to remote: {}", args.remote);

tests/cli/clone-http.t

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ Test josh clone via HTTP (no filter)
2828
Fetched from remote: origin
2929
Already on 'master'
3030

31-
Cloned repository to: repo1-clone-josh
31+
Cloned repository to: ${TESTTMP}/repo1-clone-josh
3232

3333
$ cd repo1-clone-josh
3434
$ ls
@@ -53,7 +53,7 @@ Test josh clone via HTTP (with filter)
5353
Fetched from remote: origin
5454
Already on 'master'
5555

56-
Cloned repository to: repo1-clone-josh-filtered
56+
Cloned repository to: ${TESTTMP}/repo1-clone-josh-filtered
5757

5858
$ cd repo1-clone-josh-filtered
5959
$ ls
@@ -75,7 +75,7 @@ Test josh clone via HTTP (with explicit filter argument)
7575
Fetched from remote: origin
7676
Already on 'master'
7777

78-
Cloned repository to: repo1-clone-josh-explicit
78+
Cloned repository to: ${TESTTMP}/repo1-clone-josh-explicit
7979

8080
$ cd repo1-clone-josh-explicit
8181
$ ls

0 commit comments

Comments
 (0)