about summary refs log tree commit diff
path: root/wikarden/src/git.rs
blob: a9a0e0bca1a11f506aeac978b7566933c78c4508 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
use camino::{Utf8Path, Utf8PathBuf};
use git2::{Commit, Index, Repository, Signature, Status};

use crate::WikError;

/// based on https://github.com/rust-lang/git2-rs/blob/main/examples/add.rs#L41
pub struct GitContext {
	path: Utf8PathBuf,
	repo: Repository,
	index: Option<Index>,
}

impl GitContext {
	pub fn open<P: AsRef<Utf8Path>>(path: P) -> Self {
		let path = path.as_ref();
		let repo = Repository::open(&path).unwrap();

		Self {
			path: path.into(),
			repo,
			index: None,
		}
	}

	pub fn add(&mut self, path: &Utf8Path) -> Result<(), WikError> {
		let root = self.path.clone();
		if let Ok(stripped) = path.strip_prefix(&root) {
			return self.add_rel(stripped);
		}
		Ok(())
	}

	pub fn add_rel(&mut self, path: &Utf8Path) -> Result<(), WikError> {
		if self.file_dirty(path.as_std_path()) {
			self.index()
				.add_path(path.as_std_path())
				.map_err(|e| WikError::GitError { git: e })
		} else {
			Ok(())
		}
	}

	pub fn commit<'s, S: AsRef<str>>(
		&mut self,
		signature: Signature<'s>,
		msg: S,
	) -> Result<(), WikError> {
		let index = self.index();
		let oid = index.write_tree()?;

		let last_commit = self.head_commit().ok();
		let tree = self.repo.find_tree(oid)?;

		let parents: &[&git2::Commit] = match last_commit.as_ref() {
			Some(cm) => &[cm],
			None => &[],
		};

		self.repo.commit(
			Some("HEAD"),
			&signature,
			&signature,
			msg.as_ref(),
			&tree,
			parents,
		)?;

		Ok(())
	}

	fn head_commit<'c>(&'c self) -> Result<Commit<'c>, WikError> {
		self.repo
			.head()?
			.resolve()?
			.peel(git2::ObjectType::Commit)?
			.into_commit()
			.map_err(|_| WikError::GitError {
				git: git2::Error::from_str("Couldn't find commit!"),
			})
	}

	fn index(&mut self) -> &mut Index {
		self.index.get_or_insert_with(|| self.repo.index().unwrap())
	}

	pub fn write_index(&mut self) -> Result<(), WikError> {
		if let Some(index) = self.index.as_mut() {
			index.write().map_err(<_>::into)
		} else {
			Ok(())
		}
	}

	fn file_dirty(&self, path: &std::path::Path) -> bool {
		let status = match self.repo.status_file(path) {
			Err(_) => return false,
			Ok(status) => status,
		};

		status.contains(Status::WT_MODIFIED) || status.contains(Status::WT_NEW)
	}
}