mirror of
https://github.com/gitbutlerapp/gitbutler.git
synced 2024-12-23 01:22:12 +03:00
Virtual Commits (#507)
* add virtual commits to vbranches, return and render them * remove file changes from status
This commit is contained in:
parent
6651a605c1
commit
e46f3be1a4
@ -4,10 +4,12 @@ mod writer;
|
|||||||
pub use reader::BranchReader as Reader;
|
pub use reader::BranchReader as Reader;
|
||||||
pub use writer::BranchWriter as Writer;
|
pub use writer::BranchWriter as Writer;
|
||||||
|
|
||||||
use std::{fmt, ops, path};
|
use std::{fmt, ops, path, vec};
|
||||||
|
|
||||||
use anyhow::{anyhow, Context, Result};
|
use anyhow::{anyhow, Context, Result};
|
||||||
|
|
||||||
|
use super::VirtualBranchCommit;
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
#[derive(Debug, PartialEq, Eq, Hash, Clone)]
|
||||||
pub struct Ownership {
|
pub struct Ownership {
|
||||||
pub file_path: path::PathBuf,
|
pub file_path: path::PathBuf,
|
||||||
|
@ -26,6 +26,18 @@ pub struct VirtualBranch {
|
|||||||
pub name: String,
|
pub name: String,
|
||||||
pub active: bool,
|
pub active: bool,
|
||||||
pub files: Vec<VirtualBranchFile>,
|
pub files: Vec<VirtualBranchFile>,
|
||||||
|
pub commits: Vec<VirtualBranchCommit>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||||
|
#[serde(rename_all = "camelCase")]
|
||||||
|
pub struct VirtualBranchCommit {
|
||||||
|
pub id: String,
|
||||||
|
pub description: String,
|
||||||
|
pub created_at: u128,
|
||||||
|
pub author_name: String,
|
||||||
|
pub author_email: String,
|
||||||
|
pub is_remote: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, PartialEq, Clone, Serialize)]
|
#[derive(Debug, PartialEq, Clone, Serialize)]
|
||||||
@ -193,23 +205,93 @@ pub fn remote_branches(
|
|||||||
Ok(branches)
|
Ok(branches)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// just for debugging for now
|
||||||
|
fn print_diff(diff: git2::Diff) -> Result<()> {
|
||||||
|
diff.print(git2::DiffFormat::Patch, |delta, hunk, line| {
|
||||||
|
println!(
|
||||||
|
"delta: {:?} {:?}",
|
||||||
|
line.origin(),
|
||||||
|
std::str::from_utf8(line.content()).unwrap()
|
||||||
|
);
|
||||||
|
true
|
||||||
|
})?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
pub fn list_virtual_branches(
|
pub fn list_virtual_branches(
|
||||||
gb_repository: &gb_repository::Repository,
|
gb_repository: &gb_repository::Repository,
|
||||||
project_repository: &project_repository::Repository,
|
project_repository: &project_repository::Repository,
|
||||||
) -> Result<Vec<VirtualBranch>> {
|
) -> Result<Vec<VirtualBranch>> {
|
||||||
let mut branches: Vec<VirtualBranch> = Vec::new();
|
let mut branches: Vec<VirtualBranch> = Vec::new();
|
||||||
|
let default_target = get_default_target(gb_repository)?;
|
||||||
|
let default_sha = default_target.sha.clone();
|
||||||
|
|
||||||
let statuses = get_status_by_branch(gb_repository, project_repository)?;
|
let statuses = get_status_by_branch(gb_repository, project_repository)?;
|
||||||
for (branch, files) in &statuses {
|
for (branch, files) in &statuses {
|
||||||
let mut vfiles = vec![];
|
let mut vfiles = vec![];
|
||||||
for file in files {
|
|
||||||
vfiles.push(file.clone());
|
// check if head tree does not match target tree
|
||||||
|
// if so, we diff the head tree and the new write_tree output to see what is new and filter the hunks to just those
|
||||||
|
if default_sha != branch.head {
|
||||||
|
let vtree = write_tree(gb_repository, project_repository, &files)?;
|
||||||
|
let repo = &project_repository.git_repository;
|
||||||
|
// get the trees
|
||||||
|
let commit_old = repo.find_commit(branch.head)?;
|
||||||
|
let tree_old = commit_old.tree()?;
|
||||||
|
let vtree_tree = repo.find_tree(vtree)?;
|
||||||
|
|
||||||
|
// do a diff between branch.head and the tree we _would_ commit
|
||||||
|
let diff = repo.diff_tree_to_tree(Some(&tree_old), Some(&vtree_tree), None)?;
|
||||||
|
let hunks_by_filepath = diff_to_hunks_by_filepath(diff, project_repository)?;
|
||||||
|
|
||||||
|
vfiles = hunks_by_filepath
|
||||||
|
.iter()
|
||||||
|
.map(|(file_path, hunks)| VirtualBranchFile {
|
||||||
|
id: file_path.clone(),
|
||||||
|
path: file_path.to_string(),
|
||||||
|
hunks: hunks.clone(),
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
} else {
|
||||||
|
for file in files {
|
||||||
|
vfiles.push(file.clone());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let mut commits = vec![];
|
||||||
|
|
||||||
|
// find all commits on head that are not on target.sha
|
||||||
|
let repo = &project_repository.git_repository;
|
||||||
|
let mut revwalk = repo.revwalk()?;
|
||||||
|
revwalk.set_sorting(git2::Sort::TOPOLOGICAL)?;
|
||||||
|
revwalk.push(branch.head)?;
|
||||||
|
revwalk.hide(default_target.sha)?;
|
||||||
|
for oid in revwalk {
|
||||||
|
let oid = oid?;
|
||||||
|
let commit = repo.find_commit(oid)?;
|
||||||
|
let timestamp = commit.time().seconds() as u128;
|
||||||
|
let signature = commit.author();
|
||||||
|
let name = signature.name().unwrap().to_string();
|
||||||
|
let email = signature.email().unwrap().to_string();
|
||||||
|
let message = commit.message().unwrap().to_string();
|
||||||
|
let sha = oid.to_string();
|
||||||
|
let commit = VirtualBranchCommit {
|
||||||
|
id: sha,
|
||||||
|
created_at: timestamp * 1000,
|
||||||
|
author_name: name,
|
||||||
|
author_email: email,
|
||||||
|
description: message,
|
||||||
|
is_remote: false,
|
||||||
|
};
|
||||||
|
commits.push(commit);
|
||||||
|
}
|
||||||
|
|
||||||
let branch = VirtualBranch {
|
let branch = VirtualBranch {
|
||||||
id: branch.id.to_string(),
|
id: branch.id.to_string(),
|
||||||
name: branch.name.to_string(),
|
name: branch.name.to_string(),
|
||||||
active: branch.applied,
|
active: branch.applied,
|
||||||
files: vfiles,
|
files: vfiles,
|
||||||
|
commits,
|
||||||
};
|
};
|
||||||
branches.push(branch);
|
branches.push(branch);
|
||||||
}
|
}
|
||||||
@ -363,35 +445,10 @@ fn find_owner(stack: &[branch::Branch], needle: &branch::Ownership) -> Option<br
|
|||||||
explicitly_owned_by.or(implicitly_owned_by).cloned()
|
explicitly_owned_by.or(implicitly_owned_by).cloned()
|
||||||
}
|
}
|
||||||
|
|
||||||
// list the virtual branches and their file statuses (statusi?)
|
fn diff_to_hunks_by_filepath(
|
||||||
pub fn get_status_by_branch(
|
diff: git2::Diff,
|
||||||
gb_repository: &gb_repository::Repository,
|
project_repository: &project_repository::Repository,
|
||||||
project_repository: &project_repository::Repository<'_>,
|
) -> Result<HashMap<String, Vec<VirtualBranchHunk>>> {
|
||||||
) -> Result<Vec<(branch::Branch, Vec<VirtualBranchFile>)>> {
|
|
||||||
let current_session = gb_repository
|
|
||||||
.get_or_create_current_session()
|
|
||||||
.context("failed to get or create currnt session")?;
|
|
||||||
let current_session_reader = sessions::Reader::open(gb_repository, ¤t_session)
|
|
||||||
.context("failed to open current session")?;
|
|
||||||
|
|
||||||
let target_reader = target::Reader::new(¤t_session_reader);
|
|
||||||
let default_target = match target_reader.read_default() {
|
|
||||||
Ok(target) => Ok(target),
|
|
||||||
Err(reader::Error::NotFound) => {
|
|
||||||
println!(" no base sha set, run butler setup");
|
|
||||||
return Ok(vec![]);
|
|
||||||
}
|
|
||||||
Err(e) => Err(e),
|
|
||||||
}
|
|
||||||
.context("failed to read default target")?;
|
|
||||||
|
|
||||||
let diff = project_repository
|
|
||||||
.workdir_diff(&default_target.sha)
|
|
||||||
.context(format!(
|
|
||||||
"failed to get diff workdir with {}",
|
|
||||||
default_target.sha
|
|
||||||
))?;
|
|
||||||
|
|
||||||
// find all the hunks
|
// find all the hunks
|
||||||
let mut hunks_by_filepath: HashMap<String, Vec<VirtualBranchHunk>> = HashMap::new();
|
let mut hunks_by_filepath: HashMap<String, Vec<VirtualBranchHunk>> = HashMap::new();
|
||||||
let mut current_diff = String::new();
|
let mut current_diff = String::new();
|
||||||
@ -428,11 +485,13 @@ pub fn get_status_by_branch(
|
|||||||
.workdir()
|
.workdir()
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.join(file_path);
|
.join(file_path);
|
||||||
let metadata = file_path.metadata().unwrap();
|
let mtime = 0;
|
||||||
let mtime = FileTime::from_last_modification_time(&metadata);
|
if let Ok(metadata) = file_path.metadata() {
|
||||||
// convert seconds and nanoseconds to milliseconds
|
let mtime = FileTime::from_last_modification_time(&metadata);
|
||||||
let mtime = mtime.seconds() as u128 * 1000;
|
// convert seconds and nanoseconds to milliseconds
|
||||||
mtimes.insert(file_path, mtime);
|
let mtime = mtime.seconds() as u128 * 1000;
|
||||||
|
mtimes.insert(file_path, mtime);
|
||||||
|
}
|
||||||
mtime
|
mtime
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -513,6 +572,39 @@ pub fn get_status_by_branch(
|
|||||||
file_path,
|
file_path,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Ok(hunks_by_filepath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// list the virtual branches and their file statuses (statusi?)
|
||||||
|
pub fn get_status_by_branch(
|
||||||
|
gb_repository: &gb_repository::Repository,
|
||||||
|
project_repository: &project_repository::Repository<'_>,
|
||||||
|
) -> Result<Vec<(branch::Branch, Vec<VirtualBranchFile>)>> {
|
||||||
|
let current_session = gb_repository
|
||||||
|
.get_or_create_current_session()
|
||||||
|
.context("failed to get or create currnt session")?;
|
||||||
|
let current_session_reader = sessions::Reader::open(gb_repository, ¤t_session)
|
||||||
|
.context("failed to open current session")?;
|
||||||
|
|
||||||
|
let target_reader = target::Reader::new(¤t_session_reader);
|
||||||
|
let default_target = match target_reader.read_default() {
|
||||||
|
Ok(target) => Ok(target),
|
||||||
|
Err(reader::Error::NotFound) => {
|
||||||
|
println!(" no base sha set, run butler setup");
|
||||||
|
return Ok(vec![]);
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
.context("failed to read default target")?;
|
||||||
|
|
||||||
|
let diff = project_repository
|
||||||
|
.workdir_diff(&default_target.sha)
|
||||||
|
.context(format!(
|
||||||
|
"failed to get diff workdir with {}",
|
||||||
|
default_target.sha
|
||||||
|
))?;
|
||||||
|
|
||||||
|
let hunks_by_filepath = diff_to_hunks_by_filepath(diff, project_repository)?;
|
||||||
|
|
||||||
let mut virtual_branches = Iterator::new(¤t_session_reader)
|
let mut virtual_branches = Iterator::new(¤t_session_reader)
|
||||||
.context("failed to read virtual branches")?
|
.context("failed to read virtual branches")?
|
||||||
@ -628,12 +720,7 @@ pub fn get_status_by_branch(
|
|||||||
Ok(statuses)
|
Ok(statuses)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn commit(
|
fn get_default_target(gb_repository: &gb_repository::Repository) -> Result<target::Target> {
|
||||||
gb_repository: &gb_repository::Repository,
|
|
||||||
project_repository: &project_repository::Repository,
|
|
||||||
branch_id: &str,
|
|
||||||
message: &str,
|
|
||||||
) -> Result<()> {
|
|
||||||
let current_session = gb_repository
|
let current_session = gb_repository
|
||||||
.get_or_create_current_session()
|
.get_or_create_current_session()
|
||||||
.expect("failed to get or create currnt session");
|
.expect("failed to get or create currnt session");
|
||||||
@ -645,34 +732,52 @@ pub fn commit(
|
|||||||
Ok(target) => target,
|
Ok(target) => target,
|
||||||
Err(e) => panic!("failed to read default target: {}", e),
|
Err(e) => panic!("failed to read default target: {}", e),
|
||||||
};
|
};
|
||||||
|
Ok(default_target)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_tree(
|
||||||
|
gb_repository: &gb_repository::Repository,
|
||||||
|
project_repository: &project_repository::Repository,
|
||||||
|
files: &Vec<VirtualBranchFile>,
|
||||||
|
) -> Result<git2::Oid> {
|
||||||
|
let default_target = get_default_target(gb_repository)?;
|
||||||
|
|
||||||
|
// read the base sha into an index
|
||||||
|
let git_repository = &project_repository.git_repository;
|
||||||
|
let base_commit = git_repository.find_commit(default_target.sha).unwrap();
|
||||||
|
let base_tree = base_commit.tree().unwrap();
|
||||||
|
let mut index = git_repository.index().unwrap();
|
||||||
|
index.read_tree(&base_tree).unwrap();
|
||||||
|
|
||||||
|
// now update the index with content in the working directory for each file
|
||||||
|
for file in files {
|
||||||
|
// convert this string to a Path
|
||||||
|
let file = std::path::Path::new(&file.path);
|
||||||
|
|
||||||
|
// TODO: deal with removals too
|
||||||
|
index.add_path(file).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// now write out the tree
|
||||||
|
let tree_oid = index.write_tree().unwrap();
|
||||||
|
Ok(tree_oid)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit(
|
||||||
|
gb_repository: &gb_repository::Repository,
|
||||||
|
project_repository: &project_repository::Repository,
|
||||||
|
branch_id: &str,
|
||||||
|
message: &str,
|
||||||
|
) -> Result<()> {
|
||||||
// get the files to commit
|
// get the files to commit
|
||||||
let statuses = get_status_by_branch(gb_repository, project_repository)
|
let statuses = get_status_by_branch(gb_repository, project_repository)
|
||||||
.expect("failed to get status by branch");
|
.expect("failed to get status by branch");
|
||||||
for (mut branch, files) in statuses {
|
for (mut branch, files) in statuses {
|
||||||
if branch.id == branch_id {
|
if branch.id == branch_id {
|
||||||
// read the base sha into an index
|
let tree_oid = write_tree(gb_repository, project_repository, &files)?;
|
||||||
let git_repository = &project_repository.git_repository;
|
|
||||||
let base_commit = git_repository.find_commit(default_target.sha).unwrap();
|
|
||||||
let base_tree = base_commit.tree().unwrap();
|
|
||||||
let parent_commit = git_repository.find_commit(branch.head).unwrap();
|
|
||||||
let mut index = git_repository.index().unwrap();
|
|
||||||
index.read_tree(&base_tree).unwrap();
|
|
||||||
|
|
||||||
// now update the index with content in the working directory for each file
|
|
||||||
for file in files {
|
|
||||||
// convert this string to a Path
|
|
||||||
let file = std::path::Path::new(&file.path);
|
|
||||||
|
|
||||||
// TODO: deal with removals too
|
|
||||||
index.add_path(file).unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
// now write out the tree
|
|
||||||
let tree_oid = index.write_tree().unwrap();
|
|
||||||
|
|
||||||
// only commit if it's a new tree
|
|
||||||
if tree_oid != branch.tree {
|
if tree_oid != branch.tree {
|
||||||
|
let git_repository = &project_repository.git_repository;
|
||||||
|
let parent_commit = git_repository.find_commit(branch.head).unwrap();
|
||||||
let tree = git_repository.find_tree(tree_oid).unwrap();
|
let tree = git_repository.find_tree(tree_oid).unwrap();
|
||||||
// now write a commit
|
// now write a commit
|
||||||
let (author, committer) = gb_repository.git_signatures().unwrap();
|
let (author, committer) = gb_repository.git_signatures().unwrap();
|
||||||
@ -734,6 +839,80 @@ mod tests {
|
|||||||
Ok(repository)
|
Ok(repository)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commit_on_branch_then_change_file_then_get_status() -> Result<()> {
|
||||||
|
let repository = test_repository()?;
|
||||||
|
let project = projects::Project::try_from(&repository)?;
|
||||||
|
let gb_repo_path = tempdir()?.path().to_str().unwrap().to_string();
|
||||||
|
let storage = storage::Storage::from_path(tempdir()?.path());
|
||||||
|
let user_store = users::Storage::new(storage.clone());
|
||||||
|
let project_store = projects::Storage::new(storage);
|
||||||
|
project_store.add_project(&project)?;
|
||||||
|
|
||||||
|
let file_path = std::path::Path::new("test.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line1\nline2\nline3\nline4\n",
|
||||||
|
)?;
|
||||||
|
let file_path2 = std::path::Path::new("test2.txt");
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path2),
|
||||||
|
"line5\nline6\nline7\nline8\n",
|
||||||
|
)?;
|
||||||
|
commit_all(&repository)?;
|
||||||
|
|
||||||
|
let gb_repo = gb_repository::Repository::open(
|
||||||
|
gb_repo_path,
|
||||||
|
project.id.clone(),
|
||||||
|
project_store,
|
||||||
|
user_store,
|
||||||
|
)?;
|
||||||
|
let project_repository = project_repository::Repository::open(&project)?;
|
||||||
|
|
||||||
|
target::Writer::new(&gb_repo).write_default(&target::Target {
|
||||||
|
name: "origin".to_string(),
|
||||||
|
remote: "origin".to_string(),
|
||||||
|
sha: repository.head().unwrap().target().unwrap(),
|
||||||
|
})?;
|
||||||
|
|
||||||
|
let branch1_id = create_virtual_branch(&gb_repo, "test_branch")
|
||||||
|
.expect("failed to create virtual branch");
|
||||||
|
let branch_writer = branch::Writer::new(&gb_repo);
|
||||||
|
branch_writer.write_selected(&Some(branch1_id.clone()))?;
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path),
|
||||||
|
"line0\nline1\nline2\nline3\nline4\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
|
||||||
|
let branch = &branches[0];
|
||||||
|
assert_eq!(branch.files.len(), 1);
|
||||||
|
assert_eq!(branch.commits.len(), 0);
|
||||||
|
|
||||||
|
// commit
|
||||||
|
commit(&gb_repo, &project_repository, &branch1_id, "test commit")?;
|
||||||
|
|
||||||
|
// status (no files)
|
||||||
|
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
|
||||||
|
let branch = &branches[0];
|
||||||
|
assert_eq!(branch.files.len(), 0);
|
||||||
|
assert_eq!(branch.commits.len(), 1);
|
||||||
|
|
||||||
|
std::fs::write(
|
||||||
|
std::path::Path::new(&project.path).join(file_path2),
|
||||||
|
"line5\nline6\nlineBLAH\nline7\nline8\n",
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// should have just the last change now, the other line is committed
|
||||||
|
let branches = list_virtual_branches(&gb_repo, &project_repository)?;
|
||||||
|
let branch = &branches[0];
|
||||||
|
assert_eq!(branch.files.len(), 1);
|
||||||
|
assert_eq!(branch.commits.len(), 1);
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn create_branch() -> Result<()> {
|
fn create_branch() -> Result<()> {
|
||||||
let repository = test_repository()?;
|
let repository = test_repository()?;
|
||||||
|
@ -41,6 +41,7 @@ export async function load(e: PageLoadEvent) {
|
|||||||
const branches: Branch[] = sortBranchHunks(
|
const branches: Branch[] = sortBranchHunks(
|
||||||
plainToInstance(Branch, await getVirtualBranches({ projectId }))
|
plainToInstance(Branch, await getVirtualBranches({ projectId }))
|
||||||
);
|
);
|
||||||
|
console.log(branches);
|
||||||
return { projectId, target, remoteBranches, remoteBranchesData, branches };
|
return { projectId, target, remoteBranches, remoteBranchesData, branches };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -87,12 +87,12 @@
|
|||||||
on:consider={handleDndEvent}
|
on:consider={handleDndEvent}
|
||||||
on:finalize={handleDndEvent}
|
on:finalize={handleDndEvent}
|
||||||
>
|
>
|
||||||
{#each branches.filter((c) => c.active) as { id, name, files, description } (id)}
|
{#each branches.filter((c) => c.active) as { id, name, files, commits, description } (id)}
|
||||||
<Lane
|
<Lane
|
||||||
bind:name
|
bind:name
|
||||||
bind:commitMessage={description}
|
bind:commitMessage={description}
|
||||||
bind:files
|
bind:files
|
||||||
commits={testCommits}
|
{commits}
|
||||||
on:empty={handleEmpty}
|
on:empty={handleEmpty}
|
||||||
{projectId}
|
{projectId}
|
||||||
branchId={id}
|
branchId={id}
|
||||||
|
@ -192,31 +192,33 @@
|
|||||||
</div>
|
</div>
|
||||||
{/each}
|
{/each}
|
||||||
</div>
|
</div>
|
||||||
<div class="relative">
|
{#if remoteCommits.length > 0}
|
||||||
<!-- Commit bubble track -->
|
<div class="relative">
|
||||||
<div class="absolute top-0 h-full w-0.5 bg-light-600" style="left: 0.925rem" />
|
<!-- Commit bubble track -->
|
||||||
<!-- Section title for remote commits -->
|
<div class="absolute top-0 h-full w-0.5 bg-light-600" style="left: 0.925rem" />
|
||||||
<div class="flex w-full px-2 pb-4">
|
<!-- Section title for remote commits -->
|
||||||
<div class="z-10 w-6">
|
|
||||||
<div
|
|
||||||
class="h-4 w-4 rounded-full border-2 border-light-200 bg-light-200 text-white dark:border-dark-200 dark:bg-dark-200 dark:text-black"
|
|
||||||
>
|
|
||||||
<!-- Target HEAD commit bubble -->
|
|
||||||
<IconGithub />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex-grow">Pushed to origin/master</div>
|
|
||||||
</div>
|
|
||||||
{#each remoteCommits as commit (commit.id)}
|
|
||||||
<div class="flex w-full px-2 pb-4">
|
<div class="flex w-full px-2 pb-4">
|
||||||
<div class="z-10 w-6 py-2">
|
<div class="z-10 w-6">
|
||||||
<!-- Pushed commit bubble -->
|
|
||||||
<div
|
<div
|
||||||
class="rounded--b-sm h-4 w-4 rounded-full border-2 border-light-200 bg-light-600 dark:border-dark-200 dark:bg-dark-200"
|
class="h-4 w-4 rounded-full border-2 border-light-200 bg-light-200 text-white dark:border-dark-200 dark:bg-dark-200 dark:text-black"
|
||||||
/>
|
>
|
||||||
|
<!-- Target HEAD commit bubble -->
|
||||||
|
<IconGithub />
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<CommitCard {commit} />
|
<div class="flex-grow">Pushed to origin/master</div>
|
||||||
</div>
|
</div>
|
||||||
{/each}
|
{#each remoteCommits as commit (commit.id)}
|
||||||
</div>
|
<div class="flex w-full px-2 pb-4">
|
||||||
|
<div class="z-10 w-6 py-2">
|
||||||
|
<!-- Pushed commit bubble -->
|
||||||
|
<div
|
||||||
|
class="rounded--b-sm h-4 w-4 rounded-full border-2 border-light-200 bg-light-600 dark:border-dark-200 dark:bg-dark-200"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<CommitCard {commit} />
|
||||||
|
</div>
|
||||||
|
{/each}
|
||||||
|
</div>
|
||||||
|
{/if}
|
||||||
</div>
|
</div>
|
||||||
|
@ -24,6 +24,7 @@ export class Branch extends DndItem {
|
|||||||
active!: boolean;
|
active!: boolean;
|
||||||
@Type(() => File)
|
@Type(() => File)
|
||||||
files!: File[];
|
files!: File[];
|
||||||
|
commits!: Commit[];
|
||||||
description!: string;
|
description!: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user