Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gcd, gls, and git conform cd #4

Open
wants to merge 6 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion src/cli.rs
Original file line number Diff line number Diff line change
Expand Up @@ -68,5 +68,13 @@ pub enum Commands {
#[arg(short, long, group = "output")]
#[arg(default_value_t = false)]
remotes: bool
}
},
/// Change directory to a tracked repository
Cd {
/// Name of the repository to change to
#[arg(required = true)]
repo_name: String,
},
/// Enable CD functionality by adding an alias to your shell configuration
EnableCd
}
57 changes: 57 additions & 0 deletions src/core/api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ use crate::utils::{
use std::fs::{self, OpenOptions};
use std::io::Write as _;
use std::path::Path;
use std::env;

/// Scans only specified directories
pub fn scan_dirs(mut dirs: Vec<String>, tracking_file: &TrackingFile, scan_hidden: bool) -> Result<(), String> {
Expand Down Expand Up @@ -219,3 +220,59 @@ pub async fn check_all(tracking_file: &TrackingFile, flags: &[bool]) -> Result<(

Ok(())
}

pub fn cd_to_repo(repo_name: &str, tracking_file: &TrackingFile) -> Result<String, String> {
if tracking_file.contents.is_empty() {
return Err(String::from("No repository is being tracked"));
}

tracking_file.contents
.lines()
.find(|line| Path::new(line).file_name().and_then(|name| name.to_str()) == Some(repo_name))
.map(String::from)
.ok_or_else(|| format!("Repository '{}' not found in tracking file", repo_name))
}

/// Enables the CD functionality by adding an alias or function to the user's shell configuration
pub fn enable_cd() -> Result<(), String> {
let home_dir = env::var("HOME").map_err(|_| "Failed to get home directory")?;
let shell = env::var("SHELL").map_err(|_| "Failed to get current shell")?;

let (config_file, function_content) = if shell.ends_with("bash") || shell.ends_with("zsh") {
(
format!("{}/.{}rc", home_dir, shell.split('/').last().unwrap()),
r#"
alias gls='git conform ls'
alias gcd='git_conform_cd'

git_conform_cd() {
if [ -z "$1" ]; then
echo "Usage: git_conform_cd <repository-name>"
return 1
fi

local repo_path
repo_path=$(git-conform cd "$1")

if [ $? -ne 0 ]; then
echo "$repo_path" # This will be the error message
return 1
fi

cd "$repo_path" || return 1
}
"#
)
} else {
return Err(format!("Unsupported shell: {}", shell));
};

fs::OpenOptions::new()
.append(true)
.open(&config_file)
.and_then(|mut file| file.write_all(function_content.as_bytes()))
.map_err(|e| format!("Failed to update shell config: {}", e))?;

println!("CD functionality enabled. Please restart your shell or run 'source {}' to apply changes.", config_file);
Ok(())
}
15 changes: 14 additions & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ use crate::core::api::{
scan_all,
list,
add,
cd_to_repo,
enable_cd,
remove_repos,
remove_all,
check_repos,
Expand Down Expand Up @@ -128,6 +130,17 @@ async fn main() {
else if let Err(e) = check_repos(repos.to_owned(), &[*status, *remotes]).await {
handle_error(&e, 6);
}
}
},
Commands::Cd { repo_name } => {
match cd_to_repo(&repo_name, &tracking_file) {
Ok(path) => println!("{}", path),
Err(e) => eprintln!("Error: {}", e),
}
},
Commands::EnableCd => {
if let Err(e) = enable_cd() {
eprintln!("Error enabling CD functionality: {}", e);
}
},
};
}
133 changes: 133 additions & 0 deletions tests/cd.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
mod common;

use git_conform::core::api::{add, cd_to_repo};
use git_conform::utils::TrackingFile;
use std::fs;
use std::path::Path;
use serial_test::serial;

#[test]
#[serial]
fn test_cd_to_existing_repo() {
let (_home_dir, mut tracking_file, tests_dir) = common::setup().unwrap();

// Add some repositories to the tracking file
let repos = vec![
format!("{}/repo1", tests_dir),
format!("{}/repo2", tests_dir),
format!("{}/repo3", tests_dir),
];
assert!(add(repos.clone(), &tracking_file).is_ok());

// Manually update tracking_file contents
tracking_file.contents = repos.join("\n");

// Test cd_to_repo for each added repository
for n in 1..=3 {
let repo_name = format!("repo{}", n);
let expected_path = format!("{}/repo{}", tests_dir, n);
assert_eq!(cd_to_repo(&repo_name, &tracking_file), Ok(expected_path));
}

common::cleanup(&tests_dir).unwrap();
}

#[test]
#[serial]
fn test_cd_to_nonexistent_repo() {
let (_home_dir, mut tracking_file, tests_dir) = common::setup().unwrap();

// Add a repository to ensure the tracking file is not empty
let repo = format!("{}/repo1", tests_dir);
assert!(add(vec![repo.clone()], &tracking_file).is_ok());

// Manually update tracking_file contents
tracking_file.contents = repo;

// Try to cd to a non-existent repository
let fake_repo = "fake_repo";
assert_eq!(
cd_to_repo(fake_repo, &tracking_file),
Err(format!("Repository '{}' not found in tracking file", fake_repo))
);

common::cleanup(&tests_dir).unwrap();
}

#[test]
#[serial]
fn test_cd_with_empty_tracking_file() {
let (_home_dir, tracking_file, tests_dir) = common::setup().unwrap();

// Ensure the tracking file is empty (this is already the case after setup)

// Try to cd to any repository with an empty tracking file
let repo_name = "any_repo";
assert_eq!(
cd_to_repo(repo_name, &tracking_file),
Err(String::from("No repository is being tracked"))
);

common::cleanup(&tests_dir).unwrap();
}

#[test]
#[serial]
fn test_cd_to_hidden_repo() {
let (_home_dir, mut tracking_file, tests_dir) = common::setup().unwrap();

// Add a hidden repository to the tracking file
let hidden_repo = format!("{}/.hidden/repo1", tests_dir);
assert!(add(vec![hidden_repo.clone()], &tracking_file).is_ok());

// Manually update tracking_file contents
tracking_file.contents = hidden_repo.clone();

// Test cd_to_repo for the hidden repository
assert_eq!(cd_to_repo("repo1", &tracking_file), Ok(hidden_repo));

common::cleanup(&tests_dir).unwrap();
}

#[test]
#[serial]
fn test_cd_multiple_repos_same_name() {
let (_home_dir, mut tracking_file, tests_dir) = common::setup().unwrap();

// Add repositories with the same name in different directories
let repos = vec![
format!("{}/repo1", tests_dir),
format!("{}/.hidden/repo1", tests_dir),
];
assert!(add(repos.clone(), &tracking_file).is_ok());

// Manually update tracking_file contents
tracking_file.contents = repos.join("\n");

// Test cd_to_repo with a repo name that exists multiple times
let expected_path = format!("{}/repo1", tests_dir);
assert_eq!(cd_to_repo("repo1", &tracking_file), Ok(expected_path));

common::cleanup(&tests_dir).unwrap();
}

#[test]
#[serial]
fn test_cd_to_fake_repo() {
let (_home_dir, mut tracking_file, tests_dir) = common::setup().unwrap();

// Add a real repository to ensure the tracking file is not empty
let real_repo = format!("{}/repo1", tests_dir);
assert!(add(vec![real_repo.clone()], &tracking_file).is_ok());

// Manually update tracking_file contents
tracking_file.contents = real_repo;

// Try to cd to a fake repository
assert_eq!(
cd_to_repo("fake_repo1", &tracking_file),
Err(String::from("Repository 'fake_repo1' not found in tracking file"))
);

common::cleanup(&tests_dir).unwrap();
}
6 changes: 6 additions & 0 deletions tests/common/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -108,3 +108,9 @@ pub fn setup() -> Result<(String, TrackingFile, String), String> {

Ok((home_dir, tracking_file, tests_dir))
}

pub fn cleanup(tests_dir: &str) -> Result<(), String> {
fs::remove_dir_all(tests_dir)
.map_err(|e| format!("Failed to remove test directory: {}", e))?;
Ok(())
}