1mod branch;
2mod hook;
3mod input;
4mod reference;
5mod remote;
6mod repo;
7
8use std::env;
9use std::io::BufRead;
10
11use anyhow::{Context, Result};
12
13use hook::{GitHook, GitHookType};
14
15pub fn install_hooks(force: bool) -> Result<()> {
17 let current_dir = env::current_dir().context("Could not get current directory")?;
18 let repo = repo::Repository::open(current_dir)?;
19
20 println!("Installing git-snip hook script.");
21 let h = GitHook::default();
22 hook::install(repo.path(), &h, GitHookType::PostMerge, force)?;
23 hook::install(repo.path(), &h, GitHookType::PostRewrite, force)?;
24
25 Ok(())
26}
27
28pub fn snip(no_confirm: bool, reader: impl BufRead) -> Result<()> {
30 let current_dir = env::current_dir().context("Could not get current directory")?;
31 let repo = repo::Repository::open(current_dir)?;
32
33 snip_repo(&repo, no_confirm, reader)
34}
35
36fn snip_repo(repo: &repo::Repository, no_confirm: bool, reader: impl BufRead) -> Result<()> {
37 let result = repo.orphaned_branches();
38
39 if let Some(head) = &result.skipped_head {
40 println!("Cannot delete current branch: {head}");
41 }
42
43 let mut branches_to_delete = result.branches;
44 if branches_to_delete.is_empty() {
45 println!("No local branches to delete.");
46 return Ok(());
47 }
48
49 if !no_confirm {
50 println!("Local branches to delete:");
51 for branch in &branches_to_delete {
52 println!(" - {branch}");
53 }
54
55 let user_input = input::prompt("Delete these branches? (y/n): ", reader);
56 if user_input != "y" && user_input != "yes" {
57 println!("Aborting.");
58 return Ok(());
59 }
60 }
61
62 for branch in &mut branches_to_delete {
63 println!("Deleting branch: {branch}");
64 branch.delete()?;
65 }
66
67 Ok(())
68}
69
70#[cfg(test)]
71mod tests {
72 use std::io::Cursor;
73
74 use crate::test_utilities;
75
76 use super::*;
77
78 fn open_repo(testdir: &tempfile::TempDir) -> repo::Repository {
79 repo::Repository::open(testdir.path()).unwrap()
80 }
81
82 #[test]
83 fn test_snip_no_confirm() {
84 let (testdir, repo) = test_utilities::create_mock_repo();
86 let commit = test_utilities::get_latest_commit(&repo);
87 repo.branch("orphan-1", &commit, false).unwrap();
88 repo.branch("orphan-2", &commit, false).unwrap();
89
90 let result = snip_repo(&open_repo(&testdir), true, Cursor::new(""));
92
93 assert!(result.is_ok());
95 assert!(repo
96 .find_branch("orphan-1", git2::BranchType::Local)
97 .is_err());
98 assert!(repo
99 .find_branch("orphan-2", git2::BranchType::Local)
100 .is_err());
101 }
102
103 #[test]
104 fn test_snip_user_confirms() {
105 let (testdir, repo) = test_utilities::create_mock_repo();
107 let commit = test_utilities::get_latest_commit(&repo);
108 repo.branch("orphan-1", &commit, false).unwrap();
109
110 let result = snip_repo(&open_repo(&testdir), false, Cursor::new("y\n"));
112
113 assert!(result.is_ok());
115 assert!(repo
116 .find_branch("orphan-1", git2::BranchType::Local)
117 .is_err());
118 }
119
120 #[test]
121 fn test_snip_user_declines() {
122 let (testdir, repo) = test_utilities::create_mock_repo();
124 let commit = test_utilities::get_latest_commit(&repo);
125 repo.branch("orphan-1", &commit, false).unwrap();
126
127 let result = snip_repo(&open_repo(&testdir), false, Cursor::new("n\n"));
129
130 assert!(result.is_ok());
132 assert!(repo
133 .find_branch("orphan-1", git2::BranchType::Local)
134 .is_ok());
135 }
136
137 #[test]
138 fn test_snip_no_orphaned_branches() {
139 let (testdir, _repo) = test_utilities::create_mock_repo();
141
142 let result = snip_repo(&open_repo(&testdir), true, Cursor::new(""));
144
145 assert!(result.is_ok());
147 }
148}
149
150#[cfg(test)]
151pub mod test_utilities {
152 use git2::{Repository, RepositoryInitOptions};
153 use tempfile::TempDir;
154
155 pub fn create_mock_repo() -> (TempDir, Repository) {
158 let tempdir = TempDir::new().unwrap();
159 let mut opts = RepositoryInitOptions::new();
160 opts.initial_head("main");
161 let repo = Repository::init_opts(tempdir.path(), &opts).unwrap();
162
163 {
165 let mut config = repo.config().unwrap();
166 config.set_str("user.name", "name").unwrap();
167 config.set_str("user.email", "email").unwrap();
168 let mut index = repo.index().unwrap();
169 let id = index.write_tree().unwrap();
170
171 let tree = repo.find_tree(id).unwrap();
172 let sig = repo.signature().unwrap();
173 repo.commit(Some("HEAD"), &sig, &sig, "initial\n\nbody", &tree, &[])
174 .unwrap();
175 }
176 (tempdir, repo)
177 }
178
179 pub fn get_latest_commit(repo: &git2::Repository) -> git2::Commit<'_> {
181 let head = repo.head().unwrap();
182 let commit = head.peel_to_commit().unwrap();
183 commit
184 }
185}