Skip to content

Commit fdee9a2

Browse files
committed
Merge branch 'walk-with-commitgraph'
2 parents c8e8b9f + 20f73c8 commit fdee9a2

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

60 files changed

+1412
-732
lines changed

Cargo.lock

Lines changed: 17 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,6 +250,7 @@ members = [
250250
"gix-tui",
251251
"gix-tix",
252252
"gix-archive",
253+
"gix-revwalk",
253254

254255
"cargo-smart-release",
255256
"tests/tools",

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ is usable to some extent.
8080
* [gix-pathspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-pathspec)
8181
* [gix-index](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-index)
8282
* [gix-revision](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-revision)
83+
* [gix-revwalk](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-revwalk)
8384
* [gix-command](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-command)
8485
* [gix-prompt](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-prompt)
8586
* [gix-refspec](https://github.com/Byron/gitoxide/blob/main/crate-status.md#gix-refspec)

cargo-smart-release/src/git/history.rs

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -43,13 +43,14 @@ pub fn collect(repo: &gix::Repository) -> anyhow::Result<Option<commit::History>
4343
.id()
4444
.ancestors()
4545
.sorting(gix::traverse::commit::Sorting::ByCommitTimeNewestFirst)
46+
.use_commit_graph(false)
4647
.all()?
4748
{
48-
let commit_id = commit_id?;
49+
let commit = commit_id?;
4950
let (message, tree_id, parent_tree_id, commit_time) = {
5051
let (message, tree_id, commit_time, parent_commit_id) = {
51-
let object = commit_id.object()?;
52-
let commit = object.to_commit_ref();
52+
let object = commit.object()?;
53+
let commit = object.decode()?;
5354
let parent = commit.parents().next();
5455
(commit.message.to_vec(), commit.tree(), commit.committer.time, parent)
5556
};
@@ -65,7 +66,7 @@ pub fn collect(repo: &gix::Repository) -> anyhow::Result<Option<commit::History>
6566
Err(_) => {
6667
log::warn!(
6768
"Commit message of {} could not be decoded to UTF-8 - ignored",
68-
commit_id.as_ref()
69+
commit.id
6970
);
7071
continue;
7172
}
@@ -76,7 +77,7 @@ pub fn collect(repo: &gix::Repository) -> anyhow::Result<Option<commit::History>
7677
data_by_tree_id.insert(tree_id, handle.find_object(tree_id)?.data.to_owned());
7778
}
7879
items.push(commit::history::Item {
79-
id: commit_id.detach(),
80+
id: commit.id,
8081
commit_time,
8182
message: commit::Message::from(message),
8283
tree_id,

crate-status.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -477,12 +477,14 @@ Make it the best-performing implementation and the most convenient one.
477477

478478
### gix-revision
479479
* [x] `describe()` (similar to `git name-rev`)
480-
* [x] primitives to help with graph traversal, along with commit-graph acceleration.
481480
* parse specifications
482481
* [x] parsing and navigation
483482
* [x] revision ranges
484483
* [ ] full date parsing support (depends on `gix-date`)
485484

485+
### gix-revision
486+
* [x] primitives to help with graph traversal, along with commit-graph acceleration.
487+
486488
### gix-submodule
487489
* CRUD for submodules
488490
* try to handle with all the nifty interactions and be a little more comfortable than what git offers, lay a foundation for smarter git submodules.

gitoxide-core/Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ default = []
1818
## Discover all git repositories within a directory. Particularly useful with [skim](https://github.com/lotabout/skim).
1919
organize = ["dep:gix-url", "dep:jwalk"]
2020
## Derive the amount of time invested into a git repository akin to [git-hours](https://github.com/kimmobrunfeldt/git-hours).
21-
estimate-hours = ["dep:itertools", "dep:fs-err", "dep:crossbeam-channel", "dep:mime_guess"]
21+
estimate-hours = ["dep:itertools", "dep:fs-err", "dep:crossbeam-channel", "dep:smallvec"]
2222
## Gather information about repositories and store it in a database for easy querying.
2323
query = ["dep:rusqlite"]
2424

@@ -64,7 +64,7 @@ jwalk = { version = "0.8.0", optional = true }
6464
itertools = { version = "0.10.1", optional = true }
6565
fs-err = { version = "2.6.0", optional = true }
6666
crossbeam-channel = { version = "0.5.6", optional = true }
67-
mime_guess = { version = "2.0.4", optional = true }
67+
smallvec = { version = "1.10.0", optional = true }
6868

6969
# for 'query'
7070
rusqlite = { version = "0.29.0", optional = true, features = ["bundled"] }

gitoxide-core/src/hours/core.rs

Lines changed: 168 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
11
use std::collections::{hash_map::Entry, HashMap};
2+
use std::sync::atomic::{AtomicUsize, Ordering};
3+
use std::sync::Arc;
24

35
use gix::bstr::BStr;
6+
use gix::odb::FindExt;
47
use itertools::Itertools;
8+
use smallvec::SmallVec;
59

6-
use crate::hours::{FileStats, LineStats, WorkByEmail, WorkByPerson};
10+
use crate::hours::util::{add_lines, remove_lines};
11+
use crate::hours::{CommitIdx, FileStats, LineStats, WorkByEmail, WorkByPerson};
712

813
const MINUTES_PER_HOUR: f32 = 60.0;
914
pub const HOURS_PER_WORKDAY: f32 = 8.0;
@@ -61,6 +66,168 @@ pub fn estimate_hours(
6166
}
6267
}
6368

69+
type CommitChangeLineCounters = (
70+
Option<Arc<AtomicUsize>>,
71+
Option<Arc<AtomicUsize>>,
72+
Option<Arc<AtomicUsize>>,
73+
);
74+
75+
type SpawnResultWithReturnChannelAndWorkers<'scope> = (
76+
crossbeam_channel::Sender<Vec<(CommitIdx, Option<gix::hash::ObjectId>, gix::hash::ObjectId)>>,
77+
Vec<std::thread::ScopedJoinHandle<'scope, anyhow::Result<Vec<(CommitIdx, FileStats, LineStats)>>>>,
78+
);
79+
80+
pub fn spawn_tree_delta_threads<'scope>(
81+
scope: &'scope std::thread::Scope<'scope, '_>,
82+
threads: usize,
83+
line_stats: bool,
84+
repo: gix::Repository,
85+
stat_counters: CommitChangeLineCounters,
86+
) -> SpawnResultWithReturnChannelAndWorkers<'scope> {
87+
let (tx, rx) = crossbeam_channel::unbounded::<Vec<(CommitIdx, Option<gix::hash::ObjectId>, gix::hash::ObjectId)>>();
88+
let stat_workers = (0..threads)
89+
.map(|_| {
90+
scope.spawn({
91+
let stats_counters = stat_counters.clone();
92+
let mut repo = repo.clone();
93+
repo.object_cache_size_if_unset((850 * 1024 * 1024) / threads);
94+
let rx = rx.clone();
95+
move || -> Result<_, anyhow::Error> {
96+
let mut out = Vec::new();
97+
let (commit_counter, change_counter, lines_counter) = stats_counters;
98+
let mut attributes = line_stats
99+
.then(|| -> anyhow::Result<_> {
100+
repo.index_or_load_from_head().map_err(Into::into).and_then(|index| {
101+
repo.attributes(
102+
&index,
103+
gix::worktree::cache::state::attributes::Source::IdMapping,
104+
gix::worktree::cache::state::ignore::Source::IdMapping,
105+
None,
106+
)
107+
.map_err(Into::into)
108+
.map(|attrs| {
109+
let matches = attrs.selected_attribute_matches(["binary", "text"]);
110+
(attrs, matches)
111+
})
112+
})
113+
})
114+
.transpose()?;
115+
for chunk in rx {
116+
for (commit_idx, parent_commit, commit) in chunk {
117+
if let Some(c) = commit_counter.as_ref() {
118+
c.fetch_add(1, Ordering::SeqCst);
119+
}
120+
if gix::interrupt::is_triggered() {
121+
return Ok(out);
122+
}
123+
let mut files = FileStats::default();
124+
let mut lines = LineStats::default();
125+
let from = match parent_commit {
126+
Some(id) => match repo.find_object(id).ok().and_then(|c| c.peel_to_tree().ok()) {
127+
Some(tree) => tree,
128+
None => continue,
129+
},
130+
None => repo.empty_tree(),
131+
};
132+
let to = match repo.find_object(commit).ok().and_then(|c| c.peel_to_tree().ok()) {
133+
Some(c) => c,
134+
None => continue,
135+
};
136+
from.changes()?
137+
.track_filename()
138+
.track_rewrites(None)
139+
.for_each_to_obtain_tree(&to, |change| {
140+
use gix::object::tree::diff::change::Event::*;
141+
if let Some(c) = change_counter.as_ref() {
142+
c.fetch_add(1, Ordering::SeqCst);
143+
}
144+
match change.event {
145+
Rewrite { .. } => {
146+
unreachable!("we turned that off")
147+
}
148+
Addition { entry_mode, id } => {
149+
if entry_mode.is_no_tree() {
150+
files.added += 1;
151+
add_lines(line_stats, lines_counter.as_deref(), &mut lines, id);
152+
}
153+
}
154+
Deletion { entry_mode, id } => {
155+
if entry_mode.is_no_tree() {
156+
files.removed += 1;
157+
remove_lines(line_stats, lines_counter.as_deref(), &mut lines, id);
158+
}
159+
}
160+
Modification {
161+
entry_mode,
162+
previous_entry_mode,
163+
id,
164+
previous_id,
165+
} => {
166+
match (previous_entry_mode.is_blob(), entry_mode.is_blob()) {
167+
(false, false) => {}
168+
(false, true) => {
169+
files.added += 1;
170+
add_lines(line_stats, lines_counter.as_deref(), &mut lines, id);
171+
}
172+
(true, false) => {
173+
files.removed += 1;
174+
remove_lines(
175+
line_stats,
176+
lines_counter.as_deref(),
177+
&mut lines,
178+
previous_id,
179+
);
180+
}
181+
(true, true) => {
182+
files.modified += 1;
183+
if let Some((attrs, matches)) = attributes.as_mut() {
184+
let entry = attrs.at_entry(
185+
change.location,
186+
Some(false),
187+
|id, buf| repo.objects.find_blob(id, buf),
188+
)?;
189+
let is_text_file = if entry.matching_attributes(matches) {
190+
let attrs: SmallVec<[_; 2]> =
191+
matches.iter_selected().collect();
192+
let binary = &attrs[0];
193+
let text = &attrs[1];
194+
!binary.assignment.state.is_set()
195+
&& !text.assignment.state.is_unset()
196+
} else {
197+
// In the absence of binary or text markers, we assume it's text.
198+
true
199+
};
200+
201+
if let Some(Ok(diff)) =
202+
is_text_file.then(|| change.event.diff()).flatten()
203+
{
204+
let mut nl = 0;
205+
let counts = diff.line_counts();
206+
nl += counts.insertions as usize + counts.removals as usize;
207+
lines.added += counts.insertions as usize;
208+
lines.removed += counts.removals as usize;
209+
if let Some(c) = lines_counter.as_ref() {
210+
c.fetch_add(nl, Ordering::SeqCst);
211+
}
212+
}
213+
}
214+
}
215+
}
216+
}
217+
}
218+
Ok::<_, std::io::Error>(Default::default())
219+
})?;
220+
out.push((commit_idx, files, lines));
221+
}
222+
}
223+
Ok(out)
224+
}
225+
})
226+
})
227+
.collect::<Vec<_>>();
228+
(tx, stat_workers)
229+
}
230+
64231
pub fn deduplicate_identities(persons: &[WorkByEmail]) -> Vec<WorkByPerson> {
65232
let mut email_to_index = HashMap::<&'static BStr, usize>::with_capacity(persons.len());
66233
let mut name_to_index = HashMap::<&'static BStr, usize>::with_capacity(persons.len());

0 commit comments

Comments
 (0)