Skip to content

Conversation

@faukah
Copy link

@faukah faukah commented Oct 6, 2025

Previously, nh os info always printed all columns, which is frankly unnecessary if you're missing
things such as specializations.

  • Remove empty columns from the print output.
  • Furthermore, fix formatting for systems with more than 1000 genererations.

Previous:
image

New:
image

Sanity Checking

  • I have updated the changelog as per my changes
  • I have tested, and self-reviewed my code
  • Style and consistency
    • I ran nix fmt to format my Nix code
    • I ran cargo fmt to format my Rust code
    • I have added appropriate documentation to new code
    • My changes are consistent with the rest of the codebase
  • Correctness
    • I ran cargo clippy and fixed any new linter warnings.
  • If new changes are particularly complex:
    • My code includes comments in particularly complex areas to explain the
      logic
    • I have documented the motive for those changes in the PR body or commit
      description.
  • Tested on platform(s)
    • x86_64-linux
    • aarch64-linux
    • x86_64-darwin
    • aarch64-darwin

Add a 👍 reaction to pull requests you find important.

Summary by CodeRabbit

  • Improvements

    • nh os info now hides empty columns (e.g., Config Revision, Specialisations) when no data is available.
    • Displays “Unknown” for missing values (e.g., closure size) to improve clarity.
    • Table headers and column widths automatically adjust to visible fields for cleaner output.
  • Documentation

    • Updated changelog under Unreleased/Changed to note that nh os info hides empty columns.
    • Retains existing note that nh os info supports --fields selection with an additional per-generation “Closure Size” column.

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

Note

Reviews paused

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Walkthrough

Implements optional fields in generation metadata and adjusts output rendering to hide empty columns. Changes include making configuration_revision and specialisations optional, altering get_closure_size to return a String with “Unknown” fallback, and updating print_info to compute visible fields based on available data. Changelog updated accordingly.

Changes

Cohort / File(s) Summary of changes
Documentation
CHANGELOG.md
Added Unreleased/Changed entry noting that “nh os info” hides empty columns; retained entry about --fields support and per-generation “Closure Size”.
Generations listing and display logic
src/generations.rs
GenerationInfo fields made optional: configuration_revision: Option, specialisations: Option<Vec>. get_closure_size now returns String with “Unknown” fallback. describe updated for optional fields. Kernel module version retrieval simplified to return “Unknown” on failure. Specialisations computed as Option. print_info now conditionally hides Confrev/Spec columns when empty across all generations; header/widths and row rendering updated to use visible_fields and Option handling. Minor refactors (map_or_else, Self usage).

Sequence Diagram(s)

sequenceDiagram
    participant U as User
    participant CLI as nh os info
    participant G as generations::describe/print_info
    participant FS as Filesystem

    U->>CLI: Run "nh os info"
    CLI->>G: describe()
    G->>FS: Read generation dirs, metadata, kernel modules
    FS-->>G: Data or errors
    G->>G: Build GenerationInfo (Option fields)
    G->>G: get_closure_size() -> String ("Unknown" fallback)
    CLI->>G: print_info(--fields?)
    G->>G: Compute visible_fields (hide empty columns)
    G-->>U: Render table with dynamic columns
    note over G: Confrev/Spec omitted if empty across all gens
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Suggested reviewers

  • NotAShelf

Pre-merge checks and finishing touches

❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Title Check ⚠️ Warning The provided title "Faukah/generations" does not describe the core functionality changes, which involve omitting empty table columns and correcting formatting of large generation counts in the nh os info output, and instead appears as a file path or component name unrelated to the actual objectives of the pull request. Rename the pull request title to clearly reflect the main change, for example: "Omit empty columns and fix large generation count formatting in nh os info", so reviewers can quickly understand the purpose of the changeset.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@faukah faukah changed the title Faukah/generations generations: cleanup; hide empty columns Oct 6, 2025
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 0

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
src/generations.rs (1)

98-166: Closure size detection can miss due to path mismatch; add robust fallback

Matching JSON entries by path can fail depending on whether nix returns the symlink path or its target. When this happens, size becomes “Unknown” despite being present.

  • Match either store_path or the input path.
  • Fallback to the first array entry’s closureSize if no match.
  • Optional: label “GiB” since the divisor is 2^30.
 pub fn get_closure_size(generation_dir: &Path) -> std::string::String {
   let store_path = generation_dir
     .read_link()
     .unwrap_or_else(|_| generation_dir.to_path_buf());
+  let input_path_str = generation_dir.to_string_lossy().to_string();
   match process::Command::new("nix")
     .arg("path-info")
-    .arg(generation_dir)
+    .arg(generation_dir)
     .arg("-Sh")
     .arg("--json")
     .output()
   {
     Ok(output) => {
       let output_str = String::from_utf8_lossy(&output.stdout);
       match serde_json::from_str::<serde_json::Value>(&output_str) {
         #[allow(clippy::cast_precision_loss)]
         Ok(json) => {
           // `nix path-info --json` returns an array, we need to handle it
           let store_path_str = store_path.to_string_lossy();
-          let closure_size = json.as_array().and_then(|arr| {
+          let closure_size = json.as_array().and_then(|arr| {
             arr.iter().find_map(|entry| {
               let path = entry.get("path").and_then(|v| v.as_str());
               let size =
                 entry.get("closureSize").and_then(serde_json::Value::as_u64);
               if let (Some(path), Some(size)) = (path, size) {
-                if path == store_path_str {
+                if path == store_path_str || path == input_path_str {
                   return Some(size);
                 }
               }
               None
             })
           });
+          // Fallback to the first entry if specific path wasn't found
+          let closure_size = closure_size.or_else(|| {
+            json.as_array().and_then(|arr| {
+              arr.get(0)
+                .and_then(|e| e.get("closureSize").and_then(serde_json::Value::as_u64))
+            })
+          });
           if closure_size.is_none() {
             let paths: Vec<String> = json
               .as_array()
               .map(|arr| {
                 arr
                   .iter()
                   .filter_map(|entry| {
                     entry.get("path").and_then(|v| {
                       v.as_str().map(std::string::ToString::to_string)
                     })
                   })
                   .collect()
               })
               .unwrap_or_default();
             debug!(
               "get_closure_size: store_path not found or closureSize missing. \
                store_path: {store_path_str}, json paths: {:?}, output: {}",
               paths, output_str
             );
           }
           closure_size.map_or_else(
-            || "Unknown".to_string(),
-            |bytes| format!("{:.1} GB", bytes as f64 / 1_073_741_824.0),
+            || "Unknown".to_string(),
+            |bytes| format!("{:.1} GiB", bytes as f64 / 1_073_741_824.0),
           )
         },
         Err(e) => {
           debug!(
             "get_closure_size: failed to parse JSON: {e} output: {output_str}"
           );
           "Unknown".to_string()
         },
       }
     },
     Err(e) => {
       debug!("get_closure_size: failed to run nix path-info: {e:?}");
       "Unknown".to_string"
     },
   }
 }
🧹 Nitpick comments (2)
CHANGELOG.md (1)

21-22: Mention the formatting fix for large generation counts too

Changelog only notes hidden empty columns. Include the fix for formatting when generation numbers exceed 999 to reflect the PR’s scope.

-- `nh os info` now hides empty columns.
+- `nh os info` now hides empty columns and fixes formatting for generation numbers ≥ 1000.
src/generations.rs (1)

381-386: Ensure ID column width ≥ header to keep header/rows aligned

When generation counts are small, id width can be less than "Generation No" length, causing header to push subsequent columns. Make width the max of header name and content width.

-  let widths = ColumnWidths {
-    id:      max_generation_no + 10, // "Generation No"
+  let id_header_len = "Generation No".len();
+  let widths = ColumnWidths {
+    id:      (max_generation_no + 10).max(id_header_len),
     date:    20,                     // "Build Date"
     nver:    max_nixos_version_len,
     kernel:  max_kernel_len,
     confrev: 22, // "Configuration Revision"
     spec:    15, // "Specialisations"
     size:    12, // "Closure Size"
   };

Also applies to: 388-395

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between c215925 and 174e877.

📒 Files selected for processing (2)
  • CHANGELOG.md (1 hunks)
  • src/generations.rs (13 hunks)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: treewide-checks
  • GitHub Check: Test NH on Linux
  • GitHub Check: Test NH on Darwin
  • GitHub Check: Build NH on Linux
  • GitHub Check: Build NH on Darwin
🔇 Additional comments (3)
src/generations.rs (3)

24-28: Optional fields + conditional columns: LGTM

  • Using Option for configuration_revision and specialisations, plus filtering visible_fields, cleanly hides empty columns.
  • Row rendering with unwrap_or_default yields blanks where appropriate.

Looks correct and matches the PR goal.

If a user explicitly specifies --fields including Confrev/Spec, is hiding still desired when all values are empty? Confirm product intent.

Also applies to: 351-367, 422-445


206-218: Kernel module version detection fallback: LGTM

map_or_else with “Unknown” on read_dir failures improves resilience. No issues.

Also applies to: 219-231


243-247: Small correctness/style tweaks: LGTM

  • Trim/filter on configuration_revision avoids blank noise.
  • Specialisations as Option with empty-to-None normalization is good.
  • clippy::too_many_lines expectation is fine here.

Also applies to: 253-264, 319-319

@faukah
Copy link
Author

faukah commented Oct 6, 2025

@coderabbitai shut up

@coderabbitai
Copy link

coderabbitai bot commented Oct 6, 2025

@faukah, I understand. I can pause automatic reviews for this PR if you'd like. Just let me know if you need anything in the future.

✅ Actions performed

Reviews paused.

@faukah faukah force-pushed the faukah/generations branch from 174e877 to 42cbade Compare October 23, 2025 10:47
faukah added 2 commits October 23, 2025 14:42
Previously, `nh os info` always printed all columns,
which is frankly unnecessary if you're missing
things such as specializations.

- Remove empty columns from the print output.
- Furthermore, fix formatting for systems with more
than 1000 genererations.
@faukah faukah force-pushed the faukah/generations branch from 42cbade to 7a9055e Compare October 23, 2025 12:42
@NotAShelf NotAShelf merged commit 149022d into nix-community:master Oct 29, 2025
8 checks passed
@faukah faukah deleted the faukah/generations branch October 29, 2025 09:02
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