Skip to content

Commit 037ccb7

Browse files
committed
Read and store origin url when adding repo
* add Git trait abstraction and inject * add test coverage for read/write url * remove old unit test that's now covered with integration test * update test.sh for remotes I'm not super-happy with the toml layout, but the format I designed isn't supported by toml.rs yet - see toml-rs/toml-rs#406
1 parent a4e352c commit 037ccb7

File tree

7 files changed

+158
-71
lines changed

7 files changed

+158
-71
lines changed

src/git.rs

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
use std::process::Command;
2+
3+
pub trait Git {
4+
fn read_url(&self, path: &str, remote_name: &str) -> String;
5+
}
6+
7+
pub struct GitImpl {}
8+
9+
impl Git for GitImpl {
10+
/// hacky call to external git command to get url of origin
11+
fn read_url(&self, path: &str, remote_name: &str) -> String {
12+
repo_capture_exec(
13+
&path,
14+
"git",
15+
&["config".to_string(), format!("remote.{}.url", remote_name)].to_vec(),
16+
)
17+
.trim()
18+
.to_owned()
19+
}
20+
}
21+
22+
/// Run a command and capture the output for use internally
23+
fn repo_capture_exec(path: &str, cmd: &str, args: &Vec<String>) -> String {
24+
let output = Command::new(cmd)
25+
.args(args)
26+
.current_dir(path)
27+
.output()
28+
.expect(&format!(
29+
"Error running external command {} {:?} in folder {}",
30+
cmd, args, path
31+
));
32+
33+
String::from_utf8(output.stdout).expect("Error converting stdout to string")
34+
}

src/gitopolis.rs

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,31 @@
1+
use crate::git::Git;
12
use crate::repos::{Repo, Repos};
23
use crate::storage::Storage;
4+
use log::info;
35
use std::collections::BTreeMap;
46

57
pub struct Gitopolis {
68
storage: Box<dyn Storage>,
9+
git: Box<dyn Git>,
710
}
811

912
impl Gitopolis {
10-
pub fn new(storage: Box<dyn Storage>) -> Gitopolis {
11-
Gitopolis { storage }
13+
pub fn new(storage: Box<dyn Storage>, git: Box<dyn Git>) -> Gitopolis {
14+
Gitopolis { storage, git }
1215
}
1316

1417
pub fn add(&mut self, repo_folders: &Vec<String>) {
1518
let mut repos = self.load();
16-
repos.add(repo_folders);
19+
for repo_folder in repo_folders {
20+
if let Some(_) = repos.repo_index(repo_folder) {
21+
info!("{} already added, ignoring.", repo_folder);
22+
continue;
23+
}
24+
// todo: read all remotes, not just origin https://github.com/timabell/gitopolis/i
25+
let remote_name = "origin";
26+
let url = self.git.read_url(&repo_folder, remote_name);
27+
repos.add(repo_folder, url, remote_name);
28+
}
1729
self.save(repos)
1830
}
1931
pub fn remove(&mut self, repo_folders: &Vec<String>) {

src/lib.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
pub mod exec;
2+
pub mod git;
23
pub mod gitopolis;
34
pub mod list;
45
pub mod repos;

src/main.rs

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use clap::{Parser, Subcommand};
22
use gitopolis::exec::exec;
3+
use gitopolis::git::GitImpl;
34
use gitopolis::gitopolis::Gitopolis;
45
use gitopolis::list::list;
56
use gitopolis::storage::StorageImpl;
@@ -48,9 +49,12 @@ fn main() {
4849

4950
let args = Args::parse();
5051

51-
let mut gitopolis = Gitopolis::new(Box::new(StorageImpl {
52-
path: ".gitopolis.toml",
53-
}));
52+
let mut gitopolis = Gitopolis::new(
53+
Box::new(StorageImpl {
54+
path: ".gitopolis.toml",
55+
}),
56+
Box::new(GitImpl {}),
57+
);
5458

5559
match &args.command {
5660
Some(Commands::Add { repo_folders }) => {

src/repos.rs

Lines changed: 19 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
use log::info;
22
use serde_derive::{Deserialize, Serialize};
3+
use std::collections::BTreeMap;
34

45
#[derive(Debug, Serialize, Deserialize)]
56
pub struct Repos {
@@ -11,7 +12,7 @@ pub struct Repos {
1112
pub struct Repo {
1213
pub path: String,
1314
pub tags: Vec<String>,
14-
// pub remotes: Vec<Remote>,
15+
pub remotes: BTreeMap<String, Remote>,
1516
}
1617

1718
#[derive(Debug, Deserialize, Serialize)]
@@ -36,20 +37,23 @@ impl Repos {
3637
self.repos.iter().position(|r| r.path == *folder_name)
3738
}
3839

39-
pub fn add(&mut self, repo_folders: &Vec<String>) {
40-
for repo_folder in repo_folders {
41-
if let Some(_) = self.repo_index(repo_folder) {
42-
info!("{} already added, ignoring.", repo_folder);
43-
continue;
44-
}
45-
let repo = Repo {
46-
path: repo_folder.to_owned(),
47-
tags: Vec::new(),
48-
// remotes: Vec::new(),
49-
};
50-
self.repos.push(repo);
51-
info!("Added {}", repo_folder);
52-
}
40+
pub fn add(&mut self, repo_folder: &str, url: String, remote_name: &str) {
41+
let mut remotes: BTreeMap<String, Remote> = BTreeMap::new();
42+
remotes.insert(
43+
remote_name.to_owned(),
44+
Remote {
45+
name: remote_name.to_owned(),
46+
url: url.to_owned(),
47+
},
48+
);
49+
50+
let repo = Repo {
51+
path: repo_folder.to_owned(),
52+
tags: Vec::new(),
53+
remotes,
54+
};
55+
self.repos.push(repo);
56+
info!("Added {}", repo_folder);
5357
}
5458

5559
pub fn remove(&mut self, repo_folders: &Vec<String>) {
@@ -82,18 +86,3 @@ impl Repos {
8286
}
8387
}
8488
}
85-
86-
#[cfg(test)]
87-
mod tests {
88-
use crate::repos::Repos;
89-
90-
#[test]
91-
fn add() {
92-
let mut repos = Repos::new();
93-
let mut folders: Vec<String> = Vec::new();
94-
folders.push("onions.git".to_owned());
95-
folders.push("potatoes".to_owned());
96-
repos.add(&folders);
97-
assert_eq!(folders.len(), repos.repos.len());
98-
}
99-
}

test.sh

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,48 @@
1-
#!/bin/sh -v
2-
set -e
1+
#!/bin/sh
2+
# Hacky smoke test of whole thing, needs making proper
3+
set -e # stop on error
34

4-
if [ -f .gitopolis.toml ]; then
5-
rm .gitopolis.toml
5+
# Arrange - make test folder & repos
6+
src=`pwd`
7+
exe="$src/target/debug/gitopolis"
8+
cd /tmp/
9+
10+
test_folder=gitopolis_test
11+
if [ -d "$test_folder" ]; then
12+
rm -rf "$test_folder"
613
fi
7-
cargo run help
8-
cargo run add foo bar "baz aroony" deleteme
9-
cargo run list
10-
cargo run remove deleteme
11-
cargo run tag red foo bar
12-
cargo run tag deadtag bar
13-
cargo run tag -r deadtag bar
14+
mkdir "$test_folder"
15+
cd "$test_folder"
16+
17+
(
18+
mkdir foo
19+
cd foo
20+
git init >> /dev/null
21+
git remote add origin "[email protected]/some_repo.git"
22+
)
23+
24+
# Act - try various commands
25+
echo "$exe help"
26+
eval "$exe help"
27+
echo
28+
29+
echo "$exe add foo"
30+
eval "$exe add foo"
31+
eval "$exe tag RED foo"
32+
echo
33+
echo "====== .gitopolis.toml ======"
34+
cat .gitopolis.toml
35+
echo
36+
37+
echo "$exe list"
38+
eval "$exe list"
39+
eval "$exe tag -r RED foo"
40+
echo
41+
echo "====== .gitopolis.toml ======"
42+
cat .gitopolis.toml
43+
echo
44+
45+
echo "$exe remove foo"
46+
eval "$exe remove foo"
47+
echo "====== .gitopolis.toml ======"
1448
cat .gitopolis.toml

tests/gitopolis_tests.rs

Lines changed: 37 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,48 @@
1+
use gitopolis::git::Git;
12
use gitopolis::gitopolis::Gitopolis;
2-
use gitopolis::repos::Repos;
33
use gitopolis::storage::Storage;
44

55
#[test]
66
fn add_repo() {
77
let expected_toml = "[[repos]]
8-
path = \"foo\"
8+
path = \"test_repo\"
99
tags = []
10+
[repos.remotes.origin]
11+
name = \"origin\"
12+
url = \"git://example.org/test_url\"
1013
";
11-
let mut gitopolis = Gitopolis::new(Box::new(FakeStorage {
12-
exists: false,
13-
contents: "".to_string(),
14-
file_saved_callback: Box::new(|state| assert_eq!(expected_toml.to_owned(), state)),
15-
}));
14+
let mut gitopolis = Gitopolis::new(
15+
Box::new(FakeStorage {
16+
exists: false,
17+
contents: "".to_string(),
18+
file_saved_callback: Box::new(|state| assert_eq!(expected_toml.to_owned(), state)),
19+
}),
20+
Box::new(FakeGit {}),
21+
);
1622
let mut folders = Vec::new();
17-
folders.push("foo".to_string());
23+
folders.push("test_repo".to_string());
1824
gitopolis.add(&folders);
1925
}
2026

2127
#[test]
2228
fn read() {
23-
let gitopolis = Gitopolis::new(Box::new(FakeStorage {
24-
exists: true,
25-
contents: "[[repos]]
26-
path = \"foo\"
27-
tags = [\"red\"]
28-
29-
[[repos]]
30-
path = \"bar\"
31-
tags = [\"red\"]
32-
33-
[[repos]]
34-
path = \"baz aroony\"
35-
tags = []"
29+
let gitopolis = Gitopolis::new(
30+
Box::new(FakeStorage {
31+
exists: true,
32+
contents: "[[repos]]
33+
path = \"test_repo\"
34+
tags = []
35+
[repos.remotes.origin]
36+
name = \"origin\"
37+
url = \"git://example.org/test_url\"\
38+
"
3639
.to_string(),
37-
file_saved_callback: Box::new(|_| {}),
38-
}));
40+
file_saved_callback: Box::new(|_| {}),
41+
}),
42+
Box::new(FakeGit {}),
43+
);
3944
let r = gitopolis.read();
40-
assert_eq!(3, r.repos.len())
45+
assert_eq!(1, r.repos.len())
4146
}
4247

4348
struct FakeStorage {
@@ -59,3 +64,11 @@ impl Storage for FakeStorage {
5964
self.contents.to_owned()
6065
}
6166
}
67+
68+
struct FakeGit {}
69+
70+
impl Git for FakeGit {
71+
fn read_url(&self, _path: &str, _remote_name: &str) -> String {
72+
"git://example.org/test_url".to_string()
73+
}
74+
}

0 commit comments

Comments
 (0)