Skip to content

performance(but): avoid rebuilding entire IdMap to get change ID#14847

Merged
slarse merged 1 commit into
masterfrom
reduce-performance-loss-on-printing-commit
Jul 20, 2026
Merged

performance(but): avoid rebuilding entire IdMap to get change ID#14847
slarse merged 1 commit into
masterfrom
reduce-performance-loss-on-printing-commit

Conversation

@slarse

@slarse slarse commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Changes to theme.rs introduced a relatively significant performance hit to many commands when formatting output for humans. They'd recompute the entire IdMap from context, which entails rebuilding the workspace projection. I measured a 5-8% performance hit to but commit from this computation, which is too much for just output formatting.

This commit somewhat reverts that by reusing pre-mutation change IDs when available, otherwise trying to fetch from an existing IdMap, or if that is not available fetching the commit from the repo.

For the most part, we're trusting that change IDs stay the same. This is overall not well structured and we should take more care with the but2 commands, which I've left unimplemented and deferred for GB-1771.

@github-actions github-actions Bot added rust Pull requests that update Rust code CLI The command-line program `but` labels Jul 17, 2026
.success()
.stdout_eq(str![[r#"
Moved 2 commits → after 1#2
Moved 2 commits → after 1

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Some of these outputs turned a bit weird as they contain collisions, and we're not resolving from the IdMap here. It's mostly fine because collisions aren't that likely, but it's a topic for improvement.

@slarse
slarse marked this pull request as ready for review July 17, 2026 21:01
Copilot AI review requested due to automatic review settings July 17, 2026 21:01

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses a performance regression in the but CLI’s human-formatted output by avoiding expensive IdMap rebuilds when rendering commit identifiers. It introduces a cheaper change-id retrieval path and updates multiple commands (and their snapshots) to use the new commit rendering approach.

Changes:

  • Introduces utils::get_change_id_for_commit() to read a commit’s change-id from headers (or synthesize a deterministic fallback from the commit OID).
  • Reworks theme::Commit to optionally render using a provided ChangeId, removing the previous CommitRef/new_commit_ref* flow that could trigger costly IdMap construction.
  • Updates legacy and newer command outputs and corresponding snapshot tests to match the new commit-id rendering behavior.

Reviewed changes

Copilot reviewed 22 out of 22 changed files in this pull request and generated 9 comments.

Show a summary per file
File Description
crates/but/src/theme.rs Changes commit display to accept an optional change-id and renders a short prefix when present.
crates/but/src/utils/object_id.rs Adds get_change_id_for_commit() helper for retrieving/synthesizing change-ids.
crates/but/src/utils/mod.rs Re-exports the new change-id helper; introduces an (currently empty) change_id module.
crates/but/src/utils/change_id.rs New (empty) module placeholder.
crates/but/src/id/mod.rs Uses the new helper for mapping commit→change-id and moves MIN_DISPLAYED_CHANGE_ID_CHARS usage to theme.
crates/but/src/utils/rejection.rs Updates commit rendering call site to the new theme::Commit(…, Option<ChangeId>) signature.
crates/but/src/command/commit/move.rs Avoids IdMap rebuild by pre-fetching change-ids and using theme::Commit in human output.
crates/but/src/command/legacy/absorb.rs Removes repo-shortening dependency in plan rendering and updates commit display calls.
crates/but/src/command/legacy/branch/show.rs Computes change-ids per commit and uses theme::Commit for human output.
crates/but/src/command/legacy/commit.rs Uses get_change_id_for_commit() to display new commit refs without rebuilding IdMap.
crates/but/src/command/legacy/commit2.rs Updates output to use the new theme::Commit signature (currently without change-ids).
crates/but/src/command/legacy/move2.rs Updates output to use the new theme::Commit signature (currently without change-ids).
crates/but/src/command/legacy/reword.rs Uses a precomputed change-id for display after rewriting a commit.
crates/but/src/command/legacy/resolve.rs Updates commit ref formatting to use theme::Commit directly.
crates/but/src/command/legacy/rub/amend.rs Uses a precomputed change-id for display after rewriting a commit.
crates/but/src/command/legacy/rub/mod.rs Updates multiple operations to use theme::Commit and (sometimes) get_change_id_for_commit().
crates/but/src/command/legacy/rub/squash.rs Reuses pre-mutation change-ids for displaying the final rewritten commit.
crates/but/src/command/legacy/show.rs Stores object/change ids for display and uses theme::Commit rendering.
crates/but/src/command/legacy/squash2.rs Updates output to use the new theme::Commit signature (currently without change-ids).
crates/but/tests/but/command/move.rs Updates snapshots for move output under the new commit display formatting.
crates/but/tests/but/command/absorb.rs Updates snapshots for absorb output under the new commit display formatting.
crates/but/tests/but/command/branch/show.rs Updates snapshots for branch show output under the new commit display formatting.

Comment thread crates/but/src/utils/mod.rs Outdated
Comment thread crates/but/src/utils/change_id.rs Outdated
Comment thread crates/but/src/id/mod.rs
Comment thread crates/but/src/utils/object_id.rs Outdated
Comment thread crates/but/src/theme.rs
Comment thread crates/but/src/command/commit/move.rs
Comment thread crates/but/src/command/legacy/rub/amend.rs
Comment thread crates/but/src/command/legacy/reword.rs
Comment thread crates/but/src/command/legacy/rub/squash.rs
@slarse
slarse marked this pull request as draft July 17, 2026 21:27
@slarse
slarse force-pushed the reduce-performance-loss-on-printing-commit branch from 4740df5 to 53995e6 Compare July 20, 2026 06:06
@slarse
slarse marked this pull request as ready for review July 20, 2026 06:06
Copilot AI review requested due to automatic review settings July 20, 2026 06:06
@slarse
slarse enabled auto-merge July 20, 2026 06:06
@slarse
slarse disabled auto-merge July 20, 2026 06:06
@slarse slarse closed this Jul 20, 2026
@slarse slarse reopened this Jul 20, 2026
@slarse
slarse enabled auto-merge July 20, 2026 06:09

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 3 comments.

Comments suppressed due to low confidence (3)

crates/but/src/command/commit/move.rs:212

  • Change IDs are computed from the pre-move OIDs, but the output uses post_move_id(...) which may differ when the move rewrites commits. If a commit had no stored change-id header, get_change_id_for_commit synthesizes from the commit OID, so reusing the pre-move value can print a change-id that doesn’t correspond to the post-move commit and can’t be used to refer to it.
    let t = theme::get();
    if let Some(out) = out.for_human() {
        let action = if after { "after" } else { "before" };
        if sources.len() == 1 {
            let source = theme::Commit(

crates/but/src/command/legacy/rub/amend.rs:37

  • change_id is computed from the pre-amend commit OID and then reused for the newly created commit. When the original commit lacks a stored change-id header, get_change_id_for_commit() synthesizes from the commit OID, so the amended commit will have a different synthetic change-id and the output will be incorrect.
    let change_id = {
        let repo = ctx.repo.get()?;
        crate::utils::get_change_id_for_commit(&repo, oid)?
    };
    let (new_commit, rejected) =

crates/but/src/command/legacy/reword.rs:216

  • The displayed change-id is computed from the pre-reword commit OID and reused for the rewritten commit. If the original commit had no stored change-id header (so the value is synthesized from the OID), the new commit will have a different synthetic change-id and the output will be wrong.
        let change_id = {
            let repo = ctx.repo.get()?;
            get_change_id_for_commit(&repo, commit_oid)?
        };
        let new_commit_oid = but_api::commit::reword::commit_reword_with_perm(

Comment thread crates/but/src/command/legacy/show.rs Outdated
Comment thread crates/but/src/command/legacy/branch/show.rs Outdated
Comment thread crates/but/src/command/legacy/branch/show.rs Outdated
@slarse
slarse disabled auto-merge July 20, 2026 06:16
Changes to `theme.rs` introduced a relatively significant performance hit to
many commands when formatting output for humans. They'd recompute the entire
`IdMap` from context, which entails rebuilding the workspace projection.

This commit reverts that by reusing pre-mutation change IDs when available,
otherwise trying to fetch from an existing `IdMap`, or if that is not available
fetching the commit from the repo.

This is overall not well structured and we should take more care with the
`but2` commands, which I've left unimplemented and deferred for GB-1771.
Copilot AI review requested due to automatic review settings July 20, 2026 06:19
@slarse
slarse force-pushed the reduce-performance-loss-on-printing-commit branch from 53995e6 to aae2c6d Compare July 20, 2026 06:19
@slarse
slarse enabled auto-merge July 20, 2026 06:19

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 22 out of 22 changed files in this pull request and generated 4 comments.

Comments suppressed due to low confidence (3)

crates/but/src/theme.rs:650

  • theme::Commit truncates the full ChangeId to a fixed 3-byte prefix. This ignores IdMap's disambiguated short_id (which can be longer than 3) and drops any #N collision suffix, so the rendered commit ref can become ambiguous and diverge from what but status/CLI parsing expects.
pub struct Commit(pub gix::ObjectId, pub Option<ChangeId>);

impl Display for Commit {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        let t = get();

        if let Some(change_id) = &self.1 {
            let s = change_id
                .get(..MIN_DISPLAYED_CHANGE_ID_CHARS.min(change_id.len()))
                .unwrap_or_default();
            write!(f, "{}", t.change_id.paint(s.to_str_lossy()))
        } else {
            write!(
                f,
                "{}",
                t.commit_id.paint(self.0.to_hex_with_len(7).to_string())
            )
        }

crates/but/src/command/legacy/reword.rs:226

  • change_id is captured from the pre-reword commit and reused for rendering the new commit ID. If the original commit has no explicit Change-Id header, get_change_id_for_commit() synthesizes an ID from the old commit OID, which will differ after rewriting—so the printed ID can be wrong for the new commit. Compute the change-id from new_commit_oid.new_commit (or only reuse the explicit header value).
        let change_id = {
            let repo = ctx.repo.get()?;
            get_change_id_for_commit(&repo, commit_oid)?
        };
        let new_commit_oid = but_api::commit::reword::commit_reword_with_perm(
            ctx,
            commit_oid,
            BString::from(new_message),
            DryRun::No,
            perm,
        )?;

        if let Some(out) = out.for_human() {
            let new_commit = crate::theme::Commit(new_commit_oid.new_commit, Some(change_id));
            writeln!(out, "Updated commit message for {new_commit}")?;

crates/but/src/command/legacy/rub/amend.rs:43

  • change_id is captured from the pre-amend commit OID (oid) and reused to render the rewritten commit ID. If the target commit has no explicit Change-Id header, the synthesized ID is derived from the old commit OID and will not match the new commit after rewriting. Compute the change-id from the resulting new_commit OID (or only reuse the explicit header value).
    let change_id = {
        let repo = ctx.repo.get()?;
        crate::utils::get_change_id_for_commit(&repo, oid)?
    };
    let (new_commit, rejected) =
        amend_diff_specs(ctx, diff_specs, oid, target_branch.as_deref(), perm)?;
    update_workspace_commit(ctx, false)?;
    if let Some(out) = out.for_human() {
        let new_commit = new_commit
            .map(|id| theme::Commit(id, Some(change_id)).to_string())
            .unwrap_or_default();

Comment thread crates/but/src/command/legacy/branch/show.rs
Comment thread crates/but/src/command/legacy/branch/show.rs
Comment thread crates/but/src/command/legacy/branch/show.rs
Comment thread crates/but/src/command/legacy/show.rs
@slarse
slarse merged commit 9f96a23 into master Jul 20, 2026
47 checks passed
@slarse
slarse deleted the reduce-performance-loss-on-printing-commit branch July 20, 2026 06:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CLI The command-line program `but` rust Pull requests that update Rust code

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants