Thanks to visit codestin.com
Credit goes to github.com

Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions crates/but-api/src/commit.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
use bstr::{BString, ByteSlice};
use but_api_macros::but_api;
use but_oplog::legacy::{OperationKind, SnapshotDetails};
use but_rebase::graph_rebase::GraphExt;
use tracing::instrument;

/// Rewords a commit, but without updating the oplog.
/// Rewords a commit
///
/// Returns the ID of the newly renamed commit
#[but_api]
Expand All @@ -15,8 +16,15 @@ pub fn commit_reword_only(
) -> anyhow::Result<gix::ObjectId> {
let mut guard = ctx.exclusive_worktree_access();
let (repo, _, graph) = ctx.graph_and_meta_mut_and_repo_from_head(guard.write_permission())?;
let editor = graph.to_editor(&repo)?;

let (outcome, edited_commit_selector) =
but_workspace::commit::reword(editor, commit_id, message.as_bstr())?;

let outcome = outcome.materialize()?;
let id = outcome.lookup_pick(edited_commit_selector)?;

but_workspace::commit::reword(&graph, &repo, commit_id, message.as_bstr())
Ok(id)
}

/// Rewords a commit, but without updating the oplog.
Expand Down
35 changes: 33 additions & 2 deletions crates/but-rebase/src/graph_rebase/commit.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
//! Provides some slightly higher level tools to help with manipulating commits, in preparation for use in the editor.

use anyhow::Result;
use anyhow::{Context as _, Result};
use gix::prelude::ObjectIdExt;

use crate::{
Expand All @@ -15,11 +15,42 @@ impl Editor {
}

/// Writes a commit with correct signing to the in memory repository.
pub fn write_commit(
pub fn new_commit(
&self,
commit: but_core::Commit<'_>,
date_mode: DateMode,
) -> Result<gix::ObjectId> {
create(&self.repo, commit.inner, date_mode)
}

/// Creates a commit with only the signature and author set correctly.
///
/// The ID of the commit is all zeros & the commit hasn't been written into any ODB
pub fn empty_commit(&self) -> Result<but_core::Commit<'_>> {
let kind = gix::hash::Kind::Sha1;
let committer = dbg!(self.repo.committer())
.transpose()?
.context("Need committer to be configured when creating a new commit")?
.into();
let author = self
.repo
.committer()
.transpose()?
.context("Need author to be configured when creating a new commit")?
.into();
let obj = gix::objs::Commit {
tree: gix::ObjectId::empty_tree(kind),
parents: vec![].into(),
committer,
author,
encoding: None,
message: b"".into(),
extra_headers: vec![],
};

Ok(but_core::Commit::<'_> {
id: gix::ObjectId::null(kind).attach(&self.repo),
inner: obj,
})
}
}
22 changes: 20 additions & 2 deletions crates/but-rebase/src/graph_rebase/creation.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ use anyhow::Result;
use but_graph::{Commit, CommitFlags, Graph, Segment};
use petgraph::Direction;

use crate::graph_rebase::{Checkouts, Edge, Editor, Step, StepGraph, StepGraphIndex};
use crate::graph_rebase::{
Checkout, Edge, Editor, RevisionHistory, Selector, Step, StepGraph, StepGraphIndex,
};

/// Provides an extension for creating an Editor out of the segment graph
pub trait GraphExt {
Expand All @@ -25,6 +27,8 @@ impl GraphExt for Graph {
// References are ordered from child-most to parent-most
let mut references: BTreeMap<gix::ObjectId, Vec<gix::refs::FullName>> = BTreeMap::new();

let mut head_refname = None;

self.visit_all_segments_including_start_until(
entrypoint.segment_index,
Direction::Outgoing,
Expand All @@ -37,6 +41,10 @@ impl GraphExt for Graph {
.entry(commit.id)
.and_modify(|rs| rs.push(refname.to_owned()))
.or_insert_with(|| vec![refname.to_owned()]);

if head_refname.is_none() {
head_refname = Some(refname.to_owned());
}
}

// Make a note to create a references that sit on commits
Expand Down Expand Up @@ -75,6 +83,8 @@ impl GraphExt for Graph {

let commit_ids = commits.iter().map(|c| c.id).collect::<HashSet<_>>();

let mut head_selectors = vec![];

for c in &commits {
let has_no_parents = c.parent_ids.is_empty();
let missing_parent_steps = c.parent_ids.iter().any(|p| !commit_ids.contains(p));
Expand Down Expand Up @@ -102,6 +112,13 @@ impl GraphExt for Graph {
let ref_ni = graph.add_node(Step::Reference { refname: r.clone() });
graph.add_edge(ref_ni, ni, Edge { order: 0 });
ni = ref_ni;

if Some(r) == head_refname.as_ref() {
head_selectors.push(Selector {
revision: 0,
id: ref_ni,
});
}
}
}

Expand Down Expand Up @@ -129,8 +146,9 @@ impl GraphExt for Graph {
initial_references: references.values().flatten().cloned().collect(),
// TODO(CTO): We need to eventually list all worktrees that we own
// here so we can `safe_checkout` them too.
checkouts: vec![Checkouts::Head],
checkouts: head_selectors.into_iter().map(Checkout::Head).collect(),
repo: repo.clone().with_object_memory(),
history: RevisionHistory::new(),
})
}
}
Expand Down
62 changes: 34 additions & 28 deletions crates/but-rebase/src/graph_rebase/materialize.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
//! Functions for materializing a rebase
use std::collections::HashMap;

use anyhow::Result;
use crate::graph_rebase::{
Checkout, MaterializeOutcome, Step, SuccessfulRebase, util::collect_ordered_parents,
};
use anyhow::{Context, Result, bail};
use but_core::{
ObjectStorageExt as _,
worktree::{
Expand All @@ -10,15 +11,6 @@ use but_core::{
},
};

use crate::graph_rebase::{Checkouts, rebase::SuccessfulRebase};

/// The outcome of a materialize
#[derive(Debug, Clone)]
pub struct MaterializeOutcome {
/// A mapping of any commits that were rewritten as part of the rebase
pub commit_mapping: HashMap<gix::ObjectId, gix::ObjectId>,
}

impl SuccessfulRebase {
/// Materializes a history rewrite
pub fn materialize(mut self) -> Result<MaterializeOutcome> {
Expand All @@ -29,29 +21,43 @@ impl SuccessfulRebase {

for checkout in self.checkouts {
match checkout {
Checkouts::Head => {
let head_oid = repo.head_commit()?.id;
if let Some(new_head) = self.commit_mapping.get(&head_oid) {
// If the head has changed (which means it's in the
// commit mapping), perform a safe checkout.
safe_checkout_from_head(
*new_head,
&repo,
Options {
uncommitted_changes:
UncommitedWorktreeChanges::KeepAndAbortOnConflict,
skip_head_update: true,
},
)?;
}
Checkout::Head(selector) => {
let selector = self.history.normailze_selector(selector)?;
let step = self.graph[selector.id].clone();

let new_head = match step {
Step::None => bail!("Checkout selector is pointing to none"),
Step::Pick { id, .. } => id,
Step::Reference { .. } => {
let parents = collect_ordered_parents(&self.graph, selector.id);
let parent_step_id =
parents.first().context("No first parent to reference")?;
let Step::Pick { id, .. } = self.graph[*parent_step_id] else {
bail!("collect_ordered_parents should always return a commit pick");
};
id
}
};

// If the head has changed (which means it's in the
// commit mapping), perform a safe checkout.
safe_checkout_from_head(
new_head,
&repo,
Options {
uncommitted_changes: UncommitedWorktreeChanges::KeepAndAbortOnConflict,
skip_head_update: true,
},
)?;
}
}
}

repo.edit_references(self.ref_edits.clone())?;

Ok(MaterializeOutcome {
commit_mapping: self.commit_mapping,
graph: self.graph,
history: self.history,
})
}
}
112 changes: 108 additions & 4 deletions crates/but-rebase/src/graph_rebase/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,17 @@

mod creation;
pub mod rebase;
use anyhow::{Result, bail};
use gix::refs::transaction::RefEdit;
use std::collections::HashMap;

use anyhow::Context;
pub use creation::GraphExt;
pub mod cherry_pick;
pub mod commit;
pub mod materialize;
pub mod mutate;
pub(crate) mod util;

/// Utilities for testing
pub mod testing;
Expand Down Expand Up @@ -65,16 +71,17 @@ type StepGraphIndex = petgraph::stable_graph::NodeIndex;
type StepGraph = petgraph::stable_graph::StableDiGraph<Step, Edge>;

/// Points to a step in the rebase editor.
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Copy)]
pub struct Selector {
id: StepGraphIndex,
revision: usize,
}

/// Represents places where `safe_checkout` should be called from
#[derive(Debug, Clone)]
pub(crate) enum Checkouts {
pub(crate) enum Checkout {
/// The HEAD of the `repo` the editor was created for.
Head,
Head(Selector),
}

/// Used to manipulate a set of picks.
Expand All @@ -86,7 +93,104 @@ pub struct Editor {
/// deleted.
initial_references: Vec<gix::refs::FullName>,
/// Worktrees that we might need to perform `safe_checkout` on.
checkouts: Vec<Checkouts>,
checkouts: Vec<Checkout>,
/// The in-memory repository that the rebase engine works with.
repo: gix::Repository,
history: RevisionHistory,
}

/// Represents a successful rebase, and any valid, but potentially conflicting scenarios it had.
#[allow(unused)]
#[derive(Debug, Clone)]
pub struct SuccessfulRebase {
pub(crate) repo: gix::Repository,
/// Any reference edits that need to be committed as a result of the history
/// rewrite
pub(crate) ref_edits: Vec<RefEdit>,
/// The new step graph
pub(crate) graph: StepGraph,
pub(crate) checkouts: Vec<Checkout>,
pub(crate) history: RevisionHistory,
}

/// The outcome of a materialize
#[derive(Debug, Clone)]
pub struct MaterializeOutcome {
pub(crate) graph: StepGraph,
pub(crate) history: RevisionHistory,
}

/// An extenstion trait that provides lookup for different steps that a selector
/// might point to.
pub trait LookupStep {
/// Look up the step that a given selector cooresponds to.
fn lookup_step(&self, selector: Selector) -> Result<Step>;

/// Look up the step a given selector and asserts it's a pick.
fn lookup_pick(&self, selector: Selector) -> Result<gix::ObjectId> {
match self.lookup_step(selector)? {
Step::Pick { id, .. } => Ok(id),
_ => bail!("Expected selector to point to be a pick"),
}
}

/// Look up the step a given selector and asserts it's a pick.
fn lookup_reference(&self, selector: Selector) -> Result<gix::refs::FullName> {
match self.lookup_step(selector)? {
Step::Reference { refname } => Ok(refname),
_ => bail!("Expected selector to point to be a reference"),
}
}
}

impl LookupStep for Editor {
fn lookup_step(&self, selector: Selector) -> Result<Step> {
lookup_step(&self.graph, &self.history, selector)
}
}

impl LookupStep for SuccessfulRebase {
fn lookup_step(&self, selector: Selector) -> Result<Step> {
lookup_step(&self.graph, &self.history, selector)
}
}

impl LookupStep for MaterializeOutcome {
fn lookup_step(&self, selector: Selector) -> Result<Step> {
lookup_step(&self.graph, &self.history, selector)
}
}

fn lookup_step(graph: &StepGraph, history: &RevisionHistory, selector: Selector) -> Result<Step> {
let normalized = history.normailze_selector(selector)?;
Ok(graph[normalized.id].clone())
}

#[derive(Debug, Clone, Default)]
pub(crate) struct RevisionHistory {
mappings: Vec<HashMap<StepGraphIndex, StepGraphIndex>>,
}

impl RevisionHistory {
pub(crate) fn new() -> Self {
Default::default()
}

pub(crate) fn current_revision(&self) -> usize {
self.mappings.len()
}

pub(crate) fn normailze_selector(&self, mut selector: Selector) -> Result<Selector> {
while selector.revision < self.current_revision() {
selector.id = *self.mappings[selector.revision]
.get(&selector.id)
.context("Failed to normalize selector, selector was missing from the mapping")?;
selector.revision += 1;
}
Ok(selector)
}

pub(crate) fn add_revision(&mut self, mapping: HashMap<StepGraphIndex, StepGraphIndex>) {
self.mappings.push(mapping);
}
}
Loading
Loading