Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Repository::in_progress_operation() #382

Merged
merged 17 commits into from
Apr 18, 2022
Merged
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/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ jobs:
- name: Setup dependencies (macos)
if: startsWith(matrix.os, 'macos')
run:
brew install tree openssl
brew install tree openssl gnu-sed
- name: "cargo check default features"
if: startsWith(matrix.os, 'windows')
uses: actions-rs/cargo@v1
Expand Down
4 changes: 3 additions & 1 deletion git-repository/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -192,7 +192,9 @@ pub enum Path {

///
mod types;
pub use types::{Commit, DetachedObject, Head, Id, Object, Reference, Repository, Tag, ThreadSafeRepository, Tree};
pub use types::{
Commit, DetachedObject, Head, Id, Object, Reference, Repository, RepositoryState, Tag, ThreadSafeRepository, Tree,
};

pub mod commit;
pub mod head;
Expand Down
2 changes: 2 additions & 0 deletions git-repository/src/repository/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,8 @@ mod location;

mod snapshots;

mod state;

mod impls;

mod cache;
Expand Down
42 changes: 42 additions & 0 deletions git-repository/src/repository/state.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
use crate::RepositoryState;

impl crate::Repository {
/// Returns the status of an in progress operation on a repository or [`None`]
/// if nothing is happening.
pub fn in_progress_operation(&self) -> Option<RepositoryState> {
inferiorhumanorgans marked this conversation as resolved.
Show resolved Hide resolved
let git_dir = self.path();

// This is modeled on the logic from wt_status_get_state in git's wt-status.c and
// ps1 from git-prompt.sh.

if git_dir.join("rebase-apply/applying").is_file() {
Some(RepositoryState::ApplyMailbox)
} else if git_dir.join("rebase-apply/rebasing").is_file() {
Some(RepositoryState::Rebase)
} else if git_dir.join("rebase-apply").is_dir() {
Some(RepositoryState::ApplyMailboxRebase)
} else if git_dir.join("rebase-merge/interactive").is_file() {
Some(RepositoryState::RebaseInteractive)
} else if git_dir.join("rebase-merge").is_dir() {
Some(RepositoryState::Rebase)
} else if git_dir.join("CHERRY_PICK_HEAD").is_file() {
if git_dir.join("todo").is_file() {
Some(RepositoryState::CherryPickSequence)
} else {
Some(RepositoryState::CherryPick)
}
} else if git_dir.join("MERGE_HEAD").is_file() {
Some(RepositoryState::Merge)
} else if git_dir.join("BISECT_LOG").is_file() {
Some(RepositoryState::Bisect)
} else if git_dir.join("REVERT_HEAD").is_file() {
if git_dir.join("todo").is_file() {
Some(RepositoryState::RevertSequence)
} else {
Some(RepositoryState::Revert)
}
} else {
None
}
}
}
25 changes: 25 additions & 0 deletions git-repository/src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,31 @@ pub struct Repository {
pub(crate) config: crate::config::Cache,
}

/// The state of a git repository
#[derive(Debug, PartialEq)]
pub enum RepositoryState {
/// Apply mailbox in progress
ApplyMailbox,
/// Rebase while an apply mailbox operation is in progress
ApplyMailboxRebase,
/// Bisect in progress
Bisect,
/// Cherry pick operation in progress
CherryPick,
/// Cherry pick with multiple commits pending in the sequencer in progress
CherryPickSequence,
/// Merge operation in progress
Merge,
/// Rebase in progress
Rebase,
/// Interactive rebase in progress
RebaseInteractive,
/// Revert operation in progress
Revert,
/// Revert operation with multiple commits pending in the sequencer in progress
RevertSequence,
}

/// An instance with access to everything a git repository entails, best imagined as container implementing `Sync + Send` for _most_
/// for system resources required to interact with a `git` repository which are loaded in once the instance is created.
///
Expand Down
31 changes: 31 additions & 0 deletions git-repository/tests/fixtures/make_cherry_pick_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#!/bin/bash
set -eu -o pipefail

git init -q

git config commit.gpgsign false

git config advice.statusHints false
git config advice.resolveConflict false
git config advice.commitBeforeMerge false
git config advice.skippedCherryPicks false

git config init.defaultBranch master

unset GIT_AUTHOR_DATE
unset GIT_COMMITTER_DATE

touch 1
git add 1
git commit -m 1 1
git checkout -b other-branch
echo other-branch > 1
git add 1
git commit -m 1.other 1
git checkout master
echo master > 1
git add 1
git commit -m 1.master 1

# This should fail and leave us in a cherry-pick state
git cherry-pick other-branch || true
41 changes: 41 additions & 0 deletions git-repository/tests/fixtures/make_rebase_i_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
#!/bin/bash
set -eu -o pipefail

git init -q

git config commit.gpgsign false

git config advice.statusHints false
git config advice.resolveConflict false
git config advice.commitBeforeMerge false
git config advice.skippedCherryPicks false

git config init.defaultBranch master

unset GIT_AUTHOR_DATE
unset GIT_COMMITTER_DATE

touch 1 2 3
git add 1
git commit -m 1 1
git add 2
git commit -m 2 2
git add 3
git commit -m 3 3

# NOTE: This relies on GNU sed behavior and will fail on *BSDs (including macOS) without GNU
# sed installed.
sed=$(which gsed sed | head -1 || true)

# GNU sed recognizes long arguments, BSD sed does not
# NOTE: We can't rely on $? because set -e guarantees the script will terminate on a non-zero exit
${sed} --version 2&>/dev/null && sed_exit_code=success || sed_exit_code=fail
if [ "${sed_exit_code}" = "fail" ]; then
printf "\n** GNU sed is required for this test but was not found **\n"
exit 1
fi
unset sed_exit_code

# NOTE: Starting around git 2.35.0 --preserve-merges was renamed to --rebase-merges
# however --preserve-merges first appeared in git 2.18. That should cover most use cases.
EDITOR="${sed} -i.bak -z 's/pick/edit/2'" git rebase --rebase-merges --interactive HEAD~2
25 changes: 25 additions & 0 deletions git-repository/tests/fixtures/make_revert_repo.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
#!/bin/bash
set -eu -o pipefail

git init -q

git config commit.gpgsign false

git config advice.statusHints false
git config advice.resolveConflict false
git config advice.commitBeforeMerge false
git config advice.skippedCherryPicks false

git config init.defaultBranch master

unset GIT_AUTHOR_DATE
unset GIT_COMMITTER_DATE

touch 1 2 3
git add 1
git commit -m 1 1
git add 2
git commit -m 2 2
git add 3
git commit -m 3 3
git revert --no-commit HEAD~1
1 change: 1 addition & 0 deletions git-repository/tests/repo.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,3 +27,4 @@ mod discover;
mod easy;
mod init;
mod reference;
mod state;
54 changes: 54 additions & 0 deletions git-repository/tests/state/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
use crate::{repo, Result};
use anyhow::anyhow;
use git_repository::{bstr::ByteSlice, RepositoryState};

// Can we identify that a cherry pick operation is in progress
#[test]
fn cherry_pick() -> Result {
let repo = repo("make_cherry_pick_repo.sh").map(|r| r.to_thread_local())?;

let head = repo.head()?;
let head_name = head
.referent_name()
.ok_or_else(|| anyhow!("detached head?"))?
.shorten()
.to_str()?;
assert_eq!("master", head_name);

assert_eq!(Some(RepositoryState::CherryPick), repo.in_progress_operation());

Ok(())
}

// Can we identify that we're in the middle of an interactive rebase?
#[test]
fn rebase_interactive() -> Result {
let repo = repo("make_rebase_i_repo.sh").map(|r| r.to_thread_local())?;

let head = repo.head()?;
// TODO: Get rebase head/target
let head_name = head.referent_name();
assert!(head_name.is_none());

assert_eq!(Some(RepositoryState::RebaseInteractive), repo.in_progress_operation());

Ok(())
}

// Can we identify a revert operation when we see it?
#[test]
fn revert() -> Result {
let repo = repo("make_revert_repo.sh").map(|r| r.to_thread_local())?;

let head = repo.head()?;
let head_name = head
.referent_name()
.ok_or_else(|| anyhow!("detached head?"))?
.shorten()
.to_str()?;
assert_eq!("master", head_name);

assert_eq!(Some(RepositoryState::Revert), repo.in_progress_operation());

Ok(())
}