simple_op_store: serialize RefTarget in new format (breaks downgrades)

This is breaking change. Old jj binary will panic if it sees a view saved by
new jj. Alternatively, we can store both new and legacy data for backward
compatibility.
This commit is contained in:
Yuya Nishihara 2023-07-16 00:54:50 +09:00
parent 8351a743f6
commit 4834d12c37
10 changed files with 152 additions and 199 deletions

View File

@ -11,6 +11,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
* The minimum supported Rust version (MSRV) is now 1.71.0.
* The storage format of branches, tags, and git refs has changed. Newly-stored
repository data will no longer be loadable by older binaries.
### New features
* `jj log` output is now topologically grouped.

View File

@ -14,17 +14,14 @@
#![allow(missing_docs)]
use std::collections::{btree_map, BTreeMap, HashMap, HashSet};
use std::collections::{BTreeMap, HashMap, HashSet};
use std::fmt::{Debug, Error, Formatter};
use std::ops::{Deref, DerefMut};
use itertools::Itertools as _;
use once_cell::sync::Lazy;
use thiserror::Error;
use crate::backend::{id_type, CommitId, ObjectId, Timestamp};
use crate::conflicts::Conflict;
use crate::content_hash::ContentHash;
content_hash! {
#[derive(PartialEq, Eq, PartialOrd, Ord, Clone, Hash)]
@ -56,9 +53,11 @@ impl WorkspaceId {
id_type!(pub ViewId);
id_type!(pub OperationId);
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RefTarget {
conflict: Conflict<Option<CommitId>>,
content_hash! {
#[derive(PartialEq, Eq, Hash, Clone, Debug)]
pub struct RefTarget {
conflict: Conflict<Option<CommitId>>,
}
}
impl Default for RefTarget {
@ -67,26 +66,6 @@ impl Default for RefTarget {
}
}
impl ContentHash for RefTarget {
fn hash(&self, state: &mut impl digest::Update) {
// TODO: Leverage generic implementation. Unlike RefTargetMap, this just exists
// in order to minimize the test changes. We can freely switch to the generic
// version.
if self.is_absent() {
state.update(&[0]); // None
} else if let Some(id) = self.as_normal() {
state.update(&[1]); // Some(
state.update(&0u32.to_le_bytes()); // Normal(
id.hash(state);
} else {
state.update(&[1]); // Some(
state.update(&1u32.to_le_bytes()); // Conflict(
self.removed_ids().cloned().collect_vec().hash(state);
self.added_ids().cloned().collect_vec().hash(state);
}
}
}
impl RefTarget {
/// Creates non-conflicting target pointing to no commit.
pub fn absent() -> Self {
@ -176,72 +155,6 @@ impl<'a> RefTargetOptionExt for Option<&'a RefTarget> {
}
}
/// Wrapper to exclude absent `RefTarget` entries from `ContentHash`.
#[derive(Default, PartialEq, Eq, Clone, Debug)]
pub struct RefTargetMap(pub BTreeMap<String, RefTarget>);
impl RefTargetMap {
pub fn new() -> Self {
RefTargetMap(BTreeMap::new())
}
}
// TODO: Update serialization code to preserve absent RefTarget entries, and
// remove this wrapper.
impl ContentHash for RefTargetMap {
fn hash(&self, state: &mut impl digest::Update) {
// Derived from content_hash.rs. It's okay for this to produce a different hash
// value than the inner map, but the value must not be equal to the map which
// preserves absent RefTarget entries.
let iter = self.0.iter().filter(|(_, v)| v.is_present());
state.update(&(iter.clone().count() as u64).to_le_bytes());
for (k, v) in iter {
k.hash(state);
if let Some(id) = v.as_normal() {
state.update(&0u32.to_le_bytes()); // Normal(
id.hash(state);
} else {
state.update(&1u32.to_le_bytes()); // Conflict(
v.removed_ids().cloned().collect_vec().hash(state);
v.added_ids().cloned().collect_vec().hash(state);
}
}
}
}
// Abuse Deref as this is a temporary workaround. See the comment above.
impl Deref for RefTargetMap {
type Target = BTreeMap<String, RefTarget>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for RefTargetMap {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl IntoIterator for RefTargetMap {
type Item = (String, RefTarget);
type IntoIter = btree_map::IntoIter<String, RefTarget>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl<'a> IntoIterator for &'a RefTargetMap {
type Item = (&'a String, &'a RefTarget);
type IntoIter = btree_map::Iter<'a, String, RefTarget>;
fn into_iter(self) -> Self::IntoIter {
self.0.iter()
}
}
content_hash! {
#[derive(Default, PartialEq, Eq, Clone, Debug)]
pub struct BranchTarget {
@ -252,7 +165,7 @@ content_hash! {
// has been deleted locally and you pull from a remote, maybe it should make a difference
// whether the branch is known to have existed on the remote. We may not want to resurrect
// the branch if the branch's state on the remote was just not known.
pub remote_targets: RefTargetMap,
pub remote_targets: BTreeMap<String, RefTarget>,
}
}
@ -266,8 +179,8 @@ content_hash! {
/// Heads of the set of public commits.
pub public_head_ids: HashSet<CommitId>,
pub branches: BTreeMap<String, BranchTarget>,
pub tags: RefTargetMap,
pub git_refs: RefTargetMap,
pub tags: BTreeMap<String, RefTarget>,
pub git_refs: BTreeMap<String, RefTarget>,
/// The commit the Git HEAD points to.
// TODO: Support multiple Git worktrees?
// TODO: Do we want to store the current branch name too?

View File

@ -128,16 +128,15 @@ mod tests {
use super::*;
use crate::backend::ObjectId;
use crate::op_store::RefTargetMap;
#[test]
fn test_classify_branch_push_action_unchanged() {
let commit_id1 = CommitId::from_hex("11");
let branch = BranchTarget {
local_target: RefTarget::normal(commit_id1.clone()),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(commit_id1),
}),
},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
@ -150,7 +149,7 @@ mod tests {
let commit_id1 = CommitId::from_hex("11");
let branch = BranchTarget {
local_target: RefTarget::normal(commit_id1.clone()),
remote_targets: RefTargetMap::new(),
remote_targets: btreemap! {},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
@ -166,9 +165,9 @@ mod tests {
let commit_id1 = CommitId::from_hex("11");
let branch = BranchTarget {
local_target: RefTarget::absent(),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(commit_id1.clone()),
}),
},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
@ -185,9 +184,9 @@ mod tests {
let commit_id2 = CommitId::from_hex("22");
let branch = BranchTarget {
local_target: RefTarget::normal(commit_id2.clone()),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(commit_id1.clone()),
}),
},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
@ -204,9 +203,9 @@ mod tests {
let commit_id2 = CommitId::from_hex("22");
let branch = BranchTarget {
local_target: RefTarget::from_legacy_form([], [commit_id1.clone(), commit_id2]),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(commit_id1),
}),
},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),
@ -220,12 +219,12 @@ mod tests {
let commit_id2 = CommitId::from_hex("22");
let branch = BranchTarget {
local_target: RefTarget::normal(commit_id1.clone()),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::from_legacy_form(
[],
[commit_id1, commit_id2],
),
}),
},
};
assert_eq!(
classify_branch_push_action(&branch, "origin"),

View File

@ -14,6 +14,7 @@
#![allow(missing_docs)]
use std::collections::BTreeMap;
use std::fmt::Debug;
use std::fs;
use std::io::{ErrorKind, Write};
@ -29,7 +30,7 @@ use crate::content_hash::blake2b_hash;
use crate::file_util::persist_content_addressed_temp_file;
use crate::op_store::{
BranchTarget, OpStore, OpStoreError, OpStoreResult, Operation, OperationId, OperationMetadata,
RefTarget, RefTargetMap, View, ViewId, WorkspaceId,
RefTarget, View, ViewId, WorkspaceId,
};
impl From<PersistError> for OpStoreError {
@ -314,7 +315,7 @@ fn view_from_proto(proto: crate::protos::op_store::View) -> View {
for branch_proto in proto.branches {
let local_target = ref_target_from_proto(branch_proto.local_target);
let mut remote_targets = RefTargetMap::new();
let mut remote_targets = BTreeMap::new();
for remote_branch in branch_proto.remote_branches {
remote_targets.insert(
remote_branch.remote_name,
@ -356,8 +357,26 @@ fn view_from_proto(proto: crate::protos::op_store::View) -> View {
view
}
#[allow(deprecated)]
fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
let term_to_proto = |term: &Option<CommitId>| crate::protos::op_store::ref_conflict::Term {
value: term.as_ref().map(|id| id.to_bytes()),
};
let conflict = value.as_conflict();
let conflict_proto = crate::protos::op_store::RefConflict {
removes: conflict.removes().iter().map(term_to_proto).collect(),
adds: conflict.adds().iter().map(term_to_proto).collect(),
};
let proto = crate::protos::op_store::RefTarget {
value: Some(crate::protos::op_store::ref_target::Value::Conflict(
conflict_proto,
)),
};
Some(proto)
}
#[allow(deprecated)]
#[cfg(test)]
fn ref_target_to_proto_legacy(value: &RefTarget) -> Option<crate::protos::op_store::RefTarget> {
if let Some(id) = value.as_normal() {
let proto = crate::protos::op_store::RefTarget {
value: Some(crate::protos::op_store::ref_target::Value::CommitId(
@ -366,7 +385,6 @@ fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::Ref
};
Some(proto)
} else if value.has_conflict() {
// TODO: Preserve "absent" targets, and remove op_store::RefTargetMap hack.
let ref_conflict_proto = crate::protos::op_store::RefConflictLegacy {
removes: value.removed_ids().map(|id| id.to_bytes()).collect(),
adds: value.added_ids().map(|id| id.to_bytes()).collect(),
@ -384,6 +402,8 @@ fn ref_target_to_proto(value: &RefTarget) -> Option<crate::protos::op_store::Ref
}
fn ref_target_from_proto(maybe_proto: Option<crate::protos::op_store::RefTarget>) -> RefTarget {
// TODO: Delete legacy format handling when we decide to drop support for views
// saved by jj <= 0.8.
let Some(proto) = maybe_proto else {
// Legacy absent id
return RefTarget::absent();
@ -443,24 +463,24 @@ mod tests {
branches: btreemap! {
"main".to_string() => BranchTarget {
local_target: branch_main_local_target,
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => branch_main_origin_target,
}),
},
},
"deleted".to_string() => BranchTarget {
local_target: RefTarget::absent(),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => branch_deleted_origin_target,
}),
},
},
},
tags: RefTargetMap(btreemap! {
tags: btreemap! {
"v1.0".to_string() => tag_v1_target,
}),
git_refs: RefTargetMap(btreemap! {
},
git_refs: btreemap! {
"refs/heads/main".to_string() => git_refs_main_target,
"refs/heads/feature".to_string() => git_refs_feature_target,
}),
},
git_head: RefTarget::normal(CommitId::from_hex("fff111")),
wc_commit_ids: hashmap! {
WorkspaceId::default() => default_wc_commit_id,
@ -501,7 +521,7 @@ mod tests {
// Test exact output so we detect regressions in compatibility
assert_snapshot!(
ViewId::new(blake2b_hash(&create_view()).to_vec()).hex(),
@"7f47fa81494d7189cb1827b83b3f834662f0f61b4c4090298067e85cdc60f773bf639c4e6a3554a4e401650218ca240291ce591f45a1c501ade1d2b9f97e1a37"
@"3c1c6efecfc0809130a5bf139aec77e6299cd7d5985b95c01a29318d40a5e2defc9bd12329e91511e545fbad065f60ce5da91f5f0368c9bf549ca761bb047f7e"
);
}
@ -535,14 +555,32 @@ mod tests {
}
#[test]
fn test_ref_target_legacy_roundtrip() {
let target = RefTarget::absent();
fn test_ref_target_change_delete_order_roundtrip() {
let target = RefTarget::from_conflict(Conflict::new(
vec![Some(CommitId::from_hex("111111"))],
vec![Some(CommitId::from_hex("222222")), None],
));
let maybe_proto = ref_target_to_proto(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
let target = RefTarget::normal(CommitId::from_hex("111111"));
// If it were legacy format, order of None entry would be lost.
let target = RefTarget::from_conflict(Conflict::new(
vec![Some(CommitId::from_hex("111111"))],
vec![None, Some(CommitId::from_hex("222222"))],
));
let maybe_proto = ref_target_to_proto(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
}
#[test]
fn test_ref_target_legacy_roundtrip() {
let target = RefTarget::absent();
let maybe_proto = ref_target_to_proto_legacy(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
let target = RefTarget::normal(CommitId::from_hex("111111"));
let maybe_proto = ref_target_to_proto_legacy(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
// N-way conflict
let target = RefTarget::from_legacy_form(
@ -553,7 +591,7 @@ mod tests {
CommitId::from_hex("555555"),
],
);
let maybe_proto = ref_target_to_proto(&target);
let maybe_proto = ref_target_to_proto_legacy(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
// Change-delete conflict
@ -561,7 +599,7 @@ mod tests {
[CommitId::from_hex("111111")],
[CommitId::from_hex("222222")],
);
let maybe_proto = ref_target_to_proto(&target);
let maybe_proto = ref_target_to_proto_legacy(&target);
assert_eq!(ref_target_from_proto(maybe_proto), target);
}
}

View File

@ -27,7 +27,7 @@ use jj_lib::commit_builder::CommitBuilder;
use jj_lib::git;
use jj_lib::git::{GitFetchError, GitPushError, GitRefUpdate, SubmoduleConfig};
use jj_lib::git_backend::GitBackend;
use jj_lib::op_store::{BranchTarget, RefTarget, RefTargetMap};
use jj_lib::op_store::{BranchTarget, RefTarget};
use jj_lib::repo::{MutableRepo, ReadonlyRepo, Repo};
use jj_lib::settings::{GitSettings, UserSettings};
use jj_lib::view::RefName;
@ -110,14 +110,14 @@ fn test_import_refs() {
let expected_main_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit2)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit1)),
}),
},
};
assert_eq!(view.get_branch("main"), Some(expected_main_branch).as_ref());
let expected_feature1_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit3)),
remote_targets: RefTargetMap::new(),
remote_targets: btreemap! {},
};
assert_eq!(
view.get_branch("feature1"),
@ -125,7 +125,7 @@ fn test_import_refs() {
);
let expected_feature2_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit4)),
remote_targets: RefTargetMap::new(),
remote_targets: btreemap! {},
};
assert_eq!(
view.get_branch("feature2"),
@ -133,9 +133,9 @@ fn test_import_refs() {
);
let expected_feature3_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit6)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit6)),
}),
},
};
assert_eq!(
view.get_branch("feature3"),
@ -234,9 +234,9 @@ fn test_import_refs_reimport() {
let commit2_target = RefTarget::normal(jj_id(&commit2));
let expected_main_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit2)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => commit1_target.clone(),
}),
},
};
assert_eq!(view.get_branch("main"), Some(expected_main_branch).as_ref());
let expected_feature2_branch = BranchTarget {
@ -244,7 +244,7 @@ fn test_import_refs_reimport() {
[jj_id(&commit4)],
[commit6.id().clone(), jj_id(&commit5)],
),
remote_targets: RefTargetMap::new(),
remote_targets: btreemap! {},
};
assert_eq!(
view.get_branch("feature2"),
@ -432,18 +432,18 @@ fn test_import_refs_reimport_with_deleted_remote_ref() {
// Even though the git repo does not have a local branch for `feature-remote-only`, jj
// creates one. This follows the model explained in docs/branches.md.
local_target: RefTarget::normal(jj_id(&commit_remote_only)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit_remote_only)),
}),
},
}),
);
assert_eq!(
view.get_branch("feature-remote-and-local"),
Some(&BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_remote_and_local)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit_remote_and_local)),
}),
},
}),
);
view.get_branch("main").unwrap(); // branch #3 of 3
@ -521,18 +521,18 @@ fn test_import_refs_reimport_with_moved_remote_ref() {
// Even though the git repo does not have a local branch for `feature-remote-only`, jj
// creates one. This follows the model explained in docs/branches.md.
local_target: RefTarget::normal(jj_id(&commit_remote_only)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit_remote_only)),
}),
},
}),
);
assert_eq!(
view.get_branch("feature-remote-and-local"),
Some(&BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_remote_and_local)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&commit_remote_and_local)),
}),
},
}),
);
view.get_branch("main").unwrap(); // branch #3 of 3
@ -565,18 +565,18 @@ fn test_import_refs_reimport_with_moved_remote_ref() {
view.get_branch("feature-remote-only"),
Some(&BranchTarget {
local_target: RefTarget::normal(jj_id(&new_commit_remote_only)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&new_commit_remote_only)),
}),
},
}),
);
assert_eq!(
view.get_branch("feature-remote-and-local"),
Some(&BranchTarget {
local_target: RefTarget::normal(jj_id(&new_commit_remote_and_local)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&new_commit_remote_and_local)),
}),
},
}),
);
view.get_branch("main").unwrap(); // branch #3 of 3
@ -706,9 +706,9 @@ fn test_import_some_refs() {
let commit_feat4_target = RefTarget::normal(jj_id(&commit_feat4));
let expected_feature1_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_feat1)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => commit_feat1_target,
}),
},
};
assert_eq!(
view.get_branch("feature1"),
@ -716,9 +716,9 @@ fn test_import_some_refs() {
);
let expected_feature2_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_feat2)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => commit_feat2_target,
}),
},
};
assert_eq!(
view.get_branch("feature2"),
@ -726,9 +726,9 @@ fn test_import_some_refs() {
);
let expected_feature3_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_feat3)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => commit_feat3_target,
}),
},
};
assert_eq!(
view.get_branch("feature3"),
@ -736,9 +736,9 @@ fn test_import_some_refs() {
);
let expected_feature4_branch = BranchTarget {
local_target: RefTarget::normal(jj_id(&commit_feat4)),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => commit_feat4_target,
}),
},
};
assert_eq!(
view.get_branch("feature4"),
@ -1143,9 +1143,9 @@ fn test_import_export_no_auto_local_branch() {
let expected_branch = BranchTarget {
local_target: RefTarget::absent(),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(jj_id(&git_commit)),
}),
},
};
assert_eq!(
mut_repo.view().get_branch("main"),
@ -1495,9 +1495,9 @@ fn test_fetch_initial_commit() {
btreemap! {
"main".to_string() => BranchTarget {
local_target: initial_commit_target.clone(),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => initial_commit_target,
})
},
},
}
);
@ -1560,9 +1560,9 @@ fn test_fetch_success() {
btreemap! {
"main".to_string() => BranchTarget {
local_target: new_commit_target.clone(),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => new_commit_target,
}),
},
},
}
);

View File

@ -14,7 +14,7 @@
use std::sync::Arc;
use jj_lib::op_store::{BranchTarget, RefTarget, RefTargetMap, WorkspaceId};
use jj_lib::op_store::{BranchTarget, RefTarget, WorkspaceId};
use jj_lib::repo::{ReadonlyRepo, Repo};
use jj_lib::settings::UserSettings;
use jj_lib::transaction::Transaction;
@ -309,14 +309,14 @@ fn test_merge_views_branches() {
main_branch_local_tx2.id().clone(),
],
),
remote_targets: RefTargetMap(btreemap! {
remote_targets: btreemap! {
"origin".to_string() => RefTarget::normal(main_branch_origin_tx1.id().clone()),
"alternate".to_string() => RefTarget::normal(main_branch_alternate_tx0.id().clone()),
}),
},
};
let expected_feature_branch = BranchTarget {
local_target: RefTarget::normal(feature_branch_tx1.id().clone()),
remote_targets: RefTargetMap::new(),
remote_targets: btreemap! {},
};
assert_eq!(
repo.view().branches(),

View File

@ -53,15 +53,15 @@ fn test_concurrent_operations_auto_rebase() {
test_env.jj_cmd_success(&repo_path, &["describe", "-m", "initial"]);
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(stdout, @r###"
@ cde29280d4a9 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
@ cfc96ff553b9 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
describe commit 123ed18e4c4c0d77428df41112bc02ffc83fb935
args: jj describe -m initial
7c212e0863fd test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
65a6c90b9544 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
snapshot working copy
args: jj describe -m initial
a99a3fd5c51e test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
19b8089fc78b test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
f1c462c494be test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
"###);
let op_id_hex = stdout[3..15].to_string();
@ -160,21 +160,21 @@ fn test_concurrent_snapshot_wc_reloadable() {
let template = r#"id ++ "\n" ++ description ++ "\n" ++ tags"#;
let op_log_stdout = test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]);
insta::assert_snapshot!(op_log_stdout, @r###"
@ bacc8f507ccede29c282bb1459b71ffd233a9e29f4ec11b027422923592c4ef949e4465fd101c99ee6cfd39af26f29cd5910ef3b16985538e50fd21523bcf3e1
@ 9be517934aaabc351597e88ed4119aa9454ae3588ab7f28646a810272c82f3dafb1deb20b3c978dbb58ba9abc8f08fe870fe3c7ce5f682411991e83eee40a77f
commit 323b414dd255b51375d7f4392b7b2641ffe4289f
args: jj commit -m 'new child1'
6aa8b9099660e021813be72b4230692f782699448947cc76ffe5245d23108184dd60bb795fba350eb39abc2b0643169623cf59bb0c04130102388f8d87791070
d967c09eb12b38dad2065a0bc9e251824247f9f84ba406a7356f5405e4c93c21562178a3f00cafedfa1df1435ba496265f39da9d1ccebaccb78bdcb4bd7031e1
snapshot working copy
args: jj commit -m 'new child1'
7a31786821de03db03d5b9b7ec97454d10b90eee33844a86ebd282124c8ab43babcf79091cd1c47796ed8cc6ff23febabb35a10e0aad1f96428f958eb834b30d
b6d168ba4fb4534257b6e58d53eb407582567342358eab07cf5a01a7e4d797313b692f27664c2fb7935b2380d398d0298233c9732f821b8c687e35607ea08a55
commit 3d918700494a9895696e955b85fa05eb0d314cc6
args: jj commit -m initial
36440b76f049c4999505fa1dd3b21bfdb1a1f93c64e04f0a3797e704145780df959afcd3babd59c536544c6e7a6b883594abe7b38e8c4be5fe6d66a3cd8f4f9d
5e9e3f82fc14750ff985c5a39f1935ed8876b973b8800b56bc03d1c9754795e724956d862d1fcb2c533d06ca36abc9fa9f7cb7d3b2b64e993e9a87f80d5af670
snapshot working copy
args: jj commit -m initial
a99a3fd5c51e8f7ccb9ae2f9fb749612a23f0a7cf25d8c644f36c35c077449ce3c66f49d098a5a704ca5e47089a7f019563a5b8cbc7d451619e0f90c82241ceb
19b8089fc78b7c49171f3c8934248be6f89f52311005e961cab5780f9f138b142456d77b27d223d7ee84d21d8c30c4a80100eaf6735b548b1acd0da688f94c80
add workspace 'default'
56b94dfc38e7d54340377f566e96ab97dc6163ea7841daf49fb2e1d1ceb27e26274db1245835a1a421fb9d06e6e0fe1e4f4aa1b0258c6e86df676ad9111d0dab
f1c462c494be39f6690928603c5393f908866bc8d81d8cd1ae0bb2ea02cb4f78cafa47165fa5b7cda258e2178f846881de199066991960a80954ba6066ba0821
initialize repo
"###);
let op_log_lines = op_log_stdout.lines().collect_vec();

View File

@ -126,7 +126,7 @@ fn test_debug_operation_id() {
let stdout =
test_env.jj_cmd_success(&workspace_path, &["debug", "operation", "--display", "id"]);
assert_snapshot!(filter_index_stats(&stdout), @r###"
a99a3fd5c51e8f7ccb9ae2f9fb749612a23f0a7cf25d8c644f36c35c077449ce3c66f49d098a5a704ca5e47089a7f019563a5b8cbc7d451619e0f90c82241ceb
19b8089fc78b7c49171f3c8934248be6f89f52311005e961cab5780f9f138b142456d77b27d223d7ee84d21d8c30c4a80100eaf6735b548b1acd0da688f94c80
"###
);
}

View File

@ -38,12 +38,12 @@ fn test_op_log() {
],
);
insta::assert_snapshot!(&stdout, @r###"
@ 45108169c0f8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
@ 98f7262e4a06 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
args: jj describe -m 'description 0'
a99a3fd5c51e test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
19b8089fc78b test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
f1c462c494be test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
"###);
let op_log_lines = stdout.lines().collect_vec();
@ -126,14 +126,14 @@ fn test_op_log_template() {
let render = |template| test_env.jj_cmd_success(&repo_path, &["op", "log", "-T", template]);
insta::assert_snapshot!(render(r#"id ++ "\n""#), @r###"
@ a99a3fd5c51e8f7ccb9ae2f9fb749612a23f0a7cf25d8c644f36c35c077449ce3c66f49d098a5a704ca5e47089a7f019563a5b8cbc7d451619e0f90c82241ceb
56b94dfc38e7d54340377f566e96ab97dc6163ea7841daf49fb2e1d1ceb27e26274db1245835a1a421fb9d06e6e0fe1e4f4aa1b0258c6e86df676ad9111d0dab
@ 19b8089fc78b7c49171f3c8934248be6f89f52311005e961cab5780f9f138b142456d77b27d223d7ee84d21d8c30c4a80100eaf6735b548b1acd0da688f94c80
f1c462c494be39f6690928603c5393f908866bc8d81d8cd1ae0bb2ea02cb4f78cafa47165fa5b7cda258e2178f846881de199066991960a80954ba6066ba0821
"###);
insta::assert_snapshot!(
render(r#"separate(" ", id.short(5), current_operation, user,
time.start(), time.end(), time.duration()) ++ "\n""#), @r###"
@ a99a3 true test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
56b94 false test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
@ 19b80 true test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
f1c46 false test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 2001-02-03 04:05:07.000 +07:00 less than a microsecond
"###);
// Test the default template, i.e. with relative start time and duration. We
// don't generally use that template because it depends on the current time,
@ -147,9 +147,9 @@ fn test_op_log_template() {
let regex = Regex::new(r"\d\d years").unwrap();
let stdout = test_env.jj_cmd_success(&repo_path, &["op", "log"]);
insta::assert_snapshot!(regex.replace_all(&stdout, "NN years"), @r###"
@ a99a3fd5c51e test-username@host.example.com NN years ago, lasted less than a microsecond
@ 19b8089fc78b test-username@host.example.com NN years ago, lasted less than a microsecond
add workspace 'default'
56b94dfc38e7 test-username@host.example.com NN years ago, lasted less than a microsecond
f1c462c494be test-username@host.example.com NN years ago, lasted less than a microsecond
initialize repo
"###);
}
@ -163,24 +163,24 @@ fn test_op_log_builtin_templates() {
test_env.jj_cmd_success(&repo_path, &["describe", "-m", "description 0"]);
insta::assert_snapshot!(render(r#"builtin_op_log_compact"#), @r###"
@ 45108169c0f8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
@ 98f7262e4a06 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
args: jj describe -m 'description 0'
a99a3fd5c51e test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
19b8089fc78b test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
f1c462c494be test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
"###);
insta::assert_snapshot!(render(r#"builtin_op_log_comfortable"#), @r###"
@ 45108169c0f8 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
@ 98f7262e4a06 test-username@host.example.com 2001-02-03 04:05:08.000 +07:00 - 2001-02-03 04:05:08.000 +07:00
describe commit 230dd059e1b059aefc0da06a2e5a7dbf22362f22
args: jj describe -m 'description 0'
a99a3fd5c51e test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
19b8089fc78b test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
f1c462c494be test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
"###);
@ -207,18 +207,18 @@ fn test_op_log_word_wrap() {
// ui.log-word-wrap option works
insta::assert_snapshot!(render(&["op", "log"], 40, false), @r###"
@ a99a3fd5c51e test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
@ 19b8089fc78b test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7 test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
f1c462c494be test-username@host.example.com 2001-02-03 04:05:07.000 +07:00 - 2001-02-03 04:05:07.000 +07:00
initialize repo
"###);
insta::assert_snapshot!(render(&["op", "log"], 40, true), @r###"
@ a99a3fd5c51e
@ 19b8089fc78b
test-username@host.example.com
2001-02-03 04:05:07.000 +07:00 -
2001-02-03 04:05:07.000 +07:00
add workspace 'default'
56b94dfc38e7
f1c462c494be
test-username@host.example.com
2001-02-03 04:05:07.000 +07:00 -
2001-02-03 04:05:07.000 +07:00

View File

@ -114,14 +114,14 @@ fn test_workspaces_conflicting_edits() {
"###);
let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]);
insta::assert_snapshot!(stderr, @r###"
Error: The working copy is stale (not updated since operation 815bb8fcbd7a).
Error: The working copy is stale (not updated since operation 5c95db542ebd).
Hint: Run `jj workspace update-stale` to update it.
See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
"###);
// Same error on second run, and from another command
let stderr = test_env.jj_cmd_failure(&secondary_path, &["log"]);
insta::assert_snapshot!(stderr, @r###"
Error: The working copy is stale (not updated since operation 815bb8fcbd7a).
Error: The working copy is stale (not updated since operation 5c95db542ebd).
Hint: Run `jj workspace update-stale` to update it.
See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
"###);
@ -199,7 +199,7 @@ fn test_workspaces_updated_by_other() {
"###);
let stderr = test_env.jj_cmd_failure(&secondary_path, &["st"]);
insta::assert_snapshot!(stderr, @r###"
Error: The working copy is stale (not updated since operation 815bb8fcbd7a).
Error: The working copy is stale (not updated since operation 5c95db542ebd).
Hint: Run `jj workspace update-stale` to update it.
See https://github.com/martinvonz/jj/blob/main/docs/working-copy.md#stale-working-copy for more information.
"###);