-
Notifications
You must be signed in to change notification settings - Fork 2.6k
Use gix
for cargo package
#15534
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Use gix
for cargo package
#15534
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for kicking of this!
Regardless the approach taken here. I wonder if it is possible to avoid checking repo state completely. In PathSource::list_files
we already walked through it. And maybe we can attach extra info in PathEntry
. Of course it'll slow down list_files
a bit, but with that we could perhaps resolve #14941?.
It is just a bit relevant to this PR though. We can still merge this when ready.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's about time I get started with this - this setup here is super-hacky, but it's designed to allow a very step-wise conversion, hopefully allowing to work on it 15 minutes at a time.
Regarding #14941, I hope with #14955 I have found a representative issue. Right now I can't tell how that should be solved, so I'd probably go for having a gitoxide
alternative for cargo package
here and tackle the related issues another time. Ideally, the new implementation will be a bit faster and I hope I can test that on the aws-sdk-rust
repository.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I couldn't resist to profile the aws-sdk-rust
publishing step in Cargo and here is a picture of the run so far.


I don't know if this will ever finish, and I don't know how they can even get this published, maybe it just takes a very long time.
The culprit, maybe among other things, may be repo.status_file()
calls for which there isn't an equivalent in gitoxide
anyway. Thus I think this will look very differently when this PR is done and maybe that's an avenue for aws-sdk-rust
to publish much faster.
I will definitely test that once it's working and share results :).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops I linked to the wrong issue. Was meant to say #14955
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just realized that dirty_files_outside_pkg_root
is probably the reason for the individual git_status_file
calls.
The gitoxide
implementation can certainly be backported to the git2
one if there is demand, so potential gains could be available sooner.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also I realize that even with these improvements, and without --no-vcs-info
or similar it will still do one git-status costing about 1s each for each of these 404 crates, wasting nearly 7 minutes. Nonetheless, it's probably acceptable compared to the over three ours that it took otherwise (which would be equivalent to ~10800 git status checks).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
And I think #15416 is also properly fixed now, which also means the output is different in 2 tests when using the gitoxide
implementation.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice! Really appreciate the effort here!
We are probably not going to backports, as the backporting process involves a bit more work, and usually reserved for some regression that has no way out.
Also, given the cargo team has agreed that the dirtiness check is best effort and informational only, I think once the PR is ready we can merge it without FCP.
Thanks again, really a great job!
7a409fd
to
f5d1c6b
Compare
This comment has been minimized.
This comment has been minimized.
f5d1c6b
to
39f3f02
Compare
39f3f02
to
071475b
Compare
7105c4f
to
0e60ce6
Compare
fde1e43
to
b79bd00
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is ready for a first review. Only gitoxide
needs a release which is planned for the 22nd, which is when this PR becomes formally mergeable as it can point to the new release instead of the Git repository.
Especially after RustConf where I was quite apologetic and feared gitoxide
could be removed again for not making progress, I thought maybe we should instead consider stabilizing the well-working parts earlier, such as in:
- nightly with opt-in
- stable with opt-out
- stable, with
git2
codepath removed.
cargo package
would be candidate for it as the new implementation is more correct and as fast as it can be.
What do you think?
use std::collections::HashSet; | ||
use std::path::{Path, PathBuf}; | ||
use tracing::debug; | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I kept the structure and comments as similar as I could, compared to git2
, so it's quite straightforward to compare.
// TODO: Either remove this whole block, or have a test. | ||
// It's hard to have no Cargo.toml here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's unclear to me how this could possibly happen - there is no test for it either. From looking at git blame
it wasn't obvious what motivated this check.
Probably nothing wrong with keeping it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe only some parts of the package is under git? Like src
is a git repo.
// TODO: Either remove this whole block, or have a test. | ||
// It's hard to have no Cargo.toml here? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe only some parts of the package is under git? Like src
is a git repo.
if relative_package_root.is_some_and(|pkg_root| !rel_path.starts_with(pkg_root)) { | ||
dirty_files_outside_of_package_root.push(path); | ||
continue; | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity. If we adopt the same optimization in git2 version, how the performance would look like?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Maybe only some parts of the package is under git? Like src is a git repo.
It seemed that what the code really was testing is if the file exists, and not if it was tracked by Git. Thus I made it an explicit 'file exists' check, which seemed strange as Cargo won't function without such a manifest. Removing the whole block doesn't fail CI either, so it's a bit unclear why it's there. There is still no tool I know that could do forensics and find everything that Git knows about it, that would certainly be interesting.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Out of curiosity. If we adopt the same optimization in git2 version, how the performance would look like?
The 'architecture' changes here could all be applied to the git2
implementation, with the same effects. Performance problems should be solved then.
Edit: If the team decides not to fast-track gix
here, then I think the AWS team could trivially contribute a port of this to solve their problems. Of course, I vote for gix
as I believe it's more correct when dealing with ignored files, which should solve a problem I am having when releasing gitoxide
😅 (it claims an ignored file is dirty, despite it being ignored).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks like we are mostly doing the same thing except using gitoxide. Do I understand correctly?
Apart from the dirty_files_outside_of_package_root
work, have you found any other stuff to optmize?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The driver of these changes is the lack of single-file APIs, and I noticed no other (previously unknown) optimisation opportunities.
tldr; I don't think there are simple optimisations left, except for maybe the N^2 portion, but even there it's hard to make it show up in a profiler.
But now that I am thinking about it, there might be one: I a workspace like aws-sdk-rust
I believe this code runs once for each crate, meaning there will be a full git status
each time. It would certainly require more complex changes to be able to cache these between the publish calls. Each one takes a little less than a second there, so it's quite some time in aggregate.
But before that, I'd probably optimize the fetch crates
stage to run only once, it takes most of the time.
While the PR is not ready to merge yet, I am kicking off the process to get a consensus on how we want to merge this. @rfcbot fcp merge Proposal: Switch to gix for Reasons:
|
Team member @weihanglo has proposed to merge this. The next step is review by the rest of the tagged team members: No concerns currently listed. Once a majority of reviewers approve (and at most 2 approvals are outstanding), this will enter its final comment period. If you spot a major issue that hasn't been raised at any point in this process, please speak up! See this document for info about what commands tagged team members can give me. |
This comment has been minimized.
This comment has been minimized.
🔔 This is now entering its final comment period, as per the review above. 🔔 |
b79bd00
to
383795e
Compare
The final comment period, with a disposition to merge, as per the review above, is now complete. As the automated representative of the governance process, I would like to thank the author for their work and everyone else who contributed. This will be merged soon. |
383795e
to
276aa24
Compare
This should also help fixing these spurious "cannot package because some excluded file is untracked" issues.
While at it, adjust two test expectations to deal with improved detail of status checks.
276aa24
to
80f48cc
Compare
80f48cc
to
46ad7af
Compare
It's too bad this had to wait for so long but I decided to finally get it off my queue, and replaced the Did I miss anything that was part of the review? Further, the Cargo test-suite discovered a bug that makes submodule listings fail. I will fix that in |
If it is an existing bug, we can wait and fix it later. Regardless, do whatever makes sense and is easy to follow on both review and implementation side. |
9529113
to
1667fb6
Compare
I realised that the Unfortunately, there still is a failure on Windows related to how For today I am out of time, but I'd hope to debug this more tomorrow. |
This will fix an issue with submodules that couldn't be queried in an unborn repository.
bae72aa
to
dcf1b40
Compare
r? @weihanglo rustbot has assigned @weihanglo. Use |
dcf1b40
to
d41d177
Compare
Without me doing anything, the checks have passed. My last commit ( Let's see how prone it now is to failure. The test-setup is simple as well and it is something that should be reproducible in I marked the PR as ready for review nonetheless, in case CI passes, and will be back to investigate this phantom otherwise. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice!! Just some nits and let me know what you think :)
}) | ||
.tree_index_track_renames(TrackRenames::Disabled) | ||
.index_worktree_submodules(None) | ||
.into_iter(None /* pathspec patterns */) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How is the recent benchmark? Just compared with 6833aa7 against aws-sdk-rust and this PR slows down the process. Haven't checked what caused that. I assume it might be we collecting status for the entire repo for each workspace member, instead of using pathspec?
cargo package --allow-dirty --workspace --no-verify --no-metadata --offline
I ran this btw.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't have time today as planned to look into this, but just had to leave a comment here nonetheless.
This is a very interesting find! On the latest main
of the aws-sdk-rust
repository, the status time for the whole repository is ~600ms for me (hot cache). It's strange that this takes so long in your measurements and also, that the git2
version that uses status_file
on each file is fast(er) all of the sudden. It's not at all what I measured in my run.
So I will definitely look into this.
Edit: I can definitely reproduce the slowdown, awesome catch!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I started with hyperfine
:
❯ hyperfine -w3 '/Users/byron/dev/github.com/rust-lang/cargo/target/release/cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline' 'cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline'
Benchmark 1: /Users/byron/dev/github.com/rust-lang/cargo/target/release/cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline
Time (mean ± σ): 967.5 ms ± 75.5 ms [User: 718.7 ms, System: 3491.2 ms]
Range (min … max): 923.2 ms … 1111.0 ms 10 runs
Warning: Statistical outliers were detected. Consider re-running this benchmark on a quiet system without any interferences from other programs. It might help to use the '--warmup' or '--prepare' options.
Benchmark 2: cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline
Time (mean ± σ): 593.1 ms ± 7.2 ms [User: 506.7 ms, System: 93.1 ms]
Range (min … max): 585.3 ms … 610.4 ms 10 runs
Summary
cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline ran
1.63 ± 0.13 times faster than /Users/byron/dev/github.com/rust-lang/cargo/target/release/cargo package --no-verify --no-metadata -p aws-smithy-runtime-api --offline
Even though the result is less pronounced, it's still 1.6x slower than it was before. The timings also fluctuate much more, probably a side-effect of spawning many threads for the full status check.
The profile run shows the same. Here we see how gitoxide
splits up the status run into parallel operations, with the dirwalk to find untracked files being the slowest at ~700ms wallclock, the index check is parallelized and finishes in ~300ms wallclock, and finally there is the tree-index diff with ~80ms.

Also, there is no other bottleneck when looking at the actual package
function.

This means in theory, if the root of the project would be provided here…
cargo/src/cargo/ops/cargo_package/vcs.rs
Line 292 in a67e9ec
.into_iter(None /* pathspec patterns */) |
…one should be able to save a lot of time.
Initially I thought pathspecs weren't supported, but realised that dirty_files_outside_package_root
is the reason it runs the status on the whole repository.
To me it feels inevitable to run the whole status, but the previous implementation certainly shows how it could be done. I also wonder where the performance problems are that I encountered.
My intuition here would be to eat the cost when publishing single repositories, but when multiple are done, it could do the full-repo status just once, while using a pathspec to do a per-project status instead.
I am looking at this loop:
cargo/src/cargo/ops/cargo_package/mod.rs
Lines 288 to 294 in 46ad7af
for (pkg, cli_features) in sorted_pkgs { | |
let opts = PackageOpts { | |
cli_features: cli_features.clone(), | |
to_package: ops::Packages::Default, | |
..opts.clone() | |
}; | |
let ar_files = prepare_archive(ws, &pkg, &opts)?; |
If that was done, I'd think that a package operation per project could be about 200ms, down from 960ms or 600ms respectively.
For today I am out of time, but if you think this is acceptable (i.e. possible regression for single-packages, but speedup for multiple packages), then I'd implement it as described here. The last 'record' to package all of aws-sdk-rust
is 12m (without --offline
), it could probably be 10m without that extra expense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Btw, just a walkby comment: it seemed to me like you were comparing yourself to distributed (not locally built) cargo? If that's the case, I just wanted to note that we build Cargo on CI with lto=thin
, which is otherwise not configured in the Cargo repo. The LTO config should extend also to the built C/C++ code, so to libgit.
This should also help fixing these spurious "cannot package because some excluded file is untracked" issues.
Tasks
vcs.rs
cleanupfinal check by myselfmove split & rename into its own commit. Probably squash all changes except for the gix upgrade.master
already.gix
-submodules()
call isn't bare-repo safe.gix status
seems to go through a symlink, arriving at the wrong conclusion, on Windows.aws-sdk-rust
.Notes for the Reviewer
cargo package
on1.81.0
#14955.Related to GitoxideLabs/gitoxide#106.