Skip to content

Conversation

@rmax
Copy link

@rmax rmax commented Dec 29, 2025

Co-authored-by: Copilot


Description

Adds --since=DURATION flag to filter files by recency. Files are shown if either their modified_time or created_time (whichever is more recent) falls within the specified duration. Files without timestamps are excluded.

Implementation:

  • Added humantime dependency for duration parsing
  • Added since_duration: Option<Duration> field to FileFilter
  • Implemented filtering in filter_child_files() and filter_argument_files()
  • Uses max(modified_time, created_time) for timestamp comparison - files qualify if EITHER timestamp is recent
  • Works across all view modes (grid, long, tree, one-line)
  • Compatible with existing filters (--ignore-glob, --only-dirs, etc.)
  • Code follows Rust idioms with proper documentation and clippy compliance

Filtering Logic:
The filter uses the maximum of created and modified timestamps, ensuring files appear if EITHER:

  • They were recently created (even if never modified)
  • They were recently modified (even if created long ago)

This provides intuitive behavior for users looking for recent activity.

Duration formats:

  • Short: 10s, 5m, 2h, 7d, 1w
  • Long: 5 seconds, 10 minutes, 2 hours

Example usage:

# Show files modified or created in last hour
eza --since 1h

# Combine with tree view
eza --tree --since 30m

# With other filters
eza -l --since 1d --only-files

Files modified:

  • Cargo.toml: Added humantime and test dependencies (tempfile, filetime)
  • src/options/flags.rs: Added SINCE flag
  • src/options/filter.rs: Duration parsing logic
  • src/fs/filter.rs: Timestamp filtering with helper function (using idiomatic let...else pattern and max() for timestamp comparison with comprehensive explanatory comments)
  • src/options/help.rs, man/eza.1.md: Documentation
  • CHANGELOG.md: Added feature entry
  • tests/since_filter.rs: 7 integration tests
How Has This Been Tested?
  • 7 new integration tests covering:
    • Recent file detection with max(created, modified) logic
    • Short time windows
    • Various view modes (grid, long, tree, one-line)
    • Edge cases and invalid duration handling
  • Tests account for filesystem limitations where created time cannot be set backwards on most systems
  • All 467 tests pass (455 library + 7 integration + 2 CLI + 3 doc)
  • Zero clippy warnings - all code follows Rust best practices
  • Manual verification across grid, long, tree, and one-line views
  • Tested with files having different timestamp configurations
  • Verified behavior with files where created time and modified time differ significantly
Original prompt

FEATURE: Implement --since as a functional filter (feature/since-filter)

Motivation

Users often need to quickly identify files that have been recently modified or created, especially in large directories or when using recursive views. While sorting by time is available, it still requires the user to scan through all files. A functional --since filter allows users to restrict the output to only those files that fall within a specific time window, improving productivity and clarity.

This is particularly useful for agentic workflows and automated monitoring where visualizing the creation of files as they appear is useful. For example, running watch eza --tree --since 1m provides a live, focused view of project activity without the noise of older files.

Use Cases

  • Development Monitoring: Keep a terminal open with watch eza --since 30s to see which files are being touched by a build process or a test suite in real-time.
  • Log Analysis: Quickly list only the logs generated in the last hour: eza --since 1h /var/log.
  • Cleanup Tasks: Identify and verify recently created temporary files or artifacts before moving them.
  • Agentic Feedback: Provide AI agents with a concise list of files they have just created or modified to verify their own actions.

Goal

  • Add a functional --since=<DURATION> filter that only displays files that were created or modified within the given duration (relative durations interpreted as now - duration).

Scope & Constraints

  • The flag is called --since=<DURATION> and uses humantime for parsing (supporting e.g. 5s, 30m, 2h, 1d, 1w, and ISO durations like PT5S).
  • Implementation should respect existing dereferencing behavior for symbolic links and work with existing filtering rules (ignore patterns, only-dirs/only-files, etc.).
  • Keep changes minimal and well-tested. Prefer safe defaults and non-destructive edits.

Branching & Workflow

  1. Create new branch from main: feature/since-filter.
  2. Work locally and run full test-suite frequently.
  3. Create small, focused commits and push them to a remote branch only when asked.

High-level Plan (phases)

Phase 1 — Parsing & Option Plumbing

  • Add or confirm the SINCE flag in src/options/flags.rs and include it in ALL_ARGS.
  • Centralize duration parsing: ensure Options::since_duration returns Optionstd::time::Duration and uses humantime::parse_duration (already present; verify).
  • Wire since_duration into a new field on FileFilter (e.g., pub since_duration: Option<std::time::Duration>).

Phase 2 — Filtering Logic

  • Implement timestamp filtering in src/fs/filter.rs:
    • Modify filter_child_files and filter_argument_files to apply the since_duration predicate.
    • For each candidate File, compute a recency timestamp as the maximum of the available timestamps (modified_time and created_time). If neither timestamp is available, treat the file conservatively as not recent (i.e., exclude it when --since is used).
    • Compare: file passes if recency >= (now - since_duration).
    • Use the existing File API (File::modified_time(), File::created_time()) so symlink dereferencing behaviour is respected.
  • Keep ignore patterns and other filters in place; the --since filter is an additional filter that reduces the file set.

Phase 3 — Output Clean-up

  • Remove the marker-adding logic from src/output/file_name.rs (delete code that appended " *") and remove since_duration field from output Options since it is no longer needed in output formatting.

Phase 4 — Tests & Validation

  • Unit tests:

    • Parser tests for accepting valid/invalid durations (existing test in src/options/file_name.rs should be extended if needed).
    • Add unit tests around the filter logic that mock File time values (if possible) and verify the expected behavior of the predicate.
  • Integration tests:

    • Create tests/since_filter.rs which uses tempfile + filetime to create test files with deterministic timestamps (old vs. recent) and runs the binary with --since asserting only the recent files are present.
    • Ensure tests cover various views: lines, grid, long (details), and tree. Make assertions that the old file is not present in output.
    • Use sufficiently conservative durations in tests to avoid flaky failures (e.g., use --since 1d for a file with timestamp 30 minutes ago).
  • Cross-platform:

    • created_time may not be available on all platforms; tests should primarily rely on modified_time and fall back to created_time when present.

Phase 5 — Docs & Man Page

  • Update src/options/help.rs to describe that --since filters files by recency (examples included).
  • Add an example and description in man/eza.1.md showing eza --since 24h and eza --since 30m --tree.
  • Add a line to CHANGELOG.md or CHANGELOG describing the feature change.

Phase 6 — Acceptance & Release

  • Acceptance criteria:
    • cargo test passes (unit & integratio...

@diffray-bot
Copy link

Changes Summary

This PR implements a new --since=DURATION flag that filters files by recency, displaying only those with modification or creation times within a specified duration. The feature uses the humantime crate for duration parsing and applies filtering consistently across all view modes (grid, long, tree, one-line).

Type: feature

Components Affected: CLI Options Parser, File Filtering System, Help Documentation, Test Suite

Files Changed
File Summary Change Impact
/tmp/workspace/Cargo.toml Added humantime dependency (v2.1) for duration parsing, and dev-dependencies tempfile and filetime for integration tests ✏️ 🟡
/tmp/workspace/src/options/flags.rs Added SINCE flag definition with required value, and included it in ALL_ARGS array ✏️ 🟢
/tmp/workspace/src/options/filter.rs Added deduce_since_duration() method that parses humantime duration strings into std::time::Duration using humantime::parse_duration() ✏️ 🟡
/tmp/workspace/src/fs/filter.rs Added since_duration: Option<Duration> field to FileFilter struct; implemented is_recent_file() helper to check if file is recent; integrated since filtering in filter_child_files() and filter_argument_files(); uses max(modified_time, created_time) for timestamp comparison ✏️ 🔴
/tmp/workspace/src/options/help.rs Added help text documentation for --since flag with example duration formats (10m, 1h, 2d, 1w) ✏️ 🟢
/tmp/workspace/man/eza.1.md Added manual page documentation for --since flag (3 lines added) ✏️ 🟢
/tmp/workspace/tests/since_filter.rs New integration test file with 7 comprehensive tests covering: recent file detection, short time windows, multiple view modes (grid, long, tree, one-line), and invalid duration handling using tempfile and filetime crates 🟡
/tmp/workspace/CHANGELOG.md Added feature entry for --since flag in Unreleased section ✏️ 🟢
/tmp/workspace/Cargo.lock Updated lock file to reflect new dependencies (humantime, tempfile, filetime) ✏️ 🟢
Architecture Impact
  • New Patterns: Additional filter predicate in FileFilter pipeline, Option type wrapping for optional feature parameters
  • Dependencies: added: humantime 2.1 (runtime dependency), added: tempfile 3.13 (dev-dependency), added: filetime 0.2 (dev-dependency)
  • Coupling: The --since filter is integrated into the existing file filtering chain (ignore patterns, only-dirs, only-files, etc.), maintaining orthogonal composition. No tight coupling introduced.

Risk Areas: Timestamp comparison logic using max(modified_time, created_time) - cross-platform edge case with files that have no timestamps or only partial timestamp support, SystemTime::checked_sub() subtraction could fail on overflow; current code returns true (includes file) which is safe but conservative, chrono::NaiveDateTime conversion to SystemTime uses UNIX_EPOCH arithmetic; potential precision loss depending on platform's time representation, Integration tests depend on filesystem's ability to modify file times via filetime crate; behavior may vary across platforms (noted in test comments), Performance impact of checking every file's timestamps (two File API calls per file) during filtering

Suggestions
  • Consider adding a unit test that explicitly tests the timestamp conversion logic (naive_datetime_to_systemtime) with edge cases like epoch time, far future dates, and invalid timestamps
  • The conservative behavior (include file if cutoff calculation overflows) is safe but worth documenting in code comments for future maintainers
  • Integration tests explicitly note platform limitations with created time - consider adding a skip condition or more detailed output for platforms where this varies
  • Verify performance impact on large directory listings by benchmarking the double timestamp lookup in is_recent_file()
  • Consider whether the max(modified, created) semantics should be configurable or if there's a use case for separate --since-modified and --since-created flags

Full review in progress... | Powered by diffray

Comment on lines +184 to 191
/// Convert a `NaiveDateTime` to `SystemTime` for comparison
fn naive_datetime_to_systemtime(dt: Option<chrono::NaiveDateTime>) -> Option<std::time::SystemTime> {
dt.and_then(|dt| {
dt.and_utc().timestamp().try_into().ok().and_then(|secs: u64| {
std::time::SystemTime::UNIX_EPOCH.checked_add(std::time::Duration::from_secs(secs))
})
})
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Private helper lacks edge case documentation
Agent: rust

Category: docs

Description:
The naive_datetime_to_systemtime() helper uses try_into() from i64 to u64 without documenting that negative timestamps (pre-1970) will return None. The is_recent_file() method has partial documentation but doesn't fully explain this edge case behavior.

Suggestion:
Add a brief doc comment to naive_datetime_to_systemtime() explaining that negative Unix timestamps are treated as unavailable (returns None).

Why this matters: Explicit contracts prevent undefined behavior and misuse.

Confidence: 65%
Rule: rs_document_safety_panics_and_errors
Review ID: 0b482805-d6db-4856-9263-a5b31a9b9b5a
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +60 to +78
fn test_since_filter_with_short_window() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");

create_file_with_mtime(&temp_dir, "test.txt", 60);

std::thread::sleep(Duration::from_millis(150));

let eza = get_eza_binary();

let output = Command::new(&eza)
.arg("--since")
.arg("50ms")
.arg(temp_dir.path())
.output()
.expect("Failed to execute eza");

let stdout = String::from_utf8_lossy(&output.stdout);
assert!(!stdout.contains("test.txt"), "Should not show with very short window after delay");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Test name 'test_since_filter_with_short_window' is ambiguous
Agent: testing

Category: quality

Description:
Test name doesn't clarify the expected outcome (file exclusion). The test creates a 60-second-old file and uses a 50ms window, expecting the file to NOT be shown.

Suggestion:
Rename to 'test_since_filter_excludes_files_older_than_window' to clarify the boundary and expected behavior.

Why this matters: Descriptive test names serve as documentation and help debug failures.

Confidence: 65%
Rule: test_generic_test_names_rust
Review ID: 0b482805-d6db-4856-9263-a5b31a9b9b5a
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Comment on lines +129 to +143
fn test_since_filter_invalid_duration() {
let temp_dir = TempDir::new().expect("Failed to create temp dir");
create_file_with_mtime(&temp_dir, "test.txt", 60);

let eza = get_eza_binary();

let output = Command::new(&eza)
.arg("--since")
.arg("invalid")
.arg(temp_dir.path())
.output()
.expect("Failed to execute eza");

assert!(!output.status.success(), "Should fail with invalid duration");
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 MEDIUM - Test name doesn't describe expected behavior (failure)
Agent: testing

Category: quality

Description:
Test name 'test_since_filter_invalid_duration' describes the input but doesn't indicate the expected outcome (command should fail).

Suggestion:
Rename to 'test_since_filter_rejects_invalid_duration' to clearly indicate the expected failure behavior.

Why this matters: Descriptive test names serve as documentation and help debug failures.

Confidence: 65%
Rule: test_generic_test_names_rust
Review ID: 0b482805-d6db-4856-9263-a5b31a9b9b5a
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

@diffray-bot
Copy link

Review Summary

Free public review - Want AI code reviews on your PRs? Check out diffray.ai

Validated 8 issues: 4 kept (documentation and test naming concerns), 4 filtered (minor style preferences and rule misapplication)

Issues Found: 4

💬 See 3 individual line comment(s) for details.

📊 2 unique issue type(s) across 4 location(s)

📋 Full issue list (click to expand)

🟡 MEDIUM - Private helper lacks edge case documentation (2 occurrences)

Agent: rust

Category: docs

Why this matters: Explicit contracts prevent undefined behavior and misuse.

📍 View all locations
File Description Suggestion Confidence
src/fs/filter.rs:184-191 The naive_datetime_to_systemtime() helper uses try_into() from i64 to u64 without documenting that n... Add a brief doc comment to naive_datetime_to_systemtime() explaining that negative Unix timestamps a... 65%
src/options/filter.rs:19-48 The public deduce() method can return OptionsError::BadArgument from deduce_since_duration() but thi... Add '# Errors' section documenting that OptionsError::BadArgument can be returned if the --since dur... 65%

Rule: rs_document_safety_panics_and_errors


🟡 MEDIUM - Test name 'test_since_filter_with_short_window' is ambiguous (2 occurrences)

Agent: testing

Category: quality

Why this matters: Descriptive test names serve as documentation and help debug failures.

📍 View all locations
File Description Suggestion Confidence
tests/since_filter.rs:60-78 Test name doesn't clarify the expected outcome (file exclusion). The test creates a 60-second-old fi... Rename to 'test_since_filter_excludes_files_older_than_window' to clarify the boundary and expected ... 65%
tests/since_filter.rs:129-143 Test name 'test_since_filter_invalid_duration' describes the input but doesn't indicate the expected... Rename to 'test_since_filter_rejects_invalid_duration' to clearly indicate the expected failure beha... 65%

Rule: test_generic_test_names_rust


ℹ️ 1 issue(s) outside PR diff (click to expand)

These issues were found in lines not modified in this PR.

🟡 MEDIUM - Public FileFilter::deduce() lacks error documentation

Agent: rust

Category: docs

Why this matters: Explicit contracts prevent undefined behavior and misuse.

File: src/options/filter.rs:19-48

Description: The public deduce() method can return OptionsError::BadArgument from deduce_since_duration() but this error condition is not documented.

Suggestion: Add '# Errors' section documenting that OptionsError::BadArgument can be returned if the --since duration string cannot be parsed.

Confidence: 65%

Rule: rs_document_safety_panics_and_errors



Review ID: 0b482805-d6db-4856-9263-a5b31a9b9b5a
Rate it 👍 or 👎 to improve future reviews | Powered by diffray

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants