Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.74
toolchain: 1.85
components: rustfmt, clippy
override: true

Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/linux-git-devel.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: "Linux (Git devel)"
name: 'Linux (Git devel)'

on:
# TODO(#1416): re-enable once tests are passing on Git v2.46+
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.74
toolchain: 1.85
override: true

- uses: actions/checkout@v5
Expand Down
10 changes: 5 additions & 5 deletions .github/workflows/linux.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ jobs:
matrix:
# Use a tag from https://github.com/git/git/tags
# Make sure to update `git-version` in the `run-tests` step as well.
git-version: ["v2.24.3", "v2.29.2", "v2.33.1", "v2.37.3"]
git-version: ['v2.24.3', 'v2.29.2', 'v2.33.1', 'v2.37.3']

steps:
- uses: actions/checkout@v5
Expand All @@ -45,7 +45,7 @@ jobs:
- name: Package Git
run: tar -czf git.tar.gz git git-*

- name: "Upload artifact: git"
- name: 'Upload artifact: git'
uses: actions/upload-artifact@v4
with:
name: git-${{ matrix.git-version }}
Expand All @@ -58,7 +58,7 @@ jobs:

strategy:
matrix:
git-version: ["v2.24.3", "v2.29.2", "v2.33.1", "v2.37.3"]
git-version: ['v2.24.3', 'v2.29.2', 'v2.33.1', 'v2.37.3']

steps:
- uses: actions/checkout@v5
Expand All @@ -67,14 +67,14 @@ jobs:
with:
name: git-${{ matrix.git-version }}

- name: "Unpack artifact: git"
- name: 'Unpack artifact: git'
run: tar -xf git.tar.gz

- name: Set up Rust
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.74
toolchain: 1.85
override: true

- name: Cache dependencies
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/macos.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: macOS
on:
schedule:
# Run once every day at 6:40AM UTC.
- cron: "40 6 * * *"
- cron: '40 6 * * *'

push:
branches:
Expand All @@ -27,7 +27,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.74
toolchain: 1.85
override: true

- name: Cache dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: 1.74
toolchain: 1.85
override: true

- name: Cache dependencies
Expand Down
4 changes: 2 additions & 2 deletions .gitpod.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ image:
file: .gitpod/Dockerfile
tasks:
- init: |
rustup default 1.74
rustup default 1.85
cargo test --no-run
cargo install cargo-insta
cargo install git-branchless && git branchless init
vscode:
extensions:
- "matklad.rust-analyzer"
- 'matklad.rust-analyzer'
2 changes: 1 addition & 1 deletion git-branchless-hook/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
clippy::clone_on_ref_ptr,
clippy::dbg_macro
)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)]

use std::fmt::Write;
use std::fs::File;
Expand Down
3 changes: 2 additions & 1 deletion git-branchless-init/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
clippy::clone_on_ref_ptr,
clippy::dbg_macro
)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)]

use std::fmt::Write;
use std::io::{stdin, stdout, BufRead, BufReader, Write as WriteIo};
Expand Down Expand Up @@ -95,6 +95,7 @@ const ALL_ALIASES: &[(&str, &str)] = &[
("restack", "restack"),
("reword", "reword"),
("sl", "smartlog"),
("split", "split"),
("smartlog", "smartlog"),
("submit", "submit"),
("sw", "switch"),
Expand Down
2 changes: 1 addition & 1 deletion git-branchless-invoke/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
clippy::clone_on_ref_ptr,
clippy::dbg_macro
)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)]

use std::any::Any;
use std::collections::HashMap;
Expand Down
7 changes: 6 additions & 1 deletion git-branchless-lib/src/core/check_out.rs
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,9 @@ pub struct CheckOutCommitOptions {
/// Additional arguments to pass to `git checkout`.
pub additional_args: Vec<OsString>,

/// Ignore the `autoSwitchBranches` setting?
pub force_detach: bool,

/// Use `git reset` rather than `git checkout`; that is, leave the index and
/// working copy unchanged, and just adjust the `HEAD` pointer.
pub reset: bool,
Expand All @@ -56,6 +59,7 @@ impl Default for CheckOutCommitOptions {
fn default() -> Self {
Self {
additional_args: Default::default(),
force_detach: false,
reset: false,
render_smartlog: true,
}
Expand Down Expand Up @@ -116,6 +120,7 @@ pub fn check_out_commit(
) -> EyreExitOr<()> {
let CheckOutCommitOptions {
additional_args,
force_detach,
reset,
render_smartlog,
} = options;
Expand All @@ -134,7 +139,7 @@ pub fn check_out_commit(
create_snapshot(effects, git_run_info, repo, event_log_db, event_tx_id)?;
}

let target = if get_auto_switch_branches(repo)? && !reset {
let target = if get_auto_switch_branches(repo)? && !reset && !force_detach {
maybe_get_branch_name(target, oid, repo)?
} else {
target
Expand Down
66 changes: 65 additions & 1 deletion git-branchless-lib/src/git/diff.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use std::path::PathBuf;
use std::sync::{Arc, Mutex};

use eyre::Context;
use eyre::{Context, OptionExt};
use itertools::Itertools;
use scm_record::helpers::make_binary_description;
use scm_record::{ChangeType, File, FileMode, Section, SectionChangedLine};
Expand All @@ -15,6 +15,17 @@ pub struct Diff<'repo> {
pub(super) inner: git2::Diff<'repo>,
}

impl Diff<'_> {
/// Summarize this diff into a single line "short" format.
pub fn short_stats(&self) -> eyre::Result<String> {
let stats = self.inner.stats()?;
let buf = stats.to_buf(git2::DiffStatsFormat::SHORT, usize::MAX)?;
buf.as_str()
.ok_or_eyre("converting buf to str")
.map(|s| s.trim().to_string())
}
}

#[derive(Clone, Debug, PartialEq, Eq, Hash)]
struct GitHunk {
old_start: usize,
Expand All @@ -23,6 +34,59 @@ struct GitHunk {
new_lines: usize,
}

/// Summarize a diff for use as part of a temporary commit message.
pub fn summarize_diff_for_temporary_commit(diff: &Diff) -> eyre::Result<String> {
// this returns something like `1 file changed, 1 deletion(-)`
// diff.short_stats()

// this returns something like `test2.txt (-1)` or `2 files (+1/-2)`
let stats = diff.inner.stats()?;
let prefix = if stats.files_changed() == 1 {
let mut prefix = None;
// returning false terminates iteration, but that also returns Err, so
// catch and ignore it
let _ = diff.inner.foreach(
&mut |delta: git2::DiffDelta, _| {
if let Some(path) = delta.old_file().path() {
// prefix = Some(format!("{}", path.file_name().unwrap().to_string_lossy()));
prefix = Some(format!("{}", path.display()));
} else if let Some(path) = delta.new_file().path() {
prefix = Some(format!("{}", path.display()));
}

false
},
None,
None,
None,
);
prefix
} else {
Some(format!("{} files", stats.files_changed()))
};

let i = stats.insertions();
let d = stats.deletions();
Ok(format!(
"{prefix} ({i}{slash}{d})",
prefix = prefix.unwrap(),
i = if i > 0 {
format!("+{i}")
} else {
String::new()
},
slash = if i > 0 && d > 0 { "/" } else { "" },
d = if d > 0 {
format!("-{d}")
} else {
String::new()
}
))
// stats.files_changed()
// stats.insertions()
// stats.deletions()
}

/// Calculate the diff between the index and the working copy.
pub fn process_diff_for_record(repo: &Repo, diff: &Diff) -> eyre::Result<Vec<File<'static>>> {
let Diff { inner: diff } = diff;
Expand Down
8 changes: 7 additions & 1 deletion git-branchless-lib/src/git/index.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use tracing::instrument;

use crate::core::eventlog::EventTransactionId;

use super::{FileMode, GitRunInfo, GitRunOpts, GitRunResult, MaybeZeroOid, NonZeroOid, Repo};
use super::{FileMode, GitRunInfo, GitRunOpts, GitRunResult, MaybeZeroOid, NonZeroOid, Repo, Tree};

/// The possible stages for items in the index.
#[derive(Copy, Clone, Debug)]
Expand Down Expand Up @@ -88,6 +88,12 @@ impl Index {
},
})
}

/// Update the index from the given tree and write it to disk.
pub fn update_from_tree(&mut self, tree: &Tree) -> eyre::Result<()> {
self.inner.read_tree(&tree.inner)?;
self.inner.write().wrap_err("writing index")
}
}

/// The command to update the index, as defined by `git update-index`.
Expand Down
6 changes: 4 additions & 2 deletions git-branchless-lib/src/git/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ mod test;
mod tree;

pub use config::{Config, ConfigRead, ConfigValue, ConfigWrite};
pub use diff::{process_diff_for_record, Diff};
pub use diff::{process_diff_for_record, summarize_diff_for_temporary_commit, Diff};
pub use index::{update_index, Index, IndexEntry, Stage, UpdateIndexCommand};
pub use object::Commit;
pub use oid::{MaybeZeroOid, NonZeroOid};
Expand All @@ -34,4 +34,6 @@ pub use test::{
make_test_command_slug, SerializedNonZeroOid, SerializedTestResult, TestCommand,
TEST_ABORT_EXIT_CODE, TEST_INDETERMINATE_EXIT_CODE, TEST_SUCCESS_EXIT_CODE,
};
pub use tree::{dehydrate_tree, get_changed_paths_between_trees, hydrate_tree, Tree};
pub use tree::{
dehydrate_tree, get_changed_paths_between_trees, hydrate_tree, make_empty_tree, Tree,
};
14 changes: 14 additions & 0 deletions git-branchless-lib/src/git/status.rs
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,20 @@ impl From<git2::FileMode> for FileMode {
}
}

impl From<FileMode> for git2::FileMode {
fn from(file_mode: FileMode) -> Self {
match file_mode {
FileMode::Blob => git2::FileMode::Blob,
FileMode::BlobExecutable => git2::FileMode::BlobExecutable,
FileMode::BlobGroupWritable => git2::FileMode::BlobGroupWritable,
FileMode::Commit => git2::FileMode::Commit,
FileMode::Link => git2::FileMode::Link,
FileMode::Tree => git2::FileMode::Tree,
FileMode::Unreadable => git2::FileMode::Unreadable,
}
}
}

impl From<i32> for FileMode {
fn from(file_mode: i32) -> Self {
if file_mode == i32::from(git2::FileMode::Blob) {
Expand Down
27 changes: 27 additions & 0 deletions git-branchless-lib/src/git/tree.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ use std::fmt::Debug;
use std::path::{Path, PathBuf};

use bstr::ByteVec;
use git2::build::TreeUpdateBuilder;
use itertools::Itertools;
use thiserror::Error;
use tracing::{instrument, warn};
Expand Down Expand Up @@ -116,6 +117,31 @@ impl Tree<'_> {
.map(|maybe_entry| maybe_entry.map(|entry| entry.inner.id().into()))
}

/// Remove the given path from the Tree, creating a new Tree in the given repo.
pub fn remove(&self, repo: &Repo, path: &Path) -> Result<NonZeroOid> {
let mut builder = TreeUpdateBuilder::new();
let tree_oid = builder
.remove(path)
.create_updated(&repo.inner, &self.inner)
.map_err(Error::BuildTree)?;
Ok(make_non_zero_oid(tree_oid))
}

/// Add or replace the given path/entry from the Tree, creating a new Tree in the given repo.
pub fn add_or_replace(
&self,
repo: &Repo,
path: &Path,
entry: &TreeEntry,
) -> Result<NonZeroOid> {
let mut builder = TreeUpdateBuilder::new();
let tree_oid = builder
.upsert(path, entry.get_oid().into(), entry.get_filemode().into())
.create_updated(&repo.inner, &self.inner)
.map_err(Error::BuildTree)?;
Ok(make_non_zero_oid(tree_oid))
}

/// Get the (top-level) list of paths in this tree, for testing.
pub fn get_entry_paths_for_testing(&self) -> impl Debug {
self.inner
Expand Down Expand Up @@ -456,6 +482,7 @@ pub fn hydrate_tree(
Ok(make_non_zero_oid(tree_oid))
}

/// Create a new, empty tree.
pub fn make_empty_tree(repo: &Repo) -> Result<Tree> {
let tree_oid = hydrate_tree(repo, None, Default::default())?;
repo.find_tree_or_fail(tree_oid)
Expand Down
2 changes: 1 addition & 1 deletion git-branchless-lib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
clippy::clone_on_ref_ptr,
clippy::dbg_macro
)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_if_conditions)]
#![allow(clippy::too_many_arguments, clippy::blocks_in_conditions)]

pub mod core;
pub mod git;
Expand Down
Loading
Loading