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-12-01 21:42:53 +00:00
|
|
|
use std::cell::RefCell;
|
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;
|
2021-12-01 21:42:53 +00:00
|
|
|
use std::ops::Deref;
|
2021-05-16 17:33:52 +00:00
|
|
|
use std::path::{Path, PathBuf};
|
2021-11-17 17:19:41 +00:00
|
|
|
use std::sync::{Arc, Mutex};
|
2020-12-12 08:00:42 +00:00
|
|
|
|
2022-03-17 03:58:04 +00:00
|
|
|
use itertools::Itertools;
|
2020-12-12 08:00:42 +00:00
|
|
|
use thiserror::Error;
|
|
|
|
|
2022-03-17 03:58:04 +00:00
|
|
|
use crate::backend::{BackendError, ChangeId, CommitId};
|
2021-03-07 23:11:34 +00:00
|
|
|
use crate::commit::Commit;
|
2022-01-30 20:27:18 +00:00
|
|
|
use crate::commit_builder::CommitBuilder;
|
2022-03-26 17:16:23 +00:00
|
|
|
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;
|
2022-03-20 18:43:36 +00:00
|
|
|
use crate::op_heads_store::{LockedOpHeads, OpHeads, OpHeadsStore};
|
2021-11-17 21:06:02 +00:00
|
|
|
use crate::op_store::{BranchTarget, OpStore, OperationId, RefTarget, WorkspaceId};
|
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;
|
2022-03-26 17:38:18 +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};
|
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-11-29 05:33:37 +00:00
|
|
|
pub fn base_repo(&self) -> &ReadonlyRepo {
|
|
|
|
match self {
|
|
|
|
RepoRef::Readonly(repo) => repo,
|
|
|
|
RepoRef::Mutable(repo) => repo.base_repo.as_ref(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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,
|
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>>>,
|
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("store", &self.store)
|
|
|
|
.finish()
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl ReadonlyRepo {
|
2021-11-24 07:54:30 +00:00
|
|
|
pub fn init_local(settings: &UserSettings, repo_path: PathBuf) -> Arc<ReadonlyRepo> {
|
2022-03-30 05:14:39 +00:00
|
|
|
let repo_path = repo_path.canonicalize().unwrap();
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init_repo_dir(&repo_path);
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_local(repo_path.join("store"));
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init(settings, repo_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-11-24 07:54:30 +00:00
|
|
|
pub fn init_internal_git(settings: &UserSettings, repo_path: PathBuf) -> Arc<ReadonlyRepo> {
|
2022-03-30 05:14:39 +00:00
|
|
|
let repo_path = repo_path.canonicalize().unwrap();
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init_repo_dir(&repo_path);
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_internal_git(repo_path.join("store"));
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init(settings, repo_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,
|
2021-11-24 07:54:30 +00:00
|
|
|
repo_path: PathBuf,
|
2021-10-14 06:42:25 +00:00
|
|
|
git_repo_path: PathBuf,
|
2021-11-24 07:54:30 +00:00
|
|
|
) -> Arc<ReadonlyRepo> {
|
2022-03-30 05:14:39 +00:00
|
|
|
let repo_path = repo_path.canonicalize().unwrap();
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init_repo_dir(&repo_path);
|
2021-10-14 06:42:25 +00:00
|
|
|
let store = Store::init_external_git(repo_path.join("store"), git_repo_path);
|
2021-11-24 07:54:30 +00:00
|
|
|
ReadonlyRepo::init(settings, repo_path, store)
|
2021-05-19 21:30:50 +00:00
|
|
|
}
|
|
|
|
|
2021-11-24 07:54:30 +00:00
|
|
|
fn init_repo_dir(repo_path: &Path) {
|
|
|
|
fs::create_dir(repo_path.join("store")).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();
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
fn init(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
repo_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-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-11-17 21:06:02 +00:00
|
|
|
let mut root_view = op_store::View::default();
|
2022-01-30 20:27:18 +00:00
|
|
|
root_view.head_ids.insert(store.root_commit_id().clone());
|
2021-04-19 05:52:31 +00:00
|
|
|
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
|
|
|
Arc::new(ReadonlyRepo {
|
2021-03-03 05:43:13 +00:00
|
|
|
repo_path,
|
2020-12-12 08:00:42 +00:00
|
|
|
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),
|
|
|
|
view,
|
2021-11-17 18:05:25 +00:00
|
|
|
})
|
2020-12-12 08:00:42 +00:00
|
|
|
}
|
|
|
|
|
2022-04-28 05:30:03 +00:00
|
|
|
pub fn load_at_head(
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
repo_path: PathBuf,
|
|
|
|
) -> Result<Arc<ReadonlyRepo>, BackendError> {
|
2022-03-20 18:43:36 +00:00
|
|
|
RepoLoader::init(user_settings, repo_path)
|
|
|
|
.load_at_head()
|
2022-03-17 03:58:04 +00:00
|
|
|
.resolve(user_settings)
|
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 {
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-04-28 05:30:03 +00:00
|
|
|
pub fn reload_at_head(
|
|
|
|
&self,
|
|
|
|
user_settings: &UserSettings,
|
|
|
|
) -> Result<Arc<ReadonlyRepo>, BackendError> {
|
2022-03-17 03:58:04 +00:00
|
|
|
self.loader().load_at_head().resolve(user_settings)
|
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
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-20 18:43:36 +00:00
|
|
|
pub enum RepoAtHead {
|
|
|
|
Single(Arc<ReadonlyRepo>),
|
|
|
|
Unresolved(Box<UnresolvedHeadRepo>),
|
|
|
|
}
|
|
|
|
|
|
|
|
impl RepoAtHead {
|
2022-04-28 05:30:03 +00:00
|
|
|
pub fn resolve(self, user_settings: &UserSettings) -> Result<Arc<ReadonlyRepo>, BackendError> {
|
2022-03-20 18:43:36 +00:00
|
|
|
match self {
|
2022-04-28 05:30:03 +00:00
|
|
|
RepoAtHead::Single(repo) => Ok(repo),
|
2022-03-17 03:58:04 +00:00
|
|
|
RepoAtHead::Unresolved(unresolved) => unresolved.resolve(user_settings),
|
2022-03-20 18:43:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct UnresolvedHeadRepo {
|
2022-03-26 17:44:28 +00:00
|
|
|
pub repo_loader: RepoLoader,
|
|
|
|
pub locked_op_heads: LockedOpHeads,
|
|
|
|
pub op_heads: Vec<Operation>,
|
2022-03-20 18:43:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
impl UnresolvedHeadRepo {
|
2022-04-28 05:30:03 +00:00
|
|
|
pub fn resolve(self, user_settings: &UserSettings) -> Result<Arc<ReadonlyRepo>, BackendError> {
|
2022-03-26 17:38:18 +00:00
|
|
|
let base_repo = self.repo_loader.load_at(&self.op_heads[0]);
|
|
|
|
let mut tx = base_repo.start_transaction("resolve concurrent operations");
|
|
|
|
for other_op_head in self.op_heads.into_iter().skip(1) {
|
2022-03-26 20:42:53 +00:00
|
|
|
tx.merge_operation(other_op_head);
|
2022-04-28 05:30:03 +00:00
|
|
|
tx.mut_repo().rebase_descendants(user_settings)?;
|
2022-03-26 17:38:18 +00:00
|
|
|
}
|
|
|
|
let merged_repo = tx.write().leave_unpublished();
|
2022-03-20 18:43:36 +00:00
|
|
|
self.locked_op_heads.finish(merged_repo.operation());
|
2022-04-28 05:30:03 +00:00
|
|
|
Ok(merged_repo)
|
2022-03-20 18:43:36 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Clone)]
|
2021-01-04 17:40:46 +00:00
|
|
|
pub struct RepoLoader {
|
|
|
|
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
|
|
|
}
|
|
|
|
|
|
|
|
impl RepoLoader {
|
2021-11-21 07:03:54 +00:00
|
|
|
pub fn init(user_settings: &UserSettings, repo_path: PathBuf) -> Self {
|
2021-10-14 05:50:50 +00:00
|
|
|
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-11-21 07:03:54 +00:00
|
|
|
Self {
|
2021-01-04 17:40:46 +00:00
|
|
|
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-11-21 07:03:54 +00:00
|
|
|
}
|
2021-01-04 17:40:46 +00:00
|
|
|
}
|
|
|
|
|
2021-11-18 05:58:36 +00:00
|
|
|
pub fn repo_path(&self) -> &PathBuf {
|
|
|
|
&self.repo_path
|
|
|
|
}
|
|
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2022-03-20 18:43:36 +00:00
|
|
|
pub fn load_at_head(&self) -> RepoAtHead {
|
2022-04-21 00:08:03 +00:00
|
|
|
let op_heads = self.op_heads_store.get_heads(&self.op_store).unwrap();
|
2022-03-19 06:11:10 +00:00
|
|
|
match op_heads {
|
|
|
|
OpHeads::Single(op) => {
|
|
|
|
let view = View::new(op.view().take_store_view());
|
2022-03-20 18:43:36 +00:00
|
|
|
RepoAtHead::Single(self._finish_load(op, view))
|
2022-03-19 06:11:10 +00:00
|
|
|
}
|
|
|
|
OpHeads::Unresolved {
|
|
|
|
locked_op_heads,
|
|
|
|
op_heads,
|
2022-03-20 18:43:36 +00:00
|
|
|
} => RepoAtHead::Unresolved(Box::new(UnresolvedHeadRepo {
|
|
|
|
repo_loader: self.clone(),
|
|
|
|
locked_op_heads,
|
|
|
|
op_heads,
|
|
|
|
})),
|
2022-03-19 06:11:10 +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
|
|
|
index: Arc<ReadonlyIndex>,
|
|
|
|
) -> Arc<ReadonlyRepo> {
|
|
|
|
let repo = ReadonlyRepo {
|
|
|
|
repo_path: self.repo_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)),
|
|
|
|
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 repo = ReadonlyRepo {
|
2021-03-13 17:12:05 +00:00
|
|
|
repo_path: self.repo_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),
|
|
|
|
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-12-01 21:42:53 +00:00
|
|
|
view: RefCell<View>,
|
|
|
|
view_dirty: bool,
|
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 {
|
2022-01-05 23:08:13 +00:00
|
|
|
let mut_view = view.clone();
|
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,
|
2021-12-01 21:42:53 +00:00
|
|
|
view: RefCell::new(mut_view),
|
|
|
|
view_dirty: false,
|
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-12-01 21:42:53 +00:00
|
|
|
self.enforce_view_invariants();
|
|
|
|
let view_borrow = self.view.borrow();
|
|
|
|
let view = view_borrow.deref();
|
2021-12-02 00:30:07 +00:00
|
|
|
unsafe { std::mem::transmute(view) }
|
2021-01-31 07:44:31 +00:00
|
|
|
}
|
2021-12-01 16:45:09 +00:00
|
|
|
|
2021-12-01 21:42:53 +00:00
|
|
|
fn view_mut(&mut self) -> &mut View {
|
2021-12-02 00:30:07 +00:00
|
|
|
self.view.get_mut()
|
|
|
|
}
|
|
|
|
|
2021-12-01 16:45:09 +00:00
|
|
|
pub fn has_changes(&self) -> bool {
|
2021-12-05 20:44:08 +00:00
|
|
|
self.enforce_view_invariants();
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.borrow().deref() != &self.base_repo.view
|
2021-12-01 16:45:09 +00:00
|
|
|
}
|
2021-01-31 07:44:31 +00:00
|
|
|
|
2021-10-07 06:05:10 +00:00
|
|
|
pub fn consume(self) -> (MutableIndex, View) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.enforce_view_invariants();
|
|
|
|
(self.index, self.view.into_inner())
|
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);
|
|
|
|
}
|
|
|
|
|
2022-01-28 00:41:07 +00:00
|
|
|
pub fn clear_rewritten_commits(&mut self) {
|
|
|
|
self.rewritten_commits.clear();
|
|
|
|
}
|
|
|
|
|
2021-09-29 15:44:00 +00:00
|
|
|
/// 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);
|
|
|
|
}
|
|
|
|
|
2022-01-28 00:41:07 +00:00
|
|
|
pub fn clear_abandoned_commits(&mut self) {
|
|
|
|
self.abandoned_commits.clear();
|
|
|
|
}
|
|
|
|
|
2022-03-21 05:16:53 +00:00
|
|
|
pub fn has_rewrites(&self) -> bool {
|
|
|
|
!(self.rewritten_commits.is_empty() && self.abandoned_commits.is_empty())
|
|
|
|
}
|
|
|
|
|
2021-09-29 15:44:00 +00:00
|
|
|
/// Creates a `DescendantRebaser` to rebase descendants of the recorded
|
2022-01-28 00:41:07 +00:00
|
|
|
/// rewritten and abandoned commits.
|
2021-09-29 15:44:00 +00:00
|
|
|
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
|
|
|
}
|
|
|
|
|
2022-04-28 05:30:03 +00:00
|
|
|
pub fn rebase_descendants(&mut self, settings: &UserSettings) -> Result<usize, BackendError> {
|
2022-03-21 05:16:53 +00:00
|
|
|
if !self.has_rewrites() {
|
2022-03-23 18:30:17 +00:00
|
|
|
// Optimization
|
2022-04-28 05:30:03 +00:00
|
|
|
return Ok(0);
|
2022-03-23 18:30:17 +00:00
|
|
|
}
|
2022-01-27 06:18:39 +00:00
|
|
|
let mut rebaser = self.create_descendant_rebaser(settings);
|
2022-04-28 05:30:03 +00:00
|
|
|
rebaser.rebase_all()?;
|
|
|
|
Ok(rebaser.rebased().len())
|
2022-01-27 06:18:39 +00:00
|
|
|
}
|
|
|
|
|
2021-11-26 07:18:43 +00:00
|
|
|
pub fn set_checkout(&mut self, workspace_id: WorkspaceId, commit_id: CommitId) {
|
|
|
|
self.view_mut().set_checkout(workspace_id, commit_id);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-11-26 07:12:00 +00:00
|
|
|
pub fn remove_checkout(&mut self, workspace_id: &WorkspaceId) {
|
|
|
|
self.view_mut().remove_checkout(workspace_id);
|
|
|
|
}
|
|
|
|
|
2021-11-26 07:18:43 +00:00
|
|
|
pub fn check_out(
|
|
|
|
&mut self,
|
|
|
|
workspace_id: WorkspaceId,
|
|
|
|
settings: &UserSettings,
|
|
|
|
commit: &Commit,
|
|
|
|
) -> Commit {
|
2022-01-31 01:54:05 +00:00
|
|
|
let maybe_current_checkout_id = self.view.borrow().get_checkout(&workspace_id).cloned();
|
|
|
|
if let Some(current_checkout_id) = maybe_current_checkout_id {
|
|
|
|
let current_checkout = self.store().get_commit(¤t_checkout_id).unwrap();
|
|
|
|
assert!(current_checkout.is_open(), "current checkout is closed");
|
2022-05-21 17:23:46 +00:00
|
|
|
if current_checkout.is_empty()
|
|
|
|
&& current_checkout.description().is_empty()
|
|
|
|
&& self.view().heads().contains(current_checkout.id())
|
|
|
|
{
|
2022-03-26 21:53:51 +00:00
|
|
|
// Abandon the checkout we're leaving if it's empty and a head commit
|
2022-01-31 01:54:05 +00:00
|
|
|
self.record_abandoned_commit(current_checkout_id);
|
|
|
|
}
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
2021-12-09 08:12:10 +00:00
|
|
|
let open_commit = if !commit.is_open() {
|
2021-11-04 05:54:59 +00:00
|
|
|
// If the commit is closed, create a new open commit on top
|
2021-12-09 08:12:10 +00:00
|
|
|
CommitBuilder::for_open_commit(
|
2021-03-07 23:11:34 +00:00
|
|
|
settings,
|
|
|
|
self.store(),
|
|
|
|
commit.id().clone(),
|
2022-04-22 03:59:41 +00:00
|
|
|
commit.tree_id().clone(),
|
2021-03-07 23:11:34 +00:00
|
|
|
)
|
2021-12-09 08:12:10 +00:00
|
|
|
.write_to_repo(self)
|
2021-03-07 23:11:34 +00:00
|
|
|
} else {
|
2021-11-04 05:54:59 +00:00
|
|
|
// Otherwise the commit was open, so just use that commit as is.
|
2021-12-09 08:12:10 +00:00
|
|
|
commit.clone()
|
|
|
|
};
|
2021-11-26 07:18:43 +00:00
|
|
|
let commit_id = open_commit.id().clone();
|
|
|
|
self.set_checkout(workspace_id, commit_id);
|
2021-03-07 23:11:34 +00:00
|
|
|
open_commit
|
|
|
|
}
|
2021-03-16 23:21:31 +00:00
|
|
|
|
2021-12-01 21:42:53 +00:00
|
|
|
fn enforce_view_invariants(&self) {
|
|
|
|
if !self.view_dirty {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
let mut view_borrow_mut = self.view.borrow_mut();
|
|
|
|
let view = view_borrow_mut.store_view_mut();
|
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-12-01 21:42:53 +00:00
|
|
|
let current_heads = self.view.get_mut().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-12-01 21:42:53 +00:00
|
|
|
self.view.get_mut().add_head(head.id());
|
2021-03-15 22:26:09 +00:00
|
|
|
for parent_id in head.parent_ids() {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.get_mut().remove_head(&parent_id);
|
2021-03-15 22:26:09 +00:00
|
|
|
}
|
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-12-01 21:42:53 +00:00
|
|
|
self.view.get_mut().add_head(head.id());
|
|
|
|
self.view_dirty = true;
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-25 16:25:42 +00:00
|
|
|
pub fn remove_head(&mut self, head: &CommitId) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_head(head);
|
|
|
|
self.view_dirty = true;
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn add_public_head(&mut self, head: &Commit) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().add_public_head(head.id());
|
|
|
|
self.view_dirty = true;
|
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) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_public_head(head);
|
2021-12-05 20:44:08 +00:00
|
|
|
self.view_dirty = true;
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-12-01 21:42:53 +00:00
|
|
|
pub fn get_branch(&self, name: &str) -> Option<BranchTarget> {
|
|
|
|
self.view.borrow().get_branch(name).cloned()
|
2021-08-04 21:30:06 +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 set_branch(&mut self, name: String, target: BranchTarget) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_branch(name, target);
|
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 remove_branch(&mut self, name: &str) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_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
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_local_branch(&self, name: &str) -> Option<RefTarget> {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.borrow().get_local_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_local_branch(&mut self, name: String, target: RefTarget) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_local_branch(name, target);
|
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 remove_local_branch(&mut self, name: &str) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_local_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
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_remote_branch(&self, name: &str, remote_name: &str) -> Option<RefTarget> {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.borrow().get_remote_branch(name, remote_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_remote_branch(&mut self, name: String, remote_name: String, target: RefTarget) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_remote_branch(name, remote_name, target);
|
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 remove_remote_branch(&mut self, name: &str, remote_name: &str) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_remote_branch(name, remote_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
|
|
|
}
|
|
|
|
|
2021-08-11 17:54:35 +00:00
|
|
|
pub fn get_tag(&self, name: &str) -> Option<RefTarget> {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.borrow().get_tag(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_tag(&mut self, name: String, target: RefTarget) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_tag(name, target);
|
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 remove_tag(&mut self, name: &str) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_tag(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
|
|
|
}
|
|
|
|
|
2021-08-04 15:42:16 +00:00
|
|
|
pub fn set_git_ref(&mut self, name: String, target: RefTarget) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_git_ref(name, target);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn remove_git_ref(&mut self, name: &str) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().remove_git_ref(name);
|
2021-03-07 23:11:34 +00:00
|
|
|
}
|
|
|
|
|
2021-11-28 20:29:04 +00:00
|
|
|
pub fn set_git_head(&mut self, head_id: CommitId) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_git_head(head_id);
|
2021-11-28 20:29:04 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
pub fn clear_git_head(&mut self) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().clear_git_head();
|
2021-11-28 20:29:04 +00:00
|
|
|
}
|
|
|
|
|
2021-03-07 23:11:34 +00:00
|
|
|
pub fn set_view(&mut self, data: op_store::View) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_mut().set_view(data);
|
|
|
|
self.view_dirty = true;
|
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-03-14 18:08:31 +00:00
|
|
|
self.enforce_view_invariants();
|
2022-03-16 22:58:37 +00:00
|
|
|
self.merge_view(&base_repo.view, &other_repo.view);
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view_dirty = true;
|
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
|
|
|
|
2022-03-16 22:58:37 +00:00
|
|
|
fn merge_view(&mut self, base: &View, other: &View) {
|
|
|
|
// Merge checkouts. If there's a conflict, we keep the self side.
|
|
|
|
for (workspace_id, base_checkout) in base.checkouts() {
|
|
|
|
let self_checkout = self.view().get_checkout(workspace_id);
|
|
|
|
let other_checkout = other.get_checkout(workspace_id);
|
|
|
|
if other_checkout == Some(base_checkout) || other_checkout == self_checkout {
|
|
|
|
// The other side didn't change or both sides changed in the
|
|
|
|
// same way.
|
|
|
|
} else if let Some(other_checkout) = other_checkout {
|
|
|
|
if self_checkout == Some(base_checkout) {
|
|
|
|
self.view_mut()
|
|
|
|
.set_checkout(workspace_id.clone(), other_checkout.clone());
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// The other side removed the workspace. We want to remove it even if the self
|
|
|
|
// side changed the checkout.
|
|
|
|
self.view_mut().remove_checkout(workspace_id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
for (workspace_id, other_checkout) in other.checkouts() {
|
|
|
|
if self.view().get_checkout(workspace_id).is_none()
|
|
|
|
&& base.get_checkout(workspace_id).is_none()
|
|
|
|
{
|
|
|
|
// The other side added the workspace.
|
|
|
|
self.view_mut()
|
|
|
|
.set_checkout(workspace_id.clone(), other_checkout.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for removed_head in base.public_heads().difference(other.public_heads()) {
|
|
|
|
self.view_mut().remove_public_head(removed_head);
|
|
|
|
}
|
|
|
|
for added_head in other.public_heads().difference(base.public_heads()) {
|
|
|
|
self.view_mut().add_public_head(added_head);
|
|
|
|
}
|
|
|
|
|
2022-03-17 03:58:04 +00:00
|
|
|
let base_heads = base.heads().iter().cloned().collect_vec();
|
|
|
|
let own_heads = self.view().heads().iter().cloned().collect_vec();
|
|
|
|
let other_heads = other.heads().iter().cloned().collect_vec();
|
|
|
|
self.record_rewrites(&base_heads, &own_heads);
|
|
|
|
self.record_rewrites(&base_heads, &other_heads);
|
|
|
|
// No need to remove heads removed by `other` because we already marked them
|
|
|
|
// abandoned or rewritten.
|
2022-03-16 22:58:37 +00:00
|
|
|
for added_head in other.heads().difference(base.heads()) {
|
|
|
|
self.view_mut().add_head(added_head);
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut maybe_changed_ref_names = HashSet::new();
|
|
|
|
|
|
|
|
let base_branches: HashSet<_> = base.branches().keys().cloned().collect();
|
|
|
|
let other_branches: HashSet<_> = other.branches().keys().cloned().collect();
|
|
|
|
for branch_name in base_branches.union(&other_branches) {
|
|
|
|
let base_branch = base.branches().get(branch_name);
|
|
|
|
let other_branch = other.branches().get(branch_name);
|
|
|
|
if other_branch == base_branch {
|
|
|
|
// Unchanged on other side
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
maybe_changed_ref_names.insert(RefName::LocalBranch(branch_name.clone()));
|
|
|
|
if let Some(branch) = base_branch {
|
|
|
|
for remote in branch.remote_targets.keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::RemoteBranch {
|
|
|
|
branch: branch_name.clone(),
|
|
|
|
remote: remote.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if let Some(branch) = other_branch {
|
|
|
|
for remote in branch.remote_targets.keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::RemoteBranch {
|
|
|
|
branch: branch_name.clone(),
|
|
|
|
remote: remote.clone(),
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for tag_name in base.tags().keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::Tag(tag_name.clone()));
|
|
|
|
}
|
|
|
|
for tag_name in other.tags().keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::Tag(tag_name.clone()));
|
|
|
|
}
|
|
|
|
|
|
|
|
for git_ref_name in base.git_refs().keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::GitRef(git_ref_name.clone()));
|
|
|
|
}
|
|
|
|
for git_ref_name in other.git_refs().keys() {
|
|
|
|
maybe_changed_ref_names.insert(RefName::GitRef(git_ref_name.clone()));
|
|
|
|
}
|
|
|
|
|
|
|
|
for ref_name in maybe_changed_ref_names {
|
|
|
|
let base_target = base.get_ref(&ref_name);
|
|
|
|
let other_target = other.get_ref(&ref_name);
|
|
|
|
self.view.get_mut().merge_single_ref(
|
|
|
|
self.index.as_index_ref(),
|
|
|
|
&ref_name,
|
|
|
|
base_target.as_ref(),
|
|
|
|
other_target.as_ref(),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-03-17 03:58:04 +00:00
|
|
|
/// Finds and records commits that were rewritten or abandoned between
|
|
|
|
/// `old_heads` and `new_heads`.
|
|
|
|
fn record_rewrites(&mut self, old_heads: &[CommitId], new_heads: &[CommitId]) {
|
|
|
|
let mut removed_changes: HashMap<ChangeId, Vec<CommitId>> = HashMap::new();
|
|
|
|
for removed in self.index.walk_revs(old_heads, new_heads) {
|
|
|
|
removed_changes
|
|
|
|
.entry(removed.change_id())
|
|
|
|
.or_default()
|
|
|
|
.push(removed.commit_id());
|
|
|
|
}
|
|
|
|
if removed_changes.is_empty() {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
let mut rewritten_changes = HashSet::new();
|
|
|
|
let mut rewritten_commits: HashMap<CommitId, Vec<CommitId>> = HashMap::new();
|
|
|
|
for added in self.index.walk_revs(new_heads, old_heads) {
|
|
|
|
let change_id = added.change_id();
|
|
|
|
if let Some(old_commits) = removed_changes.get(&change_id) {
|
|
|
|
for old_commit in old_commits {
|
|
|
|
rewritten_commits
|
|
|
|
.entry(old_commit.clone())
|
|
|
|
.or_default()
|
|
|
|
.push(added.commit_id());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
rewritten_changes.insert(change_id);
|
|
|
|
}
|
|
|
|
for (old_commit, new_commits) in rewritten_commits {
|
|
|
|
for new_commit in new_commits {
|
|
|
|
self.record_rewritten_commit(old_commit.clone(), new_commit);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (change_id, removed_commit_ids) in &removed_changes {
|
|
|
|
if !rewritten_changes.contains(change_id) {
|
|
|
|
for removed_commit_id in removed_commit_ids {
|
|
|
|
self.record_abandoned_commit(removed_commit_id.clone());
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
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>,
|
|
|
|
) {
|
2021-12-01 21:42:53 +00:00
|
|
|
self.view.get_mut().merge_single_ref(
|
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.index.as_index_ref(),
|
|
|
|
ref_name,
|
|
|
|
base_target,
|
|
|
|
other_target,
|
|
|
|
);
|
|
|
|
}
|
2021-02-01 02:00:14 +00:00
|
|
|
}
|