Skip to content

Commit 2d8cf87

Browse files
committed
Prevent bad onboarding scenario
1 parent 3dbc754 commit 2d8cf87

File tree

5 files changed

+119
-32
lines changed

5 files changed

+119
-32
lines changed

crates/gitbutler-branch-actions/src/base.rs

Lines changed: 35 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,9 @@ use gitbutler_repo::{
1919
RepositoryExt,
2020
};
2121
use gitbutler_repo_actions::RepoActionsExt;
22-
use gitbutler_stack::{BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle};
22+
use gitbutler_stack::{
23+
canned_branch_name, BranchOwnershipClaims, Stack, Target, VirtualBranchesHandle,
24+
};
2325
use serde::Serialize;
2426
use tracing::instrument;
2527

@@ -196,47 +198,52 @@ pub(crate) fn set_base_branch(
196198
},
197199
);
198200

199-
let (upstream, upstream_head) = if let Refname::Local(head_name) = &head_name {
200-
let upstream_name = target_branch_ref.with_branch(head_name.branch());
201-
if upstream_name.eq(target_branch_ref) {
202-
(None, None)
203-
} else {
204-
match repo.find_reference(&Refname::from(&upstream_name).to_string()) {
205-
Ok(upstream) => {
206-
let head = upstream
207-
.peel_to_commit()
208-
.map(|commit| commit.id())
209-
.context(format!(
210-
"failed to peel upstream {} to commit",
211-
upstream.name().unwrap()
212-
))?;
213-
Ok((Some(upstream_name), Some(head)))
201+
let (upstream, upstream_head, branch_matches_target) =
202+
if let Refname::Local(head_name) = &head_name {
203+
let upstream_name = target_branch_ref.with_branch(head_name.branch());
204+
if upstream_name.eq(target_branch_ref) {
205+
(None, None, true)
206+
} else {
207+
match repo.find_reference(&Refname::from(&upstream_name).to_string()) {
208+
Ok(upstream) => {
209+
let head = upstream
210+
.peel_to_commit()
211+
.map(|commit| commit.id())
212+
.context(format!(
213+
"failed to peel upstream {} to commit",
214+
upstream.name().unwrap()
215+
))?;
216+
Ok((Some(upstream_name), Some(head), false))
217+
}
218+
Err(err) if err.code() == git2::ErrorCode::NotFound => {
219+
Ok((None, None, false))
220+
}
221+
Err(error) => Err(error),
214222
}
215-
Err(err) if err.code() == git2::ErrorCode::NotFound => Ok((None, None)),
216-
Err(error) => Err(error),
223+
.context(format!("failed to find upstream for {head_name}"))?
217224
}
218-
.context(format!("failed to find upstream for {head_name}"))?
219-
}
225+
} else {
226+
(None, None, false)
227+
};
228+
229+
let branch_name = if branch_matches_target {
230+
canned_branch_name(repo)?
220231
} else {
221-
(None, None)
232+
head_name.to_string().replace("refs/heads/", "")
222233
};
223234

224235
let mut branch = Stack::create(
225236
ctx,
226-
head_name.to_string().replace("refs/heads/", ""),
237+
branch_name,
227238
Some(head_name),
228239
upstream,
229240
upstream_head,
230-
gitbutler_diff::write::hunks_onto_commit(
231-
ctx,
232-
current_head_commit.id(),
233-
gitbutler_diff::diff_files_into_hunks(&wd_diff),
234-
)?,
241+
current_head_commit.tree_id(),
235242
current_head_commit.id(),
236243
0,
237244
None,
238245
ctx.project().ok_with_force_push.into(),
239-
true, // allow duplicate name since here we are creating a lane from an existing branch
246+
!branch_matches_target, // allow duplicate name since here we are creating a lane from an existing branch
240247
)?;
241248
branch.ownership = ownership;
242249

crates/gitbutler-branch-actions/tests/fixtures/for-details.sh

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,8 @@ git clone remote complex-repo
4141
$CLI project add --switch-to-workspace "$local_tracking_ref"
4242
for round in $(seq 10); do
4343
echo virtual-main >> file
44-
$CLI branch commit --message "virt-$round" main
44+
# We will now use a canned branch name here
45+
$CLI branch commit --message "virt-$round" a-branch-1
4546
done
4647

4748
git checkout -b non-virtual-feature main
@@ -51,5 +52,8 @@ git clone remote complex-repo
5152
done
5253

5354
# pretend the remote is at the same state as our local `main`
54-
git update-ref refs/remotes/origin/main main
55+
# This previously wanted to update the remote to match the local main, we no
56+
# longer have a "main" virutal branch, so this has been changed to the canned
57+
# branch.
58+
git update-ref refs/remotes/origin/main a-branch-1
5559
)

crates/gitbutler-branch-actions/tests/virtual_branches/init.rs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ fn dirty_target() {
7676

7777
fs::write(repo.path().join("file.txt"), "content").unwrap();
7878

79+
let old = std::env::var("GIT_AUTHOR_NAME").ok();
80+
std::env::set_var("GIT_AUTHOR_NAME", "GitButler");
7981
gitbutler_branch_actions::set_base_branch(
8082
ctx,
8183
&"refs/remotes/origin/master".parse().unwrap(),
@@ -86,7 +88,10 @@ fn dirty_target() {
8688

8789
let stacks = stack_details(ctx);
8890
assert_eq!(stacks.len(), 1);
89-
assert_eq!(stacks[0].1.derived_name, "master");
91+
assert_eq!(stacks[0].1.derived_name, "g-branch-1");
92+
if let Some(old) = old {
93+
std::env::set_var("GIT_AUTHOR_NAME", old);
94+
}
9095
}
9196

9297
#[test]
@@ -150,7 +155,7 @@ fn commit_on_target() {
150155

151156
let stacks = stack_details(ctx);
152157
assert_eq!(stacks.len(), 1);
153-
assert_eq!(stacks[0].1.derived_name, "master");
158+
assert_eq!(stacks[0].1.derived_name, "g-branch-1");
154159
assert_eq!(stacks[0].1.branch_details[0].clone().commits.len(), 1);
155160
}
156161

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
#!/bin/bash
2+
3+
echo "GIT CONFIG $GIT_CONFIG_GLOBAL"
4+
echo "DATA DIR $GITBUTLER_CLI_DATA_DIR"
5+
echo "BUT_TESTING $BUT_TESTING"
6+
7+
# Setup a remote project.
8+
# GitButler currently requires projects to have a remote
9+
mkdir remote-with-changes
10+
pushd remote-with-changes
11+
git init -b master --object-format=sha1
12+
echo "Initial content" >> initial_file.txt
13+
git add initial_file.txt
14+
git commit -am "Initial commit"
15+
popd
16+
17+
# Clone the remote into a folder
18+
git clone remote-with-changes local-with-changes
19+
pushd local-with-changes
20+
git checkout master
21+
22+
# Add an extra commit on the main branch
23+
echo "Second commit content" >> second_file.txt
24+
git add second_file.txt
25+
git commit -am "Second commit on main branch"
26+
27+
# Add some uncommitted changes
28+
echo "Uncommitted changes" >> uncommitted.txt
29+
echo "Modified initial file" >> initial_file.txt
30+
popd

e2e/playwright/tests/addingAProject.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -97,3 +97,44 @@ test('should handle gracefully adding a non-git directory', async ({ page, conte
9797

9898
await waitForTestId(page, 'add-project-not-a-git-repo-modal');
9999
});
100+
101+
test('should handle adding a project with extra commit and uncommitted changes on main branch', async ({
102+
page,
103+
context
104+
}, testInfo) => {
105+
const workdir = testInfo.outputPath('workdir');
106+
const configdir = testInfo.outputPath('config');
107+
gitbutler = await startGitButler(workdir, configdir, context);
108+
109+
const projectPath = gitbutler.pathInWorkdir('local-with-changes/');
110+
111+
// Set up repository with extra commit and uncommitted changes
112+
await gitbutler.runScript('project-with-commit-and-uncommitted-changes.sh');
113+
114+
await page.goto('/');
115+
116+
const onboardingPage = getByTestId(page, 'onboarding-page');
117+
await onboardingPage.waitFor();
118+
119+
clickByTestId(page, 'analytics-continue');
120+
121+
// Click the add local project button
122+
const fileChooserPromise = page.waitForEvent('filechooser');
123+
clickByTestId(page, 'add-local-project');
124+
125+
const fileChooser = await fileChooserPromise;
126+
await fileChooser.setFiles(projectPath);
127+
128+
clickByTestId(page, 'set-base-branch');
129+
130+
// Should load the workspace directly after setting base branch
131+
await waitForTestId(page, 'workspace-view');
132+
133+
// There should be only one stack
134+
const stacks = getByTestId(page, 'stack');
135+
await expect(stacks).toHaveCount(1);
136+
const stack = stacks.first();
137+
138+
// The stack should not have the branch called "master"
139+
await expect(stack).not.toContainText('master');
140+
});

0 commit comments

Comments
 (0)