ok/jj
1
0
Fork 0
forked from mirrors/jj

templater: make branches, tags, git_refs, and git_head return list type

I'm not going to change the default output, but this allows us to highlight
or dim only the @remote component.
This commit is contained in:
Yuya Nishihara 2023-10-25 16:31:18 +09:00
parent 2b33a97c35
commit 01d474563e
4 changed files with 63 additions and 48 deletions

View file

@ -63,6 +63,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
pattern syntax](docs/revsets.md#string-patterns). The `--glob` option is
deprecated in favor of `glob:` pattern.
* The `branches`/`tags`/`git_refs`/`git_head` template keywords now return a
list of `RefName`s. They were previously pre-formatted strings.
### Fixed bugs
* Updating the working copy to a commit where a file that's currently ignored

View file

@ -125,7 +125,6 @@ impl<'repo> CommitTemplateLanguage<'repo, '_> {
CommitTemplatePropertyKind::RefName(Box::new(property))
}
#[allow(unused)] // TODO
fn wrap_ref_name_list(
&self,
property: impl TemplateProperty<Commit, Output = Vec<RefName>> + 'repo,
@ -312,23 +311,23 @@ fn build_commit_keyword_opt<'repo>(
}
"branches" => {
let index = cache.branches_index(repo).clone();
language.wrap_string(wrap_fn(property, move |commit| {
index.get(commit.id()).join(" ") // TODO: return Vec<Branch>?
language.wrap_ref_name_list(wrap_fn(property, move |commit| {
index.get(commit.id()).to_vec()
}))
}
"tags" => {
let index = cache.tags_index(repo).clone();
language.wrap_string(wrap_fn(property, move |commit| {
index.get(commit.id()).join(" ") // TODO: return Vec<NameRef>?
language.wrap_ref_name_list(wrap_fn(property, move |commit| {
index.get(commit.id()).to_vec()
}))
}
"git_refs" => {
let index = cache.git_refs_index(repo).clone();
language.wrap_string(wrap_fn(property, move |commit| {
index.get(commit.id()).join(" ") // TODO: return Vec<NameRef>?
language.wrap_ref_name_list(wrap_fn(property, move |commit| {
index.get(commit.id()).to_vec()
}))
}
"git_head" => language.wrap_string(wrap_repo_fn(repo, property, extract_git_head)),
"git_head" => language.wrap_ref_name_list(wrap_repo_fn(repo, property, extract_git_head)),
"divergent" => language.wrap_boolean(wrap_fn(property, |commit| {
// The given commit could be hidden in e.g. obslog.
let maybe_entries = repo.resolve_change_id(commit.change_id());
@ -442,19 +441,18 @@ fn build_ref_name_method<'repo>(
/// Cache for reverse lookup refs.
#[derive(Clone, Debug, Default)]
struct RefNamesIndex {
// TODO: store Branch/NameRef type in place of String?
index: HashMap<CommitId, Vec<String>>,
index: HashMap<CommitId, Vec<RefName>>,
}
impl RefNamesIndex {
fn insert<'a>(&mut self, ids: impl IntoIterator<Item = &'a CommitId>, name: String) {
fn insert<'a>(&mut self, ids: impl IntoIterator<Item = &'a CommitId>, name: RefName) {
for id in ids {
let ref_names = self.index.entry(id.clone()).or_default();
ref_names.push(name.clone());
}
}
fn get(&self, id: &CommitId) -> &[String] {
fn get(&self, id: &CommitId) -> &[RefName] {
if let Some(names) = self.index.get(id) {
names
} else {
@ -471,28 +469,25 @@ fn build_branches_index(repo: &dyn Repo) -> RefNamesIndex {
let unsynced_remote_refs = remote_refs.iter().copied().filter(|&(_, remote_ref)| {
!remote_ref.is_tracking() || remote_ref.target != *local_target
});
let has_unsynced_tracking_refs = || {
remote_refs.iter().any(|&(_, remote_ref)| {
remote_ref.is_tracking() && remote_ref.target != *local_target
})
};
if local_target.is_present() {
let decorated_name = if local_target.has_conflict() {
format!("{branch_name}??")
} else if has_unsynced_tracking_refs() {
format!("{branch_name}*")
} else {
branch_name.to_owned()
let ref_name = RefName {
name: branch_name.to_owned(),
remote: None,
conflict: local_target.has_conflict(),
synced: remote_refs.iter().all(|&(_, remote_ref)| {
!remote_ref.is_tracking() || remote_ref.target == *local_target
}),
};
index.insert(local_target.added_ids(), decorated_name);
index.insert(local_target.added_ids(), ref_name);
}
for (remote_name, remote_ref) in unsynced_remote_refs {
let decorated_name = if remote_ref.target.has_conflict() {
format!("{branch_name}@{remote_name}??")
} else {
format!("{branch_name}@{remote_name}")
let ref_name = RefName {
name: branch_name.to_owned(),
remote: Some(remote_name.to_owned()),
conflict: remote_ref.target.has_conflict(),
synced: false,
};
index.insert(remote_ref.target.added_ids(), decorated_name);
index.insert(remote_ref.target.added_ids(), ref_name);
}
}
index
@ -503,27 +498,30 @@ fn build_ref_names_index<'a>(
) -> RefNamesIndex {
let mut index = RefNamesIndex::default();
for (name, target) in ref_pairs {
let decorated_name = if target.has_conflict() {
format!("{name}??")
} else {
name.clone()
let ref_name = RefName {
name: name.to_owned(),
remote: None,
conflict: target.has_conflict(),
synced: true, // has no tracking remotes
};
index.insert(target.added_ids(), decorated_name);
index.insert(target.added_ids(), ref_name);
}
index
}
// TODO: return NameRef?
fn extract_git_head(repo: &dyn Repo, commit: &Commit) -> String {
// TODO: maybe add option or nullable type?
fn extract_git_head(repo: &dyn Repo, commit: &Commit) -> Vec<RefName> {
let target = repo.view().git_head();
if target.added_ids().contains(commit.id()) {
if target.has_conflict() {
"HEAD@git??".to_string()
} else {
"HEAD@git".to_string()
}
let ref_name = RefName {
name: "HEAD".to_owned(),
remote: Some("git".to_owned()),
conflict: target.has_conflict(),
synced: false, // has no local counterpart
};
vec![ref_name]
} else {
"".to_string()
vec![]
}
}

View file

@ -408,7 +408,7 @@ fn test_log_branches() {
test_env.jj_cmd_ok(&origin_path, &["git", "export"]);
test_env.jj_cmd_ok(&workspace_root, &["git", "fetch"]);
let template = r#"commit_id.short() ++ " " ++ branches"#;
let template = r#"commit_id.short() ++ " " ++ if(branches, branches, "(no branches)")"#;
let output = test_env.jj_cmd_success(&workspace_root, &["log", "-T", template]);
insta::assert_snapshot!(output, @r###"
fed794e2ba44 branch3?? branch3@origin
@ -419,7 +419,21 @@ fn test_log_branches() {
@ a5b4d15489cc branch2* new-branch
8476341eb395 branch2@origin
000000000000
000000000000 (no branches)
"###);
let template = r#"branches.map(|b| separate("/", b.remote(), b.name())).join(", ")"#;
let output = test_env.jj_cmd_success(&workspace_root, &["log", "-T", template]);
insta::assert_snapshot!(output, @r###"
branch3, origin/branch3
branch3
branch1
@ branch2, new-branch
origin/branch2
"###);
}

View file

@ -25,10 +25,10 @@ The following keywords can be used in `jj log`/`jj obslog` templates.
working-copy commit as `<workspace name>@`.
* `current_working_copy: Boolean`: True for the working-copy commit of the
current workspace.
* `branches: String`
* `tags: String`
* `git_refs: String`
* `git_head: String`
* `branches: List<RefName>`
* `tags: List<RefName>`
* `git_refs: List<RefName>`
* `git_head: List<RefName>`
* `divergent: Boolean`: True if the commit's change id corresponds to multiple
visible commits.
* `hidden: Boolean`: True if the commit is not visible (a.k.a. abandoned).