Skip to content

Commit 8cce71d

Browse files
authored
Merge pull request #1251 from DanielEScherzer/repo-status-e2e
Add end-to-end tests showing use of `Repository::statuses()`
2 parents 4de6b2e + 47dd883 commit 8cce71d

1 file changed

Lines changed: 229 additions & 1 deletion

File tree

tests/end_to_end.rs

Lines changed: 229 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,12 @@
11
//! Tests for some end-to-end logic about certain operations
2-
use git2::{Error, ReferenceType, Repository, RepositoryInitOptions, StashFlags};
2+
use git2::{
3+
Error, ReferenceType, Repository, RepositoryInitOptions, StashFlags, Status, StatusOptions,
4+
};
35

46
use libgit2_sys as raw;
57
use std::ffi::{CString, OsString};
68
use std::fs;
9+
use std::path::Path;
710
use std::ptr;
811

912
use tempfile::TempDir;
@@ -238,3 +241,228 @@ fn branch_name_on_init() {
238241
assert_eq!(Some("refs/heads/somerandombranchnamehere"), target);
239242
}
240243
}
244+
245+
#[test]
246+
fn repo_status() {
247+
let td = TempDir::new().unwrap();
248+
let path = td.path();
249+
250+
let mut opts = RepositoryInitOptions::new();
251+
opts.initial_head("main");
252+
let repo = Repository::init_opts(path, &opts).unwrap();
253+
254+
let mut config = repo.config().unwrap();
255+
config.set_str("user.name", "name").unwrap();
256+
config.set_str("user.email", "email").unwrap();
257+
258+
// Create some files
259+
fs::write(path.join("BothModified"), "BothModified").unwrap();
260+
fs::write(path.join("IndexModified"), "IndexModified").unwrap();
261+
fs::write(path.join("IndexDeleted"), "IndexDeleted").unwrap();
262+
fs::write(path.join("IndexRenamed"), "IndexRenamed").unwrap();
263+
fs::write(path.join("IndexTypechange"), "IndexTypechange").unwrap();
264+
fs::write(path.join("WorktreeDeleted"), "WorktreeDeleted").unwrap();
265+
fs::write(path.join("WorktreeModified"), "WorktreeModified").unwrap();
266+
fs::write(path.join("WorktreeTypechange"), "WorktreeTypechange").unwrap();
267+
fs::write(path.join("WorktreeRenamed"), "WorktreeRenamed").unwrap();
268+
fs::write(path.join("Unchanged"), "Unchanged").unwrap();
269+
fs::write(path.join(".gitignore"), "ignored-*").unwrap();
270+
271+
let mut index = repo.index().unwrap();
272+
index.add_path(&Path::new("BothModified")).unwrap();
273+
index.add_path(&Path::new("IndexModified")).unwrap();
274+
index.add_path(&Path::new("IndexDeleted")).unwrap();
275+
index.add_path(&Path::new("IndexRenamed")).unwrap();
276+
index.add_path(&Path::new("IndexTypechange")).unwrap();
277+
index.add_path(&Path::new("Unchanged")).unwrap();
278+
index.add_path(&Path::new("WorktreeDeleted")).unwrap();
279+
index.add_path(&Path::new("WorktreeModified")).unwrap();
280+
index.add_path(&Path::new("WorktreeRenamed")).unwrap();
281+
index.add_path(&Path::new("WorktreeTypechange")).unwrap();
282+
index.add_path(&Path::new(".gitignore")).unwrap();
283+
284+
let id = index.write_tree().unwrap();
285+
286+
let tree = repo.find_tree(id).unwrap();
287+
let sig = repo.signature().unwrap();
288+
repo.commit(Some("HEAD"), &sig, &sig, "Initial files", &tree, &[])
289+
.unwrap();
290+
291+
// Modify some files that will differ between HEAD and index
292+
fs::write(path.join("BothModified"), "Modified in index").unwrap();
293+
fs::write(path.join("IndexModified"), "IndexModified-content2").unwrap();
294+
fs::write(path.join("IndexNew"), "IndexNew").unwrap();
295+
fs::remove_file(path.join("IndexDeleted")).unwrap();
296+
fs::remove_file(path.join("IndexTypechange")).unwrap();
297+
298+
fn create_symlink(to: &Path, from: &Path) {
299+
#[cfg(unix)]
300+
std::os::unix::fs::symlink(to, from).unwrap();
301+
302+
#[cfg(windows)]
303+
std::os::windows::fs::symlink_file(to, from).unwrap();
304+
}
305+
create_symlink(&path.join("Unchanged"), &path.join("IndexTypechange"));
306+
307+
fs::rename(path.join("IndexRenamed"), path.join("IndexRenamed-new")).unwrap();
308+
309+
index.add_path(&Path::new("BothModified")).unwrap();
310+
index.remove_path(&Path::new("IndexDeleted")).unwrap();
311+
index.add_path(&Path::new("IndexModified")).unwrap();
312+
index.add_path(&Path::new("IndexNew")).unwrap();
313+
index.remove_path(&Path::new("IndexRenamed")).unwrap();
314+
index.add_path(&Path::new("IndexRenamed-new")).unwrap();
315+
index.add_path(&Path::new("IndexTypechange")).unwrap();
316+
317+
// And between index and worktree
318+
fs::write(path.join("ignored-random"), "ignored-random").unwrap();
319+
fs::write(path.join("BothModified"), "Modified in worktree").unwrap();
320+
fs::remove_file(path.join("WorktreeDeleted")).unwrap();
321+
fs::write(path.join("WorktreeModified"), "New content").unwrap();
322+
fs::write(path.join("WorktreeNew"), "WorktreeNew").unwrap();
323+
fs::remove_file(path.join("WorktreeTypechange")).unwrap();
324+
fs::rename(
325+
path.join("WorktreeRenamed"),
326+
path.join("WorktreeRenamed-new"),
327+
)
328+
.unwrap();
329+
330+
create_symlink(&path.join("Unchanged"), &path.join("WorktreeTypechange"));
331+
332+
let mut opts = StatusOptions::new();
333+
opts.renames_head_to_index(true);
334+
opts.renames_index_to_workdir(true);
335+
opts.include_untracked(true);
336+
opts.include_unmodified(true);
337+
opts.include_ignored(true);
338+
let status = repo.statuses(Some(&mut opts)).unwrap();
339+
340+
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
341+
struct SimpleEntry {
342+
path: String,
343+
status: String,
344+
}
345+
let mut entries: Vec<SimpleEntry> = vec![];
346+
for entry in status.iter() {
347+
entries.push({
348+
SimpleEntry {
349+
path: entry.path().unwrap().to_string(),
350+
status: format!("{:?}", entry.status()),
351+
}
352+
});
353+
}
354+
entries.sort();
355+
macro_rules! expected {
356+
($($path:literal -> $status:expr,)+) => {
357+
vec![
358+
$(SimpleEntry { path: $path.to_string(), status: concat!("Status(", stringify!($status), ")").to_string() }),+
359+
]
360+
}
361+
}
362+
// Does not cover WT_UNREADABLE which is rare, or CONFLICTED which is for
363+
// in-progress conflicts
364+
// Doesn't show all combinations of index and worktree changes, just a few
365+
assert_eq!(
366+
expected!(
367+
// Status::CURRENT
368+
".gitignore" -> 0x0,
369+
"BothModified" -> INDEX_MODIFIED | WT_MODIFIED,
370+
"IndexDeleted" -> INDEX_DELETED,
371+
"IndexModified" -> INDEX_MODIFIED,
372+
"IndexNew" -> INDEX_NEW,
373+
"IndexRenamed" -> INDEX_RENAMED,
374+
"IndexTypechange" -> INDEX_TYPECHANGE,
375+
// Status::CURRENT
376+
"Unchanged" -> 0x0,
377+
"WorktreeDeleted" -> WT_DELETED,
378+
"WorktreeModified" -> WT_MODIFIED,
379+
"WorktreeNew" -> WT_NEW,
380+
"WorktreeRenamed" -> WT_RENAMED,
381+
"WorktreeTypechange" -> WT_TYPECHANGE,
382+
"ignored-random" -> IGNORED,
383+
),
384+
entries
385+
);
386+
}
387+
388+
#[test]
389+
fn repo_status_clean() {
390+
let td = TempDir::new().unwrap();
391+
let path = td.path();
392+
393+
let mut opts = RepositoryInitOptions::new();
394+
opts.initial_head("main");
395+
let repo = Repository::init_opts(path, &opts).unwrap();
396+
397+
let mut config = repo.config().unwrap();
398+
config.set_str("user.name", "name").unwrap();
399+
config.set_str("user.email", "email").unwrap();
400+
401+
// Create some files
402+
fs::write(path.join("MyFile"), "content").unwrap();
403+
404+
let mut index = repo.index().unwrap();
405+
index.add_path(&Path::new("MyFile")).unwrap();
406+
407+
let id = index.write_tree().unwrap();
408+
409+
let tree = repo.find_tree(id).unwrap();
410+
let sig = repo.signature().unwrap();
411+
repo.commit(Some("HEAD"), &sig, &sig, "Initial files", &tree, &[])
412+
.unwrap();
413+
414+
// A repo status is "clean" if
415+
// - there are no untracked files
416+
// - there are no modified files, in either the index or worktree
417+
let mut opts = StatusOptions::new();
418+
opts.include_untracked(true);
419+
420+
// Repo is clean:
421+
{
422+
let status = repo.statuses(Some(&mut opts)).unwrap();
423+
assert_eq!(0, status.len());
424+
}
425+
426+
fs::write(path.join("OtherFile"), "content").unwrap();
427+
428+
// Repo is dirty due to the untracked file
429+
{
430+
let status = repo.statuses(Some(&mut opts)).unwrap();
431+
assert_eq!(1, status.len());
432+
let entry = status.get(0).unwrap();
433+
assert_eq!(Some("OtherFile"), entry.path());
434+
assert_eq!(Status::WT_NEW, entry.status());
435+
}
436+
437+
// Add it to the index
438+
index.add_path(&Path::new("OtherFile")).unwrap();
439+
440+
// Still dirty
441+
{
442+
let status = repo.statuses(Some(&mut opts)).unwrap();
443+
assert_eq!(1, status.len());
444+
let entry = status.get(0).unwrap();
445+
assert_eq!(Some("OtherFile"), entry.path());
446+
assert_eq!(Status::INDEX_NEW, entry.status());
447+
}
448+
449+
// Remove the file,
450+
fs::remove_file(path.join("OtherFile")).unwrap();
451+
452+
// Still dirty because it is in the index
453+
{
454+
let status = repo.statuses(Some(&mut opts)).unwrap();
455+
assert_eq!(1, status.len());
456+
let entry = status.get(0).unwrap();
457+
assert_eq!(Some("OtherFile"), entry.path());
458+
assert_eq!(Status::INDEX_NEW | Status::WT_DELETED, entry.status());
459+
}
460+
461+
// After removing from the index, it should be clean again
462+
index.remove_path(&Path::new("OtherFile")).unwrap();
463+
464+
{
465+
let status = repo.statuses(Some(&mut opts)).unwrap();
466+
assert_eq!(0, status.len());
467+
}
468+
}

0 commit comments

Comments
 (0)