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.
|
|
|
|
|
2021-09-29 15:44:00 +00:00
|
|
|
use std::collections::{HashMap, HashSet};
|
2020-12-12 08:00:42 +00:00
|
|
|
use std::fmt::{Debug, Formatter};
|
|
|
|
use std::fs;
|
|
|
|
use std::fs::File;
|
2021-10-14 06:42:25 +00:00
|
|
|
use std::io::Read;
|
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-10-14 06:42:25 +00:00
|
|
|
use crate::backend::{BackendError, CommitId};
|
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-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;
|
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;
|
2021-09-29 15:44:00 +00:00
|
|
|
use crate::rewrite::DescendantRebaser;
|
2020-12-12 08:00:42 +00:00
|
|
|
use crate::settings::{RepoSettings, UserSettings};
|
2021-02-27 07:37:42 +00:00
|
|
|
use crate::simple_op_store::SimpleOpStore;
|
2021-09-12 06:52:38 +00:00
|
|
|
use crate::store::Store;
|
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-11-04 05:54:59 +00:00
|
|
|
use crate::{backend, op_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),
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
impl From<BackendError> for RepoError {
|
|
|
|
fn from(err: BackendError) -> Self {
|
2020-12-12 08:00:42 +00:00
|
|
|
match err {
|
2021-09-12 06:52:38 +00:00
|
|
|
BackendError::NotFound => RepoError::NotFound,
|
|
|
|
BackendError::Other(description) => RepoError::Other(description),
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
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> {
|
2021-09-12 06:52:38 +00:00
|
|
|
pub fn store(&self) -> &Arc<Store> {
|
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
|
|
|
}
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub struct ReadonlyRepo {
|
|
|
|
repo_path: PathBuf,
|
|
|
|
wc_path: PathBuf,
|
2021-09-12 06:52:38 +00:00
|
|
|
store: Arc<Store>,
|
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,
|
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)?;
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_local(repo_path.join("store"));
|
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
/// Initializes a repo with a new Git backend 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)?;
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_internal_git(repo_path.join("store"));
|
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
2020-12-28 08:26:37 +00:00
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
/// Initializes a repo with an existing Git backend at the specified path
|
2020-12-28 08:26:37 +00:00
|
|
|
pub fn init_external_git(
|
2020-12-12 08:00:42 +00:00
|
|
|
settings: &UserSettings,
|
|
|
|
wc_path: PathBuf,
|
2021-10-14 06:42:25 +00:00
|
|
|
git_repo_path: PathBuf,
|
2021-05-19 21:30:50 +00:00
|
|
|
) -> Result<Arc<ReadonlyRepo>, RepoInitError> {
|
|
|
|
let repo_path = ReadonlyRepo::init_repo_dir(&wc_path)?;
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_external_git(repo_path.join("store"), git_repo_path);
|
|
|
|
Ok(ReadonlyRepo::init(settings, repo_path, wc_path, store))
|
2021-05-19 21:30:50 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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();
|
2021-10-14 05:50:50 +00:00
|
|
|
fs::create_dir(repo_path.join("store")).unwrap();
|
2021-10-14 05:37:30 +00:00
|
|
|
fs::create_dir(repo_path.join("working_copy")).unwrap();
|
|
|
|
fs::create_dir(repo_path.join("view")).unwrap();
|
|
|
|
fs::create_dir(repo_path.join("op_store")).unwrap();
|
|
|
|
fs::create_dir(repo_path.join("op_heads")).unwrap();
|
|
|
|
fs::create_dir(repo_path.join("index")).unwrap();
|
2021-05-19 21:30:50 +00:00
|
|
|
Ok(repo_path)
|
|
|
|
}
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn init(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
repo_path: PathBuf,
|
|
|
|
wc_path: PathBuf,
|
2021-10-14 06:42:25 +00:00
|
|
|
store: Arc<Store>,
|
2020-12-12 08:00:42 +00:00
|
|
|
) -> Arc<ReadonlyRepo> {
|
|
|
|
let repo_settings = user_settings.with_repo(&repo_path).unwrap();
|
|
|
|
|
2021-11-17 18:05:25 +00:00
|
|
|
let mut working_copy = WorkingCopy::init(
|
2020-12-19 07:50:10 +00:00
|
|
|
store.clone(),
|
|
|
|
wc_path.clone(),
|
|
|
|
repo_path.join("working_copy"),
|
|
|
|
);
|
2020-12-12 08:00:42 +00:00
|
|
|
|
|
|
|
let signature = signature(user_settings);
|
2021-09-12 06:52:38 +00:00
|
|
|
let checkout_commit = backend::Commit {
|
2020-12-12 08:00:42 +00:00
|
|
|
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,
|
|
|
|
};
|
|
|
|
let checkout_commit = store.write_commit(checkout_commit);
|
2021-02-27 07:37:42 +00:00
|
|
|
|
|
|
|
let op_store: Arc<dyn OpStore> = Arc::new(SimpleOpStore::init(repo_path.join("op_store")));
|
2021-03-06 18:37:57 +00:00
|
|
|
|
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-10-14 05:37:30 +00:00
|
|
|
let (op_heads_store, init_op) =
|
|
|
|
OpHeadsStore::init(repo_path.join("op_heads"), &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
|
|
|
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
|
|
|
|
2021-11-17 18:05:25 +00:00
|
|
|
working_copy
|
|
|
|
.check_out(checkout_commit)
|
|
|
|
.expect("failed to check out root commit");
|
|
|
|
|
|
|
|
Arc::new(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-11-17 18:05:25 +00:00
|
|
|
})
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
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-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
|
2021-09-16 15:19:17 +00:00
|
|
|
.get_index_at_op(&self.operation, &self.store),
|
2021-05-07 23:34:40 +00:00
|
|
|
);
|
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()
|
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
pub fn store(&self) -> &Arc<Store> {
|
2020-12-12 08:00:42 +00:00
|
|
|
&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-10-07 06:05:10 +00:00
|
|
|
let mut_repo = MutableRepo::new(self.clone(), self.index().clone(), &self.view);
|
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,
|
2021-09-12 06:52:38 +00:00
|
|
|
store: Arc<Store>,
|
2021-01-04 17:40:46 +00:00
|
|
|
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-10-14 05:50:50 +00:00
|
|
|
let store_path = repo_path.join("store");
|
|
|
|
if store_path.is_file() {
|
|
|
|
// This is the old format. Let's be nice and upgrade any existing repos.
|
|
|
|
// TODO: Delete this in early 2022 or so
|
|
|
|
println!("The repo format has changed. Upgrading...");
|
|
|
|
let mut buf = vec![];
|
|
|
|
{
|
|
|
|
let mut store_file = File::open(&store_path).unwrap();
|
|
|
|
store_file.read_to_end(&mut buf).unwrap();
|
|
|
|
}
|
|
|
|
let contents = String::from_utf8(buf).unwrap();
|
|
|
|
assert!(contents.starts_with("git: "));
|
|
|
|
let git_backend_path_str = contents[5..].to_string();
|
|
|
|
fs::remove_file(&store_path).unwrap();
|
|
|
|
fs::create_dir(&store_path).unwrap();
|
2021-10-14 06:56:46 +00:00
|
|
|
if repo_path.join("git").is_dir() {
|
|
|
|
fs::rename(repo_path.join("git"), store_path.join("git")).unwrap();
|
|
|
|
}
|
2021-10-14 05:50:50 +00:00
|
|
|
fs::write(store_path.join("git_target"), &git_backend_path_str).unwrap();
|
|
|
|
println!("Done. .jj/git is now .jj/store/git");
|
|
|
|
}
|
|
|
|
let store = Store::load_store(repo_path.join("store"));
|
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-09-12 06:52:38 +00:00
|
|
|
pub fn store(&self) -> &Arc<Store> {
|
2021-03-12 06:12:49 +00:00
|
|
|
&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>,
|
|
|
|
) -> 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,
|
|
|
|
};
|
|
|
|
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-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-09-29 15:44:00 +00:00
|
|
|
rewritten_commits: HashMap<CommitId, HashSet<CommitId>>,
|
|
|
|
abandoned_commits: HashSet<CommitId>,
|
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-09-29 16:38:54 +00:00
|
|
|
) -> 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-09-29 16:38:54 +00:00
|
|
|
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-09-29 15:44:00 +00:00
|
|
|
rewritten_commits: Default::default(),
|
|
|
|
abandoned_commits: Default::default(),
|
2021-09-29 16:38:54 +00:00
|
|
|
}
|
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-09-12 06:52:38 +00:00
|
|
|
pub fn store(&self) -> &Arc<Store> {
|
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-10-07 06:05:10 +00:00
|
|
|
pub fn consume(self) -> (MutableIndex, View) {
|
|
|
|
(self.index, self.view)
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
|
|
|
|
2021-09-12 06:52:38 +00:00
|
|
|
pub fn write_commit(&mut self, commit: backend::Commit) -> Commit {
|
2021-03-07 23:11:34 +00:00
|
|
|
let commit = self.store().write_commit(commit);
|
|
|
|
self.add_head(&commit);
|
|
|
|
commit
|
|
|
|
}
|
|
|
|
|
2021-09-29 15:44:00 +00:00
|
|
|
/// Record a commit as having been rewritten in this transaction. This
|
|
|
|
/// record is used by `rebase_descendants()`.
|
|
|
|
///
|
|
|
|
/// Rewritten commits don't have to be recorded here. This is just a
|
|
|
|
/// convenient place to record it. It won't matter after the transaction
|
|
|
|
/// has been committed.
|
|
|
|
pub fn record_rewritten_commit(&mut self, old_id: CommitId, new_id: CommitId) {
|
|
|
|
self.rewritten_commits
|
|
|
|
.entry(old_id)
|
|
|
|
.or_default()
|
|
|
|
.insert(new_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Record a commit as having been abandoned in this transaction. This
|
|
|
|
/// record is used by `rebase_descendants()`.
|
|
|
|
///
|
|
|
|
/// Abandoned commits don't have to be recorded here. This is just a
|
|
|
|
/// convenient place to record it. It won't matter after the transaction
|
|
|
|
/// has been committed.
|
|
|
|
pub fn record_abandoned_commit(&mut self, old_id: CommitId) {
|
|
|
|
self.abandoned_commits.insert(old_id);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Creates a `DescendantRebaser` to rebase descendants of the recorded
|
|
|
|
/// rewritten and abandoned commits. Clears the records of rewritten and
|
|
|
|
/// abandoned commits.
|
|
|
|
pub fn create_descendant_rebaser<'settings, 'repo>(
|
|
|
|
&'repo mut self,
|
|
|
|
settings: &'settings UserSettings,
|
|
|
|
) -> DescendantRebaser<'settings, 'repo> {
|
2021-10-02 15:39:00 +00:00
|
|
|
DescendantRebaser::new(
|
|
|
|
settings,
|
|
|
|
self,
|
|
|
|
self.rewritten_commits.clone(),
|
|
|
|
self.abandoned_commits.clone(),
|
|
|
|
)
|
2021-09-29 15:44:00 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:11:34 +00:00
|
|
|
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");
|
2021-10-07 03:48:35 +00:00
|
|
|
if current_checkout.is_empty() {
|
2021-09-20 04:19:47 +00:00
|
|
|
// Abandon the checkout we're leaving if it's empty.
|
2021-09-29 23:00:54 +00:00
|
|
|
self.record_abandoned_commit(current_checkout_id);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
let open_commit;
|
2021-11-04 05:54:59 +00:00
|
|
|
if !commit.is_open() {
|
|
|
|
// If the commit is closed, create a new open commit on top
|
2021-03-07 23:11:34 +00:00
|
|
|
open_commit = CommitBuilder::for_open_commit(
|
|
|
|
settings,
|
|
|
|
self.store(),
|
|
|
|
commit.id().clone(),
|
2021-11-04 05:54:59 +00:00
|
|
|
commit.tree().id().clone(),
|
2021-03-07 23:11:34 +00:00
|
|
|
)
|
|
|
|
.write_to_repo(self);
|
|
|
|
} else {
|
2021-11-04 05:54:59 +00:00
|
|
|
// Otherwise the commit was open, so just use that commit as is.
|
2021-03-07 23:11:34 +00:00
|
|
|
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());
|
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-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-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-25 16:25:42 +00:00
|
|
|
pub fn remove_head(&mut self, head: &CommitId) {
|
|
|
|
self.view.remove_head(head);
|
2021-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
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-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-09-25 16:25:42 +00:00
|
|
|
pub fn remove_public_head(&mut self, head: &CommitId) {
|
|
|
|
self.view.remove_public_head(head);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_branch(&self, name: &str) -> Option<&BranchTarget> {
|
2021-08-04 21:30:06 +00:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_local_branch(&self, name: &str) -> Option<RefTarget> {
|
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
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_remote_branch(&self, name: &str, remote_name: &str) -> Option<RefTarget> {
|
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
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_tag(&self, name: &str) -> Option<RefTarget> {
|
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
|
|
|
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-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
|
|
|
}
|
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
|
|
|
}
|