mirror of
https://github.com/martinvonz/jj.git
synced 2025-01-19 19:08:08 +00:00
templater: add tracking methods to remote RefName
More tests will be added later as "branch list" templates. In "log" template, we might want to see the number of "local" commits ahead of any tracked remotes. It can be implemented later in a similar way (or as a nested remote_refs list.)
This commit is contained in:
parent
ccabb11890
commit
8e4f75552d
3 changed files with 138 additions and 2 deletions
|
@ -37,8 +37,8 @@ use crate::template_builder::{
|
|||
};
|
||||
use crate::template_parser::{self, FunctionCallNode, TemplateParseError, TemplateParseResult};
|
||||
use crate::templater::{
|
||||
self, PlainTextFormattedProperty, Template, TemplateFormatter, TemplateProperty,
|
||||
TemplatePropertyExt as _,
|
||||
self, PlainTextFormattedProperty, SizeHint, Template, TemplateFormatter, TemplateProperty,
|
||||
TemplatePropertyError, TemplatePropertyExt as _,
|
||||
};
|
||||
use crate::{revset_util, text_util};
|
||||
|
||||
|
@ -729,11 +729,23 @@ pub struct RefName {
|
|||
remote: Option<String>,
|
||||
/// Target commit ids.
|
||||
target: RefTarget,
|
||||
/// Local ref metadata which tracks this remote ref.
|
||||
tracking_ref: Option<TrackingRef>,
|
||||
/// Local ref is synchronized with all tracking remotes, or tracking remote
|
||||
/// ref is synchronized with the local.
|
||||
synced: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TrackingRef {
|
||||
/// Local ref target which tracks the other remote ref.
|
||||
target: RefTarget,
|
||||
/// Number of commits ahead of the tracking `target`.
|
||||
ahead_count: OnceCell<SizeHint>,
|
||||
/// Number of commits behind of the tracking `target`.
|
||||
behind_count: OnceCell<SizeHint>,
|
||||
}
|
||||
|
||||
impl RefName {
|
||||
// RefName is wrapped by Rc<T> to make it cheaply cloned and share
|
||||
// lazy-evaluation results across clones.
|
||||
|
@ -752,6 +764,7 @@ impl RefName {
|
|||
name: name.into(),
|
||||
remote: None,
|
||||
target,
|
||||
tracking_ref: None,
|
||||
synced,
|
||||
})
|
||||
}
|
||||
|
@ -770,10 +783,23 @@ impl RefName {
|
|||
local_target: &RefTarget,
|
||||
) -> Rc<Self> {
|
||||
let synced = remote_ref.is_tracking() && remote_ref.target == *local_target;
|
||||
let tracking_ref = remote_ref.is_tracking().then(|| {
|
||||
let count = if synced {
|
||||
OnceCell::from((0, Some(0))) // fast path for synced remotes
|
||||
} else {
|
||||
OnceCell::new()
|
||||
};
|
||||
TrackingRef {
|
||||
target: local_target.clone(),
|
||||
ahead_count: count.clone(),
|
||||
behind_count: count,
|
||||
}
|
||||
});
|
||||
Rc::new(RefName {
|
||||
name: name.into(),
|
||||
remote: Some(remote_name.into()),
|
||||
target: remote_ref.target,
|
||||
tracking_ref,
|
||||
synced,
|
||||
})
|
||||
}
|
||||
|
@ -788,6 +814,7 @@ impl RefName {
|
|||
name: name.into(),
|
||||
remote: Some(remote_name.into()),
|
||||
target,
|
||||
tracking_ref: None,
|
||||
synced: false, // has no local counterpart
|
||||
})
|
||||
}
|
||||
|
@ -808,6 +835,50 @@ impl RefName {
|
|||
fn has_conflict(&self) -> bool {
|
||||
self.target.has_conflict()
|
||||
}
|
||||
|
||||
/// Returns true if this ref is tracked by a local ref. The local ref might
|
||||
/// have been deleted (but not pushed yet.)
|
||||
fn is_tracked(&self) -> bool {
|
||||
self.tracking_ref.is_some()
|
||||
}
|
||||
|
||||
/// Returns true if this ref is tracked by a local ref, and if the local ref
|
||||
/// is present.
|
||||
fn is_tracking_present(&self) -> bool {
|
||||
self.tracking_ref
|
||||
.as_ref()
|
||||
.map_or(false, |tracking| tracking.target.is_present())
|
||||
}
|
||||
|
||||
/// Number of commits ahead of the tracking local ref.
|
||||
fn tracking_ahead_count(&self, repo: &dyn Repo) -> Result<SizeHint, TemplatePropertyError> {
|
||||
let Some(tracking) = &self.tracking_ref else {
|
||||
return Err(TemplatePropertyError("Not a tracked remote ref".into()));
|
||||
};
|
||||
tracking
|
||||
.ahead_count
|
||||
.get_or_try_init(|| {
|
||||
let self_ids = self.target.added_ids().cloned().collect_vec();
|
||||
let other_ids = tracking.target.added_ids().cloned().collect_vec();
|
||||
Ok(revset::walk_revs(repo, &self_ids, &other_ids)?.count_estimate())
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
|
||||
/// Number of commits behind of the tracking local ref.
|
||||
fn tracking_behind_count(&self, repo: &dyn Repo) -> Result<SizeHint, TemplatePropertyError> {
|
||||
let Some(tracking) = &self.tracking_ref else {
|
||||
return Err(TemplatePropertyError("Not a tracked remote ref".into()));
|
||||
};
|
||||
tracking
|
||||
.behind_count
|
||||
.get_or_try_init(|| {
|
||||
let self_ids = self.target.added_ids().cloned().collect_vec();
|
||||
let other_ids = tracking.target.added_ids().cloned().collect_vec();
|
||||
Ok(revset::walk_revs(repo, &other_ids, &self_ids)?.count_estimate())
|
||||
})
|
||||
.copied()
|
||||
}
|
||||
}
|
||||
|
||||
// If wrapping with Rc<T> becomes common, add generic impl for Rc<T>.
|
||||
|
@ -906,6 +977,42 @@ fn builtin_ref_name_methods<'repo>() -> CommitTemplateBuildMethodFnMap<'repo, Rc
|
|||
Ok(L::wrap_commit_list(out_property))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"tracked",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.is_tracked());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"tracking_present",
|
||||
|_language, _build_ctx, self_property, function| {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
let out_property = self_property.map(|ref_name| ref_name.is_tracking_present());
|
||||
Ok(L::wrap_boolean(out_property))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"tracking_ahead_count",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
let repo = language.repo;
|
||||
let out_property =
|
||||
self_property.and_then(|ref_name| ref_name.tracking_ahead_count(repo));
|
||||
Ok(L::wrap_size_hint(out_property))
|
||||
},
|
||||
);
|
||||
map.insert(
|
||||
"tracking_behind_count",
|
||||
|language, _build_ctx, self_property, function| {
|
||||
template_parser::expect_no_arguments(function)?;
|
||||
let repo = language.repo;
|
||||
let out_property =
|
||||
self_property.and_then(|ref_name| ref_name.tracking_behind_count(repo));
|
||||
Ok(L::wrap_size_hint(out_property))
|
||||
},
|
||||
);
|
||||
map
|
||||
}
|
||||
|
||||
|
|
|
@ -514,6 +514,27 @@ fn test_log_branches() {
|
|||
├─╯
|
||||
◉ L: R:
|
||||
"###);
|
||||
|
||||
let template = r#"
|
||||
remote_branches.map(|ref| concat(
|
||||
ref,
|
||||
if(ref.tracked(),
|
||||
"(+" ++ ref.tracking_ahead_count().lower()
|
||||
++ "/-" ++ ref.tracking_behind_count().lower() ++ ")"),
|
||||
))
|
||||
"#;
|
||||
let output = test_env.jj_cmd_success(
|
||||
&workspace_root,
|
||||
&["log", "-r::remote_branches()", "-T", template],
|
||||
);
|
||||
insta::assert_snapshot!(output, @r###"
|
||||
◉ branch3@origin(+0/-1)
|
||||
│ ◉ branch2@origin(+0/-1) unchanged@origin(+0/-0)
|
||||
├─╯
|
||||
│ ◉ branch1@origin(+1/-1)
|
||||
├─╯
|
||||
◉
|
||||
"###);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
|
@ -163,6 +163,14 @@ The following methods are defined.
|
|||
* `.removed_targets() -> List<Commit>`: Old target commits if conflicted.
|
||||
* `.added_targets() -> List<Commit>`: New target commits. The list usually
|
||||
contains one "normal" target.
|
||||
* `.tracked() -> Boolean`: True if the ref is tracked by a local ref. The local
|
||||
ref might have been deleted (but not pushed yet.)
|
||||
* `.tracking_present() -> Boolean`: True if the ref is tracked by a local ref,
|
||||
and if the local ref points to any commit.
|
||||
* `.tracking_ahead_count() -> SizeHint`: Number of commits ahead of the tracking
|
||||
local ref.
|
||||
* `.tracking_behind_count() -> SizeHint`: Number of commits behind of the
|
||||
tracking local ref.
|
||||
|
||||
### ShortestIdPrefix type
|
||||
|
||||
|
|
Loading…
Reference in a new issue