Skip to content

Commit

Permalink
Fix to handle stashes the same as commits (#17)
Browse files Browse the repository at this point in the history
* Fix to handle stashes

* Move comment in the wrong place

* Remove unnecessary processes

* Add description comment to test cases
  • Loading branch information
lusingander committed Jul 31, 2024
1 parent 238a0d8 commit 0e53f3e
Show file tree
Hide file tree
Showing 8 changed files with 79 additions and 26 deletions.
102 changes: 76 additions & 26 deletions src/git.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ use std::{
process::{Command, Stdio},
};

use chrono::{DateTime, Local, TimeDelta};
use chrono::{DateTime, Local};

use crate::graph::SortCommit;

Expand All @@ -33,9 +33,7 @@ impl From<&str> for CommitHash {
pub enum CommitType {
#[default]
Commit,
Stash {
parent_commit_committer_date: DateTime<Local>,
},
Stash,
}

#[derive(Debug, Default, Clone)]
Expand All @@ -49,27 +47,10 @@ pub struct Commit {
pub committer_date: DateTime<Local>,
pub subject: String,
pub body: String,
// to preserve order of the original commits from `git log`, we store the commit hashes
pub parent_commit_hashes: Vec<CommitHash>,
pub commit_type: CommitType,
}

impl Commit {
pub fn committer_date_sort_key(&self) -> DateTime<Local> {
match self.commit_type {
CommitType::Commit => self.committer_date,
CommitType::Stash {
parent_commit_committer_date,
} => {
// The unit of committer_date is seconds, so add 1 nanosecond to make sure the stash commit appears after the parent commit
parent_commit_committer_date
.checked_add_signed(TimeDelta::nanoseconds(1))
.unwrap()
}
}
}
}

#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
pub enum Ref {
Tag {
Expand Down Expand Up @@ -132,18 +113,18 @@ pub struct Repository {

ref_map: RefMap,
head: Head,
// to preserve order of the original commits from `git log`, we store the commit hashes
commit_hashes: Vec<CommitHash>,
}

impl Repository {
pub fn load(path: &Path, sort: SortCommit) -> Self {
check_git_repository(path);

// this currently passes `--all` to `git log`
// this makes the graph for stashes similar to how `git log --oneline` does
// stashes can be better presented as a single commit
// TODO: handle stashs better in the future
let commits = load_all_commits(path, sort);
let stashes = load_all_stashes(path);

let commits = merge_stashes_to_commits(commits, stashes);
let commit_hashes = commits.iter().map(|c| c.commit_hash.clone()).collect();

let (parents_map, children_map) = build_commits_maps(&commits);
Expand Down Expand Up @@ -250,7 +231,10 @@ fn check_git_repository(path: &Path) {
fn load_all_commits(path: &Path, sort: SortCommit) -> Vec<Commit> {
let mut cmd = Command::new("git")
.arg("log")
.arg("--all")
// exclude stashes and other refs
.arg("--branches")
.arg("--remotes")
.arg("--tags")
.arg(match sort {
SortCommit::Chronological => "--date-order",
SortCommit::Topological => "--topo-order",
Expand Down Expand Up @@ -300,6 +284,55 @@ fn load_all_commits(path: &Path, sort: SortCommit) -> Vec<Commit> {
commits
}

fn load_all_stashes(path: &Path) -> Vec<Commit> {
let mut cmd = Command::new("git")
.arg("stash")
.arg("list")
.arg(format!("--pretty={}", load_commits_format()))
.arg("--date=iso-strict")
.arg("-z") // use NUL as a delimiter
.current_dir(path)
.stdout(Stdio::piped())
.spawn()
.unwrap();

let stdout = cmd.stdout.take().expect("failed to open stdout");

let reader = BufReader::new(stdout);

let mut commits = Vec::new();

for bytes in reader.split(b'\0') {
let bytes = bytes.unwrap();
let s = String::from_utf8_lossy(&bytes);

let parts: Vec<&str> = s.split('\x1f').collect();
if parts.len() != 10 {
panic!("unexpected number of parts: {} [{}]", parts.len(), s);
}

let commit = Commit {
commit_hash: parts[0].into(),
author_name: parts[1].into(),
author_email: parts[2].into(),
author_date: parse_iso_date(parts[3]),
committer_name: parts[4].into(),
committer_email: parts[5].into(),
committer_date: parse_iso_date(parts[6]),
subject: parts[7].into(),
body: parts[8].into(),
parent_commit_hashes: parse_parent_commit_hashes(parts[9]),
commit_type: CommitType::Stash,
};

commits.push(commit);
}

cmd.wait().unwrap();

commits
}

fn load_commits_format() -> String {
[
"%H", "%an", "%ae", "%ad", "%cn", "%ce", "%cd", "%s", "%b", "%P",
Expand Down Expand Up @@ -347,6 +380,23 @@ fn to_commit_map(commits: Vec<Commit>) -> CommitMap {
.collect()
}

fn merge_stashes_to_commits(commits: Vec<Commit>, stashes: Vec<Commit>) -> Vec<Commit> {
// Stash commit has multiple parent commits, but the first parent commit is the commit that the stash was created from.
// If the first parent commit is not found, the stash commit is ignored.
let mut ret = Vec::new();
let mut statsh_map: HashMap<CommitHash, Commit> = stashes
.into_iter()
.map(|commit| (commit.parent_commit_hashes[0].clone(), commit))
.collect();
for commit in commits {
if let Some(stash) = statsh_map.remove(&commit.commit_hash) {
ret.push(stash);
}
ret.push(commit);
}
ret
}

fn load_refs(path: &Path) -> (RefMap, Head) {
let mut cmd = Command::new("git")
.arg("show-ref")
Expand Down
3 changes: 3 additions & 0 deletions tests/graph.rs
Original file line number Diff line number Diff line change
Expand Up @@ -654,6 +654,7 @@ fn merge_005() -> TestResult {

#[test]
fn stash_001() -> TestResult {
// Test case for multiple stashes, the most recent commit is normal commit
let dir = tempfile::tempdir()?;
let repo_path = dir.path();

Expand Down Expand Up @@ -699,6 +700,7 @@ fn stash_001() -> TestResult {

#[test]
fn stash_002() -> TestResult {
// Test case for multiple stashes, the most recent commit is stash
let dir = tempfile::tempdir()?;
let repo_path = dir.path();

Expand Down Expand Up @@ -741,6 +743,7 @@ fn stash_002() -> TestResult {

#[test]
fn stash_003() -> TestResult {
// Test case for unreachable stash
let dir = tempfile::tempdir()?;
let repo_path = dir.path();

Expand Down
Binary file modified tests/graph/stash_001_chrono.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/graph/stash_001_topo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/graph/stash_002_chrono.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/graph/stash_002_topo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/graph/stash_003_chrono.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified tests/graph/stash_003_topo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 0e53f3e

Please sign in to comment.