Skip to content

Commit 301b40f

Browse files
committed
add test for racy git detection
1 parent 55d8902 commit 301b40f

File tree

4 files changed

+97
-1
lines changed

4 files changed

+97
-1
lines changed

gix-worktree/src/index/status/worktree.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub enum Error {
2828
/// Options that control how the index status of a worktree is computed
2929
pub struct Options {
3030
/// capabilities of the file system
31-
pub fs: crate::fs::Capabilities,
31+
pub fs: fs::Capabilities,
3232
/// If set, don't use more than this amount of threads.
3333
/// Otherwise, usually use as many threads as there are logical cores.
3434
/// A value of 0 is interpreted as no-limit
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
version https://git-lfs.github.com/spec/v1
2+
oid sha256:35b728a127f3b6170bac44469ff36d5ad0be2a4247a8926f1aaffb97b5973efc
3+
size 1596
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/bin/bash
2+
set -eu -o pipefail
3+
4+
git init -q
5+
6+
echo -n "foo" > content
7+
8+
git add -A
9+
git commit -m "Commit"
10+
11+
# file size should not be changed by this
12+
echo -n "bar" > content

gix-worktree/tests/worktree/index/status.rs

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
11
use bstr::BStr;
2+
use filetime::{set_file_mtime, FileTime};
3+
use gix_index as index;
24
use gix_worktree::fs::Capabilities;
35
use gix_worktree::index::status::content::FastEq;
46
use gix_worktree::index::status::worktree::{self, Options};
57
use gix_worktree::index::status::{Change, Recorder};
68

79
use crate::fixture_path;
810

11+
// since tests are fixtures a bunch of stat information (like inode number)
12+
// changes when extracting the data so we need to disable all advanced stat
13+
// changes and only look at mtime seconds and file size to properly
14+
// test all code paths (and to trigger racy git)
15+
const TEST_OPTIONS: index::entry::stat::Options = index::entry::stat::Options {
16+
trust_ctime: false,
17+
check_stat: false,
18+
use_nsec: false,
19+
use_stdev: false,
20+
};
21+
922
fn fixture(name: &str, expected_status: &[(&BStr, Option<Change>, bool)]) {
1023
let worktree = fixture_path(name);
1124
let git_dir = worktree.join(".git");
@@ -18,6 +31,7 @@ fn fixture(name: &str, expected_status: &[(&BStr, Option<Change>, bool)]) {
1831
&FastEq,
1932
Options {
2033
fs: Capabilities::probe(git_dir),
34+
stat: TEST_OPTIONS,
2135
..Options::default()
2236
},
2337
)
@@ -43,6 +57,7 @@ fn removed() {
4357
fn unchanged() {
4458
fixture("status_unchanged", &[]);
4559
}
60+
4661
#[test]
4762
fn modified() {
4863
// run the same status check twice to ensure that racy detection
@@ -108,3 +123,69 @@ fn modified() {
108123
],
109124
);
110125
}
126+
127+
#[test]
128+
fn racy_git() {
129+
let timestamp = 940040400;
130+
// we need a writable fixture because we have to mess with mtimes manually,
131+
// because touch -d respects the locale so the test wouldn't work depending
132+
// on the timesystem you run your test in
133+
let dir = gix_testtools::scripted_fixture_writable("racy_git.sh").expect("script works");
134+
let worktree = dir.path();
135+
let git_dir = worktree.join(".git");
136+
let fs = Capabilities::probe(&git_dir);
137+
let mut index = gix_index::File::at(git_dir.join("index"), gix_hash::Kind::Sha1, Default::default()).unwrap();
138+
// we artificially mess with mtime so that it's before the timestamp
139+
// saved by git. This would usually mean an invalid fs/invalid index file
140+
// and as a result the racy git mitigation doesn't work and the worktree
141+
// shows up as unchanged even tough the file did change. This case
142+
// doesn't happen in the realworld (except for file corruption) but
143+
// makes sure we are actually hitting the right codepath
144+
index.entries[0].stat.mtime.secs = timestamp;
145+
set_file_mtime(worktree.join("content"), FileTime::from_unix_time(timestamp as i64, 0))
146+
.expect("changing filetime works");
147+
let mut recorder = Recorder::default();
148+
worktree::changes_to_obtain(
149+
&mut index,
150+
&worktree,
151+
&mut recorder,
152+
&FastEq,
153+
Options {
154+
fs,
155+
stat: TEST_OPTIONS,
156+
..Options::default()
157+
},
158+
)
159+
.unwrap();
160+
assert_eq!(recorder.records, &[]);
161+
162+
// now we also backdate the index timestamp to match the artificially created
163+
// mtime above this is now a realistic realworld racecondition which
164+
// should trigger racy git and cause proper output
165+
index.set_timestamp(FileTime::from_unix_time(timestamp as i64, 0));
166+
let mut recorder = Recorder::default();
167+
worktree::changes_to_obtain(
168+
&mut index,
169+
&worktree,
170+
&mut recorder,
171+
&FastEq,
172+
Options {
173+
fs,
174+
stat: TEST_OPTIONS,
175+
..Options::default()
176+
},
177+
)
178+
.unwrap();
179+
// be handled correctly by the racy git detection
180+
assert_eq!(
181+
recorder.records,
182+
&[(
183+
BStr::new(b"content"),
184+
Some(Change::Modification {
185+
executable_bit_changed: false,
186+
content_change: Some(()),
187+
}),
188+
false
189+
)]
190+
);
191+
}

0 commit comments

Comments
 (0)