From eed62b29f55e02ea84a4b470651d5c7247633d20 Mon Sep 17 00:00:00 2001 From: Lars Wirzenius Date: Thu, 29 Apr 2021 08:16:10 +0300 Subject: feat: make a journal a git repository and commit new entries --- jt.md | 4 ++++ src/error.rs | 20 +++++++++++++++++++ src/git.rs | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ src/journal.rs | 22 ++++++++++++++++++++ src/lib.rs | 1 + subplot/jt.py | 13 ++++++++++++ subplot/jt.yaml | 3 +++ 7 files changed, 125 insertions(+) create mode 100644 src/git.rs diff --git a/jt.md b/jt.md index 958de77..a06ccf4 100644 --- a/jt.md +++ b/jt.md @@ -130,6 +130,7 @@ given an installed jt when I run jt2 --dirname jrnl init default "My test journal" then command is successful and directory jrnl exists +then there are no uncommitted changes in jrnl when I run jt2 --dirname jrnl is-journal then command is successful @@ -169,6 +170,7 @@ and file name ends with .mdwn and journal entry contains "Abracadabra" and journal entry contains "Open sesame!" and there are no drafts in jrnl +and there are no uncommitted changes in jrnl ~~~ ~~~{#append.sh .file .numberLines} @@ -219,6 +221,7 @@ then there are two journal entries in jrnl, at FILE1 and FILE2 then journal entry contains "Abracadabra" then journal entry contains "Simsalabim" then there are no drafts in jrnl +then there are no uncommitted changes in jrnl ~~~ @@ -265,6 +268,7 @@ then stderr contains "foo" when I run jt2 --editor=none --dirname=jrnl new-topic foo "Things about Foo" then command is successful then file jrnl/foo.mdwn contains "Things about Foo" +then there are no uncommitted changes in jrnl when I run jt2 --editor=none --dirname=jrnl new --topic foo "Abracadabra" then command is successful diff --git a/src/error.rs b/src/error.rs index 89a10c1..8db0991 100644 --- a/src/error.rs +++ b/src/error.rs @@ -62,6 +62,22 @@ pub enum JournalError { #[error("failed to stat draft in {0}: {1}")] StatDraft(PathBuf, #[source] std::io::Error), + /// Error spawning git. + #[error("failed to start git in {0}: {1}")] + SpawnGit(PathBuf, std::io::Error), + + /// Git init failed. + #[error("git init failed in {0}: {1}")] + GitInit(PathBuf, String), + + /// Git add failed. + #[error("git add failed in {0}: {1}")] + GitAdd(PathBuf, String), + + /// Git commit failed. + #[error("git commit failed in {0}: {1}")] + GitCommit(PathBuf, String), + /// Error spawning editor. #[error("failed to start editor {0}: {1}")] SpawnEditor(PathBuf, #[source] std::io::Error), @@ -77,4 +93,8 @@ pub enum JournalError { /// Failed to render a Tera template. #[error("template {0} failed to render: {1}")] TemplateRender(String, #[source] tera::Error), + + /// Failed to make a path relative to a directory. + #[error("failed to make {0} relative to {1}: {2}")] + RelativePath(PathBuf, PathBuf, std::path::StripPrefixError), } diff --git a/src/git.rs b/src/git.rs new file mode 100644 index 0000000..5f1f66c --- /dev/null +++ b/src/git.rs @@ -0,0 +1,62 @@ +use crate::error::JournalError; +use log::debug; +use std::ffi::OsString; +use std::path::Path; +use std::process::Command; + +pub fn init>(dirname: P) -> Result<(), JournalError> { + let dirname = dirname.as_ref(); + debug!("git init {}", dirname.display()); + + let output = Command::new("git") + .arg("init") + .current_dir(dirname) + .output() + .map_err(|err| JournalError::SpawnGit(dirname.to_path_buf(), err))?; + debug!("git init exit code was {:?}", output.status.code()); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(JournalError::GitInit(dirname.to_path_buf(), stderr)); + } + Ok(()) +} + +pub fn add>(dirname: P, files: &[P]) -> Result<(), JournalError> { + let args = &["add", "--"]; + let mut args: Vec = args.iter().map(|x| OsString::from(x)).collect(); + for f in files { + args.push(OsString::from(f.as_ref())); + } + + let dirname = dirname.as_ref(); + debug!("git add in {}", dirname.display()); + + let output = Command::new("git") + .args(&args) + .current_dir(dirname) + .output() + .map_err(|err| JournalError::SpawnGit(dirname.to_path_buf(), err))?; + debug!("git exit code was {:?}", output.status.code()); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(JournalError::GitAdd(dirname.to_path_buf(), stderr)); + } + Ok(()) +} + +pub fn commit>(dirname: P, msg: &str) -> Result<(), JournalError> { + let dirname = dirname.as_ref(); + debug!("git commit in {}", dirname.display()); + + let output = Command::new("git") + .args(&["commit", "-m", msg]) + .current_dir(dirname) + .output() + .map_err(|err| JournalError::SpawnGit(dirname.to_path_buf(), err))?; + debug!("git exit code was {:?}", output.status.code()); + if !output.status.success() { + let stderr = String::from_utf8_lossy(&output.stderr).into_owned(); + return Err(JournalError::GitCommit(dirname.to_path_buf(), stderr)); + } + Ok(()) +} diff --git a/src/journal.rs b/src/journal.rs index 41e6d3d..2610a44 100644 --- a/src/journal.rs +++ b/src/journal.rs @@ -1,4 +1,5 @@ use crate::error::JournalError; +use crate::git; use crate::template::Templates; use chrono::{DateTime, Local}; use std::path::{Path, PathBuf}; @@ -21,6 +22,7 @@ impl Journal { pub fn init(path: &Path, entries: &Path) -> Result { std::fs::create_dir(path) .map_err(|err| JournalError::CreateDirectory(path.to_path_buf(), err))?; + git::init(path)?; std::fs::create_dir(entries) .map_err(|err| JournalError::CreateDirectory(entries.to_path_buf(), err))?; Ok(Self { @@ -49,6 +51,13 @@ impl Journal { &self.dirname } + fn relative(&self, path: &Path) -> Result { + let path = path.strip_prefix(self.dirname()).map_err(|err| { + JournalError::RelativePath(path.to_path_buf(), self.dirname().to_path_buf(), err) + })?; + Ok(path.to_path_buf()) + } + fn drafts(&self) -> PathBuf { self.dirname().join("drafts") } @@ -153,6 +162,12 @@ impl Journal { std::fs::rename(filename, &entry).map_err(|err| { JournalError::RenameEntry(filename.to_path_buf(), entry.to_path_buf(), err) })?; + + let entry = self.relative(&entry)?; + git::add(self.dirname(), &[&entry])?; + + let msg = format!("journal entry {}", entry.display()); + git::commit(self.dirname(), &msg)?; Ok(()) } @@ -165,6 +180,13 @@ impl Journal { std::fs::write(&pathname, text) .map_err(|err| JournalError::WriteTopic(pathname.to_path_buf(), err))?; self.edit(editor, &pathname)?; + + let topic = self.relative(&pathname)?; + git::add(self.dirname(), &[&topic])?; + + let msg = format!("new topic {}", topic.display()); + git::commit(self.dirname(), &msg)?; + Ok(()) } } diff --git a/src/lib.rs b/src/lib.rs index a634111..509e6f1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,6 +1,7 @@ pub mod cmd; pub mod config; pub mod error; +pub mod git; pub mod journal; pub mod opt; pub mod template; diff --git a/subplot/jt.py b/subplot/jt.py index d6023b3..0117736 100644 --- a/subplot/jt.py +++ b/subplot/jt.py @@ -166,3 +166,16 @@ def file_name_has_suffix(ctx, varname=None, suffix=None): variables = ctx.get("variables", {}) filename = variables[varname] assert filename.endswith(suffix) + + +def git_is_clean(ctx, dirname=None): + runcmd_run = globals()["runcmd_run"] + runcmd_exit_code_is_zero = globals()["runcmd_exit_code_is_zero"] + runcmd_get_stdout = globals()["runcmd_get_stdout"] + assert_eq = globals()["assert_eq"] + + runcmd_run(ctx, ["git", "status", "--porcelain"], cwd=dirname) + runcmd_exit_code_is_zero(ctx) + + stdout = runcmd_get_stdout(ctx) + assert_eq(stdout, "") diff --git a/subplot/jt.yaml b/subplot/jt.yaml index a3d4f0f..dc2a666 100644 --- a/subplot/jt.yaml +++ b/subplot/jt.yaml @@ -60,3 +60,6 @@ - then: file name <{varname}> ends with {suffix} function: file_name_has_suffix + +- then: there are no uncommitted changes in {dirname} + function: git_is_clean -- cgit v1.2.1