Skip to content

Commit

Permalink
Merge pull request #1355 from SierraSoftworks/feat/clone-batch
Browse files Browse the repository at this point in the history
feat: Add support for importing a file list of repos
  • Loading branch information
notheotherben authored Feb 10, 2025
2 parents 32f20d9 + 5cb4639 commit 6c7d4a7
Show file tree
Hide file tree
Showing 3 changed files with 115 additions and 11 deletions.
19 changes: 17 additions & 2 deletions docs/commands/repos.md
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ gt o vs
# Open a repository in VS Code
gt o gh:SierraSoftworks/git-tool code
```

::: tip
If you are already inside a repository, you can specify only an app and it will launch in the
context of the current repo, like `gt o vs` in the example above. *This can be very useful if
Expand Down Expand Up @@ -161,8 +161,23 @@ new dev-box, this is the command for you.
```powershell
# Clone a repository into the appropriate folder
gt clone gh:SierraSoftworks/git-tool
# Clone a series of repositories into the appropriate folders
# The repositories.txt file should contain a list of repositories, one per line
# e.g.
# gh:SierraSoftworks/git-tool
# gh:SierraSoftworks/tailscale-udm
# gh:SierraSoftworks/vue-template
gt clone @repositories.txt
```

::: tip
As of <Badge text="v3.7.0+"/>, you can use the `@` symbol to specify a file
containing a list of repositories to clone. This can be paired with
[`gt list -q`](#list) to quickly backup and restore your list of local repositories,
or setup a new machine.
:::

## fix <Badge text="v2.1.4+"/>
Git-Tool usually takes care of setting up your git `origin` remote, however sometimes you
want to rename projects or even entire organizations. To make your life a little bit easier,
Expand Down Expand Up @@ -195,4 +210,4 @@ in the directory you are attempting to delete.
```powershell
# Remove a repository
gt remove gh:SierraSoftworks/git-tool
```
```
3 changes: 0 additions & 3 deletions docs/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

104 changes: 98 additions & 6 deletions src/commands/clone.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use super::*;
use crate::core::Target;
use crate::tasks::*;
use clap::Arg;
use std::path::PathBuf;
use tracing_batteries::prelude::*;

pub struct CloneCommand;
Expand Down Expand Up @@ -31,12 +32,46 @@ impl CommandRunnable for CloneCommand {
"You didn't specify the repository you wanted to clone.",
"Remember to specify a repository name like this: 'git-tool clone gh:sierrasoftworks/git-tool'."))?;

let repo = core.resolver().get_best_repo(repo_name)?;

if !repo.exists() {
match sequence![GitClone {}].apply_repo(core, &repo).await {
Ok(()) => {}
Err(e) => return Err(e),
if let Some(file_path) = repo_name.strip_prefix('@') {
// Load the list of repos to clone from a file
let file_path: PathBuf = file_path.parse().map_err(|e| {
errors::user_with_internal(
"The specified file path is not valid.",
"Please make sure you are specifying a valid file path for your import file.",
e,
)
})?;

let file = std::fs::read_to_string(&file_path).map_err(|e| {
errors::user_with_internal(
"Could not read the specified clone file.",
"Please make sure the file exists and is readable.",
e,
)
})?;

let operation = sequence![GitClone {}];

for line in file.lines() {
if line.trim_start().is_empty() || line.trim_start().starts_with('#') {
continue;
}

let repo = core.resolver().get_best_repo(line.trim())?;
writeln!(core.output(), "{}", repo)?;
match operation.apply_repo(core, &repo).await {
Ok(()) => {}
Err(e) => return Err(e),
}
}
} else {
let repo = core.resolver().get_best_repo(repo_name)?;

if !repo.exists() {
match sequence![GitClone {}].apply_repo(core, &repo).await {
Ok(()) => {}
Err(e) => return Err(e),
}
}
}

Expand Down Expand Up @@ -127,4 +162,61 @@ features:
Err(err) => panic!("{}", err.message()),
}
}

#[tokio::test]
#[cfg_attr(feature = "pure-tests", ignore)]
async fn run_batch() {
let cmd = CloneCommand {};

let temp = tempdir().unwrap();

let args = cmd.app().get_matches_from(vec![
"clone",
format!("@{}", temp.path().join("import.txt").display()).as_str(),
]);

let cfg = Config::from_str(
"
directory: /dev
apps:
- name: test-app
command: test
args:
- '{{ .Target.Name }}'
features:
http_transport: true
",
)
.unwrap();

let temp_path = temp.path().to_path_buf();

std::fs::write(temp.path().join("import.txt"), "gh:git-fixtures/basic")
.expect("writing should succeed");

let core = Core::builder()
.with_config(cfg)
.with_mock_launcher(|mock| {
mock.expect_run().never();
})
.with_mock_resolver(|mock| {
let temp_path = temp_path.clone();
mock.expect_get_best_repo()
.once()
.with(mockall::predicate::eq("gh:git-fixtures/basic"))
.returning(move |_| {
Ok(Repo::new("gh:git-fixtures/basic", temp_path.join("repo")))
});
})
.build();

match cmd.run(&core, &args).await {
Ok(status) => {
assert_eq!(status, 0);
}
Err(err) => panic!("{}", err.message()),
}
}
}

0 comments on commit 6c7d4a7

Please sign in to comment.