Skip to content

Commit 2f20357

Browse files
committed
Add comments and untracked file tests
1 parent f6a6d36 commit 2f20357

File tree

2 files changed

+135
-22
lines changed

2 files changed

+135
-22
lines changed

src/main.rs

Lines changed: 28 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1381,24 +1381,22 @@ impl GitChain {
13811381
}
13821382

13831383
fn dirty_working_directory(&self) -> Result<bool, Error> {
1384-
// perform equivalent to git diff-index HEAD
1385-
let obj = self.repo.revparse_single("HEAD")?;
1386-
let tree = obj.peel(ObjectType::Tree)?;
1384+
use git2::StatusOptions;
13871385

1388-
// This is used for diff formatting for diff-index. But we're only interested in the diff stats.
1389-
// let mut opts = DiffOptions::new();
1390-
// opts.id_abbrev(40);
1386+
// Configure status collection so we can detect *any* change
1387+
// in the working directory. This mimics `git status --porcelain`
1388+
// by including untracked files and directories. Ignored files
1389+
// and paths that haven't changed are skipped so the resulting
1390+
// status list only contains meaningful modifications.
1391+
let mut opts = StatusOptions::new();
1392+
opts.include_untracked(true)
1393+
.recurse_untracked_dirs(true)
1394+
.include_ignored(false)
1395+
.include_unmodified(false);
13911396

1392-
let diff = self
1393-
.repo
1394-
.diff_tree_to_workdir_with_index(tree.as_tree(), None)?;
1395-
1396-
let diff_stats = diff.stats()?;
1397-
let has_changes = diff_stats.files_changed() > 0
1398-
|| diff_stats.insertions() > 0
1399-
|| diff_stats.deletions() > 0;
1400-
1401-
Ok(has_changes)
1397+
// If the repository reports no statuses, the working tree is clean.
1398+
let statuses = self.repo.statuses(Some(&mut opts))?;
1399+
Ok(!statuses.is_empty())
14021400
}
14031401

14041402
fn backup(&self, chain_name: &str) -> Result<(), Error> {
@@ -1420,11 +1418,15 @@ impl GitChain {
14201418
}
14211419

14221420
if self.dirty_working_directory()? {
1421+
let current_branch = self.get_current_branch_name()?;
14231422
eprintln!(
14241423
"🛑 Unable to back up branches for the chain: {}",
14251424
chain.name.bold()
14261425
);
1427-
eprintln!("You have uncommitted changes in your working directory.");
1426+
eprintln!(
1427+
"You have uncommitted changes on branch {}.",
1428+
current_branch.bold()
1429+
);
14281430
eprintln!("Please commit or stash them.");
14291431
process::exit(1);
14301432
}
@@ -1630,9 +1632,11 @@ impl GitChain {
16301632
}
16311633

16321634
if self.dirty_working_directory()? {
1633-
return Err(Error::from_str(
1634-
"You have uncommitted changes in your working directory.",
1635-
));
1635+
let current_branch = self.get_current_branch_name()?;
1636+
return Err(Error::from_str(&format!(
1637+
"You have uncommitted changes on branch {}.",
1638+
current_branch.bold()
1639+
)));
16361640
}
16371641

16381642
Ok(())
@@ -2008,9 +2012,11 @@ impl GitChain {
20082012

20092013
// Check for uncommitted changes
20102014
if self.dirty_working_directory()? {
2015+
let current_branch = self.get_current_branch_name()?;
20112016
return Err(Error::from_str(&format!(
2012-
"🛑 Unable to merge branches for the chain: {}\nYou have uncommitted changes in your working directory.\nPlease commit or stash them.",
2013-
chain_name.bold()
2017+
"🛑 Unable to merge branches for the chain: {}\nYou have uncommitted changes on branch {}.\nPlease commit or stash them.",
2018+
chain_name.bold(),
2019+
current_branch.bold()
20142020
)));
20152021
}
20162022

tests/untracked_detection.rs

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
#[path = "common/mod.rs"]
2+
pub mod common;
3+
4+
use common::{
5+
checkout_branch, commit_all, create_branch, create_new_file, first_commit_all,
6+
generate_path_to_repo, get_current_branch_name, run_test_bin_expect_err,
7+
run_test_bin_expect_ok, setup_git_repo, teardown_git_repo,
8+
};
9+
10+
#[test]
11+
fn backup_fails_with_untracked_files() {
12+
let repo_name = "backup_fails_with_untracked";
13+
let repo = setup_git_repo(repo_name);
14+
let path_to_repo = generate_path_to_repo(repo_name);
15+
16+
// initial commit on master
17+
create_new_file(&path_to_repo, "initial.txt", "initial");
18+
first_commit_all(&repo, "initial commit");
19+
20+
// create feature branch
21+
create_branch(&repo, "feature");
22+
checkout_branch(&repo, "feature");
23+
create_new_file(&path_to_repo, "feature.txt", "feature");
24+
commit_all(&repo, "feature commit");
25+
26+
// initialize chain with root master
27+
let args = vec!["init", "chain", "master"];
28+
run_test_bin_expect_ok(&path_to_repo, args);
29+
30+
// add untracked file
31+
create_new_file(&path_to_repo, "untracked.txt", "dirty");
32+
33+
// attempt backup and expect failure mentioning branch name
34+
let args = vec!["backup"];
35+
let output = run_test_bin_expect_err(&path_to_repo, args);
36+
let stderr = String::from_utf8_lossy(&output.stderr);
37+
assert!(stderr.contains("uncommitted"));
38+
assert!(stderr.contains(&get_current_branch_name(&repo)));
39+
40+
teardown_git_repo(repo_name);
41+
}
42+
43+
#[test]
44+
fn merge_fails_with_untracked_files() {
45+
let repo_name = "merge_fails_with_untracked";
46+
let repo = setup_git_repo(repo_name);
47+
let path_to_repo = generate_path_to_repo(repo_name);
48+
49+
// initial commit on master
50+
create_new_file(&path_to_repo, "initial.txt", "initial");
51+
first_commit_all(&repo, "initial commit");
52+
53+
// create feature branch and commit
54+
create_branch(&repo, "feature");
55+
checkout_branch(&repo, "feature");
56+
create_new_file(&path_to_repo, "feature.txt", "feature");
57+
commit_all(&repo, "feature commit");
58+
59+
// initialize chain with root master
60+
let args = vec!["init", "chain", "master"];
61+
run_test_bin_expect_ok(&path_to_repo, args);
62+
63+
// add untracked file
64+
create_new_file(&path_to_repo, "untracked.txt", "dirty");
65+
66+
// attempt merge and expect failure mentioning branch name
67+
let args = vec!["merge"];
68+
let output = run_test_bin_expect_err(&path_to_repo, args);
69+
let stderr = String::from_utf8_lossy(&output.stderr);
70+
assert!(stderr.contains("uncommitted"));
71+
assert!(stderr.contains(&get_current_branch_name(&repo)));
72+
73+
teardown_git_repo(repo_name);
74+
}
75+
76+
#[test]
77+
fn rebase_fails_with_untracked_files() {
78+
let repo_name = "rebase_fails_with_untracked";
79+
let repo = setup_git_repo(repo_name);
80+
let path_to_repo = generate_path_to_repo(repo_name);
81+
82+
// initial commit on master
83+
create_new_file(&path_to_repo, "initial.txt", "initial");
84+
first_commit_all(&repo, "initial commit");
85+
86+
// create feature branch and commit
87+
create_branch(&repo, "feature");
88+
checkout_branch(&repo, "feature");
89+
create_new_file(&path_to_repo, "feature.txt", "feature");
90+
commit_all(&repo, "feature commit");
91+
92+
// initialize chain with root master
93+
let args = vec!["init", "chain", "master"];
94+
run_test_bin_expect_ok(&path_to_repo, args);
95+
96+
// add untracked file
97+
create_new_file(&path_to_repo, "untracked.txt", "dirty");
98+
99+
// attempt rebase and expect failure mentioning branch name
100+
let args = vec!["rebase"];
101+
let output = run_test_bin_expect_err(&path_to_repo, args);
102+
let stderr = String::from_utf8_lossy(&output.stderr);
103+
assert!(stderr.contains("uncommitted"));
104+
assert!(stderr.contains(&get_current_branch_name(&repo)));
105+
106+
teardown_git_repo(repo_name);
107+
}

0 commit comments

Comments
 (0)