mirror of
https://github.com/martinvonz/jj.git
synced 2024-09-21 10:50:22 +03:00
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:
parent
8351a743f6
commit
4834d12c37
@ -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.
|
||||
|
@ -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?
|
||||
|
@ -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"),
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
}),
|
||||
},
|
||||
},
|
||||
}
|
||||
);
|
||||
|
@ -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(),
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
"###
|
||||
);
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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.
|
||||
"###);
|
||||
|
Loading…
Reference in New Issue
Block a user