Skip to content

Commit 0a74aa8

Browse files
committed
Basic apply and unapply
The idea is to develop both at the same time so it's easier to test that 'unapply' truly goes back to the (mostly) original state.
1 parent 456b28e commit 0a74aa8

File tree

12 files changed

+372
-13
lines changed

12 files changed

+372
-13
lines changed

crates/but-graph/src/projection/workspace.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,11 @@ impl Workspace<'_> {
149149
})
150150
}
151151

152+
/// Return `true` if `name` is contained in the workspace as segment.
153+
pub fn refname_is_segment(&self, name: &gix::refs::FullNameRef) -> bool {
154+
self.find_segment_and_stack_by_refname(name).is_some()
155+
}
156+
152157
/// Try to find `name` in any named [`StackSegment`] and return it along with the stack containing it.
153158
pub fn find_segment_and_stack_by_refname(
154159
&self,
@@ -189,9 +194,9 @@ pub enum WorkspaceKind {
189194
/// The name of the reference pointing to the workspace commit. Useful for deriving the workspace name.
190195
ref_name: gix::refs::FullName,
191196
},
192-
/// Information for when a workspace reference was advanced by hand and does not point to a
193-
/// managed workspace commit anymore.
194-
/// That commit, however, is reachable by following the first parent from the workspace reference.
197+
/// Information for when a workspace reference was *possibly* advanced by hand and does not point to a
198+
/// managed workspace commit (anymore).
199+
/// That workspace commit, may be reachable by following the first parent from the workspace reference.
195200
///
196201
/// Note that the stacks that follow *will* be in unusable if the workspace commit is in a segment below,
197202
/// but typically is usable if there is just a single real stack, or any amount of virtual stacks below
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/// Returned by [function::apply()].
2+
pub struct Outcome {
3+
/// The newly created graph, useful to project a workspace and see how the workspace looks like with the branch applied.
4+
pub graph: but_graph::Graph,
5+
/// `true` if we created the given workspace ref as it didn't exist yet.
6+
pub workspace_ref_created: bool,
7+
}
8+
9+
impl std::fmt::Debug for Outcome {
10+
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
11+
f.debug_struct("Outcome")
12+
.field("workspace_ref_created", &self.workspace_ref_created)
13+
.finish()
14+
}
15+
}
16+
17+
/// How the newly applied branch should be integrated into the workspace.
18+
#[derive(Default, Debug, Copy, Clone)]
19+
pub enum IntegrationMode {
20+
/// Do nothing but to merge it into the workspace commit.
21+
#[default]
22+
Merge,
23+
}
24+
25+
/// What to do if the applied branch conflicts?
26+
#[derive(Default, Debug, Copy, Clone)]
27+
pub enum OnWorkspaceConflict {
28+
/// Provide additional information about the stack that conflicted and the files involved in it,
29+
/// and don't perform the operation.
30+
#[default]
31+
AbortAndReportConflictingStack,
32+
}
33+
34+
/// Decide how a newly created workspace reference should be named.
35+
#[derive(Default, Debug, Clone)]
36+
pub enum WorkspaceReferenceNaming {
37+
/// Create a default workspace branch
38+
#[default]
39+
Default,
40+
/// Create a workspace with the given name instead.
41+
Given(gix::refs::FullName),
42+
}
43+
44+
/// Options for [function::apply()].
45+
#[derive(Default, Debug, Clone)]
46+
pub struct Options {
47+
/// how the branch should be brought into the workspace.
48+
pub integration_mode: IntegrationMode,
49+
/// Decide how to deal with conflicts.
50+
pub on_workspace_conflict: OnWorkspaceConflict,
51+
/// How the workspace reference should be named should it be created.
52+
/// The creation is always needed if there are more than one branch applied.
53+
pub workspace_reference_naming: WorkspaceReferenceNaming,
54+
}
55+
56+
pub(crate) mod function {
57+
use super::{Options, Outcome, WorkspaceReferenceNaming};
58+
use anyhow::bail;
59+
use but_core::RefMetadata;
60+
use but_graph::projection::WorkspaceKind;
61+
62+
/// Apply `branch` to the given `workspace`, and possibly create the workspace reference in `repo`
63+
/// along with its `meta`-data if it doesn't exist yet.
64+
/// Otherwise, add it to the existing `workspace`, and update its metadata accordingly.
65+
/// **This means that the contents of `branch` is observable from the new state of `repo`**.
66+
///
67+
/// Note that `workspace` is expected to match the state in `repo` as it's used instead of querying `repo` directly
68+
/// where possible.
69+
///
70+
/// Also note that we will create a managed workspace reference as needed if necessary, and a workspace commit if there is more than
71+
/// one reference in the workspace afterward.
72+
///
73+
/// On `error`, neither `repo` nor `meta` will have been changed.
74+
/// Otherwise, objects will have been persisted, and references and metadata will have been updated.
75+
pub fn apply<T: RefMetadata>(
76+
branch: &gix::refs::FullNameRef,
77+
workspace: &but_graph::projection::Workspace,
78+
repo: &mut gix::Repository,
79+
_meta: &mut T,
80+
Options {
81+
integration_mode: _,
82+
on_workspace_conflict: _,
83+
workspace_reference_naming,
84+
}: Options,
85+
) -> anyhow::Result<Outcome> {
86+
if workspace.refname_is_segment(branch) {
87+
return Ok(Outcome {
88+
graph: workspace.graph.clone(),
89+
workspace_ref_created: false,
90+
});
91+
}
92+
93+
if let Some(ws_ref_name) = workspace.ref_name() {
94+
if repo.try_find_reference(ws_ref_name)?.is_none() {
95+
// The workspace is the probably ad-hoc, and doesn't exist, *assume* unborn.
96+
bail!(
97+
"Cannot create reference on unborn branch '{}'",
98+
ws_ref_name.shorten()
99+
);
100+
}
101+
}
102+
let workspace_ref_name = match &workspace.kind {
103+
WorkspaceKind::Managed { ref_name }
104+
| WorkspaceKind::ManagedMissingWorkspaceCommit { ref_name } => ref_name.clone(),
105+
WorkspaceKind::AdHoc => match workspace_reference_naming {
106+
WorkspaceReferenceNaming::Default => {
107+
gix::refs::FullName::try_from("refs/heads/gitbutler/workspace")
108+
.expect("known statically")
109+
}
110+
WorkspaceReferenceNaming::Given(name) => name,
111+
},
112+
};
113+
114+
match repo.try_find_reference(workspace_ref_name.as_ref())? {
115+
None => {
116+
todo!("create ref and redo")
117+
}
118+
Some(_) => {
119+
todo!("get bearings")
120+
}
121+
}
122+
}
123+
}

crates/but-workspace/src/branch/create_reference.rs

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -218,9 +218,14 @@ pub(super) mod function {
218218
ref_name.shorten()
219219
);
220220
};
221-
position.resolve_commit(segment.commits.first().context(
222-
"BUG: empty segments aren't possible without workspace metadata",
223-
)?.into(), ws_base)?
221+
position.resolve_commit(
222+
segment
223+
.commits
224+
.first()
225+
.context("Cannot create reference on unborn branch")?
226+
.into(),
227+
ws_base,
228+
)?
224229
};
225230
(
226231
validate_id,

crates/but-workspace/src/branch/mod.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -456,6 +456,10 @@ impl Stack {
456456
}
457457
}
458458

459+
/// Functions and types related to applying a workspace branch.
460+
pub mod apply;
461+
pub use apply::function::apply;
462+
459463
/// related types for removing a workspace reference.
460464
pub mod remove_reference;
461465
pub use remove_reference::function::remove_reference;

crates/but-workspace/tests/fixtures/generated-archives/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,4 +29,6 @@
2929
/with-conflict.tar
3030
/journey*.tar
3131
/single-branch*.tar
32-
/index*.tar
32+
/index*.tar
33+
/detached-*.tar
34+
/ws-ref-*
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu -o pipefail
4+
source "${BASH_SOURCE[0]%/*}/shared.sh"
5+
6+
### Description
7+
# The HEAD is detached, and there are multiple branches.
8+
9+
git init
10+
commit M1
11+
git branch B
12+
git branch C
13+
git checkout -b A
14+
commit A1
15+
git checkout B
16+
commit B1
17+
git checkout C
18+
commit C1
19+
git checkout --detach HEAD
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu -o pipefail
4+
5+
source "${BASH_SOURCE[0]%/*}/shared.sh"
6+
7+
### Description
8+
# A newly initialized git repository, but with a known remote that has an object.
9+
10+
git init remote
11+
(cd remote
12+
commit "M1"
13+
)
14+
15+
git init unborn
16+
(cd unborn
17+
git remote add orphan ../remote
18+
git fetch orphan
19+
)

crates/but-workspace/tests/fixtures/scenario/with-remotes-and-workspace.sh

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
#!/usr/bin/env bash
22

3-
### General Description
4-
5-
# Various directories with different scenarios for testing stack information *with* a workspace commit,
6-
# and of course with a remote and a branch to integrate with.
73
set -eu -o pipefail
84

95
source "${BASH_SOURCE[0]%/*}/shared.sh"
106

7+
### General Description
8+
9+
# Various directories with different scenarios for testing stack information *with* a workspace commit,
10+
# and of course with a remote and a branch to integrate with.
1111

1212
git init remote
1313
(cd remote
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#!/usr/bin/env bash
2+
3+
set -eu -o pipefail
4+
5+
source "${BASH_SOURCE[0]%/*}/shared.sh"
6+
7+
### General Description
8+
9+
# A ws-ref points to a commit, which is also owned by a stack. There is another branch outside of the workspace pointing to the same commit.
10+
git init
11+
12+
git checkout -b A
13+
commit A
14+
git branch B
15+
git checkout -b gitbutler/workspace

0 commit comments

Comments
 (0)