Skip to content

Commit a99bcca

Browse files
committed
perf(cli): Split off highly divergent branches
In #216, a user is backporting changes on Linux and hadn't marked protected branches which would serve as bases, causing git-stack to topologically sort commits that are very distant from each other, causing a simple showing of the commit graph to take 20-30s. This change infers that these branches are do not belong to their base and split them off. Now showing the commit graph takes about 6s as it tries each base.
1 parent 8227f34 commit a99bcca

File tree

4 files changed

+77
-5
lines changed

4 files changed

+77
-5
lines changed

docs/reference.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,7 @@ Configuration is read from the following (in precedence order):
129129
| stack.protected-branch | \- | multivar of globs | Branch names that match these globs (`.gitignore` syntax) are considered protected branches |
130130
| stack.protect-commit-count | \- | integer | Protect commits that are on a branch with `count`+ commits |
131131
| stack.protect-commit-age | \- | time delta (e.g. 10days) | Protect commits that older than the specified time |
132+
| stack.auto-base-commit-count | \- | integer | Split off branches that are more than `count` commits away from the implied base |
132133
| stack.stack | --stack | "current", "dependents", "descendants", "all" | Which development branch-stacks to operate on |
133134
| stack.push-remote | \- | string | Development remote for pushing local branches |
134135
| stack.pull-remote | \- | string | Upstream remote for pulling protected branches |

src/bin/git-stack/args.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,7 @@ impl Args {
8989
protected_branches: None,
9090
protect_commit_count: None,
9191
protect_commit_age: None,
92+
auto_base_commit_count: None,
9293
stack: self.stack,
9394
push_remote: None,
9495
pull_remote: None,

src/bin/git-stack/stack.rs

Lines changed: 47 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -135,8 +135,13 @@ impl State {
135135
(None, None, git_stack::config::Stack::All) => {
136136
let mut stack_branches = std::collections::BTreeMap::new();
137137
for (branch_id, branch) in branches.iter() {
138-
let base_branch =
139-
resolve_implicit_base(&repo, branch_id, &branches, &protected_branches);
138+
let base_branch = resolve_implicit_base(
139+
&repo,
140+
branch_id,
141+
&branches,
142+
&protected_branches,
143+
repo_config.auto_base_commit_count(),
144+
);
140145
stack_branches
141146
.entry(base_branch)
142147
.or_insert_with(git_stack::git::Branches::default)
@@ -163,6 +168,7 @@ impl State {
163168
head_commit.id,
164169
&branches,
165170
&protected_branches,
171+
repo_config.auto_base_commit_count(),
166172
);
167173
// HACK: Since `base` might have come back with a remote branch, treat it as an
168174
// "onto" to find the local version.
@@ -175,6 +181,7 @@ impl State {
175181
head_commit.id,
176182
&branches,
177183
&protected_branches,
184+
repo_config.auto_base_commit_count(),
178185
);
179186
let base = resolve_base_from_onto(&repo, &onto);
180187
(base, onto)
@@ -787,9 +794,45 @@ fn resolve_implicit_base(
787794
head_oid: git2::Oid,
788795
branches: &git_stack::git::Branches,
789796
protected_branches: &git_stack::git::Branches,
797+
auto_base_commit_count: Option<usize>,
790798
) -> AnnotatedOid {
791-
let branch = match git_stack::git::find_protected_base(repo, protected_branches, head_oid) {
799+
match git_stack::git::find_protected_base(repo, protected_branches, head_oid) {
792800
Some(branch) => {
801+
let merge_base_id = repo
802+
.merge_base(branch.id, head_oid)
803+
.expect("to be a base, there must be a merge base");
804+
if let Some(max_commit_count) = auto_base_commit_count {
805+
let ahead_count = repo
806+
.commit_count(merge_base_id, head_oid)
807+
.expect("merge_base should ensure a count exists ");
808+
let behind_count = repo
809+
.commit_count(merge_base_id, branch.id)
810+
.expect("merge_base should ensure a count exists ");
811+
if max_commit_count <= ahead_count + behind_count {
812+
let assumed_base_oid =
813+
git_stack::git::infer_base(repo, head_oid).unwrap_or(head_oid);
814+
log::warn!(
815+
"{} is {} ahead and {} behind {}, using {} as --base instead",
816+
branches
817+
.get(head_oid)
818+
.map(|b| b[0].to_string())
819+
.or_else(|| {
820+
repo.find_commit(head_oid)?
821+
.summary
822+
.to_str()
823+
.ok()
824+
.map(ToOwned::to_owned)
825+
})
826+
.unwrap_or_else(|| "target".to_owned()),
827+
ahead_count,
828+
behind_count,
829+
branch,
830+
assumed_base_oid
831+
);
832+
return AnnotatedOid::new(assumed_base_oid);
833+
}
834+
}
835+
793836
log::debug!(
794837
"Chose branch {} as the base for {}",
795838
branch,
@@ -816,8 +859,7 @@ fn resolve_implicit_base(
816859
);
817860
AnnotatedOid::new(assumed_base_oid)
818861
}
819-
};
820-
branch
862+
}
821863
}
822864

823865
fn resolve_base_from_onto(repo: &git_stack::git::GitRepo, onto: &AnnotatedOid) -> AnnotatedOid {

src/config.rs

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ pub struct RepoConfig {
55
pub protected_branches: Option<Vec<String>>,
66
pub protect_commit_count: Option<usize>,
77
pub protect_commit_age: Option<std::time::Duration>,
8+
pub auto_base_commit_count: Option<usize>,
89
pub stack: Option<Stack>,
910
pub push_remote: Option<String>,
1011
pub pull_remote: Option<String>,
@@ -20,6 +21,7 @@ pub struct RepoConfig {
2021
static PROTECTED_STACK_FIELD: &str = "stack.protected-branch";
2122
static PROTECT_COMMIT_COUNT: &str = "stack.protect-commit-count";
2223
static PROTECT_COMMIT_AGE: &str = "stack.protect-commit-age";
24+
static AUTO_BASE_COMMIT_COUNT: &str = "stack.auto-base-commit-count";
2325
static STACK_FIELD: &str = "stack.stack";
2426
static PUSH_REMOTE_FIELD: &str = "stack.push-remote";
2527
static PULL_REMOTE_FIELD: &str = "stack.pull-remote";
@@ -34,6 +36,7 @@ static DEFAULT_PROTECTED_BRANCHES: [&str; 4] = ["main", "master", "dev", "stable
3436
static DEFAULT_PROTECT_COMMIT_COUNT: usize = 50;
3537
static DEFAULT_PROTECT_COMMIT_AGE: std::time::Duration =
3638
std::time::Duration::from_secs(60 * 60 * 24 * 14);
39+
static DEFAULT_AUTO_BASE_COMMIT_COUNT: usize = 500;
3740
const DEFAULT_CAPACITY: usize = 30;
3841

3942
impl RepoConfig {
@@ -132,6 +135,10 @@ impl RepoConfig {
132135
{
133136
config.protect_commit_age = Some(value);
134137
}
138+
} else if key == AUTO_BASE_COMMIT_COUNT {
139+
if let Some(value) = value.as_ref().and_then(|v| FromStr::from_str(v).ok()) {
140+
config.auto_base_commit_count = Some(value);
141+
}
135142
} else if key == STACK_FIELD {
136143
if let Some(value) = value.as_ref().and_then(|v| FromStr::from_str(v).ok()) {
137144
config.stack = Some(value);
@@ -190,6 +197,7 @@ impl RepoConfig {
190197
let mut conf = Self::default();
191198
conf.protect_commit_count = Some(conf.protect_commit_count().unwrap_or(0));
192199
conf.protect_commit_age = Some(conf.protect_commit_age());
200+
conf.auto_base_commit_count = Some(conf.auto_base_commit_count().unwrap_or(0));
193201
conf.stack = Some(conf.stack());
194202
conf.push_remote = Some(conf.push_remote().to_owned());
195203
conf.pull_remote = Some(conf.pull_remote().to_owned());
@@ -240,6 +248,11 @@ impl RepoConfig {
240248
.ok()
241249
.and_then(|s| humantime::parse_duration(&s).ok());
242250

251+
let auto_base_commit_count = config
252+
.get_i64(AUTO_BASE_COMMIT_COUNT)
253+
.ok()
254+
.map(|i| i.max(0) as usize);
255+
243256
let push_remote = config
244257
.get_string(PUSH_REMOTE_FIELD)
245258
.ok()
@@ -279,6 +292,7 @@ impl RepoConfig {
279292
protected_branches,
280293
protect_commit_count,
281294
protect_commit_age,
295+
auto_base_commit_count,
282296
push_remote,
283297
pull_remote,
284298
stack,
@@ -320,6 +334,7 @@ impl RepoConfig {
320334
}
321335
self.protect_commit_count = other.protect_commit_count.or(self.protect_commit_count);
322336
self.protect_commit_age = other.protect_commit_age.or(self.protect_commit_age);
337+
self.auto_base_commit_count = other.auto_base_commit_count.or(self.auto_base_commit_count);
323338
self.push_remote = other.push_remote.or(self.push_remote);
324339
self.pull_remote = other.pull_remote.or(self.pull_remote);
325340
self.stack = other.stack.or(self.stack);
@@ -349,6 +364,13 @@ impl RepoConfig {
349364
.unwrap_or(DEFAULT_PROTECT_COMMIT_AGE)
350365
}
351366

367+
pub fn auto_base_commit_count(&self) -> Option<usize> {
368+
let auto_base_commit_count = self
369+
.auto_base_commit_count
370+
.unwrap_or(DEFAULT_AUTO_BASE_COMMIT_COUNT);
371+
(auto_base_commit_count != 0).then(|| auto_base_commit_count)
372+
}
373+
352374
pub fn push_remote(&self) -> &str {
353375
self.push_remote.as_deref().unwrap_or("origin")
354376
}
@@ -412,6 +434,12 @@ impl std::fmt::Display for RepoConfig {
412434
PROTECT_COMMIT_AGE.split_once('.').unwrap().1,
413435
humantime::format_duration(self.protect_commit_age())
414436
)?;
437+
writeln!(
438+
f,
439+
"\t{}={}",
440+
AUTO_BASE_COMMIT_COUNT.split_once('.').unwrap().1,
441+
self.auto_base_commit_count().unwrap_or(0)
442+
)?;
415443
writeln!(
416444
f,
417445
"\t{}={}",

0 commit comments

Comments
 (0)