-
Notifications
You must be signed in to change notification settings - Fork 24
Expand file tree
/
Copy pathcommit.rs
More file actions
160 lines (134 loc) · 4.73 KB
/
commit.rs
File metadata and controls
160 lines (134 loc) · 4.73 KB
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
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
use async_trait::async_trait;
use clap::{Arg, Command};
use std::io::Write;
use tempfile::TempDir;
use liboxen::config::UserConfig;
use liboxen::error::OxenError;
use liboxen::model::LocalRepository;
use liboxen::repositories;
use crate::cmd::RunCmd;
use crate::helpers::check_repo_migration_needed;
pub const NAME: &str = "commit";
pub struct CommitCmd;
#[async_trait]
impl RunCmd for CommitCmd {
fn name(&self) -> &str {
NAME
}
fn args(&self) -> Command {
// Setups the CLI args for the command
Command::new(NAME)
.about("Commit the staged files to the repository.")
.arg(
Arg::new("message")
.help("The message for the commit. Should be descriptive about what changed.")
.long("message")
.short('m')
.required(false)
.action(clap::ArgAction::Set),
)
.arg(
Arg::new("allow_empty")
.help("Allow creating a commit with no changes")
.long("allow-empty")
.action(clap::ArgAction::SetTrue),
)
}
async fn run(&self, args: &clap::ArgMatches) -> Result<(), OxenError> {
let message = match args.get_one::<String>("message") {
Some(msg) => msg.clone(),
None => get_message_from_editor(UserConfig::get().ok().as_ref())?,
};
let allow_empty = args.get_flag("allow_empty");
let repo = LocalRepository::from_current_dir()?;
check_repo_migration_needed(&repo)?;
println!("Committing with message: {message}");
if allow_empty {
repositories::commits::commit_allow_empty(&repo, &message)?;
} else {
repositories::commit(&repo, &message)?;
}
Ok(())
}
}
fn resolve_editor(maybe_config: Option<&UserConfig>) -> Option<String> {
// 1. Check UserConfig
if let Some(config) = maybe_config
&& let Some(ref editor) = config.editor
&& !editor.is_empty()
{
return Some(editor.to_string());
}
// 2. Fall back to VISUAL env var
if let Ok(editor) = std::env::var("VISUAL")
&& !editor.is_empty()
{
return Some(editor);
}
// 3. Fall back to EDITOR env var
if let Ok(editor) = std::env::var("EDITOR")
&& !editor.is_empty()
{
return Some(editor);
}
None
}
fn get_message_from_editor(maybe_config: Option<&UserConfig>) -> Result<String, OxenError> {
let editor = resolve_editor(maybe_config).ok_or_else(|| {
OxenError::basic_str(
"No editor is configured and no commit message was provided via -m.\n\n\
To set your preferred editor, run:\n \
oxen config --editor <EDITOR>\n\n\
Or manually add the following to ~/.config/oxen/user_config.toml:\n \
editor = \"vim\"",
)
})?;
// Create a temp file with a comment template
// NOTE: when temp_dir is dropped the directory it made will be deleted
let temp_dir = TempDir::new()?;
let timestamp = std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_nanos();
let temp_path = temp_dir
.path()
.join(format!("oxen_commit_msg_{timestamp}.txt"));
let template = "\n# Please enter the commit message for your changes.\n\
# Lines starting with '#' will be ignored, and an empty message aborts the commit.\n";
{
let mut file = std::fs::File::create(&temp_path)?;
file.write_all(template.as_bytes())?;
}
// Spawn the editor
// Split the editor string to support commands like "code --wait"
let parts: Vec<&str> = editor.split_whitespace().collect();
if parts.is_empty() {
return Err(OxenError::basic_str(
"Must supply valid editor path, not an empty/whitespace-only string.",
));
}
let status = std::process::Command::new(parts[0])
.args(&parts[1..])
.arg(&temp_path)
.status()?;
if !status.success() {
return Err(OxenError::basic_str(&format!(
"Editor '{editor}' exited with non-zero status."
)));
}
// Read the file and strip comments
let contents = std::fs::read_to_string(&temp_path)?;
let _ = std::fs::remove_file(&temp_path);
let message: String = contents
.lines()
.filter(|line| !line.trim_start().starts_with('#'))
.collect::<Vec<&str>>()
.join("\n");
let message = message.trim().to_string();
if message.is_empty() {
return Err(OxenError::basic_str(
"Aborting commit due to empty commit message.",
));
}
Ok(message)
}