2020-12-12 08:00:42 +00:00
|
|
|
// Copyright 2020 Google LLC
|
|
|
|
//
|
|
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
// you may not use this file except in compliance with the License.
|
|
|
|
// You may obtain a copy of the License at
|
|
|
|
//
|
|
|
|
// https://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
//
|
|
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
// See the License for the specific language governing permissions and
|
|
|
|
// limitations under the License.
|
|
|
|
|
|
|
|
use std::fmt::{Debug, Formatter};
|
|
|
|
use std::fs;
|
|
|
|
use std::fs::File;
|
2021-05-16 16:36:48 +00:00
|
|
|
use std::io::Write;
|
2021-05-16 17:33:52 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2020-12-12 08:00:42 +00:00
|
|
|
use std::sync::{Arc, Mutex, MutexGuard};
|
|
|
|
|
|
|
|
use thiserror::Error;
|
|
|
|
|
2021-03-07 23:11:34 +00:00
|
|
|
use crate::commit::Commit;
|
|
|
|
use crate::commit_builder::{new_change_id, signature, CommitBuilder};
|
|
|
|
use crate::dag_walk::topo_order_reverse;
|
2021-02-01 02:13:37 +00:00
|
|
|
use crate::evolution::{EvolutionRef, MutableEvolution, ReadonlyEvolution};
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::git_store::GitStore;
|
2021-01-31 02:50:27 +00:00
|
|
|
use crate::index::{IndexRef, MutableIndex, ReadonlyIndex};
|
2021-03-03 05:43:13 +00:00
|
|
|
use crate::index_store::IndexStore;
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::local_store::LocalStore;
|
2021-03-11 06:59:11 +00:00
|
|
|
use crate::op_heads_store::OpHeadsStore;
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
use crate::op_store::{BranchTarget, OpStore, OperationId, RefTarget};
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::operation::Operation;
|
|
|
|
use crate::settings::{RepoSettings, UserSettings};
|
2021-02-27 07:37:42 +00:00
|
|
|
use crate::simple_op_store::SimpleOpStore;
|
2021-03-07 23:11:34 +00:00
|
|
|
use crate::store::{CommitId, Store, StoreError};
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::store_wrapper::StoreWrapper;
|
2021-02-01 02:00:14 +00:00
|
|
|
use crate::transaction::Transaction;
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
use crate::view::{RefName, View};
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::working_copy::WorkingCopy;
|
2021-03-14 17:46:35 +00:00
|
|
|
use crate::{conflicts, op_store, store};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
#[derive(Debug, Error, PartialEq, Eq)]
|
|
|
|
pub enum RepoError {
|
|
|
|
#[error("Object not found")]
|
|
|
|
NotFound,
|
|
|
|
#[error("Error: {0}")]
|
|
|
|
Other(String),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<StoreError> for RepoError {
|
|
|
|
fn from(err: StoreError) -> Self {
|
|
|
|
match err {
|
|
|
|
StoreError::NotFound => RepoError::NotFound,
|
|
|
|
StoreError::Other(description) => RepoError::Other(description),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub type RepoResult<T> = Result<T, RepoError>;
|
|
|
|
|
2021-01-31 07:44:31 +00:00
|
|
|
// TODO: Should we implement From<&ReadonlyRepo> and From<&MutableRepo> for
|
|
|
|
// RepoRef?
|
|
|
|
#[derive(Clone, Copy)]
|
2021-04-11 16:13:00 +00:00
|
|
|
pub enum RepoRef<'a> {
|
2021-01-31 07:44:31 +00:00
|
|
|
Readonly(&'a ReadonlyRepo),
|
2021-04-11 16:13:00 +00:00
|
|
|
Mutable(&'a MutableRepo),
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
impl<'a> RepoRef<'a> {
|
|
|
|
pub fn store(&self) -> &Arc<StoreWrapper> {
|
2021-01-31 07:44:31 +00:00
|
|
|
match self {
|
|
|
|
RepoRef::Readonly(repo) => repo.store(),
|
|
|
|
RepoRef::Mutable(repo) => repo.store(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
pub fn op_store(&self) -> &Arc<dyn OpStore> {
|
2021-03-10 23:48:32 +00:00
|
|
|
match self {
|
|
|
|
RepoRef::Readonly(repo) => repo.op_store(),
|
|
|
|
RepoRef::Mutable(repo) => repo.op_store(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-13 05:47:21 +00:00
|
|
|
pub fn index(&self) -> IndexRef<'a> {
|
2021-01-31 02:50:27 +00:00
|
|
|
match self {
|
|
|
|
RepoRef::Readonly(repo) => IndexRef::Readonly(repo.index()),
|
|
|
|
RepoRef::Mutable(repo) => IndexRef::Mutable(repo.index()),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
pub fn view(&self) -> &View {
|
2021-01-31 07:44:31 +00:00
|
|
|
match self {
|
2021-05-31 17:52:30 +00:00
|
|
|
RepoRef::Readonly(repo) => repo.view(),
|
|
|
|
RepoRef::Mutable(repo) => repo.view(),
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-03-15 11:15:35 +00:00
|
|
|
pub fn evolution(&self) -> EvolutionRef {
|
2021-01-31 07:44:31 +00:00
|
|
|
match self {
|
2021-02-01 02:13:37 +00:00
|
|
|
RepoRef::Readonly(repo) => EvolutionRef::Readonly(repo.evolution()),
|
|
|
|
RepoRef::Mutable(repo) => EvolutionRef::Mutable(repo.evolution()),
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ReadonlyRepo {
|
|
|
|
repo_path: PathBuf,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
store: Arc<StoreWrapper>,
|
2021-03-10 23:48:32 +00:00
|
|
|
op_store: Arc<dyn OpStore>,
|
2021-03-11 07:14:00 +00:00
|
|
|
op_heads_store: Arc<OpHeadsStore>,
|
2021-05-07 23:34:40 +00:00
|
|
|
operation: Operation,
|
2020-12-12 08:00:42 +00:00
|
|
|
settings: RepoSettings,
|
2021-03-06 18:37:57 +00:00
|
|
|
index_store: Arc<IndexStore>,
|
2020-12-18 16:32:15 +00:00
|
|
|
index: Mutex<Option<Arc<ReadonlyIndex>>>,
|
2020-12-12 08:00:42 +00:00
|
|
|
working_copy: Arc<Mutex<WorkingCopy>>,
|
2021-05-31 17:52:30 +00:00
|
|
|
view: View,
|
2021-03-15 06:04:04 +00:00
|
|
|
evolution: Mutex<Option<Arc<ReadonlyEvolution>>>,
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for ReadonlyRepo {
|
|
|
|
fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), std::fmt::Error> {
|
|
|
|
f.debug_struct("Repo")
|
|
|
|
.field("repo_path", &self.repo_path)
|
|
|
|
.field("wc_path", &self.wc_path)
|
|
|
|
.field("store", &self.store)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-19 21:30:50 +00:00
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
|
|
pub enum RepoInitError {
|
|
|
|
#[error("The destination repo ({0}) already exists")]
|
|
|
|
DestinationExists(PathBuf),
|
|
|
|
}
|
|
|
|
|
2021-01-04 07:11:22 +00:00
|
|
|
#[derive(Error, Debug, PartialEq)]
|
|
|
|
pub enum RepoLoadError {
|
2021-05-15 16:16:31 +00:00
|
|
|
#[error("There is no Jujutsu repo in {0}")]
|
2021-01-04 07:11:22 +00:00
|
|
|
NoRepoHere(PathBuf),
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
impl ReadonlyRepo {
|
2021-05-19 21:30:50 +00:00
|
|
|
pub fn init_local(
|
|
|
|
settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
) -> Result<Arc<ReadonlyRepo>, RepoInitError> {
|
|
|
|
let repo_path = ReadonlyRepo::init_repo_dir(&wc_path)?;
|
2020-12-12 08:00:42 +00:00
|
|
|
let store_path = repo_path.join("store");
|
|
|
|
fs::create_dir(&store_path).unwrap();
|
|
|
|
let store = Box::new(LocalStore::init(store_path));
|
2021-05-19 21:30:50 +00:00
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2020-12-28 08:26:37 +00:00
|
|
|
/// Initializes a repo with a new Git store in .jj/git/ (bare Git repo)
|
2021-05-19 21:30:50 +00:00
|
|
|
pub fn init_internal_git(
|
|
|
|
settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
) -> Result<Arc<ReadonlyRepo>, RepoInitError> {
|
|
|
|
let repo_path = ReadonlyRepo::init_repo_dir(&wc_path)?;
|
2020-12-28 08:26:37 +00:00
|
|
|
let git_store_path = repo_path.join("git");
|
|
|
|
git2::Repository::init_bare(&git_store_path).unwrap();
|
|
|
|
let store_path = repo_path.join("store");
|
|
|
|
let git_store_path = fs::canonicalize(git_store_path).unwrap();
|
|
|
|
let mut store_file = File::create(store_path).unwrap();
|
|
|
|
store_file.write_all(b"git: git").unwrap();
|
2021-02-27 07:00:46 +00:00
|
|
|
let store = Box::new(GitStore::load(&git_store_path));
|
2021-05-19 21:30:50 +00:00
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
2020-12-28 08:26:37 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Initializes a repo with an existing Git store at the specified path
|
|
|
|
pub fn init_external_git(
|
2020-12-12 08:00:42 +00:00
|
|
|
settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
git_store_path: PathBuf,
|
2021-05-19 21:30:50 +00:00
|
|
|
) -> Result<Arc<ReadonlyRepo>, RepoInitError> {
|
|
|
|
let repo_path = ReadonlyRepo::init_repo_dir(&wc_path)?;
|
2020-12-12 08:00:42 +00:00
|
|
|
let store_path = repo_path.join("store");
|
|
|
|
let git_store_path = fs::canonicalize(git_store_path).unwrap();
|
|
|
|
let mut store_file = File::create(store_path).unwrap();
|
|
|
|
store_file
|
2020-12-28 08:26:37 +00:00
|
|
|
.write_all(format!("git: {}", git_store_path.to_str().unwrap()).as_bytes())
|
2020-12-12 08:00:42 +00:00
|
|
|
.unwrap();
|
2021-02-27 07:00:46 +00:00
|
|
|
let store = Box::new(GitStore::load(&git_store_path));
|
2021-05-19 21:30:50 +00:00
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
|
|
|
}
|
|
|
|
|
|
|
|
fn init_repo_dir(wc_path: &Path) -> Result<PathBuf, RepoInitError> {
|
|
|
|
let repo_path = wc_path.join(".jj");
|
|
|
|
if repo_path.exists() {
|
|
|
|
Err(RepoInitError::DestinationExists(repo_path))
|
|
|
|
} else {
|
|
|
|
fs::create_dir(&repo_path).unwrap();
|
|
|
|
Ok(repo_path)
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn init(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
repo_path: PathBuf,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
store: Box<dyn Store>,
|
|
|
|
) -> Arc<ReadonlyRepo> {
|
|
|
|
let repo_settings = user_settings.with_repo(&repo_path).unwrap();
|
|
|
|
let store = StoreWrapper::new(store);
|
|
|
|
|
|
|
|
fs::create_dir(repo_path.join("working_copy")).unwrap();
|
2020-12-19 07:50:10 +00:00
|
|
|
let working_copy = WorkingCopy::init(
|
|
|
|
store.clone(),
|
|
|
|
wc_path.clone(),
|
|
|
|
repo_path.join("working_copy"),
|
|
|
|
);
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
fs::create_dir(repo_path.join("view")).unwrap();
|
|
|
|
let signature = signature(user_settings);
|
|
|
|
let checkout_commit = store::Commit {
|
|
|
|
parents: vec![],
|
|
|
|
predecessors: vec![],
|
|
|
|
root_tree: store.empty_tree_id().clone(),
|
|
|
|
change_id: new_change_id(),
|
|
|
|
description: "".to_string(),
|
|
|
|
author: signature.clone(),
|
|
|
|
committer: signature,
|
|
|
|
is_open: true,
|
|
|
|
is_pruned: false,
|
|
|
|
};
|
|
|
|
let checkout_commit = store.write_commit(checkout_commit);
|
2021-02-27 07:37:42 +00:00
|
|
|
|
|
|
|
std::fs::create_dir(repo_path.join("op_store")).unwrap();
|
|
|
|
let op_store: Arc<dyn OpStore> = Arc::new(SimpleOpStore::init(repo_path.join("op_store")));
|
2021-03-06 18:37:57 +00:00
|
|
|
|
2021-03-11 06:59:11 +00:00
|
|
|
let op_heads_dir = repo_path.join("op_heads");
|
|
|
|
std::fs::create_dir(&op_heads_dir).unwrap();
|
2021-04-19 05:52:31 +00:00
|
|
|
let mut root_view = op_store::View::new(checkout_commit.id().clone());
|
|
|
|
root_view.head_ids.insert(checkout_commit.id().clone());
|
|
|
|
root_view
|
|
|
|
.public_head_ids
|
|
|
|
.insert(store.root_commit_id().clone());
|
2021-05-07 23:34:40 +00:00
|
|
|
let (op_heads_store, init_op) = OpHeadsStore::init(op_heads_dir, &op_store, &root_view);
|
2021-03-11 06:59:11 +00:00
|
|
|
let op_heads_store = Arc::new(op_heads_store);
|
|
|
|
|
2021-03-06 18:37:57 +00:00
|
|
|
fs::create_dir(repo_path.join("index")).unwrap();
|
|
|
|
let index_store = Arc::new(IndexStore::init(repo_path.join("index")));
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
let view = View::new(root_view);
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let repo = ReadonlyRepo {
|
2021-03-03 05:43:13 +00:00
|
|
|
repo_path,
|
2020-12-12 08:00:42 +00:00
|
|
|
wc_path,
|
|
|
|
store,
|
2021-03-10 23:48:32 +00:00
|
|
|
op_store,
|
2021-03-11 07:14:00 +00:00
|
|
|
op_heads_store,
|
2021-05-07 23:34:40 +00:00
|
|
|
operation: init_op,
|
2020-12-12 08:00:42 +00:00
|
|
|
settings: repo_settings,
|
2021-03-03 05:43:13 +00:00
|
|
|
index_store,
|
2020-12-12 08:00:42 +00:00
|
|
|
index: Mutex::new(None),
|
|
|
|
working_copy: Arc::new(Mutex::new(working_copy)),
|
|
|
|
view,
|
2021-03-15 06:04:04 +00:00
|
|
|
evolution: Mutex::new(None),
|
2020-12-12 08:00:42 +00:00
|
|
|
};
|
2021-03-15 06:04:04 +00:00
|
|
|
let repo = Arc::new(repo);
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
repo.working_copy_locked()
|
2020-12-19 07:50:10 +00:00
|
|
|
.check_out(checkout_commit)
|
2020-12-12 08:00:42 +00:00
|
|
|
.expect("failed to check out root commit");
|
|
|
|
repo
|
|
|
|
}
|
|
|
|
|
2021-01-04 07:11:22 +00:00
|
|
|
pub fn load(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
) -> Result<Arc<ReadonlyRepo>, RepoLoadError> {
|
2021-05-19 20:30:28 +00:00
|
|
|
Ok(RepoLoader::init(user_settings, wc_path)?.load_at_head())
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2021-03-13 17:12:05 +00:00
|
|
|
pub fn loader(&self) -> RepoLoader {
|
2021-03-13 17:12:05 +00:00
|
|
|
RepoLoader {
|
|
|
|
wc_path: self.wc_path.clone(),
|
|
|
|
repo_path: self.repo_path.clone(),
|
|
|
|
repo_settings: self.settings.clone(),
|
|
|
|
store: self.store.clone(),
|
|
|
|
op_store: self.op_store.clone(),
|
|
|
|
op_heads_store: self.op_heads_store.clone(),
|
|
|
|
index_store: self.index_store.clone(),
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 07:44:31 +00:00
|
|
|
pub fn as_repo_ref(&self) -> RepoRef {
|
2021-06-14 07:18:38 +00:00
|
|
|
RepoRef::Readonly(self)
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
pub fn repo_path(&self) -> &PathBuf {
|
|
|
|
&self.repo_path
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn working_copy_path(&self) -> &PathBuf {
|
|
|
|
&self.wc_path
|
|
|
|
}
|
|
|
|
|
2021-03-14 05:38:37 +00:00
|
|
|
pub fn op_id(&self) -> &OperationId {
|
2021-05-07 23:34:40 +00:00
|
|
|
self.operation.id()
|
2021-03-14 05:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-05-07 23:34:40 +00:00
|
|
|
pub fn operation(&self) -> &Operation {
|
|
|
|
&self.operation
|
2021-03-14 05:38:37 +00:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
pub fn view(&self) -> &View {
|
2021-01-31 07:44:31 +00:00
|
|
|
&self.view
|
|
|
|
}
|
|
|
|
|
2021-03-15 06:04:04 +00:00
|
|
|
pub fn evolution(&self) -> Arc<ReadonlyEvolution> {
|
|
|
|
let mut locked_evolution = self.evolution.lock().unwrap();
|
|
|
|
if locked_evolution.is_none() {
|
|
|
|
locked_evolution.replace(Arc::new(ReadonlyEvolution::new(self)));
|
|
|
|
}
|
|
|
|
locked_evolution.as_ref().unwrap().clone()
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 05:47:21 +00:00
|
|
|
pub fn index(&self) -> &Arc<ReadonlyIndex> {
|
2020-12-12 08:00:42 +00:00
|
|
|
let mut locked_index = self.index.lock().unwrap();
|
|
|
|
if locked_index.is_none() {
|
2021-05-07 23:34:40 +00:00
|
|
|
locked_index.replace(
|
|
|
|
self.index_store
|
|
|
|
.get_index_at_op(&self.operation, self.store.as_ref()),
|
|
|
|
);
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
2021-04-13 05:47:21 +00:00
|
|
|
let index: &Arc<ReadonlyIndex> = locked_index.as_ref().unwrap();
|
|
|
|
// Extend lifetime from that of mutex lock to that of self. Safe since we never
|
|
|
|
// change value once it's been set (except in `reindex()` but that
|
|
|
|
// requires a mutable reference).
|
|
|
|
let index: &Arc<ReadonlyIndex> = unsafe { std::mem::transmute(index) };
|
|
|
|
index
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-04-13 05:47:21 +00:00
|
|
|
pub fn reindex(&mut self) -> &Arc<ReadonlyIndex> {
|
2021-03-03 05:43:13 +00:00
|
|
|
self.index_store.reinit();
|
2020-12-12 08:00:42 +00:00
|
|
|
{
|
|
|
|
let mut locked_index = self.index.lock().unwrap();
|
|
|
|
locked_index.take();
|
|
|
|
}
|
|
|
|
self.index()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn working_copy(&self) -> &Arc<Mutex<WorkingCopy>> {
|
|
|
|
&self.working_copy
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn working_copy_locked(&self) -> MutexGuard<WorkingCopy> {
|
|
|
|
self.working_copy.as_ref().lock().unwrap()
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn store(&self) -> &Arc<StoreWrapper> {
|
|
|
|
&self.store
|
|
|
|
}
|
|
|
|
|
2021-03-10 23:48:32 +00:00
|
|
|
pub fn op_store(&self) -> &Arc<dyn OpStore> {
|
|
|
|
&self.op_store
|
|
|
|
}
|
|
|
|
|
2021-03-11 07:14:00 +00:00
|
|
|
pub fn op_heads_store(&self) -> &Arc<OpHeadsStore> {
|
|
|
|
&self.op_heads_store
|
|
|
|
}
|
|
|
|
|
2021-03-06 18:37:57 +00:00
|
|
|
pub fn index_store(&self) -> &Arc<IndexStore> {
|
2021-03-03 06:53:20 +00:00
|
|
|
&self.index_store
|
|
|
|
}
|
|
|
|
|
2020-12-12 08:00:42 +00:00
|
|
|
pub fn settings(&self) -> &RepoSettings {
|
|
|
|
&self.settings
|
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
pub fn start_transaction(self: &Arc<ReadonlyRepo>, description: &str) -> Transaction {
|
2021-03-15 11:15:35 +00:00
|
|
|
let locked_evolution = self.evolution.lock().unwrap();
|
2021-04-11 16:13:00 +00:00
|
|
|
let mut_repo = MutableRepo::new(
|
|
|
|
self.clone(),
|
2021-04-13 05:47:21 +00:00
|
|
|
self.index().clone(),
|
2021-04-11 16:13:00 +00:00
|
|
|
&self.view,
|
|
|
|
locked_evolution.as_ref(),
|
|
|
|
);
|
2021-02-01 02:07:37 +00:00
|
|
|
Transaction::new(mut_repo, description)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 20:30:28 +00:00
|
|
|
pub fn reload(&self) -> Arc<ReadonlyRepo> {
|
2021-04-11 16:23:16 +00:00
|
|
|
self.loader().load_at_head()
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 20:30:28 +00:00
|
|
|
pub fn reload_at(&self, operation: &Operation) -> Arc<ReadonlyRepo> {
|
2021-04-11 16:23:16 +00:00
|
|
|
self.loader().load_at(operation)
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:40:46 +00:00
|
|
|
pub struct RepoLoader {
|
|
|
|
wc_path: PathBuf,
|
|
|
|
repo_path: PathBuf,
|
|
|
|
repo_settings: RepoSettings,
|
|
|
|
store: Arc<StoreWrapper>,
|
|
|
|
op_store: Arc<dyn OpStore>,
|
2021-03-11 06:59:11 +00:00
|
|
|
op_heads_store: Arc<OpHeadsStore>,
|
2021-03-06 18:37:57 +00:00
|
|
|
index_store: Arc<IndexStore>,
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2021-05-16 17:33:52 +00:00
|
|
|
fn find_repo_dir(mut wc_dir: &Path) -> Option<PathBuf> {
|
|
|
|
loop {
|
|
|
|
let repo_path = wc_dir.join(".jj");
|
|
|
|
if repo_path.is_dir() {
|
|
|
|
return Some(repo_path);
|
|
|
|
}
|
|
|
|
if let Some(wc_dir_parent) = wc_dir.parent() {
|
|
|
|
wc_dir = wc_dir_parent;
|
|
|
|
} else {
|
|
|
|
return None;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:40:46 +00:00
|
|
|
impl RepoLoader {
|
2021-03-13 17:12:05 +00:00
|
|
|
pub fn init(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
|
|
|
) -> Result<RepoLoader, RepoLoadError> {
|
2021-05-16 17:33:52 +00:00
|
|
|
let repo_path = find_repo_dir(&wc_path).ok_or(RepoLoadError::NoRepoHere(wc_path))?;
|
|
|
|
let wc_path = repo_path.parent().unwrap().to_owned();
|
2021-05-16 16:36:48 +00:00
|
|
|
let store = StoreWrapper::load_store(&repo_path);
|
2021-01-04 17:40:46 +00:00
|
|
|
let repo_settings = user_settings.with_repo(&repo_path).unwrap();
|
|
|
|
let op_store: Arc<dyn OpStore> = Arc::new(SimpleOpStore::load(repo_path.join("op_store")));
|
2021-03-11 06:59:11 +00:00
|
|
|
let op_heads_store = Arc::new(OpHeadsStore::load(repo_path.join("op_heads")));
|
2021-03-06 18:37:57 +00:00
|
|
|
let index_store = Arc::new(IndexStore::load(repo_path.join("index")));
|
2021-01-04 17:40:46 +00:00
|
|
|
Ok(RepoLoader {
|
|
|
|
wc_path,
|
|
|
|
repo_path,
|
|
|
|
repo_settings,
|
|
|
|
store,
|
|
|
|
op_store,
|
2021-03-11 06:59:11 +00:00
|
|
|
op_heads_store,
|
2021-03-03 05:43:13 +00:00
|
|
|
index_store,
|
2021-01-04 17:40:46 +00:00
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-03-12 06:12:49 +00:00
|
|
|
pub fn store(&self) -> &Arc<StoreWrapper> {
|
|
|
|
&self.store
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn index_store(&self) -> &Arc<IndexStore> {
|
|
|
|
&self.index_store
|
|
|
|
}
|
|
|
|
|
2021-01-04 17:40:46 +00:00
|
|
|
pub fn op_store(&self) -> &Arc<dyn OpStore> {
|
|
|
|
&self.op_store
|
|
|
|
}
|
|
|
|
|
2021-05-07 06:29:53 +00:00
|
|
|
pub fn op_heads_store(&self) -> &Arc<OpHeadsStore> {
|
|
|
|
&self.op_heads_store
|
|
|
|
}
|
|
|
|
|
2021-05-19 20:30:28 +00:00
|
|
|
pub fn load_at_head(&self) -> Arc<ReadonlyRepo> {
|
2021-06-14 07:18:38 +00:00
|
|
|
let op = self.op_heads_store.get_single_op_head(self).unwrap();
|
2021-05-31 17:52:30 +00:00
|
|
|
let view = View::new(op.view().take_store_view());
|
2021-05-07 23:34:40 +00:00
|
|
|
self._finish_load(op, view)
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2021-05-19 20:30:28 +00:00
|
|
|
pub fn load_at(&self, op: &Operation) -> Arc<ReadonlyRepo> {
|
2021-05-31 17:52:30 +00:00
|
|
|
let view = View::new(op.view().take_store_view());
|
2021-05-07 23:34:40 +00:00
|
|
|
self._finish_load(op.clone(), view)
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2021-05-07 06:29:53 +00:00
|
|
|
pub fn create_from(
|
|
|
|
&self,
|
|
|
|
operation: Operation,
|
2021-05-31 17:52:30 +00:00
|
|
|
view: View,
|
2021-05-07 06:29:53 +00:00
|
|
|
working_copy: Arc<Mutex<WorkingCopy>>,
|
|
|
|
index: Arc<ReadonlyIndex>,
|
|
|
|
evolution: Option<Arc<ReadonlyEvolution>>,
|
|
|
|
) -> Arc<ReadonlyRepo> {
|
|
|
|
let repo = ReadonlyRepo {
|
|
|
|
repo_path: self.repo_path.clone(),
|
|
|
|
wc_path: self.wc_path.clone(),
|
|
|
|
store: self.store.clone(),
|
|
|
|
op_store: self.op_store.clone(),
|
|
|
|
op_heads_store: self.op_heads_store.clone(),
|
|
|
|
operation,
|
|
|
|
settings: self.repo_settings.clone(),
|
|
|
|
index_store: self.index_store.clone(),
|
|
|
|
index: Mutex::new(Some(index)),
|
|
|
|
working_copy,
|
|
|
|
view,
|
|
|
|
evolution: Mutex::new(evolution),
|
|
|
|
};
|
|
|
|
Arc::new(repo)
|
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
fn _finish_load(&self, operation: Operation, view: View) -> Arc<ReadonlyRepo> {
|
2021-01-04 17:40:46 +00:00
|
|
|
let working_copy = WorkingCopy::load(
|
|
|
|
self.store.clone(),
|
|
|
|
self.wc_path.clone(),
|
|
|
|
self.repo_path.join("working_copy"),
|
|
|
|
);
|
|
|
|
let repo = ReadonlyRepo {
|
2021-03-13 17:12:05 +00:00
|
|
|
repo_path: self.repo_path.clone(),
|
|
|
|
wc_path: self.wc_path.clone(),
|
|
|
|
store: self.store.clone(),
|
|
|
|
op_store: self.op_store.clone(),
|
|
|
|
op_heads_store: self.op_heads_store.clone(),
|
2021-05-07 23:34:40 +00:00
|
|
|
operation,
|
2021-03-13 17:12:05 +00:00
|
|
|
settings: self.repo_settings.clone(),
|
|
|
|
index_store: self.index_store.clone(),
|
2021-01-04 17:40:46 +00:00
|
|
|
index: Mutex::new(None),
|
|
|
|
working_copy: Arc::new(Mutex::new(working_copy)),
|
|
|
|
view,
|
2021-03-15 06:04:04 +00:00
|
|
|
evolution: Mutex::new(None),
|
2021-01-04 17:40:46 +00:00
|
|
|
};
|
2021-05-19 20:30:28 +00:00
|
|
|
Arc::new(repo)
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
pub struct MutableRepo {
|
|
|
|
base_repo: Arc<ReadonlyRepo>,
|
2021-03-08 08:04:26 +00:00
|
|
|
index: MutableIndex,
|
2021-05-31 17:52:30 +00:00
|
|
|
view: View,
|
2021-03-15 11:15:35 +00:00
|
|
|
evolution: Mutex<Option<MutableEvolution>>,
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
impl MutableRepo {
|
2021-02-01 02:00:14 +00:00
|
|
|
pub fn new(
|
2021-04-11 16:13:00 +00:00
|
|
|
base_repo: Arc<ReadonlyRepo>,
|
2021-01-31 02:50:27 +00:00
|
|
|
index: Arc<ReadonlyIndex>,
|
2021-05-31 17:52:30 +00:00
|
|
|
view: &View,
|
2021-03-15 11:15:35 +00:00
|
|
|
evolution: Option<&Arc<ReadonlyEvolution>>,
|
2021-04-11 16:13:00 +00:00
|
|
|
) -> Arc<MutableRepo> {
|
2021-02-01 02:00:14 +00:00
|
|
|
let mut_view = view.start_modification();
|
2021-01-31 02:50:27 +00:00
|
|
|
let mut_index = MutableIndex::incremental(index);
|
2021-03-15 11:15:35 +00:00
|
|
|
let mut_evolution = evolution.map(|evolution| evolution.start_modification());
|
|
|
|
Arc::new(MutableRepo {
|
2021-03-16 18:36:21 +00:00
|
|
|
base_repo,
|
2021-03-08 08:04:26 +00:00
|
|
|
index: mut_index,
|
|
|
|
view: mut_view,
|
2021-03-15 11:15:35 +00:00
|
|
|
evolution: Mutex::new(mut_evolution),
|
|
|
|
})
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 07:44:31 +00:00
|
|
|
pub fn as_repo_ref(&self) -> RepoRef {
|
2021-06-14 07:18:38 +00:00
|
|
|
RepoRef::Mutable(self)
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-04-11 16:13:00 +00:00
|
|
|
pub fn base_repo(&self) -> &Arc<ReadonlyRepo> {
|
|
|
|
&self.base_repo
|
2021-03-10 23:48:32 +00:00
|
|
|
}
|
|
|
|
|
2021-01-31 07:44:31 +00:00
|
|
|
pub fn store(&self) -> &Arc<StoreWrapper> {
|
2021-03-16 18:36:21 +00:00
|
|
|
self.base_repo.store()
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-10 23:48:32 +00:00
|
|
|
pub fn op_store(&self) -> &Arc<dyn OpStore> {
|
2021-03-16 18:36:21 +00:00
|
|
|
self.base_repo.op_store()
|
2021-01-31 02:50:27 +00:00
|
|
|
}
|
|
|
|
|
2021-03-10 23:48:32 +00:00
|
|
|
pub fn index(&self) -> &MutableIndex {
|
|
|
|
&self.index
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
pub fn view(&self) -> &View {
|
2021-03-08 08:04:26 +00:00
|
|
|
&self.view
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-05-31 17:52:30 +00:00
|
|
|
pub fn consume(self) -> (MutableIndex, View, Option<MutableEvolution>) {
|
2021-05-07 06:29:53 +00:00
|
|
|
(self.index, self.view, self.evolution.lock().unwrap().take())
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|
|
|
|
|
2021-03-15 11:15:35 +00:00
|
|
|
pub fn evolution(&self) -> &MutableEvolution {
|
|
|
|
let mut locked_evolution = self.evolution.lock().unwrap();
|
|
|
|
if locked_evolution.is_none() {
|
|
|
|
locked_evolution.replace(MutableEvolution::new(self));
|
|
|
|
}
|
|
|
|
let evolution = locked_evolution.as_ref().unwrap();
|
|
|
|
// Extend lifetime from lifetime of MutexGuard to lifetime of self. Safe because
|
|
|
|
// the value won't change again except for by invalidate_evolution(), which
|
|
|
|
// requires a mutable reference.
|
|
|
|
unsafe { std::mem::transmute(evolution) }
|
|
|
|
}
|
|
|
|
|
2021-03-15 21:42:07 +00:00
|
|
|
pub fn evolution_mut(&mut self) -> Option<&mut MutableEvolution> {
|
2021-03-15 11:15:35 +00:00
|
|
|
let mut locked_evolution = self.evolution.lock().unwrap();
|
2021-03-15 21:42:07 +00:00
|
|
|
let maybe_evolution = locked_evolution.as_mut();
|
2021-03-15 11:15:35 +00:00
|
|
|
// Extend lifetime from lifetime of MutexGuard to lifetime of self. Safe because
|
2021-03-14 18:08:31 +00:00
|
|
|
// the value won't change again except for by invalidate_evolution(), which
|
2021-03-15 11:15:35 +00:00
|
|
|
// requires a mutable reference.
|
2021-03-15 21:42:07 +00:00
|
|
|
unsafe { std::mem::transmute(maybe_evolution) }
|
2021-03-15 11:15:35 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn invalidate_evolution(&mut self) {
|
|
|
|
self.evolution.lock().unwrap().take();
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:11:34 +00:00
|
|
|
pub fn write_commit(&mut self, commit: store::Commit) -> Commit {
|
|
|
|
let commit = self.store().write_commit(commit);
|
|
|
|
self.add_head(&commit);
|
|
|
|
commit
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_checkout(&mut self, id: CommitId) {
|
2021-03-08 07:49:52 +00:00
|
|
|
self.view.set_checkout(id);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn check_out(&mut self, settings: &UserSettings, commit: &Commit) -> Commit {
|
2021-03-08 07:49:52 +00:00
|
|
|
let current_checkout_id = self.view.checkout().clone();
|
2021-03-07 23:11:34 +00:00
|
|
|
let current_checkout = self.store().get_commit(¤t_checkout_id).unwrap();
|
|
|
|
assert!(current_checkout.is_open(), "current checkout is closed");
|
|
|
|
if current_checkout.is_empty()
|
|
|
|
&& !(current_checkout.is_pruned() || self.evolution().is_obsolete(¤t_checkout_id))
|
|
|
|
{
|
|
|
|
// Prune the checkout we're leaving if it's empty.
|
|
|
|
// TODO: Also prune it if the only changes are conflicts that got materialized.
|
|
|
|
CommitBuilder::for_rewrite_from(settings, self.store(), ¤t_checkout)
|
|
|
|
.set_pruned(true)
|
|
|
|
.write_to_repo(self);
|
|
|
|
}
|
|
|
|
let store = self.store();
|
|
|
|
// Create a new tree with any conflicts resolved.
|
|
|
|
let mut tree_builder = store.tree_builder(commit.tree().id().clone());
|
|
|
|
for (path, conflict_id) in commit.tree().conflicts() {
|
|
|
|
let conflict = store.read_conflict(&conflict_id).unwrap();
|
|
|
|
let materialized_value =
|
|
|
|
conflicts::conflict_to_materialized_value(store, &path, &conflict);
|
|
|
|
tree_builder.set(path, materialized_value);
|
|
|
|
}
|
|
|
|
let tree_id = tree_builder.write_tree();
|
|
|
|
let open_commit;
|
|
|
|
if !commit.is_open() || &tree_id != commit.tree().id() {
|
|
|
|
// If the commit is closed, or if it had conflicts, create a new open commit on
|
|
|
|
// top
|
|
|
|
open_commit = CommitBuilder::for_open_commit(
|
|
|
|
settings,
|
|
|
|
self.store(),
|
|
|
|
commit.id().clone(),
|
|
|
|
tree_id,
|
|
|
|
)
|
|
|
|
.write_to_repo(self);
|
|
|
|
} else {
|
|
|
|
// Otherwise the commit was open and didn't have any conflicts, so just use
|
|
|
|
// that commit as is.
|
|
|
|
open_commit = commit.clone();
|
|
|
|
}
|
|
|
|
let id = open_commit.id().clone();
|
2021-03-08 07:49:52 +00:00
|
|
|
self.view.set_checkout(id);
|
2021-03-07 23:11:34 +00:00
|
|
|
open_commit
|
|
|
|
}
|
2021-03-16 23:21:31 +00:00
|
|
|
|
2021-03-14 18:08:31 +00:00
|
|
|
fn enforce_view_invariants(&mut self) {
|
2021-03-14 20:39:45 +00:00
|
|
|
let view = self.view.store_view_mut();
|
2021-03-14 18:08:31 +00:00
|
|
|
// TODO: This is surely terribly slow on large repos, at least in its current
|
2021-03-17 06:55:20 +00:00
|
|
|
// form. We should avoid calling it in most cases (avoid adding a head that's
|
|
|
|
// already reachable in the view).
|
2021-03-14 20:39:45 +00:00
|
|
|
view.public_head_ids = self
|
|
|
|
.index
|
|
|
|
.heads(view.public_head_ids.iter())
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
2021-03-14 18:08:31 +00:00
|
|
|
view.head_ids.extend(view.public_head_ids.iter().cloned());
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
for branch_target in view.branches.values() {
|
|
|
|
if let Some(ref_target) = &branch_target.local_target {
|
|
|
|
view.head_ids.extend(ref_target.removes());
|
|
|
|
view.head_ids.extend(ref_target.adds());
|
|
|
|
}
|
|
|
|
for ref_target in branch_target.remote_targets.values() {
|
|
|
|
view.head_ids.extend(ref_target.removes());
|
|
|
|
view.head_ids.extend(ref_target.adds());
|
2021-07-11 17:58:01 +00:00
|
|
|
}
|
|
|
|
}
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
for ref_target in view.tags.values() {
|
|
|
|
view.head_ids.extend(ref_target.removes());
|
|
|
|
view.head_ids.extend(ref_target.adds());
|
|
|
|
}
|
|
|
|
for ref_target in view.git_refs.values() {
|
|
|
|
view.head_ids.extend(ref_target.removes());
|
|
|
|
view.head_ids.extend(ref_target.adds());
|
|
|
|
}
|
2021-03-14 20:39:45 +00:00
|
|
|
view.head_ids = self
|
|
|
|
.index
|
|
|
|
.heads(view.head_ids.iter())
|
|
|
|
.iter()
|
|
|
|
.cloned()
|
|
|
|
.collect();
|
2021-03-14 18:08:31 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:11:34 +00:00
|
|
|
pub fn add_head(&mut self, head: &Commit) {
|
2021-03-08 07:49:52 +00:00
|
|
|
let current_heads = self.view.heads();
|
2021-03-07 23:11:34 +00:00
|
|
|
// Use incremental update for common case of adding a single commit on top a
|
|
|
|
// current head. TODO: Also use incremental update when adding a single
|
|
|
|
// commit on top a non-head.
|
|
|
|
if head
|
|
|
|
.parent_ids()
|
|
|
|
.iter()
|
|
|
|
.all(|parent_id| current_heads.contains(parent_id))
|
|
|
|
{
|
2021-03-08 07:49:52 +00:00
|
|
|
self.index.add_commit(head);
|
2021-03-15 22:29:34 +00:00
|
|
|
self.view.add_head(head.id());
|
2021-03-15 22:26:09 +00:00
|
|
|
for parent_id in head.parent_ids() {
|
|
|
|
self.view.remove_head(&parent_id);
|
|
|
|
}
|
2021-03-15 21:42:07 +00:00
|
|
|
if let Some(evolution) = self.evolution_mut() {
|
|
|
|
evolution.add_commit(head)
|
|
|
|
}
|
2021-03-07 23:11:34 +00:00
|
|
|
} else {
|
|
|
|
let missing_commits = topo_order_reverse(
|
|
|
|
vec![head.clone()],
|
|
|
|
Box::new(|commit: &Commit| commit.id().clone()),
|
|
|
|
Box::new(|commit: &Commit| -> Vec<Commit> {
|
|
|
|
commit
|
|
|
|
.parents()
|
|
|
|
.into_iter()
|
2021-03-08 07:49:52 +00:00
|
|
|
.filter(|parent| !self.index.has_id(parent.id()))
|
2021-03-07 23:11:34 +00:00
|
|
|
.collect()
|
|
|
|
}),
|
|
|
|
);
|
|
|
|
for missing_commit in missing_commits.iter().rev() {
|
2021-03-08 07:49:52 +00:00
|
|
|
self.index.add_commit(missing_commit);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
2021-03-15 22:29:34 +00:00
|
|
|
self.view.add_head(head.id());
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-03-15 11:15:35 +00:00
|
|
|
self.invalidate_evolution();
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_head(&mut self, head: &Commit) {
|
2021-03-15 22:29:34 +00:00
|
|
|
self.view.remove_head(head.id());
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-03-15 11:15:35 +00:00
|
|
|
self.invalidate_evolution();
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_public_head(&mut self, head: &Commit) {
|
2021-03-15 22:29:34 +00:00
|
|
|
self.view.add_public_head(head.id());
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-03-15 21:42:07 +00:00
|
|
|
if let Some(evolution) = self.evolution_mut() {
|
|
|
|
evolution.add_commit(head)
|
|
|
|
}
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_public_head(&mut self, head: &Commit) {
|
2021-03-15 22:29:34 +00:00
|
|
|
self.view.remove_public_head(head.id());
|
2021-03-15 11:15:35 +00:00
|
|
|
self.invalidate_evolution();
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-08-04 21:30:06 +00:00
|
|
|
pub fn get_branch(&mut self, name: &str) -> Option<&BranchTarget> {
|
|
|
|
self.view.get_branch(name)
|
|
|
|
}
|
|
|
|
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
pub fn set_branch(&mut self, name: String, target: BranchTarget) {
|
|
|
|
self.view.set_branch(name, target);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_branch(&mut self, name: &str) {
|
|
|
|
self.view.remove_branch(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_local_branch(&mut self, name: &str) -> Option<RefTarget> {
|
|
|
|
self.view.get_local_branch(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_local_branch(&mut self, name: String, target: RefTarget) {
|
|
|
|
self.view.set_local_branch(name, target);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_local_branch(&mut self, name: &str) {
|
|
|
|
self.view.remove_local_branch(name);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_remote_branch(&mut self, name: &str, remote_name: &str) -> Option<RefTarget> {
|
|
|
|
self.view.get_remote_branch(name, remote_name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_remote_branch(&mut self, name: String, remote_name: String, target: RefTarget) {
|
|
|
|
self.view.set_remote_branch(name, remote_name, target);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_remote_branch(&mut self, name: &str, remote_name: &str) {
|
|
|
|
self.view.remove_remote_branch(name, remote_name);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn get_tag(&mut self, name: &str) -> Option<RefTarget> {
|
|
|
|
self.view.get_tag(name)
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_tag(&mut self, name: String, target: RefTarget) {
|
|
|
|
self.view.set_tag(name, target);
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_tag(&mut self, name: &str) {
|
|
|
|
self.view.remove_tag(name);
|
|
|
|
}
|
|
|
|
|
2021-08-04 15:42:16 +00:00
|
|
|
pub fn set_git_ref(&mut self, name: String, target: RefTarget) {
|
|
|
|
self.view.set_git_ref(name, target);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_git_ref(&mut self, name: &str) {
|
2021-03-08 07:49:52 +00:00
|
|
|
self.view.remove_git_ref(name);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn set_view(&mut self, data: op_store::View) {
|
2021-03-08 07:49:52 +00:00
|
|
|
self.view.set_view(data);
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-03-15 11:15:35 +00:00
|
|
|
self.invalidate_evolution();
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
2021-03-13 17:12:05 +00:00
|
|
|
|
|
|
|
pub fn merge(&mut self, base_repo: &ReadonlyRepo, other_repo: &ReadonlyRepo) {
|
|
|
|
// First, merge the index, so we can take advantage of a valid index when
|
|
|
|
// merging the view. Merging in base_repo's index isn't typically
|
|
|
|
// necessary, but it can be if base_repo is ahead of either self or other_repo
|
|
|
|
// (e.g. because we're undoing an operation that hasn't been published).
|
2021-06-14 07:18:38 +00:00
|
|
|
self.index.merge_in(base_repo.index());
|
|
|
|
self.index.merge_in(other_repo.index());
|
2021-03-13 17:12:05 +00:00
|
|
|
|
2021-07-19 06:04:21 +00:00
|
|
|
self.view
|
|
|
|
.merge(self.index.as_index_ref(), &base_repo.view, &other_repo.view);
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-03-13 17:12:05 +00:00
|
|
|
|
2021-03-15 11:15:35 +00:00
|
|
|
self.invalidate_evolution();
|
2021-03-13 17:12:05 +00:00
|
|
|
}
|
view: add support for ref-based branches and tags to model
I've finally decided to copy Git's branching model (issue #21), except
that I'm letting the name identify the branch across
remotes. Actually, now that I think about, that makes them more like
Mercurial's "bookmarks". Each branch will record the commit it points
to locally, as well as the commits it points to on each remote (as far
as the repo knows, of course). Those records are effectively the same
thing as Git's "remote-tracking branches"; the difference is that we
consider them the same branch. Consequently, when you pull a new
branch from a remote, we'll create that branch locally.
For example, if you pull branch "main" from a remote called "origin",
that will result in a local branch called "main", and also a record of
the position on the remote, which we'll show as "main@origin" in the
CLI (not part of this commit). If you then update the branch locally
and also pull a new target for it from "origin", the local "main"
branch will be divergent. I plan to make it so that pushing "main"
will update the remote's "main" iff it was currently at "main@origin"
(i.e. like using Git's `git push --force-with-lease`).
This commit adds a place to store information about branches in the
view model. The existing git_refs field will be used as input for the
branch information. For example, we can use it to tell if
"refs/heads/main" has changed and how it has changed. We will then use
that ref diff to update our own record of the "main" branch. That will
come later. In order to let git_refs take a back seat, I've also added
tags (like Git's lightweight tags) to the model in this commit.
I haven't ruled out *also* having some more persistent type of
branches (like Mercurials branches or topics).
2021-07-15 08:31:48 +00:00
|
|
|
|
|
|
|
pub fn merge_single_ref(
|
|
|
|
&mut self,
|
|
|
|
ref_name: &RefName,
|
|
|
|
base_target: Option<&RefTarget>,
|
|
|
|
other_target: Option<&RefTarget>,
|
|
|
|
) {
|
|
|
|
self.view.merge_single_ref(
|
|
|
|
self.index.as_index_ref(),
|
|
|
|
ref_name,
|
|
|
|
base_target,
|
|
|
|
other_target,
|
|
|
|
);
|
|
|
|
}
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|