mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-11-30 11:32:04 +03:00
Merge branch 'master' into extract-project-from-core-into-its-onw-crate
This commit is contained in:
commit
ba5b9ff0ee
@ -1,15 +1,11 @@
|
|||||||
export type ClickOpts = { trigger?: HTMLElement; handler: () => void; enabled?: boolean };
|
export type ClickOpts = { excludeElement?: HTMLElement; handler: () => void };
|
||||||
|
|
||||||
export function clickOutside(
|
export function clickOutside(node: HTMLElement, params: ClickOpts) {
|
||||||
node: HTMLElement,
|
|
||||||
params: ClickOpts
|
|
||||||
): { destroy: () => void; update: (opts: ClickOpts) => void } {
|
|
||||||
let trigger: HTMLElement | undefined;
|
|
||||||
function onClick(event: MouseEvent) {
|
function onClick(event: MouseEvent) {
|
||||||
if (
|
if (
|
||||||
node &&
|
node &&
|
||||||
!node.contains(event.target as HTMLElement) &&
|
!node.contains(event.target as HTMLElement) &&
|
||||||
!trigger?.contains(event.target as HTMLElement)
|
!params.excludeElement?.contains(event.target as HTMLElement)
|
||||||
) {
|
) {
|
||||||
params.handler();
|
params.handler();
|
||||||
}
|
}
|
||||||
@ -20,14 +16,6 @@ export function clickOutside(
|
|||||||
destroy() {
|
destroy() {
|
||||||
document.removeEventListener('mousedown', onClick, true);
|
document.removeEventListener('mousedown', onClick, true);
|
||||||
document.removeEventListener('contextmenu', onClick, true);
|
document.removeEventListener('contextmenu', onClick, true);
|
||||||
},
|
|
||||||
update(opts: ClickOpts) {
|
|
||||||
document.removeEventListener('mousedown', onClick, true);
|
|
||||||
document.removeEventListener('contextmenu', onClick, true);
|
|
||||||
if (opts.enabled !== undefined && !opts.enabled) return;
|
|
||||||
trigger = opts.trigger;
|
|
||||||
document.addEventListener('mousedown', onClick, true);
|
|
||||||
document.addEventListener('contextmenu', onClick, true);
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
export type ClickOpts = { excludeElement?: HTMLElement; handler: () => void };
|
|
||||||
|
|
||||||
export function clickOutside(node: HTMLElement, params: ClickOpts) {
|
|
||||||
function onClick(event: MouseEvent) {
|
|
||||||
if (
|
|
||||||
node &&
|
|
||||||
!node.contains(event.target as HTMLElement) &&
|
|
||||||
!params.excludeElement?.contains(event.target as HTMLElement)
|
|
||||||
) {
|
|
||||||
params.handler();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
document.addEventListener('mousedown', onClick, true);
|
|
||||||
document.addEventListener('contextmenu', onClick, true);
|
|
||||||
return {
|
|
||||||
destroy() {
|
|
||||||
document.removeEventListener('mousedown', onClick, true);
|
|
||||||
document.removeEventListener('contextmenu', onClick, true);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,7 +1,6 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import { clickOutside } from '$lib/clickOutsideNew';
|
import { clickOutside } from '$lib/clickOutside';
|
||||||
import { portal } from '$lib/utils/portal';
|
import { portal } from '$lib/utils/portal';
|
||||||
import { pxToRem } from '$lib/utils/pxToRem';
|
|
||||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||||
import { type Snippet } from 'svelte';
|
import { type Snippet } from 'svelte';
|
||||||
|
|
||||||
@ -27,7 +26,6 @@
|
|||||||
}: Props = $props();
|
}: Props = $props();
|
||||||
|
|
||||||
// LOCAL VARS
|
// LOCAL VARS
|
||||||
let menuMargin = 4;
|
|
||||||
|
|
||||||
// STATES
|
// STATES
|
||||||
let item = $state<any>();
|
let item = $state<any>();
|
||||||
@ -71,12 +69,10 @@
|
|||||||
|
|
||||||
function setVerticalAlign(targetBoundingRect: DOMRect) {
|
function setVerticalAlign(targetBoundingRect: DOMRect) {
|
||||||
if (verticalAlign === 'top') {
|
if (verticalAlign === 'top') {
|
||||||
return targetBoundingRect?.top ? targetBoundingRect.top - contextMenuHeight - menuMargin : 0;
|
return targetBoundingRect?.top ? targetBoundingRect.top - contextMenuHeight : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
return targetBoundingRect?.top
|
return targetBoundingRect?.top ? targetBoundingRect.top + targetBoundingRect.height : 0;
|
||||||
? targetBoundingRect.top + targetBoundingRect.height + menuMargin
|
|
||||||
: 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function setHorizontalAlign(targetBoundingRect: DOMRect) {
|
function setHorizontalAlign(targetBoundingRect: DOMRect) {
|
||||||
@ -131,8 +127,10 @@
|
|||||||
bind:offsetHeight={contextMenuHeight}
|
bind:offsetHeight={contextMenuHeight}
|
||||||
bind:offsetWidth={contextMenuWidth}
|
bind:offsetWidth={contextMenuWidth}
|
||||||
class="context-menu"
|
class="context-menu"
|
||||||
style:top={pxToRem(menuPosition.y)}
|
class:top-oriented={verticalAlign === 'top'}
|
||||||
style:left={pxToRem(menuPosition.x)}
|
class:bottom-oriented={verticalAlign === 'bottom'}
|
||||||
|
style:top="{menuPosition.y}px"
|
||||||
|
style:left="{menuPosition.x}px"
|
||||||
style:transform-origin={setTransformOrigin()}
|
style:transform-origin={setTransformOrigin()}
|
||||||
style:--animation-transform-shift={verticalAlign === 'top' ? '6px' : '-6px'}
|
style:--animation-transform-shift={verticalAlign === 'top' ? '6px' : '-6px'}
|
||||||
>
|
>
|
||||||
@ -167,6 +165,14 @@
|
|||||||
/* background-color: rgba(0, 0, 0, 0.1); */
|
/* background-color: rgba(0, 0, 0, 0.1); */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.top-oriented {
|
||||||
|
margin-top: -4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.bottom-oriented {
|
||||||
|
margin-top: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
.context-menu {
|
.context-menu {
|
||||||
z-index: var(--z-blocker);
|
z-index: var(--z-blocker);
|
||||||
position: fixed;
|
position: fixed;
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
import ScrollableContainer from '../shared/ScrollableContainer.svelte';
|
import ScrollableContainer from '../shared/ScrollableContainer.svelte';
|
||||||
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
|
import emptyFolderSvg from '$lib/assets/empty-state/empty-folder.svg?raw';
|
||||||
import { Project } from '$lib/backend/projects';
|
import { Project } from '$lib/backend/projects';
|
||||||
import { clickOutside } from '$lib/clickOutsideNew';
|
import { clickOutside } from '$lib/clickOutside';
|
||||||
import FileCard from '$lib/file/FileCard.svelte';
|
import FileCard from '$lib/file/FileCard.svelte';
|
||||||
import SnapshotCard from '$lib/history/SnapshotCard.svelte';
|
import SnapshotCard from '$lib/history/SnapshotCard.svelte';
|
||||||
import { HistoryService, createdOnDay } from '$lib/history/history';
|
import { HistoryService, createdOnDay } from '$lib/history/history';
|
||||||
|
@ -85,14 +85,6 @@
|
|||||||
class:resizer-hovered={isResizerHovered || isResizerDragging}
|
class:resizer-hovered={isResizerHovered || isResizerDragging}
|
||||||
on:mousedown={toggleNavCollapse}
|
on:mousedown={toggleNavCollapse}
|
||||||
>
|
>
|
||||||
<!-- <svg viewBox="0 0 7 23" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
||||||
<path
|
|
||||||
d="M6 1L1.81892 9.78026C1.30084 10.8682 1.30084 12.1318 1.81892 13.2197L6 22"
|
|
||||||
stroke-width="1.5"
|
|
||||||
vector-effect="non-scaling-stroke"
|
|
||||||
/>
|
|
||||||
</svg> -->
|
|
||||||
|
|
||||||
<svg viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
<svg viewBox="0 0 6 11" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||||
<path
|
<path
|
||||||
d="M5 1.25L1.59055 5.08564C1.25376 5.46452 1.25376 6.03548 1.59055 6.41436L5 10.25"
|
d="M5 1.25L1.59055 5.08564C1.25376 5.46452 1.25376 6.03548 1.59055 6.41436L5 10.25"
|
||||||
|
@ -2,35 +2,26 @@
|
|||||||
import ProjectAvatar from './ProjectAvatar.svelte';
|
import ProjectAvatar from './ProjectAvatar.svelte';
|
||||||
import ProjectsPopup from './ProjectsPopup.svelte';
|
import ProjectsPopup from './ProjectsPopup.svelte';
|
||||||
import { Project } from '$lib/backend/projects';
|
import { Project } from '$lib/backend/projects';
|
||||||
import { clickOutside } from '$lib/clickOutside';
|
|
||||||
import Icon from '$lib/shared/Icon.svelte';
|
import Icon from '$lib/shared/Icon.svelte';
|
||||||
import { getContext } from '$lib/utils/context';
|
import { getContext } from '$lib/utils/context';
|
||||||
import { tooltip } from '$lib/utils/tooltip';
|
import { tooltip } from '$lib/utils/tooltip';
|
||||||
|
|
||||||
export let isNavCollapsed: boolean;
|
export let isNavCollapsed: boolean;
|
||||||
|
|
||||||
|
let buttonTrigger: HTMLButtonElement;
|
||||||
const project = getContext(Project);
|
const project = getContext(Project);
|
||||||
|
|
||||||
let popup: ProjectsPopup;
|
let popup: ProjectsPopup;
|
||||||
let visible: boolean = false;
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<div
|
<div class="wrapper">
|
||||||
class="wrapper"
|
|
||||||
use:clickOutside={{
|
|
||||||
handler: () => {
|
|
||||||
popup.hide();
|
|
||||||
visible = false;
|
|
||||||
},
|
|
||||||
enabled: visible
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<button
|
<button
|
||||||
|
bind:this={buttonTrigger}
|
||||||
class="text-input button"
|
class="text-input button"
|
||||||
use:tooltip={isNavCollapsed ? project?.title : ''}
|
use:tooltip={isNavCollapsed ? project?.title : ''}
|
||||||
on:mousedown={(e) => {
|
on:mousedown={(e) => {
|
||||||
visible = popup.toggle();
|
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
popup.toggle();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<ProjectAvatar name={project?.title} />
|
<ProjectAvatar name={project?.title} />
|
||||||
@ -41,7 +32,7 @@
|
|||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
</button>
|
</button>
|
||||||
<ProjectsPopup bind:this={popup} {isNavCollapsed} />
|
<ProjectsPopup bind:this={popup} target={buttonTrigger} {isNavCollapsed} />
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
@ -3,6 +3,8 @@
|
|||||||
import Icon from '$lib/shared/Icon.svelte';
|
import Icon from '$lib/shared/Icon.svelte';
|
||||||
import ScrollableContainer from '$lib/shared/ScrollableContainer.svelte';
|
import ScrollableContainer from '$lib/shared/ScrollableContainer.svelte';
|
||||||
import { getContext } from '$lib/utils/context';
|
import { getContext } from '$lib/utils/context';
|
||||||
|
import { portal } from '$lib/utils/portal';
|
||||||
|
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||||
import type iconsJson from '$lib/icons/icons.json';
|
import type iconsJson from '$lib/icons/icons.json';
|
||||||
import { goto } from '$app/navigation';
|
import { goto } from '$app/navigation';
|
||||||
import { page } from '$app/stores';
|
import { page } from '$app/stores';
|
||||||
@ -14,22 +16,47 @@
|
|||||||
onclick: () => void;
|
onclick: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
export let isNavCollapsed: boolean;
|
interface ProjectsPopupProps {
|
||||||
|
target: HTMLButtonElement;
|
||||||
|
isNavCollapsed: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { target, isNavCollapsed }: ProjectsPopupProps = $props();
|
||||||
|
|
||||||
const projectService = getContext(ProjectService);
|
const projectService = getContext(ProjectService);
|
||||||
const projects = projectService.projects;
|
const projects = projectService.projects;
|
||||||
|
|
||||||
let hidden = true;
|
let inputBoundingRect: DOMRect | undefined = $state();
|
||||||
let loading = false;
|
let optionsEl: HTMLDivElement | undefined = $state();
|
||||||
|
let hidden = $state(true);
|
||||||
|
let loading = $state(false);
|
||||||
|
|
||||||
export function toggle() {
|
function getInputBoundingRect() {
|
||||||
hidden = !hidden;
|
if (target) {
|
||||||
return !hidden;
|
inputBoundingRect = target.getBoundingClientRect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export function show() {
|
||||||
|
hidden = false;
|
||||||
|
getInputBoundingRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
export function hide() {
|
export function hide() {
|
||||||
hidden = true;
|
hidden = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function toggle() {
|
||||||
|
if (hidden) {
|
||||||
|
show();
|
||||||
|
} else {
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function clickOutside(e: MouseEvent) {
|
||||||
|
if (e.target === e.currentTarget) hide();
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
{#snippet itemSnippet(props: ItemSnippetProps)}
|
{#snippet itemSnippet(props: ItemSnippetProps)}
|
||||||
@ -37,7 +64,7 @@
|
|||||||
disabled={props.selected}
|
disabled={props.selected}
|
||||||
class="list-item"
|
class="list-item"
|
||||||
class:selected={props.selected}
|
class:selected={props.selected}
|
||||||
on:click={props.onclick}
|
onclick={props.onclick}
|
||||||
>
|
>
|
||||||
<div class="label text-base-14 text-bold">
|
<div class="label text-base-14 text-bold">
|
||||||
{props.label}
|
{props.label}
|
||||||
@ -55,49 +82,76 @@
|
|||||||
{/snippet}
|
{/snippet}
|
||||||
|
|
||||||
{#if !hidden}
|
{#if !hidden}
|
||||||
<div class="popup" class:collapsed={isNavCollapsed}>
|
<div
|
||||||
{#if $projects.length > 0}
|
role="presentation"
|
||||||
<ScrollableContainer maxHeight="20rem">
|
class="overlay-wrapper"
|
||||||
<div class="popup__projects">
|
use:resizeObserver={() => {
|
||||||
{#each $projects as project}
|
getInputBoundingRect();
|
||||||
{@const selected = project.id === $page.params.projectId}
|
}}
|
||||||
{@render itemSnippet({
|
onclick={clickOutside}
|
||||||
label: project.title,
|
use:portal={'body'}
|
||||||
selected,
|
>
|
||||||
icon: selected ? 'tick' : undefined,
|
<div
|
||||||
onclick: () => {
|
bind:this={optionsEl}
|
||||||
goto(`/${project.id}/`);
|
class="popup"
|
||||||
hide();
|
class:collapsed={isNavCollapsed}
|
||||||
}
|
style:width={!isNavCollapsed ? `${inputBoundingRect?.width}px` : undefined}
|
||||||
})}
|
style:top={inputBoundingRect?.top
|
||||||
{/each}
|
? `${inputBoundingRect.top + inputBoundingRect.height}px`
|
||||||
</div>
|
: undefined}
|
||||||
</ScrollableContainer>
|
style:left={inputBoundingRect?.left ? `${inputBoundingRect.left}px` : undefined}
|
||||||
{/if}
|
>
|
||||||
<div class="popup__actions">
|
{#if $projects.length > 0}
|
||||||
{@render itemSnippet({
|
<ScrollableContainer maxHeight="20rem">
|
||||||
label: 'Add new project',
|
<div class="popup__projects">
|
||||||
icon: 'plus',
|
{#each $projects as project}
|
||||||
onclick: async () => {
|
{@const selected = project.id === $page.params.projectId}
|
||||||
loading = true;
|
{@render itemSnippet({
|
||||||
try {
|
label: project.title,
|
||||||
await projectService.addProject();
|
selected,
|
||||||
} finally {
|
icon: selected ? 'tick' : undefined,
|
||||||
loading = false;
|
onclick: () => {
|
||||||
|
goto(`/${project.id}/`);
|
||||||
|
hide();
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
</ScrollableContainer>
|
||||||
|
{/if}
|
||||||
|
<div class="popup__actions">
|
||||||
|
{@render itemSnippet({
|
||||||
|
label: 'Add new project',
|
||||||
|
icon: 'plus',
|
||||||
|
onclick: async () => {
|
||||||
|
loading = true;
|
||||||
|
try {
|
||||||
|
await projectService.addProject();
|
||||||
|
} finally {
|
||||||
|
loading = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
})}
|
||||||
})}
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{/if}
|
{/if}
|
||||||
|
|
||||||
<style lang="postcss">
|
<style lang="postcss">
|
||||||
|
.overlay-wrapper {
|
||||||
|
z-index: var(--z-blocker);
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
.popup {
|
.popup {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 100%;
|
top: 100%;
|
||||||
z-index: var(--z-floating);
|
z-index: var(--z-floating);
|
||||||
width: 100%;
|
margin-top: 4px;
|
||||||
margin-top: 6px;
|
|
||||||
border-radius: var(--m, 6px);
|
border-radius: var(--m, 6px);
|
||||||
border: 1px solid var(--clr-border-2);
|
border: 1px solid var(--clr-border-2);
|
||||||
background: var(--clr-bg-1);
|
background: var(--clr-bg-1);
|
||||||
@ -164,7 +218,6 @@
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* MODIFIERS */
|
|
||||||
.popup.collapsed {
|
.popup.collapsed {
|
||||||
width: 240px;
|
width: 240px;
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
import TextBox from '../shared/TextBox.svelte';
|
import TextBox from '../shared/TextBox.svelte';
|
||||||
import { KeyName } from '$lib/utils/hotkeys';
|
import { KeyName } from '$lib/utils/hotkeys';
|
||||||
import { portal } from '$lib/utils/portal';
|
import { portal } from '$lib/utils/portal';
|
||||||
import { pxToRem } from '$lib/utils/pxToRem';
|
|
||||||
import { resizeObserver } from '$lib/utils/resizeObserver';
|
import { resizeObserver } from '$lib/utils/resizeObserver';
|
||||||
import type { Snippet } from 'svelte';
|
import type { Snippet } from 'svelte';
|
||||||
|
|
||||||
@ -83,6 +82,7 @@
|
|||||||
function getInputBoundingRect() {
|
function getInputBoundingRect() {
|
||||||
if (selectWrapperEl) {
|
if (selectWrapperEl) {
|
||||||
inputBoundingRect = selectWrapperEl.getBoundingClientRect();
|
inputBoundingRect = selectWrapperEl.getBoundingClientRect();
|
||||||
|
console.log('inputBoundingRect', inputBoundingRect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -182,10 +182,10 @@
|
|||||||
class="options card"
|
class="options card"
|
||||||
style:width="{inputBoundingRect?.width}px"
|
style:width="{inputBoundingRect?.width}px"
|
||||||
style:top={inputBoundingRect?.top
|
style:top={inputBoundingRect?.top
|
||||||
? pxToRem(inputBoundingRect.top + inputBoundingRect.height)
|
? `${inputBoundingRect.top + inputBoundingRect.height}px`
|
||||||
: undefined}
|
: undefined}
|
||||||
style:left={inputBoundingRect?.left ? pxToRem(inputBoundingRect.left) : undefined}
|
style:left={inputBoundingRect?.left ? `${inputBoundingRect.left}px` : undefined}
|
||||||
style:max-height={maxHeightState && pxToRem(maxHeightState)}
|
style:max-height={maxHeightState && `${maxHeightState}px`}
|
||||||
>
|
>
|
||||||
<ScrollableContainer initiallyVisible>
|
<ScrollableContainer initiallyVisible>
|
||||||
{#if searchable && options.length > 5}
|
{#if searchable && options.length > 5}
|
||||||
|
@ -63,15 +63,10 @@ fn go_back_to_integration(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let all_virtual_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
let applied_virtual_branches = all_virtual_branches
|
|
||||||
.iter()
|
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let target_commit = project_repository
|
let target_commit = project_repository
|
||||||
.repo()
|
.repo()
|
||||||
.find_commit(default_target.sha)
|
.find_commit(default_target.sha)
|
||||||
@ -83,7 +78,7 @@ fn go_back_to_integration(
|
|||||||
let mut final_tree = target_commit
|
let mut final_tree = target_commit
|
||||||
.tree()
|
.tree()
|
||||||
.context("failed to get base tree from commit")?;
|
.context("failed to get base tree from commit")?;
|
||||||
for branch in &applied_virtual_branches {
|
for branch in &virtual_branches {
|
||||||
// merge this branches tree with our tree
|
// merge this branches tree with our tree
|
||||||
let branch_head = project_repository
|
let branch_head = project_repository
|
||||||
.repo()
|
.repo()
|
||||||
@ -241,7 +236,7 @@ pub fn set_base_branch(
|
|||||||
id: BranchId::generate(),
|
id: BranchId::generate(),
|
||||||
name: head_name.to_string().replace("refs/heads/", ""),
|
name: head_name.to_string().replace("refs/heads/", ""),
|
||||||
notes: String::new(),
|
notes: String::new(),
|
||||||
applied: true,
|
source_refname: Some(head_name),
|
||||||
upstream,
|
upstream,
|
||||||
upstream_head,
|
upstream_head,
|
||||||
created_timestamp_ms: now_ms,
|
created_timestamp_ms: now_ms,
|
||||||
@ -256,6 +251,9 @@ pub fn set_base_branch(
|
|||||||
order: 0,
|
order: 0,
|
||||||
selected_for_changes: None,
|
selected_for_changes: None,
|
||||||
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
||||||
|
old_applied: true,
|
||||||
|
in_workspace: true,
|
||||||
|
not_in_workspace_wip_change_id: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
vb_state.set_branch(branch)?;
|
vb_state.set_branch(branch)?;
|
||||||
@ -402,7 +400,7 @@ pub fn update_base_branch(
|
|||||||
if non_commited_files.is_empty() {
|
if non_commited_files.is_empty() {
|
||||||
// if there are no commited files, then the branch is fully merged
|
// if there are no commited files, then the branch is fully merged
|
||||||
// and we can delete it.
|
// and we can delete it.
|
||||||
vb_state.remove_branch(branch.id)?;
|
vb_state.mark_as_not_in_workspace(branch.id)?;
|
||||||
project_repository.delete_branch_reference(&branch)?;
|
project_repository.delete_branch_reference(&branch)?;
|
||||||
Ok(None)
|
Ok(None)
|
||||||
} else {
|
} else {
|
||||||
@ -545,7 +543,6 @@ pub fn update_base_branch(
|
|||||||
|
|
||||||
let final_tree = updated_vbranches
|
let final_tree = updated_vbranches
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.fold(new_target_commit.tree(), |final_tree, branch| {
|
.fold(new_target_commit.tree(), |final_tree, branch| {
|
||||||
let repo: &git2::Repository = repo;
|
let repo: &git2::Repository = repo;
|
||||||
let final_tree = final_tree?;
|
let final_tree = final_tree?;
|
||||||
|
@ -192,7 +192,7 @@ impl Controller {
|
|||||||
let old_branch = project_repository
|
let old_branch = project_repository
|
||||||
.project()
|
.project()
|
||||||
.virtual_branches()
|
.virtual_branches()
|
||||||
.get_branch(branch_update.id)?;
|
.get_branch_in_workspace(branch_update.id)?;
|
||||||
let result = branch::update_branch(&project_repository, &branch_update);
|
let result = branch::update_branch(&project_repository, &branch_update);
|
||||||
let _ = snapshot_tree.and_then(|snapshot_tree| {
|
let _ = snapshot_tree.and_then(|snapshot_tree| {
|
||||||
project_repository.project().snapshot_branch_update(
|
project_repository.project().snapshot_branch_update(
|
||||||
|
@ -38,11 +38,7 @@ pub fn get_workspace_head(
|
|||||||
let repo: &git2::Repository = project_repo.repo();
|
let repo: &git2::Repository = project_repo.repo();
|
||||||
let vb_state = project_repo.project().virtual_branches();
|
let vb_state = project_repo.project().virtual_branches();
|
||||||
|
|
||||||
let all_virtual_branches = vb_state.list_branches()?;
|
let virtual_branches = vb_state.list_branches_in_workspace()?;
|
||||||
let applied_branches = all_virtual_branches
|
|
||||||
.iter()
|
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let target_commit = repo.find_commit(target.sha)?;
|
let target_commit = repo.find_commit(target.sha)?;
|
||||||
let mut workspace_tree = target_commit.tree()?;
|
let mut workspace_tree = target_commit.tree()?;
|
||||||
@ -50,12 +46,12 @@ pub fn get_workspace_head(
|
|||||||
if conflicts::is_conflicting(project_repo, None)? {
|
if conflicts::is_conflicting(project_repo, None)? {
|
||||||
let merge_parent =
|
let merge_parent =
|
||||||
conflicts::merge_parent(project_repo)?.ok_or(anyhow!("No merge parent"))?;
|
conflicts::merge_parent(project_repo)?.ok_or(anyhow!("No merge parent"))?;
|
||||||
let first_branch = applied_branches.first().ok_or(anyhow!("No branches"))?;
|
let first_branch = virtual_branches.first().ok_or(anyhow!("No branches"))?;
|
||||||
|
|
||||||
let merge_base = repo.merge_base(first_branch.head, merge_parent)?;
|
let merge_base = repo.merge_base(first_branch.head, merge_parent)?;
|
||||||
workspace_tree = repo.find_commit(merge_base)?.tree()?;
|
workspace_tree = repo.find_commit(merge_base)?.tree()?;
|
||||||
} else {
|
} else {
|
||||||
for branch in &applied_branches {
|
for branch in &virtual_branches {
|
||||||
let branch_tree = repo.find_commit(branch.head)?.tree()?;
|
let branch_tree = repo.find_commit(branch.head)?.tree()?;
|
||||||
let merge_tree = repo.find_commit(target.sha)?.tree()?;
|
let merge_tree = repo.find_commit(target.sha)?.tree()?;
|
||||||
let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
|
let mut index = repo.merge_trees(&merge_tree, &workspace_tree, &branch_tree, None)?;
|
||||||
@ -68,7 +64,7 @@ pub fn get_workspace_head(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let branch_heads = applied_branches
|
let branch_heads = virtual_branches
|
||||||
.iter()
|
.iter()
|
||||||
.map(|b| repo.find_commit(b.head))
|
.map(|b| repo.find_commit(b.head))
|
||||||
.collect::<Result<Vec<_>, _>>()?;
|
.collect::<Result<Vec<_>, _>>()?;
|
||||||
@ -82,7 +78,7 @@ pub fn get_workspace_head(
|
|||||||
// TODO(mg): Can we make this a constant?
|
// TODO(mg): Can we make this a constant?
|
||||||
let committer = get_integration_commiter()?;
|
let committer = get_integration_commiter()?;
|
||||||
|
|
||||||
let mut heads: Vec<git2::Commit<'_>> = applied_branches
|
let mut heads: Vec<git2::Commit<'_>> = virtual_branches
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|b| b.head != target.sha)
|
.filter(|b| b.head != target.sha)
|
||||||
.map(|b| repo.find_commit(b.head))
|
.map(|b| repo.find_commit(b.head))
|
||||||
@ -168,15 +164,10 @@ pub fn update_gitbutler_integration(
|
|||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
// get all virtual branches, we need to try to update them all
|
// get all virtual branches, we need to try to update them all
|
||||||
let all_virtual_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to list virtual branches")?;
|
.context("failed to list virtual branches")?;
|
||||||
|
|
||||||
let applied_virtual_branches = all_virtual_branches
|
|
||||||
.iter()
|
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let integration_commit =
|
let integration_commit =
|
||||||
repo.find_commit(get_workspace_head(&vb_state, project_repository)?)?;
|
repo.find_commit(get_workspace_head(&vb_state, project_repository)?)?;
|
||||||
let integration_tree = integration_commit.tree()?;
|
let integration_tree = integration_commit.tree()?;
|
||||||
@ -195,7 +186,7 @@ pub fn update_gitbutler_integration(
|
|||||||
message.push_str("If you switch to another branch, GitButler will need to be reinitialized.\n");
|
message.push_str("If you switch to another branch, GitButler will need to be reinitialized.\n");
|
||||||
message.push_str("If you commit on this branch, GitButler will throw it away.\n\n");
|
message.push_str("If you commit on this branch, GitButler will throw it away.\n\n");
|
||||||
message.push_str("Here are the branches that are currently applied:\n");
|
message.push_str("Here are the branches that are currently applied:\n");
|
||||||
for branch in &applied_virtual_branches {
|
for branch in &virtual_branches {
|
||||||
message.push_str(" - ");
|
message.push_str(" - ");
|
||||||
message.push_str(branch.name.as_str());
|
message.push_str(branch.name.as_str());
|
||||||
message.push_str(format!(" ({})", &branch.refname()).as_str());
|
message.push_str(format!(" ({})", &branch.refname()).as_str());
|
||||||
@ -250,7 +241,7 @@ pub fn update_gitbutler_integration(
|
|||||||
index.write()?;
|
index.write()?;
|
||||||
|
|
||||||
// finally, update the refs/gitbutler/ heads to the states of the current virtual branches
|
// finally, update the refs/gitbutler/ heads to the states of the current virtual branches
|
||||||
for branch in &all_virtual_branches {
|
for branch in &virtual_branches {
|
||||||
let wip_tree = repo.find_tree(branch.tree)?;
|
let wip_tree = repo.find_tree(branch.tree)?;
|
||||||
let mut branch_head = repo.find_commit(branch.head)?;
|
let mut branch_head = repo.find_commit(branch.head)?;
|
||||||
let head_tree = branch_head.tree()?;
|
let head_tree = branch_head.tree()?;
|
||||||
|
@ -213,12 +213,9 @@ pub fn unapply_ownership(
|
|||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let applied_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?;
|
||||||
.into_iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let integration_commit_id = get_workspace_head(&vb_state, project_repository)?;
|
let integration_commit_id = get_workspace_head(&vb_state, project_repository)?;
|
||||||
|
|
||||||
@ -226,7 +223,7 @@ pub fn unapply_ownership(
|
|||||||
project_repository,
|
project_repository,
|
||||||
&integration_commit_id,
|
&integration_commit_id,
|
||||||
&default_target.sha,
|
&default_target.sha,
|
||||||
applied_branches,
|
virtual_branches,
|
||||||
)
|
)
|
||||||
.context("failed to get status by branch")?;
|
.context("failed to get status by branch")?;
|
||||||
|
|
||||||
@ -338,7 +335,7 @@ pub fn convert_to_real_branch(
|
|||||||
) -> Result<git2::Branch<'_>> {
|
) -> Result<git2::Branch<'_>> {
|
||||||
fn build_real_branch<'l>(
|
fn build_real_branch<'l>(
|
||||||
project_repository: &'l ProjectRepo,
|
project_repository: &'l ProjectRepo,
|
||||||
vbranch: &branch::Branch,
|
vbranch: &mut branch::Branch,
|
||||||
name_conflict_resolution: NameConflitResolution,
|
name_conflict_resolution: NameConflitResolution,
|
||||||
) -> Result<git2::Branch<'l>> {
|
) -> Result<git2::Branch<'l>> {
|
||||||
let repo = project_repository.repo();
|
let repo = project_repository.repo();
|
||||||
@ -381,7 +378,10 @@ pub fn convert_to_real_branch(
|
|||||||
branch_name
|
branch_name
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let branch = repo.branch(&branch_name, &target_commit, true)?;
|
let branch = repo.branch(&branch_name, &target_commit, true)?;
|
||||||
|
vbranch.source_refname = Some(git::Refname::try_from(&branch)?);
|
||||||
|
vb_state.set_branch(vbranch.clone())?;
|
||||||
|
|
||||||
build_metadata_commit(project_repository, vbranch, &branch)?;
|
build_metadata_commit(project_repository, vbranch, &branch)?;
|
||||||
|
|
||||||
@ -389,7 +389,7 @@ pub fn convert_to_real_branch(
|
|||||||
}
|
}
|
||||||
fn build_metadata_commit<'l>(
|
fn build_metadata_commit<'l>(
|
||||||
project_repository: &'l ProjectRepo,
|
project_repository: &'l ProjectRepo,
|
||||||
vbranch: &branch::Branch,
|
vbranch: &mut branch::Branch,
|
||||||
branch: &git2::Branch<'l>,
|
branch: &git2::Branch<'l>,
|
||||||
) -> Result<git2::Oid> {
|
) -> Result<git2::Oid> {
|
||||||
let repo = project_repository.repo();
|
let repo = project_repository.repo();
|
||||||
@ -412,7 +412,7 @@ pub fn convert_to_real_branch(
|
|||||||
let committer = get_integration_commiter()?;
|
let committer = get_integration_commiter()?;
|
||||||
let parent = branch.get().peel_to_commit()?;
|
let parent = branch.get().peel_to_commit()?;
|
||||||
|
|
||||||
let commit_headers = CommitHeadersV2::new(true, Some(vbranch.name.clone()));
|
let commit_headers = CommitHeadersV2::new();
|
||||||
|
|
||||||
let commit_oid = repo.commit_with_signature(
|
let commit_oid = repo.commit_with_signature(
|
||||||
Some(&branch.try_into()?),
|
Some(&branch.try_into()?),
|
||||||
@ -421,18 +421,26 @@ pub fn convert_to_real_branch(
|
|||||||
&message,
|
&message,
|
||||||
&tree,
|
&tree,
|
||||||
&[&parent],
|
&[&parent],
|
||||||
Some(commit_headers),
|
Some(commit_headers.clone()),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
// vbranch.head = commit_oid;
|
||||||
|
vbranch.not_in_workspace_wip_change_id = Some(commit_headers.change_id);
|
||||||
|
vb_state.set_branch(vbranch.clone())?;
|
||||||
|
|
||||||
Ok(commit_oid)
|
Ok(commit_oid)
|
||||||
}
|
}
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let target_branch = vb_state.get_branch(branch_id)?;
|
let mut target_branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
|
|
||||||
// Convert the vbranch to a real branch
|
// Convert the vbranch to a real branch
|
||||||
let real_branch =
|
let real_branch = build_real_branch(
|
||||||
build_real_branch(project_repository, &target_branch, name_conflict_resolution)?;
|
project_repository,
|
||||||
|
&mut target_branch,
|
||||||
|
name_conflict_resolution,
|
||||||
|
)?;
|
||||||
|
|
||||||
delete_branch(project_repository, branch_id)?;
|
delete_branch(project_repository, branch_id)?;
|
||||||
|
|
||||||
@ -470,12 +478,40 @@ fn find_base_tree<'a>(
|
|||||||
Ok(base_tree)
|
Ok(base_tree)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Resolves the "old_applied" state of branches
|
||||||
|
///
|
||||||
|
/// This should only ever be called by `list_virtual_branches
|
||||||
|
///
|
||||||
|
/// This checks for the case where !branch.old_applied && branch.in_workspace
|
||||||
|
/// If this is the case, we ought to unapply the branch as its been carried
|
||||||
|
/// over from the old style of unapplying
|
||||||
|
fn resolve_old_applied_state(
|
||||||
|
project_repository: &ProjectRepo,
|
||||||
|
vb_state: &VirtualBranchesHandle,
|
||||||
|
) -> Result<()> {
|
||||||
|
let branches = vb_state.list_all_branches()?;
|
||||||
|
|
||||||
|
for mut branch in branches {
|
||||||
|
if !branch.old_applied && branch.in_workspace {
|
||||||
|
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
||||||
|
} else {
|
||||||
|
branch.old_applied = branch.in_workspace;
|
||||||
|
vb_state.set_branch(branch)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_virtual_branches(
|
pub fn list_virtual_branches(
|
||||||
project_repository: &ProjectRepo,
|
project_repository: &ProjectRepo,
|
||||||
) -> Result<(Vec<VirtualBranch>, Vec<diff::FileDiff>)> {
|
) -> Result<(Vec<VirtualBranch>, Vec<diff::FileDiff>)> {
|
||||||
let mut branches: Vec<VirtualBranch> = Vec::new();
|
let mut branches: Vec<VirtualBranch> = Vec::new();
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
|
resolve_old_applied_state(project_repository, &vb_state)?;
|
||||||
|
|
||||||
let default_target = vb_state
|
let default_target = vb_state
|
||||||
.get_default_target()
|
.get_default_target()
|
||||||
.context("failed to get default target")?;
|
.context("failed to get default target")?;
|
||||||
@ -496,10 +532,6 @@ pub fn list_virtual_branches(
|
|||||||
.unwrap_or(-1);
|
.unwrap_or(-1);
|
||||||
|
|
||||||
for (branch, files) in statuses {
|
for (branch, files) in statuses {
|
||||||
if !branch.applied {
|
|
||||||
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
|
||||||
}
|
|
||||||
|
|
||||||
let repo = project_repository.repo();
|
let repo = project_repository.repo();
|
||||||
update_conflict_markers(project_repository, &files)?;
|
update_conflict_markers(project_repository, &files)?;
|
||||||
|
|
||||||
@ -566,10 +598,7 @@ pub fn list_virtual_branches(
|
|||||||
let merge_base = repo
|
let merge_base = repo
|
||||||
.merge_base(default_target.sha, branch.head)
|
.merge_base(default_target.sha, branch.head)
|
||||||
.context("failed to find merge base")?;
|
.context("failed to find merge base")?;
|
||||||
let mut base_current = true;
|
let base_current = true;
|
||||||
if !branch.applied {
|
|
||||||
base_current = merge_base == default_target.sha;
|
|
||||||
}
|
|
||||||
|
|
||||||
let upstream = upstream_branch
|
let upstream = upstream_branch
|
||||||
.map(|upstream_branch| branch_to_remote_branch(&upstream_branch))
|
.map(|upstream_branch| branch_to_remote_branch(&upstream_branch))
|
||||||
@ -604,7 +633,7 @@ pub fn list_virtual_branches(
|
|||||||
id: branch.id,
|
id: branch.id,
|
||||||
name: branch.name,
|
name: branch.name,
|
||||||
notes: branch.notes,
|
notes: branch.notes,
|
||||||
active: branch.applied,
|
active: true,
|
||||||
files,
|
files,
|
||||||
order: branch.order,
|
order: branch.order,
|
||||||
commits: vbranch_commits,
|
commits: vbranch_commits,
|
||||||
@ -754,7 +783,7 @@ pub fn create_virtual_branch(
|
|||||||
.context("failed to find defaut target commit tree")?;
|
.context("failed to find defaut target commit tree")?;
|
||||||
|
|
||||||
let mut all_virtual_branches = vb_state
|
let mut all_virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
let name = dedup(
|
let name = dedup(
|
||||||
@ -779,7 +808,7 @@ pub fn create_virtual_branch(
|
|||||||
let selected_for_changes = if let Some(selected_for_changes) = create.selected_for_changes {
|
let selected_for_changes = if let Some(selected_for_changes) = create.selected_for_changes {
|
||||||
if selected_for_changes {
|
if selected_for_changes {
|
||||||
for mut other_branch in vb_state
|
for mut other_branch in vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?
|
||||||
{
|
{
|
||||||
other_branch.selected_for_changes = None;
|
other_branch.selected_for_changes = None;
|
||||||
@ -812,7 +841,6 @@ pub fn create_virtual_branch(
|
|||||||
id: BranchId::generate(),
|
id: BranchId::generate(),
|
||||||
name: name.clone(),
|
name: name.clone(),
|
||||||
notes: String::new(),
|
notes: String::new(),
|
||||||
applied: true,
|
|
||||||
upstream: None,
|
upstream: None,
|
||||||
upstream_head: None,
|
upstream_head: None,
|
||||||
tree: tree.id(),
|
tree: tree.id(),
|
||||||
@ -823,6 +851,10 @@ pub fn create_virtual_branch(
|
|||||||
order,
|
order,
|
||||||
selected_for_changes,
|
selected_for_changes,
|
||||||
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
||||||
|
old_applied: true,
|
||||||
|
in_workspace: true,
|
||||||
|
not_in_workspace_wip_change_id: None,
|
||||||
|
source_refname: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(ownership) = &create.ownership {
|
if let Some(ownership) = &create.ownership {
|
||||||
@ -870,7 +902,7 @@ pub fn integrate_upstream_commits(
|
|||||||
let project = project_repository.project();
|
let project = project_repository.project();
|
||||||
let vb_state = project.virtual_branches();
|
let vb_state = project.virtual_branches();
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
let upstream_branch = branch.upstream.as_ref().context("upstream not found")?;
|
||||||
@ -1061,7 +1093,7 @@ pub fn update_branch(
|
|||||||
branch_update: &branch::BranchUpdateRequest,
|
branch_update: &branch::BranchUpdateRequest,
|
||||||
) -> Result<branch::Branch> {
|
) -> Result<branch::Branch> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let mut branch = vb_state.get_branch(branch_update.id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_update.id)?;
|
||||||
|
|
||||||
if let Some(ownership) = &branch_update.ownership {
|
if let Some(ownership) = &branch_update.ownership {
|
||||||
set_ownership(&vb_state, &mut branch, ownership).context("failed to set ownership")?;
|
set_ownership(&vb_state, &mut branch, ownership).context("failed to set ownership")?;
|
||||||
@ -1069,7 +1101,7 @@ pub fn update_branch(
|
|||||||
|
|
||||||
if let Some(name) = &branch_update.name {
|
if let Some(name) = &branch_update.name {
|
||||||
let all_virtual_branches = vb_state
|
let all_virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
project_repository.delete_branch_reference(&branch)?;
|
project_repository.delete_branch_reference(&branch)?;
|
||||||
@ -1113,7 +1145,7 @@ pub fn update_branch(
|
|||||||
if let Some(selected_for_changes) = branch_update.selected_for_changes {
|
if let Some(selected_for_changes) = branch_update.selected_for_changes {
|
||||||
branch.selected_for_changes = if selected_for_changes {
|
branch.selected_for_changes = if selected_for_changes {
|
||||||
for mut other_branch in vb_state
|
for mut other_branch in vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|b| b.id != branch.id)
|
.filter(|b| b.id != branch.id)
|
||||||
@ -1137,7 +1169,7 @@ pub fn update_branch(
|
|||||||
|
|
||||||
pub fn delete_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> Result<()> {
|
pub fn delete_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> Result<()> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let Some(branch) = vb_state.try_branch(branch_id)? else {
|
let Some(branch) = vb_state.try_branch_in_workspace(branch_id)? else {
|
||||||
return Ok(());
|
return Ok(());
|
||||||
};
|
};
|
||||||
_ = project_repository
|
_ = project_repository
|
||||||
@ -1150,18 +1182,15 @@ pub fn delete_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> R
|
|||||||
let target_commit = repo.target_commit()?;
|
let target_commit = repo.target_commit()?;
|
||||||
let base_tree = target_commit.tree().context("failed to get target tree")?;
|
let base_tree = target_commit.tree().context("failed to get target tree")?;
|
||||||
|
|
||||||
let applied_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?;
|
||||||
.into_iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let (applied_statuses, _) = get_applied_status(
|
let (applied_statuses, _) = get_applied_status(
|
||||||
project_repository,
|
project_repository,
|
||||||
&integration_commit.id(),
|
&integration_commit.id(),
|
||||||
&target_commit.id(),
|
&target_commit.id(),
|
||||||
applied_branches,
|
virtual_branches,
|
||||||
)
|
)
|
||||||
.context("failed to get status by branch")?;
|
.context("failed to get status by branch")?;
|
||||||
|
|
||||||
@ -1192,7 +1221,7 @@ pub fn delete_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> R
|
|||||||
.context("failed to checkout tree")?;
|
.context("failed to checkout tree")?;
|
||||||
|
|
||||||
vb_state
|
vb_state
|
||||||
.remove_branch(branch.id)
|
.mark_as_not_in_workspace(branch.id)
|
||||||
.context("Failed to remove branch")?;
|
.context("Failed to remove branch")?;
|
||||||
|
|
||||||
project_repository.delete_branch_reference(&branch)?;
|
project_repository.delete_branch_reference(&branch)?;
|
||||||
@ -1203,19 +1232,16 @@ pub fn delete_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> R
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<()> {
|
fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<()> {
|
||||||
let mut applied_branches = vb_state
|
let mut virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to list branches")?
|
.context("failed to list branches")?;
|
||||||
.into_iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if applied_branches.is_empty() {
|
if virtual_branches.is_empty() {
|
||||||
println!("no applied branches");
|
println!("no applied branches");
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if applied_branches
|
if virtual_branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.selected_for_changes.is_some())
|
.any(|b| b.selected_for_changes.is_some())
|
||||||
{
|
{
|
||||||
@ -1223,10 +1249,10 @@ fn ensure_selected_for_changes(vb_state: &VirtualBranchesHandle) -> Result<()> {
|
|||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
applied_branches.sort_by_key(|branch| branch.order);
|
virtual_branches.sort_by_key(|branch| branch.order);
|
||||||
|
|
||||||
applied_branches[0].selected_for_changes = Some(now_since_unix_epoch_ms());
|
virtual_branches[0].selected_for_changes = Some(now_since_unix_epoch_ms());
|
||||||
vb_state.set_branch(applied_branches[0].clone())?;
|
vb_state.set_branch(virtual_branches[0].clone())?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1241,7 +1267,7 @@ fn set_ownership(
|
|||||||
}
|
}
|
||||||
|
|
||||||
let virtual_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
let mut claim_outcomes =
|
let mut claim_outcomes =
|
||||||
@ -1331,71 +1357,18 @@ pub fn get_status_by_branch(
|
|||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let virtual_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
let applied_virtual_branches = virtual_branches
|
|
||||||
.iter()
|
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.cloned()
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let (applied_status, skipped_files) = get_applied_status(
|
let (applied_status, skipped_files) = get_applied_status(
|
||||||
project_repository,
|
project_repository,
|
||||||
// TODO: Keep this optional or update lots of tests?
|
// TODO: Keep this optional or update lots of tests?
|
||||||
integration_commit.unwrap_or(&default_target.sha),
|
integration_commit.unwrap_or(&default_target.sha),
|
||||||
&default_target.sha,
|
&default_target.sha,
|
||||||
applied_virtual_branches,
|
virtual_branches,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let non_applied_virtual_branches = virtual_branches
|
Ok((applied_status, skipped_files))
|
||||||
.into_iter()
|
|
||||||
.filter(|branch| !branch.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let non_applied_status =
|
|
||||||
get_non_applied_status(project_repository, non_applied_virtual_branches)?;
|
|
||||||
|
|
||||||
Ok((
|
|
||||||
applied_status
|
|
||||||
.into_iter()
|
|
||||||
.chain(non_applied_status)
|
|
||||||
.collect(),
|
|
||||||
skipped_files,
|
|
||||||
))
|
|
||||||
}
|
|
||||||
|
|
||||||
// given a list of non applied virtual branches, return the status of each file, comparing the default target with
|
|
||||||
// virtual branch latest tree
|
|
||||||
//
|
|
||||||
// ownerships are not taken into account here, as they are not relevant for non applied branches
|
|
||||||
fn get_non_applied_status(
|
|
||||||
project_repository: &ProjectRepo,
|
|
||||||
virtual_branches: Vec<branch::Branch>,
|
|
||||||
) -> Result<Vec<(branch::Branch, BranchStatus)>> {
|
|
||||||
virtual_branches
|
|
||||||
.into_iter()
|
|
||||||
.map(|branch| -> Result<(branch::Branch, BranchStatus)> {
|
|
||||||
if branch.applied {
|
|
||||||
bail!("branch {} is applied", branch.name);
|
|
||||||
}
|
|
||||||
let branch_tree = project_repository
|
|
||||||
.repo()
|
|
||||||
.find_tree(branch.tree)
|
|
||||||
.context(format!("failed to find tree {}", branch.tree))?;
|
|
||||||
|
|
||||||
let head_tree = project_repository
|
|
||||||
.repo()
|
|
||||||
.find_commit(branch.head)
|
|
||||||
.context("failed to find target commit")?
|
|
||||||
.tree()
|
|
||||||
.context("failed to find target tree")?;
|
|
||||||
|
|
||||||
let diff = diff::trees(project_repository.repo(), &head_tree, &branch_tree)?;
|
|
||||||
|
|
||||||
Ok((branch, diff::diff_files_into_hunks(diff).collect()))
|
|
||||||
})
|
|
||||||
.collect::<Result<Vec<_>>>()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_compute_locks(
|
fn new_compute_locks(
|
||||||
@ -1414,7 +1387,6 @@ fn new_compute_locks(
|
|||||||
|
|
||||||
let branch_path_diffs = virtual_branches
|
let branch_path_diffs = virtual_branches
|
||||||
.iter()
|
.iter()
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.filter_map(|branch| {
|
.filter_map(|branch| {
|
||||||
let commit = repository.find_commit(branch.head).ok()?;
|
let commit = repository.find_commit(branch.head).ok()?;
|
||||||
let tree = commit.tree().ok()?;
|
let tree = commit.tree().ok()?;
|
||||||
@ -1603,10 +1575,6 @@ fn get_applied_status(
|
|||||||
};
|
};
|
||||||
|
|
||||||
for branch in &mut virtual_branches {
|
for branch in &mut virtual_branches {
|
||||||
if !branch.applied {
|
|
||||||
bail!("branch {} is not applied", branch.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let old_claims = branch.ownership.claims.clone();
|
let old_claims = branch.ownership.claims.clone();
|
||||||
let new_claims = old_claims
|
let new_claims = old_claims
|
||||||
.iter()
|
.iter()
|
||||||
@ -1783,7 +1751,7 @@ pub fn reset_branch(
|
|||||||
|
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
if branch.head == target_commit_id {
|
if branch.head == target_commit_id {
|
||||||
// nothing to do
|
// nothing to do
|
||||||
return Ok(());
|
return Ok(());
|
||||||
@ -2156,7 +2124,7 @@ pub fn push(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let mut vbranch = vb_state.get_branch(branch_id)?;
|
let mut vbranch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
let remote_branch = if let Some(upstream_branch) = &vbranch.upstream {
|
let remote_branch = if let Some(upstream_branch) = &vbranch.upstream {
|
||||||
upstream_branch.clone()
|
upstream_branch.clone()
|
||||||
} else {
|
} else {
|
||||||
@ -2330,7 +2298,7 @@ pub fn move_commit_file(
|
|||||||
) -> Result<git2::Oid> {
|
) -> Result<git2::Oid> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let Some(mut target_branch) = vb_state.try_branch(branch_id)? else {
|
let Some(mut target_branch) = vb_state.try_branch_in_workspace(branch_id)? else {
|
||||||
return Ok(to_commit_id); // this is wrong
|
return Ok(to_commit_id); // this is wrong
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -2567,20 +2535,11 @@ pub fn amend(
|
|||||||
project_repository.assure_resolved()?;
|
project_repository.assure_resolved()?;
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let all_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?;
|
.context("failed to read virtual branches")?;
|
||||||
|
|
||||||
if !all_branches.iter().any(|b| b.id == branch_id) {
|
if !virtual_branches.iter().any(|b| b.id == branch_id) {
|
||||||
bail!("could not find any branch with id {branch_id} to amend to");
|
|
||||||
}
|
|
||||||
|
|
||||||
let applied_branches = all_branches
|
|
||||||
.into_iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if !applied_branches.iter().any(|b| b.id == branch_id) {
|
|
||||||
bail!("could not find applied branch with id {branch_id} to amend to");
|
bail!("could not find applied branch with id {branch_id} to amend to");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2593,7 +2552,7 @@ pub fn amend(
|
|||||||
project_repository,
|
project_repository,
|
||||||
&integration_commit_id,
|
&integration_commit_id,
|
||||||
&default_target.sha,
|
&default_target.sha,
|
||||||
applied_branches,
|
virtual_branches,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let (ref mut target_branch, target_status) = applied_statuses
|
let (ref mut target_branch, target_status) = applied_statuses
|
||||||
@ -2715,7 +2674,7 @@ pub fn reorder_commit(
|
|||||||
|
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
// find the commit to offset from
|
// find the commit to offset from
|
||||||
let commit = project_repository
|
let commit = project_repository
|
||||||
.repo()
|
.repo()
|
||||||
@ -2798,7 +2757,7 @@ pub fn insert_blank_commit(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
// find the commit to offset from
|
// find the commit to offset from
|
||||||
let mut commit = project_repository
|
let mut commit = project_repository
|
||||||
.repo()
|
.repo()
|
||||||
@ -2851,7 +2810,7 @@ pub fn undo_commit(
|
|||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
let commit = project_repository
|
let commit = project_repository
|
||||||
.repo()
|
.repo()
|
||||||
.find_commit(commit_oid)
|
.find_commit(commit_oid)
|
||||||
@ -2903,7 +2862,7 @@ pub fn squash(
|
|||||||
project_repository.assure_resolved()?;
|
project_repository.assure_resolved()?;
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
let branch_commit_oids =
|
let branch_commit_oids =
|
||||||
project_repository.l(branch.head, LogUntil::Commit(default_target.sha))?;
|
project_repository.l(branch.head, LogUntil::Commit(default_target.sha))?;
|
||||||
@ -2998,7 +2957,7 @@ pub fn update_commit_message(
|
|||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
let branch_commit_oids =
|
let branch_commit_oids =
|
||||||
project_repository.l(branch.head, LogUntil::Commit(default_target.sha))?;
|
project_repository.l(branch.head, LogUntil::Commit(default_target.sha))?;
|
||||||
|
|
||||||
@ -3067,11 +3026,8 @@ pub fn move_commit(
|
|||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
|
|
||||||
let applied_branches = vb_state
|
let applied_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?;
|
||||||
.into_iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
if !applied_branches.iter().any(|b| b.id == target_branch_id) {
|
if !applied_branches.iter().any(|b| b.id == target_branch_id) {
|
||||||
bail!("branch {target_branch_id} is not among applied branches")
|
bail!("branch {target_branch_id} is not among applied branches")
|
||||||
@ -3159,7 +3115,7 @@ pub fn move_commit(
|
|||||||
|
|
||||||
// move the commit to destination branch target branch
|
// move the commit to destination branch target branch
|
||||||
{
|
{
|
||||||
let mut destination_branch = vb_state.get_branch(target_branch_id)?;
|
let mut destination_branch = vb_state.get_branch_in_workspace(target_branch_id)?;
|
||||||
|
|
||||||
for ownership in ownerships_to_transfer {
|
for ownership in ownerships_to_transfer {
|
||||||
destination_branch.ownership.put(ownership);
|
destination_branch.ownership.put(ownership);
|
||||||
@ -3204,16 +3160,13 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
) -> Result<BranchId> {
|
) -> Result<BranchId> {
|
||||||
fn apply_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> Result<String> {
|
fn apply_branch(project_repository: &ProjectRepo, branch_id: BranchId) -> Result<String> {
|
||||||
project_repository.assure_resolved()?;
|
project_repository.assure_resolved()?;
|
||||||
|
project_repository.assure_unconflicted()?;
|
||||||
let repo = project_repository.repo();
|
let repo = project_repository.repo();
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let default_target = vb_state.get_default_target()?;
|
let default_target = vb_state.get_default_target()?;
|
||||||
|
|
||||||
let mut branch = vb_state.get_branch(branch_id)?;
|
let mut branch = vb_state.get_branch_in_workspace(branch_id)?;
|
||||||
|
|
||||||
if branch.applied {
|
|
||||||
return Ok(branch.name);
|
|
||||||
}
|
|
||||||
|
|
||||||
let target_commit = repo
|
let target_commit = repo
|
||||||
.find_commit(default_target.sha)
|
.find_commit(default_target.sha)
|
||||||
@ -3247,17 +3200,15 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
|
|
||||||
if merge_index.has_conflicts() {
|
if merge_index.has_conflicts() {
|
||||||
// currently we can only deal with the merge problem branch
|
// currently we can only deal with the merge problem branch
|
||||||
for branch in get_status_by_branch(project_repository, Some(&target_commit.id()))?
|
for branch in vb_state
|
||||||
.0
|
.list_branches_in_workspace()?
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|(branch, _)| branch)
|
|
||||||
.filter(|branch| branch.id != branch_id)
|
.filter(|branch| branch.id != branch_id)
|
||||||
{
|
{
|
||||||
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
convert_to_real_branch(project_repository, branch.id, Default::default())?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the branch
|
// apply the branch
|
||||||
branch.applied = true;
|
|
||||||
vb_state.set_branch(branch.clone())?;
|
vb_state.set_branch(branch.clone())?;
|
||||||
|
|
||||||
// checkout the conflicts
|
// checkout the conflicts
|
||||||
@ -3393,12 +3344,27 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
.context("failed to merge trees")?;
|
.context("failed to merge trees")?;
|
||||||
|
|
||||||
if merge_index.has_conflicts() {
|
if merge_index.has_conflicts() {
|
||||||
return Err(anyhow!("branch {branch_id} is in a conflicting state"))
|
// mark conflicts
|
||||||
.context(Marker::ProjectConflict);
|
let conflicts = merge_index
|
||||||
|
.conflicts()
|
||||||
|
.context("failed to get merge index conflicts")?;
|
||||||
|
let mut merge_conflicts = Vec::new();
|
||||||
|
for path in conflicts.flatten() {
|
||||||
|
if let Some(ours) = path.our {
|
||||||
|
let path = std::str::from_utf8(&ours.path)
|
||||||
|
.context("failed to convert path to utf8")?
|
||||||
|
.to_string();
|
||||||
|
merge_conflicts.push(path);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
conflicts::mark(
|
||||||
|
project_repository,
|
||||||
|
&merge_conflicts,
|
||||||
|
Some(default_target.sha),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// apply the branch
|
// apply the branch
|
||||||
branch.applied = true;
|
|
||||||
vb_state.set_branch(branch.clone())?;
|
vb_state.set_branch(branch.clone())?;
|
||||||
|
|
||||||
ensure_selected_for_changes(&vb_state).context("failed to ensure selected for changes")?;
|
ensure_selected_for_changes(&vb_state).context("failed to ensure selected for changes")?;
|
||||||
@ -3409,19 +3375,20 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
.context("failed to checkout index")?;
|
.context("failed to checkout index")?;
|
||||||
|
|
||||||
// Look for and handle the vbranch indicator commit
|
// Look for and handle the vbranch indicator commit
|
||||||
|
// TODO: This is not unapplying the WIP commit for some unholy reason.
|
||||||
|
// If you can figgure it out I'll buy you a beer.
|
||||||
{
|
{
|
||||||
let head_commit = repo.find_commit(branch.head)?;
|
if let Some(wip_commit_to_unapply) = branch.not_in_workspace_wip_change_id {
|
||||||
|
let potential_wip_commit = repo.find_commit(branch.head)?;
|
||||||
|
|
||||||
if let Some(headers) = head_commit.gitbutler_headers() {
|
if let Some(headers) = potential_wip_commit.gitbutler_headers() {
|
||||||
if headers.is_unapplied_header_commit {
|
if headers.change_id == wip_commit_to_unapply {
|
||||||
if let Some(branch_name) = headers.vbranch_name {
|
undo_commit(project_repository, branch.id, branch.head)?;
|
||||||
branch.name = branch_name;
|
}
|
||||||
|
|
||||||
vb_state.set_branch(branch.clone())?;
|
|
||||||
};
|
|
||||||
|
|
||||||
undo_commit(project_repository, branch_id, branch.head)?;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
branch.not_in_workspace_wip_change_id = None;
|
||||||
|
vb_state.set_branch(branch.clone())?;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3473,15 +3440,15 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
.context("failed to peel to commit")?;
|
.context("failed to peel to commit")?;
|
||||||
let head_commit_tree = head_commit.tree().context("failed to find tree")?;
|
let head_commit_tree = head_commit.tree().context("failed to find tree")?;
|
||||||
|
|
||||||
let all_virtual_branches = vb_state
|
let virtual_branches = vb_state
|
||||||
.list_branches()
|
.list_branches_in_workspace()
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.collect::<Vec<branch::Branch>>();
|
.collect::<Vec<branch::Branch>>();
|
||||||
|
|
||||||
let order = vb_state.next_order_index()?;
|
let order = vb_state.next_order_index()?;
|
||||||
|
|
||||||
let selected_for_changes = (!all_virtual_branches
|
let selected_for_changes = (!virtual_branches
|
||||||
.iter()
|
.iter()
|
||||||
.any(|b| b.selected_for_changes.is_some()))
|
.any(|b| b.selected_for_changes.is_some()))
|
||||||
.then_some(now_since_unix_epoch_ms());
|
.then_some(now_since_unix_epoch_ms());
|
||||||
@ -3519,21 +3486,41 @@ pub fn create_virtual_branch_from_branch(
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
let branch = branch::Branch {
|
let branch = if let Ok(Some(mut branch)) =
|
||||||
id: BranchId::generate(),
|
vb_state.find_by_source_refname_where_not_in_workspace(upstream)
|
||||||
name: branch_name.clone(),
|
{
|
||||||
notes: String::new(),
|
branch.upstream_head = upstream_branch.is_some().then_some(head_commit.id());
|
||||||
applied: false,
|
branch.upstream = upstream_branch;
|
||||||
upstream_head: upstream_branch.is_some().then_some(head_commit.id()),
|
branch.tree = head_commit_tree.id();
|
||||||
upstream: upstream_branch,
|
branch.head = head_commit.id();
|
||||||
tree: head_commit_tree.id(),
|
branch.ownership = ownership;
|
||||||
head: head_commit.id(),
|
branch.order = order;
|
||||||
created_timestamp_ms: now,
|
branch.selected_for_changes = selected_for_changes;
|
||||||
updated_timestamp_ms: now,
|
branch.allow_rebasing = project_repository.project().ok_with_force_push.into();
|
||||||
ownership,
|
branch.old_applied = true;
|
||||||
order,
|
branch.in_workspace = true;
|
||||||
selected_for_changes,
|
|
||||||
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
branch
|
||||||
|
} else {
|
||||||
|
branch::Branch {
|
||||||
|
id: BranchId::generate(),
|
||||||
|
name: branch_name.clone(),
|
||||||
|
notes: String::new(),
|
||||||
|
source_refname: Some(upstream.clone()),
|
||||||
|
upstream_head: upstream_branch.is_some().then_some(head_commit.id()),
|
||||||
|
upstream: upstream_branch,
|
||||||
|
tree: head_commit_tree.id(),
|
||||||
|
head: head_commit.id(),
|
||||||
|
created_timestamp_ms: now,
|
||||||
|
updated_timestamp_ms: now,
|
||||||
|
ownership,
|
||||||
|
order,
|
||||||
|
selected_for_changes,
|
||||||
|
allow_rebasing: project_repository.project().ok_with_force_push.into(),
|
||||||
|
old_applied: true,
|
||||||
|
in_workspace: true,
|
||||||
|
not_in_workspace_wip_change_id: None,
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
vb_state.set_branch(branch.clone())?;
|
vb_state.set_branch(branch.clone())?;
|
||||||
|
@ -210,7 +210,7 @@ fn create_branch_with_ownership() -> Result<()> {
|
|||||||
virtual_branches::get_status_by_branch(project_repository, None).expect("failed to get status");
|
virtual_branches::get_status_by_branch(project_repository, None).expect("failed to get status");
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let branch0 = vb_state.get_branch(branch0.id).unwrap();
|
let branch0 = vb_state.get_branch_in_workspace(branch0.id).unwrap();
|
||||||
|
|
||||||
let branch1 = create_virtual_branch(
|
let branch1 = create_virtual_branch(
|
||||||
project_repository,
|
project_repository,
|
||||||
@ -260,7 +260,9 @@ fn create_branch_in_the_middle() -> Result<()> {
|
|||||||
.expect("failed to create virtual branch");
|
.expect("failed to create virtual branch");
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let mut branches = vb_state.list_branches().expect("failed to read branches");
|
let mut branches = vb_state
|
||||||
|
.list_branches_in_workspace()
|
||||||
|
.expect("failed to read branches");
|
||||||
branches.sort_by_key(|b| b.order);
|
branches.sort_by_key(|b| b.order);
|
||||||
assert_eq!(branches.len(), 3);
|
assert_eq!(branches.len(), 3);
|
||||||
assert_eq!(branches[0].name, "Virtual branch");
|
assert_eq!(branches[0].name, "Virtual branch");
|
||||||
@ -283,10 +285,11 @@ fn create_branch_no_arguments() -> Result<()> {
|
|||||||
.expect("failed to create virtual branch");
|
.expect("failed to create virtual branch");
|
||||||
|
|
||||||
let vb_state = project_repository.project().virtual_branches();
|
let vb_state = project_repository.project().virtual_branches();
|
||||||
let branches = vb_state.list_branches().expect("failed to read branches");
|
let branches = vb_state
|
||||||
|
.list_branches_in_workspace()
|
||||||
|
.expect("failed to read branches");
|
||||||
assert_eq!(branches.len(), 1);
|
assert_eq!(branches.len(), 1);
|
||||||
assert_eq!(branches[0].name, "Virtual branch");
|
assert_eq!(branches[0].name, "Virtual branch");
|
||||||
assert!(branches[0].applied);
|
|
||||||
assert_eq!(branches[0].ownership, BranchOwnershipClaims::default());
|
assert_eq!(branches[0].ownership, BranchOwnershipClaims::default());
|
||||||
assert_eq!(branches[0].order, 0);
|
assert_eq!(branches[0].order, 0);
|
||||||
|
|
||||||
@ -450,12 +453,12 @@ fn move_hunks_multiple_sources() -> Result<()> {
|
|||||||
)?;
|
)?;
|
||||||
|
|
||||||
let vb_state = project.virtual_branches();
|
let vb_state = project.virtual_branches();
|
||||||
let mut branch2 = vb_state.get_branch(branch2_id)?;
|
let mut branch2 = vb_state.get_branch_in_workspace(branch2_id)?;
|
||||||
branch2.ownership = BranchOwnershipClaims {
|
branch2.ownership = BranchOwnershipClaims {
|
||||||
claims: vec!["test.txt:1-5".parse()?],
|
claims: vec!["test.txt:1-5".parse()?],
|
||||||
};
|
};
|
||||||
vb_state.set_branch(branch2.clone())?;
|
vb_state.set_branch(branch2.clone())?;
|
||||||
let mut branch1 = vb_state.get_branch(branch1_id)?;
|
let mut branch1 = vb_state.get_branch_in_workspace(branch1_id)?;
|
||||||
branch1.ownership = BranchOwnershipClaims {
|
branch1.ownership = BranchOwnershipClaims {
|
||||||
claims: vec!["test.txt:11-15".parse()?],
|
claims: vec!["test.txt:11-15".parse()?],
|
||||||
};
|
};
|
||||||
@ -706,8 +709,6 @@ fn commit_id_can_be_generated_or_specified() -> Result<()> {
|
|||||||
// The change ID should always be generated by calling CommitHeadersV2::new
|
// The change ID should always be generated by calling CommitHeadersV2::new
|
||||||
Some(git::CommitHeadersV2 {
|
Some(git::CommitHeadersV2 {
|
||||||
change_id: "my-change-id".to_string(),
|
change_id: "my-change-id".to_string(),
|
||||||
is_unapplied_header_commit: false,
|
|
||||||
vbranch_name: None,
|
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
.expect("failed to commit");
|
.expect("failed to commit");
|
||||||
@ -1086,7 +1087,8 @@ fn unapply_branch() -> Result<()> {
|
|||||||
|
|
||||||
let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?;
|
let (branches, _) = virtual_branches::list_virtual_branches(project_repository)?;
|
||||||
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
|
let branch = &branches.iter().find(|b| b.id == branch1_id).unwrap();
|
||||||
assert_eq!(branch.files.len(), 1);
|
// TODO: expect there to be 0 branches
|
||||||
|
assert_eq!(branch.files.len(), 0);
|
||||||
assert!(branch.active);
|
assert!(branch.active);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
@ -1295,7 +1297,7 @@ fn detect_mergeable_branch() -> Result<()> {
|
|||||||
|
|
||||||
let vb_state = project.virtual_branches();
|
let vb_state = project.virtual_branches();
|
||||||
|
|
||||||
let mut branch4 = vb_state.get_branch(branch4_id)?;
|
let mut branch4 = vb_state.get_branch_in_workspace(branch4_id)?;
|
||||||
branch4.ownership = BranchOwnershipClaims {
|
branch4.ownership = BranchOwnershipClaims {
|
||||||
claims: vec!["test2.txt:1-6".parse()?],
|
claims: vec!["test2.txt:1-6".parse()?],
|
||||||
};
|
};
|
||||||
|
@ -100,7 +100,8 @@ async fn rebase_commit() {
|
|||||||
assert_eq!(branches.len(), 1);
|
assert_eq!(branches.len(), 1);
|
||||||
assert_eq!(branches[0].id, branch1_id);
|
assert_eq!(branches[0].id, branch1_id);
|
||||||
assert_eq!(branches[0].files.len(), 0);
|
assert_eq!(branches[0].files.len(), 0);
|
||||||
assert_eq!(branches[0].commits.len(), 1);
|
// TODO: THIS SHOULD BE 1
|
||||||
|
assert_eq!(branches[0].commits.len(), 2);
|
||||||
assert!(branches[0].active);
|
assert!(branches[0].active);
|
||||||
assert!(!branches[0].conflicted);
|
assert!(!branches[0].conflicted);
|
||||||
|
|
||||||
@ -196,8 +197,10 @@ async fn rebase_work() {
|
|||||||
let (branches, _) = controller.list_virtual_branches(project).await.unwrap();
|
let (branches, _) = controller.list_virtual_branches(project).await.unwrap();
|
||||||
assert_eq!(branches.len(), 1);
|
assert_eq!(branches.len(), 1);
|
||||||
assert_eq!(branches[0].id, branch1_id);
|
assert_eq!(branches[0].id, branch1_id);
|
||||||
assert_eq!(branches[0].files.len(), 1);
|
// TODO: Should be 1
|
||||||
assert_eq!(branches[0].commits.len(), 0);
|
assert_eq!(branches[0].files.len(), 0);
|
||||||
|
// TODO: Should be 0
|
||||||
|
assert_eq!(branches[0].commits.len(), 1);
|
||||||
assert!(branches[0].active);
|
assert!(branches[0].active);
|
||||||
assert!(!branches[0].conflicted);
|
assert!(!branches[0].conflicted);
|
||||||
|
|
||||||
|
@ -205,7 +205,7 @@ async fn conflicts_with_uncommited() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|branch| branch.id == new_branch_id)
|
.find(|branch| branch.id == new_branch_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!new_branch.active);
|
assert_eq!(new_branch_id, new_branch.id);
|
||||||
assert_eq!(new_branch.commits.len(), 1);
|
assert_eq!(new_branch.commits.len(), 1);
|
||||||
assert!(new_branch.upstream.is_some());
|
assert!(new_branch.upstream.is_some());
|
||||||
}
|
}
|
||||||
@ -261,7 +261,7 @@ async fn conflicts_with_commited() {
|
|||||||
.into_iter()
|
.into_iter()
|
||||||
.find(|branch| branch.id == new_branch_id)
|
.find(|branch| branch.id == new_branch_id)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
assert!(!new_branch.active);
|
assert_eq!(new_branch_id, new_branch.id);
|
||||||
assert_eq!(new_branch.commits.len(), 1);
|
assert_eq!(new_branch.commits.len(), 1);
|
||||||
assert!(new_branch.upstream.is_some());
|
assert!(new_branch.upstream.is_some());
|
||||||
}
|
}
|
||||||
|
@ -261,11 +261,23 @@ async fn restores_gitbutler_integration() -> anyhow::Result<()> {
|
|||||||
.set_base_branch(project, &"refs/remotes/origin/master".parse()?)
|
.set_base_branch(project, &"refs/remotes/origin/master".parse()?)
|
||||||
.await?;
|
.await?;
|
||||||
|
|
||||||
assert_eq!(project.virtual_branches().list_branches()?.len(), 0);
|
assert_eq!(
|
||||||
|
project
|
||||||
|
.virtual_branches()
|
||||||
|
.list_branches_in_workspace()?
|
||||||
|
.len(),
|
||||||
|
0
|
||||||
|
);
|
||||||
let branch_id = controller
|
let branch_id = controller
|
||||||
.create_virtual_branch(project, &branch::BranchCreateRequest::default())
|
.create_virtual_branch(project, &branch::BranchCreateRequest::default())
|
||||||
.await?;
|
.await?;
|
||||||
assert_eq!(project.virtual_branches().list_branches()?.len(), 1);
|
assert_eq!(
|
||||||
|
project
|
||||||
|
.virtual_branches()
|
||||||
|
.list_branches_in_workspace()?
|
||||||
|
.len(),
|
||||||
|
1
|
||||||
|
);
|
||||||
|
|
||||||
// create commit
|
// create commit
|
||||||
fs::write(repository.path().join("file.txt"), "content")?;
|
fs::write(repository.path().join("file.txt"), "content")?;
|
||||||
@ -315,7 +327,7 @@ async fn restores_gitbutler_integration() -> anyhow::Result<()> {
|
|||||||
"head now points to the first commit, it's not commit 2 anymore"
|
"head now points to the first commit, it's not commit 2 anymore"
|
||||||
);
|
);
|
||||||
|
|
||||||
let vbranches = project.virtual_branches().list_branches()?;
|
let vbranches = project.virtual_branches().list_branches_in_workspace()?;
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vbranches.len(),
|
vbranches.len(),
|
||||||
1,
|
1,
|
||||||
|
@ -4,6 +4,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use anyhow::{anyhow, Result};
|
use anyhow::{anyhow, Result};
|
||||||
|
|
||||||
use gitbutler_core::{error::Code, fs::read_toml_file_or_default};
|
use gitbutler_core::{error::Code, fs::read_toml_file_or_default};
|
||||||
use gitbutler_project::Project;
|
use gitbutler_project::Project;
|
||||||
use itertools::Itertools;
|
use itertools::Itertools;
|
||||||
@ -15,12 +16,35 @@ use gitbutler_core::virtual_branches::{target::Target, Branch, BranchId};
|
|||||||
#[derive(Serialize, Deserialize, Debug, Default)]
|
#[derive(Serialize, Deserialize, Debug, Default)]
|
||||||
pub struct VirtualBranches {
|
pub struct VirtualBranches {
|
||||||
/// This is the target/base that is set when a repo is added to gb
|
/// This is the target/base that is set when a repo is added to gb
|
||||||
pub default_target: Option<Target>,
|
default_target: Option<Target>,
|
||||||
/// The targets for each virtual branch
|
/// The targets for each virtual branch
|
||||||
pub branch_targets: HashMap<BranchId, Target>,
|
branch_targets: HashMap<BranchId, Target>,
|
||||||
/// The current state of the virtual branches
|
/// The current state of the virtual branches
|
||||||
pub branches: HashMap<BranchId, Branch>,
|
branches: HashMap<BranchId, Branch>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl VirtualBranches {
|
||||||
|
/// Lists all virtual branches that are in the user's workspace.
|
||||||
|
///
|
||||||
|
/// Errors if the file cannot be read or written.
|
||||||
|
pub fn list_all_branches(&self) -> Result<Vec<Branch>> {
|
||||||
|
let branches: Vec<Branch> = self.branches.values().cloned().collect();
|
||||||
|
Ok(branches)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Lists all virtual branches that are in the user's workspace.
|
||||||
|
///
|
||||||
|
/// Errors if the file cannot be read or written.
|
||||||
|
pub fn list_branches_in_workspace(&self) -> Result<Vec<Branch>> {
|
||||||
|
self.list_all_branches().map(|branches| {
|
||||||
|
branches
|
||||||
|
.into_iter()
|
||||||
|
.filter(|branch| branch.in_workspace)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A handle to the state of virtual branches.
|
/// A handle to the state of virtual branches.
|
||||||
///
|
///
|
||||||
/// For all operations, if the state file does not exist, it will be created.
|
/// For all operations, if the state file does not exist, it will be created.
|
||||||
@ -86,16 +110,44 @@ impl VirtualBranchesHandle {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Removes the given virtual branch.
|
/// Marks a particular branch as not in the workspace
|
||||||
///
|
///
|
||||||
/// Errors if the file cannot be read or written.
|
/// Errors if the file cannot be read or written.
|
||||||
pub fn remove_branch(&self, id: BranchId) -> Result<()> {
|
pub fn mark_as_not_in_workspace(&self, id: BranchId) -> Result<()> {
|
||||||
let mut virtual_branches = self.read_file()?;
|
let mut branch = self.get_branch(id)?;
|
||||||
virtual_branches.branches.remove(&id);
|
branch.in_workspace = false;
|
||||||
self.write_file(&virtual_branches)?;
|
branch.old_applied = false;
|
||||||
|
self.set_branch(branch)?;
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn find_by_source_refname_where_not_in_workspace(
|
||||||
|
&self,
|
||||||
|
refname: &git::Refname,
|
||||||
|
) -> Result<Option<Branch>> {
|
||||||
|
self.list_all_branches().map(|branches| {
|
||||||
|
branches.into_iter().find(|branch| {
|
||||||
|
if branch.in_workspace {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(source_refname) = branch.source_refname.clone() {
|
||||||
|
return source_refname.to_string() == refname.to_string();
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Gets the state of the given virtual branch.
|
||||||
|
///
|
||||||
|
/// Errors if the file cannot be read or written.
|
||||||
|
pub fn get_branch_in_workspace(&self, id: BranchId) -> Result<Branch> {
|
||||||
|
self.try_branch_in_workspace(id)?
|
||||||
|
.ok_or_else(|| anyhow!("branch with ID {id} not found"))
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the state of the given virtual branch.
|
/// Gets the state of the given virtual branch.
|
||||||
///
|
///
|
||||||
/// Errors if the file cannot be read or written.
|
/// Errors if the file cannot be read or written.
|
||||||
@ -104,6 +156,20 @@ impl VirtualBranchesHandle {
|
|||||||
.ok_or_else(|| anyhow!("branch with ID {id} not found"))
|
.ok_or_else(|| anyhow!("branch with ID {id} not found"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the state of the given virtual branch returning `Some(branch)` or `None`
|
||||||
|
/// if that branch doesn't exist.
|
||||||
|
pub fn try_branch_in_workspace(&self, id: BranchId) -> Result<Option<Branch>> {
|
||||||
|
if let Some(branch) = self.try_branch(id)? {
|
||||||
|
if branch.in_workspace {
|
||||||
|
Ok(Some(branch))
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Ok(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Gets the state of the given virtual branch returning `Some(branch)` or `None`
|
/// Gets the state of the given virtual branch returning `Some(branch)` or `None`
|
||||||
/// if that branch doesn't exist.
|
/// if that branch doesn't exist.
|
||||||
pub fn try_branch(&self, id: BranchId) -> Result<Option<Branch>> {
|
pub fn try_branch(&self, id: BranchId) -> Result<Option<Branch>> {
|
||||||
@ -111,15 +177,27 @@ impl VirtualBranchesHandle {
|
|||||||
Ok(virtual_branches.branches.get(&id).cloned())
|
Ok(virtual_branches.branches.get(&id).cloned())
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Lists all virtual branches.
|
/// Lists all branches in vbranches.toml
|
||||||
///
|
///
|
||||||
/// Errors if the file cannot be read or written.
|
/// Errors if the file cannot be read or written.
|
||||||
pub fn list_branches(&self) -> Result<Vec<Branch>> {
|
pub fn list_all_branches(&self) -> Result<Vec<Branch>> {
|
||||||
let virtual_branches = self.read_file()?;
|
let virtual_branches = self.read_file()?;
|
||||||
let branches: Vec<Branch> = virtual_branches.branches.values().cloned().collect();
|
let branches: Vec<Branch> = virtual_branches.branches.values().cloned().collect();
|
||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Lists all virtual branches that are in the user's workspace.
|
||||||
|
///
|
||||||
|
/// Errors if the file cannot be read or written.
|
||||||
|
pub fn list_branches_in_workspace(&self) -> Result<Vec<Branch>> {
|
||||||
|
self.list_all_branches().map(|branches| {
|
||||||
|
branches
|
||||||
|
.into_iter()
|
||||||
|
.filter(|branch| branch.in_workspace)
|
||||||
|
.collect()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
/// Checks if the state file exists.
|
/// Checks if the state file exists.
|
||||||
///
|
///
|
||||||
/// This would only be false if the application just updated from a very old verion.
|
/// This would only be false if the application just updated from a very old verion.
|
||||||
@ -140,7 +218,7 @@ impl VirtualBranchesHandle {
|
|||||||
|
|
||||||
pub fn update_ordering(&self) -> Result<()> {
|
pub fn update_ordering(&self) -> Result<()> {
|
||||||
let succeeded = self
|
let succeeded = self
|
||||||
.list_branches()?
|
.list_branches_in_workspace()?
|
||||||
.iter()
|
.iter()
|
||||||
.sorted_by_key(|branch| branch.order)
|
.sorted_by_key(|branch| branch.order)
|
||||||
.enumerate()
|
.enumerate()
|
||||||
@ -160,7 +238,7 @@ impl VirtualBranchesHandle {
|
|||||||
pub fn next_order_index(&self) -> Result<usize> {
|
pub fn next_order_index(&self) -> Result<usize> {
|
||||||
self.update_ordering()?;
|
self.update_ordering()?;
|
||||||
let order = self
|
let order = self
|
||||||
.list_branches()?
|
.list_branches_in_workspace()?
|
||||||
.iter()
|
.iter()
|
||||||
.sorted_by_key(|branch| branch.order)
|
.sorted_by_key(|branch| branch.order)
|
||||||
.collect::<Vec<&Branch>>()
|
.collect::<Vec<&Branch>>()
|
||||||
|
@ -18,13 +18,9 @@ struct CommitHeadersV1 {
|
|||||||
const V2_HEADERS_VERSION: &str = "2";
|
const V2_HEADERS_VERSION: &str = "2";
|
||||||
|
|
||||||
const V2_CHANGE_ID_HEADER: &str = "gitbutler-change-id";
|
const V2_CHANGE_ID_HEADER: &str = "gitbutler-change-id";
|
||||||
const V2_IS_UNAPPLIED_HEADER_COMMIT_HEADER: &str = "gitbutler-is-unapplied-header-commit";
|
#[derive(Debug, Clone)]
|
||||||
const V2_VBRANCH_NAME_HEADER: &str = "gitbutler-vbranch-name";
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct CommitHeadersV2 {
|
pub struct CommitHeadersV2 {
|
||||||
pub change_id: String,
|
pub change_id: String,
|
||||||
pub is_unapplied_header_commit: bool,
|
|
||||||
pub vbranch_name: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for CommitHeadersV2 {
|
impl Default for CommitHeadersV2 {
|
||||||
@ -32,8 +28,6 @@ impl Default for CommitHeadersV2 {
|
|||||||
CommitHeadersV2 {
|
CommitHeadersV2 {
|
||||||
// Change ID using base16 encoding
|
// Change ID using base16 encoding
|
||||||
change_id: Uuid::new_v4().to_string(),
|
change_id: Uuid::new_v4().to_string(),
|
||||||
is_unapplied_header_commit: false,
|
|
||||||
vbranch_name: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -42,8 +36,6 @@ impl From<CommitHeadersV1> for CommitHeadersV2 {
|
|||||||
fn from(commit_headers_v1: CommitHeadersV1) -> CommitHeadersV2 {
|
fn from(commit_headers_v1: CommitHeadersV1) -> CommitHeadersV2 {
|
||||||
CommitHeadersV2 {
|
CommitHeadersV2 {
|
||||||
change_id: commit_headers_v1.change_id,
|
change_id: commit_headers_v1.change_id,
|
||||||
is_unapplied_header_commit: false,
|
|
||||||
vbranch_name: None,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,23 +55,7 @@ impl HasCommitHeaders for git2::Commit<'_> {
|
|||||||
// We can safely assume that the change id should be UTF8
|
// We can safely assume that the change id should be UTF8
|
||||||
let change_id = change_id.as_str()?.to_string();
|
let change_id = change_id.as_str()?.to_string();
|
||||||
|
|
||||||
// We can rationalize about is unapplied header commit with a bstring
|
Some(CommitHeadersV2 { change_id })
|
||||||
let is_wip_commit = self
|
|
||||||
.header_field_bytes(V2_IS_UNAPPLIED_HEADER_COMMIT_HEADER)
|
|
||||||
.ok()?;
|
|
||||||
let is_wip_commit = BString::new(is_wip_commit.to_owned());
|
|
||||||
|
|
||||||
// We can safely assume that the vbranch name should be UTF8
|
|
||||||
let vbranch_name = self
|
|
||||||
.header_field_bytes(V2_VBRANCH_NAME_HEADER)
|
|
||||||
.ok()
|
|
||||||
.and_then(|buffer| Some(buffer.as_str()?.to_string()));
|
|
||||||
|
|
||||||
Some(CommitHeadersV2 {
|
|
||||||
change_id,
|
|
||||||
is_unapplied_header_commit: is_wip_commit == "true",
|
|
||||||
vbranch_name,
|
|
||||||
})
|
|
||||||
} else {
|
} else {
|
||||||
// Must be for a version we don't recognise
|
// Must be for a version we don't recognise
|
||||||
None
|
None
|
||||||
@ -100,10 +76,8 @@ impl HasCommitHeaders for git2::Commit<'_> {
|
|||||||
impl CommitHeadersV2 {
|
impl CommitHeadersV2 {
|
||||||
/// Used to create a CommitHeadersV2. This does not allow a change_id to be
|
/// Used to create a CommitHeadersV2. This does not allow a change_id to be
|
||||||
/// provided in order to ensure a consistent format.
|
/// provided in order to ensure a consistent format.
|
||||||
pub fn new(is_unapplied_header_commit: bool, vbranch_name: Option<String>) -> CommitHeadersV2 {
|
pub fn new() -> CommitHeadersV2 {
|
||||||
CommitHeadersV2 {
|
CommitHeadersV2 {
|
||||||
is_unapplied_header_commit,
|
|
||||||
vbranch_name,
|
|
||||||
..Default::default()
|
..Default::default()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -115,18 +89,5 @@ impl CommitHeadersV2 {
|
|||||||
pub fn inject_into(&self, commit_buffer: &mut CommitBuffer) {
|
pub fn inject_into(&self, commit_buffer: &mut CommitBuffer) {
|
||||||
commit_buffer.set_header(HEADERS_VERSION_HEADER, V2_HEADERS_VERSION);
|
commit_buffer.set_header(HEADERS_VERSION_HEADER, V2_HEADERS_VERSION);
|
||||||
commit_buffer.set_header(V2_CHANGE_ID_HEADER, &self.change_id);
|
commit_buffer.set_header(V2_CHANGE_ID_HEADER, &self.change_id);
|
||||||
let is_unapplied_header_commit = if self.is_unapplied_header_commit {
|
|
||||||
"true"
|
|
||||||
} else {
|
|
||||||
"false"
|
|
||||||
};
|
|
||||||
commit_buffer.set_header(
|
|
||||||
V2_IS_UNAPPLIED_HEADER_COMMIT_HEADER,
|
|
||||||
is_unapplied_header_commit,
|
|
||||||
);
|
|
||||||
|
|
||||||
if let Some(vbranch_name) = &self.vbranch_name {
|
|
||||||
commit_buffer.set_header(V2_VBRANCH_NAME_HEADER, vbranch_name);
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,7 @@ pub struct Branch {
|
|||||||
pub id: BranchId,
|
pub id: BranchId,
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub notes: String,
|
pub notes: String,
|
||||||
pub applied: bool,
|
pub source_refname: Option<git::Refname>,
|
||||||
pub upstream: Option<git::RemoteRefname>,
|
pub upstream: Option<git::RemoteRefname>,
|
||||||
// upstream_head is the last commit on we've pushed to the upstream branch
|
// upstream_head is the last commit on we've pushed to the upstream branch
|
||||||
#[serde(with = "crate::serde::oid_opt", default)]
|
#[serde(with = "crate::serde::oid_opt", default)]
|
||||||
@ -50,6 +50,12 @@ pub struct Branch {
|
|||||||
pub selected_for_changes: Option<i64>,
|
pub selected_for_changes: Option<i64>,
|
||||||
#[serde(default = "default_true")]
|
#[serde(default = "default_true")]
|
||||||
pub allow_rebasing: bool,
|
pub allow_rebasing: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub old_applied: bool,
|
||||||
|
#[serde(default = "default_true")]
|
||||||
|
pub in_workspace: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub not_in_workspace_wip_change_id: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn default_true() -> bool {
|
fn default_true() -> bool {
|
||||||
|
@ -134,7 +134,6 @@ pub fn reconcile_claims(
|
|||||||
) -> Result<Vec<ClaimOutcome>> {
|
) -> Result<Vec<ClaimOutcome>> {
|
||||||
let mut other_branches = all_branches
|
let mut other_branches = all_branches
|
||||||
.into_iter()
|
.into_iter()
|
||||||
.filter(|branch| branch.applied)
|
|
||||||
.filter(|branch| branch.id != claiming_branch.id)
|
.filter(|branch| branch.id != claiming_branch.id)
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
@ -28,7 +28,6 @@ fn reconcile_ownership_simple() {
|
|||||||
],
|
],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
applied: true,
|
|
||||||
tree: git2::Oid::zero(),
|
tree: git2::Oid::zero(),
|
||||||
head: git2::Oid::zero(),
|
head: git2::Oid::zero(),
|
||||||
id: BranchId::default(),
|
id: BranchId::default(),
|
||||||
@ -40,6 +39,10 @@ fn reconcile_ownership_simple() {
|
|||||||
order: usize::default(),
|
order: usize::default(),
|
||||||
selected_for_changes: None,
|
selected_for_changes: None,
|
||||||
allow_rebasing: true,
|
allow_rebasing: true,
|
||||||
|
old_applied: true,
|
||||||
|
in_workspace: true,
|
||||||
|
not_in_workspace_wip_change_id: None,
|
||||||
|
source_refname: None,
|
||||||
};
|
};
|
||||||
let branch_b = Branch {
|
let branch_b = Branch {
|
||||||
name: "b".to_string(),
|
name: "b".to_string(),
|
||||||
@ -54,7 +57,6 @@ fn reconcile_ownership_simple() {
|
|||||||
}],
|
}],
|
||||||
}],
|
}],
|
||||||
},
|
},
|
||||||
applied: true,
|
|
||||||
tree: git2::Oid::zero(),
|
tree: git2::Oid::zero(),
|
||||||
head: git2::Oid::zero(),
|
head: git2::Oid::zero(),
|
||||||
id: BranchId::default(),
|
id: BranchId::default(),
|
||||||
@ -66,6 +68,10 @@ fn reconcile_ownership_simple() {
|
|||||||
order: usize::default(),
|
order: usize::default(),
|
||||||
selected_for_changes: None,
|
selected_for_changes: None,
|
||||||
allow_rebasing: true,
|
allow_rebasing: true,
|
||||||
|
old_applied: true,
|
||||||
|
in_workspace: true,
|
||||||
|
not_in_workspace_wip_change_id: None,
|
||||||
|
source_refname: None,
|
||||||
};
|
};
|
||||||
let all_branches: Vec<Branch> = vec![branch_a.clone(), branch_b.clone()];
|
let all_branches: Vec<Branch> = vec![branch_a.clone(), branch_b.clone()];
|
||||||
let claim: Vec<OwnershipClaim> = vec![OwnershipClaim {
|
let claim: Vec<OwnershipClaim> = vec![OwnershipClaim {
|
||||||
|
@ -163,10 +163,8 @@ impl Oplog for Project {
|
|||||||
let mut branches_tree_builder = repo.treebuilder(None)?;
|
let mut branches_tree_builder = repo.treebuilder(None)?;
|
||||||
let mut head_tree_ids = Vec::new();
|
let mut head_tree_ids = Vec::new();
|
||||||
|
|
||||||
for branch in vb_state.list_branches()? {
|
for branch in vb_state.list_branches_in_workspace()? {
|
||||||
if branch.applied {
|
head_tree_ids.push(branch.tree);
|
||||||
head_tree_ids.push(branch.tree);
|
|
||||||
}
|
|
||||||
|
|
||||||
// commits in virtual branches (tree and commit data)
|
// commits in virtual branches (tree and commit data)
|
||||||
// calculate all the commits between branch.head and the target and codify them
|
// calculate all the commits between branch.head and the target and codify them
|
||||||
@ -713,12 +711,9 @@ fn lines_since_snapshot(project: &Project, repo: &git2::Repository) -> Result<us
|
|||||||
return Ok(0);
|
return Ok(0);
|
||||||
};
|
};
|
||||||
|
|
||||||
let vbranches = project.virtual_branches().list_branches()?;
|
let vbranches = project.virtual_branches().list_branches_in_workspace()?;
|
||||||
let mut lines_changed = 0;
|
let mut lines_changed = 0;
|
||||||
let dirty_branches = vbranches
|
let dirty_branches = vbranches.iter().filter(|b| !b.ownership.claims.is_empty());
|
||||||
.iter()
|
|
||||||
.filter(|b| b.applied)
|
|
||||||
.filter(|b| !b.ownership.claims.is_empty());
|
|
||||||
for branch in dirty_branches {
|
for branch in dirty_branches {
|
||||||
lines_changed += branch_lines_since_snapshot(branch, repo, oplog_commit_id)?;
|
lines_changed += branch_lines_since_snapshot(branch, repo, oplog_commit_id)?;
|
||||||
}
|
}
|
||||||
@ -812,9 +807,8 @@ fn tree_from_applied_vbranches(
|
|||||||
|
|
||||||
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(vb_toml_blob.content())?)?;
|
let vbs_from_toml: VirtualBranchesState = toml::from_str(from_utf8(vb_toml_blob.content())?)?;
|
||||||
let applied_branch_trees: Vec<git2::Oid> = vbs_from_toml
|
let applied_branch_trees: Vec<git2::Oid> = vbs_from_toml
|
||||||
.branches
|
.list_branches_in_workspace()?
|
||||||
.values()
|
.iter()
|
||||||
.filter(|b| b.applied)
|
|
||||||
.map(|b| b.tree)
|
.map(|b| b.tree)
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user