|
1 |
| -use std::cmp::Ordering; |
2 |
| -use std::collections::HashMap; |
3 | 1 | use std::{path::PathBuf, vec};
|
4 | 2 |
|
5 | 3 | use anyhow::{anyhow, Context, Result};
|
6 |
| -use gitbutler_branch::BranchCreateRequest; |
7 | 4 | use gitbutler_branch::{self, GITBUTLER_WORKSPACE_REFERENCE};
|
8 | 5 | use gitbutler_cherry_pick::RepositoryExt as _;
|
9 | 6 | use gitbutler_command_context::CommandContext;
|
10 |
| -use gitbutler_commit::commit_ext::CommitExt; |
11 |
| -use gitbutler_diff::diff_files_into_hunks; |
12 | 7 | use gitbutler_error::error::Marker;
|
13 |
| -use gitbutler_operating_modes::{OPEN_WORKSPACE_REFS, WORKSPACE_BRANCH_REF}; |
| 8 | +use gitbutler_operating_modes::OPEN_WORKSPACE_REFS; |
14 | 9 | use gitbutler_oxidize::{git2_to_gix_object_id, gix_to_git2_oid, GixRepositoryExt};
|
15 |
| -use gitbutler_project::access::{WorktreeReadPermission, WorktreeWritePermission}; |
16 |
| -use gitbutler_repo::logging::{LogUntil, RepositoryExt as _}; |
17 |
| -use gitbutler_repo::rebase::cherry_rebase_group; |
| 10 | +use gitbutler_project::access::WorktreeWritePermission; |
18 | 11 | use gitbutler_repo::SignaturePurpose;
|
19 |
| -use gitbutler_stack::{Stack, StackId, VirtualBranchesHandle}; |
| 12 | +use gitbutler_stack::{Stack, VirtualBranchesHandle}; |
20 | 13 | use tracing::instrument;
|
21 | 14 |
|
22 |
| -use crate::compute_workspace_dependencies; |
23 |
| -use crate::{branch_manager::BranchManagerExt, conflicts, VirtualBranchesExt}; |
| 15 | +use crate::{conflicts, workspace_commit::resolve_commits_above, VirtualBranchesExt}; |
24 | 16 |
|
25 | 17 | const WORKSPACE_HEAD: &str = "Workspace Head";
|
26 |
| -const GITBUTLER_INTEGRATION_COMMIT_TITLE: &str = "GitButler Integration Commit"; |
| 18 | +pub const GITBUTLER_INTEGRATION_COMMIT_TITLE: &str = "GitButler Integration Commit"; |
27 | 19 | pub const GITBUTLER_WORKSPACE_COMMIT_TITLE: &str = "GitButler Workspace Commit";
|
28 | 20 |
|
29 | 21 | /// Creates and returns a merge commit of all active branch heads.
|
@@ -295,7 +287,7 @@ pub fn update_workspace_commit(
|
295 | 287 | pub fn verify_branch(ctx: &CommandContext, perm: &mut WorktreeWritePermission) -> Result<()> {
|
296 | 288 | verify_current_branch_name(ctx)
|
297 | 289 | .and_then(verify_head_is_set)
|
298 |
| - .and_then(|()| verify_head_is_clean(ctx, perm)) |
| 290 | + .and_then(|()| resolve_commits_above(ctx, perm)) |
299 | 291 | .context(Marker::VerificationFailure)?;
|
300 | 292 | Ok(())
|
301 | 293 | }
|
@@ -325,157 +317,6 @@ fn verify_current_branch_name(ctx: &CommandContext) -> Result<&CommandContext> {
|
325 | 317 | }
|
326 | 318 | }
|
327 | 319 |
|
328 |
| -#[derive(Debug)] |
329 |
| -pub enum WorkspaceState { |
330 |
| - OffWorkspaceCommit { |
331 |
| - workspace_commit: git2::Oid, |
332 |
| - extra_commits: Vec<git2::Oid>, |
333 |
| - }, |
334 |
| - OnWorkspaceCommit, |
335 |
| -} |
336 |
| - |
337 |
| -pub fn workspace_state( |
338 |
| - ctx: &CommandContext, |
339 |
| - _perm: &WorktreeReadPermission, |
340 |
| -) -> Result<WorkspaceState> { |
341 |
| - let repository = ctx.repo(); |
342 |
| - let vb_handle = VirtualBranchesHandle::new(ctx.project().gb_dir()); |
343 |
| - let default_target = vb_handle.get_default_target()?; |
344 |
| - |
345 |
| - let head_commit = repository.head()?.peel_to_commit()?; |
346 |
| - let commits = repository.log( |
347 |
| - head_commit.id(), |
348 |
| - LogUntil::Commit(default_target.sha), |
349 |
| - false, |
350 |
| - )?; |
351 |
| - |
352 |
| - let workspace_index = commits |
353 |
| - .iter() |
354 |
| - .position(|commit| { |
355 |
| - commit.message().is_some_and(|message| { |
356 |
| - message.starts_with(GITBUTLER_WORKSPACE_COMMIT_TITLE) |
357 |
| - || message.starts_with(GITBUTLER_INTEGRATION_COMMIT_TITLE) |
358 |
| - }) |
359 |
| - }) |
360 |
| - .context("")?; |
361 |
| - let workspace_commit = &commits[workspace_index]; |
362 |
| - let extra_commits = commits[..workspace_index].to_vec(); |
363 |
| - |
364 |
| - if extra_commits.is_empty() { |
365 |
| - // no extra commits found, so we're good |
366 |
| - return Ok(WorkspaceState::OnWorkspaceCommit); |
367 |
| - } |
368 |
| - |
369 |
| - Ok(WorkspaceState::OffWorkspaceCommit { |
370 |
| - workspace_commit: workspace_commit.id(), |
371 |
| - extra_commits: extra_commits |
372 |
| - .iter() |
373 |
| - .map(git2::Commit::id) |
374 |
| - .collect::<Vec<_>>(), |
375 |
| - }) |
376 |
| -} |
377 |
| - |
378 |
| -// TODO(ST): Probably there should not be an implicit vbranch creation here. |
379 |
| -fn verify_head_is_clean(ctx: &CommandContext, perm: &mut WorktreeWritePermission) -> Result<()> { |
380 |
| - let repository = ctx.repo(); |
381 |
| - let head_commit = repository.head()?.peel_to_commit()?; |
382 |
| - |
383 |
| - let WorkspaceState::OffWorkspaceCommit { |
384 |
| - workspace_commit, |
385 |
| - extra_commits, |
386 |
| - } = workspace_state(ctx, perm.read_permission())? |
387 |
| - else { |
388 |
| - return Ok(()); |
389 |
| - }; |
390 |
| - |
391 |
| - let best_stack_id = find_best_stack_for_changes(ctx, perm, head_commit.id(), workspace_commit)?; |
392 |
| - |
393 |
| - if let Some(best_stack_id) = best_stack_id { |
394 |
| - let vb_handle = VirtualBranchesHandle::new(ctx.project().gb_dir()); |
395 |
| - let mut stack = vb_handle.get_stack_in_workspace(best_stack_id)?; |
396 |
| - |
397 |
| - let new_head = cherry_rebase_group(repository, stack.head(), &extra_commits, false)?; |
398 |
| - |
399 |
| - stack.set_stack_head( |
400 |
| - ctx, |
401 |
| - new_head, |
402 |
| - Some(repository.find_commit(new_head)?.tree_id()), |
403 |
| - )?; |
404 |
| - |
405 |
| - update_workspace_commit(&vb_handle, ctx)?; |
406 |
| - } else { |
407 |
| - // There is no stack which can hold the commits so we should just unroll those changes |
408 |
| - repository.reference(WORKSPACE_BRANCH_REF, workspace_commit, true, "")?; |
409 |
| - repository.set_head(WORKSPACE_BRANCH_REF)?; |
410 |
| - } |
411 |
| - |
412 |
| - Ok(()) |
413 |
| -} |
414 |
| - |
415 |
| -fn find_best_stack_for_changes( |
416 |
| - ctx: &CommandContext, |
417 |
| - perm: &mut WorktreeWritePermission, |
418 |
| - head_commit: git2::Oid, |
419 |
| - workspace_commit: git2::Oid, |
420 |
| -) -> Result<Option<StackId>> { |
421 |
| - let vb_state = VirtualBranchesHandle::new(ctx.project().gb_dir()); |
422 |
| - let default_target = vb_state.get_default_target()?; |
423 |
| - let repository = ctx.repo(); |
424 |
| - let stacks = vb_state.list_stacks_in_workspace()?; |
425 |
| - |
426 |
| - let head_commit = repository.find_commit(head_commit)?; |
427 |
| - |
428 |
| - let diffs = gitbutler_diff::trees( |
429 |
| - ctx.repo(), |
430 |
| - &repository.find_commit(workspace_commit)?.tree()?, |
431 |
| - &head_commit.tree()?, |
432 |
| - true, |
433 |
| - )?; |
434 |
| - let base_diffs: HashMap<_, _> = diff_files_into_hunks(&diffs).collect(); |
435 |
| - let workspace_dependencies = |
436 |
| - compute_workspace_dependencies(ctx, &default_target.sha, &base_diffs, &stacks)?; |
437 |
| - |
438 |
| - match workspace_dependencies.commit_dependent_diffs.len().cmp(&1) { |
439 |
| - Ordering::Greater => { |
440 |
| - // The commits are locked to multiple stacks. We can't correctly assign it |
441 |
| - // to any one stack, so the commits should be undone. |
442 |
| - Ok(None) |
443 |
| - } |
444 |
| - Ordering::Equal => { |
445 |
| - // There is one stack which the commits are locked to, so the commits |
446 |
| - // should be added to that particular stack. |
447 |
| - let stack_id = workspace_dependencies |
448 |
| - .commit_dependent_diffs |
449 |
| - .keys() |
450 |
| - .next() |
451 |
| - .expect("Values was asserted length 1 above"); |
452 |
| - Ok(Some(*stack_id)) |
453 |
| - } |
454 |
| - Ordering::Less => { |
455 |
| - // We should return the branch selected for changes, or create a new default branch. |
456 |
| - let mut stacks = vb_state.list_stacks_in_workspace()?; |
457 |
| - stacks.sort_by_key(|stack| stack.selected_for_changes.unwrap_or(0)); |
458 |
| - |
459 |
| - if let Some(stack) = stacks.last() { |
460 |
| - return Ok(Some(stack.id)); |
461 |
| - } |
462 |
| - |
463 |
| - let branch_manager = ctx.branch_manager(); |
464 |
| - let new_stack = branch_manager |
465 |
| - .create_virtual_branch( |
466 |
| - &BranchCreateRequest { |
467 |
| - name: Some(head_commit.message_bstr().to_string()), |
468 |
| - ..Default::default() |
469 |
| - }, |
470 |
| - perm, |
471 |
| - ) |
472 |
| - .context("failed to create virtual branch")?; |
473 |
| - |
474 |
| - Ok(Some(new_stack.id)) |
475 |
| - } |
476 |
| - } |
477 |
| -} |
478 |
| - |
479 | 320 | fn invalid_head_err(head_name: &str) -> anyhow::Error {
|
480 | 321 | anyhow!(
|
481 | 322 | "project is on {head_name}. Please checkout {} to continue",
|
|
0 commit comments