1pub mod app;
2mod bindings;
3mod calling_process;
4mod capped_input;
5pub mod cli;
6mod cmd_log;
7pub mod config;
8mod config_edit;
9mod config_git;
10mod diff_renderer;
11pub mod error;
12mod file_watcher;
13mod git;
14mod gitu_diff;
15mod item_data;
16mod items;
17mod key_parser;
18mod menu;
19mod ops;
20mod picker;
21mod prompt;
22mod rebase_todo;
23mod screen;
24mod search;
25mod stream;
26mod style;
27pub mod term;
28#[cfg(test)]
29mod tests;
30mod text_input;
31mod ui;
32
33use bindings::Bindings;
34use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventState, KeyModifiers};
35use error::Error;
36use git2::Repository;
37use items::Item;
38use ops::Action;
39use std::{
40 env,
41 io::{self, IsTerminal},
42 path::Path,
43 rc::Rc,
44 sync::Arc,
45 time::Duration,
46};
47use term::Term;
48
49use crate::calling_process::GitCommand;
50use crate::capped_input::Capped;
51use crate::config::Config;
52use crate::screen::pager::Paged;
53
54pub const LOG_FILE_NAME: &str = "gitu.log";
55
56pub type Res<T> = Result<T, Error>;
94
95pub fn run(config: Arc<Config>, args: &cli::Args, term: &mut Term) -> Res<i32> {
97 needs_terminal(io::stdout().is_terminal())?;
98 let paging = pages(args, io::stdin().is_terminal());
103 let git_argv = paging.then(calling_process::paging_for).flatten();
104 log::debug!("Paging the output of {git_argv:?}");
105
106 log::debug!("Opening repo");
107 let repo = open_repo_from_env()?;
108
109 let piped = paging
110 .then(|| read_piped_input(git_argv, repo.workdir(), config.general.max_input_bytes))
111 .transpose()?;
112
113 let mut app = app::App::create(
114 Rc::new(repo),
115 term.size().map_err(Error::Term)?,
116 args,
117 Arc::clone(&config),
118 true,
119 piped,
120 )?;
121
122 if args.quit_if_one_screen && paging && app.print_if_one_screen(term)? {
123 return Ok(app.state.exit_code);
124 }
125
126 if args.print {
127 return run_app(&mut app, args, term);
128 }
129
130 term.setup_term(&config).map_err(Error::Term)?;
131 let result = run_app(&mut app, args, term);
132 term.reset_term(&config).map_err(Error::Term)?;
133 result
134}
135
136pub(crate) fn needs_terminal(stdout_is_terminal: bool) -> Res<()> {
138 if stdout_is_terminal {
139 Ok(())
140 } else {
141 Err(Error::NotATerminal)
142 }
143}
144
145pub(crate) fn pages(args: &cli::Args, stdin_is_terminal: bool) -> bool {
148 args.command.is_none() && !stdin_is_terminal
149}
150
151fn run_app(app: &mut app::App, args: &cli::Args, term: &mut Term) -> Res<i32> {
152 if let Some(keys_string) = &args.keys {
153 let ("", keys) = key_parser::parse_keys(keys_string).expect("Couldn't parse keys") else {
154 panic!("Couldn't parse keys");
155 };
156
157 for event in keys_to_events(&keys) {
158 app.handle_event(term, event)?;
159 }
160 }
161
162 app.redraw_now(term)?;
163
164 if args.print {
165 return Ok(app.state.exit_code);
166 }
167
168 app.run(term, Duration::from_millis(100))?;
169
170 Ok(app.state.exit_code)
171}
172
173fn read_piped_input(
176 git_argv: Option<Vec<String>>,
177 workdir: Option<&Path>,
178 limit: u64,
179) -> Res<Paged> {
180 paged(io::stdin(), git_argv, workdir, limit)
181}
182
183fn paged(
188 input: impl io::Read,
189 git_argv: Option<Vec<String>>,
190 workdir: Option<&Path>,
191 limit: u64,
192) -> Res<Paged> {
193 if git_argv.clone().and_then(GitCommand::of).is_some() {
194 return Ok(Paged {
195 text: String::new(),
196 truncated: false,
197 git_argv,
198 prefix: command_prefix(workdir),
199 });
200 }
201
202 let input = Capped::read(input, limit).map_err(Error::ReadPipedInput)?;
203 Ok(Paged {
204 text: input.text,
205 truncated: input.truncated,
206 git_argv,
207 prefix: String::new(),
208 })
209}
210
211fn command_prefix(workdir: Option<&Path>) -> String {
215 if let Ok(prefix) = env::var("GIT_PREFIX") {
216 return prefix;
217 }
218
219 let relative = || {
220 let dir = env::current_dir().ok()?.canonicalize().ok()?;
221 let root = workdir?.canonicalize().ok()?;
222 Some(dir.strip_prefix(root).ok()?.to_str()?.to_owned())
223 };
224 match relative() {
225 Some(dir) if !dir.is_empty() => format!("{dir}/"),
226 _ => String::new(),
227 }
228}
229
230fn open_repo(dir: &Path) -> Res<Repository> {
231 log::debug!("Opening repo");
232 let repo = open_repo_from_env()?;
233 repo.set_workdir(dir, false).map_err(Error::OpenRepo)?;
234 Ok(repo)
235}
236
237fn open_repo_from_env() -> Res<Repository> {
238 match Repository::open_from_env() {
239 Ok(repo) => Ok(repo),
240 Err(err) => Err(Error::OpenRepo(err)),
241 }
242}
243
244fn keys_to_events(keys: &[(KeyModifiers, KeyCode)]) -> Vec<Event> {
245 keys.iter()
246 .map(|(mods, key)| {
247 Event::Key(KeyEvent {
248 code: *key,
249 modifiers: *mods,
250 kind: event::KeyEventKind::Press,
251 state: KeyEventState::NONE,
252 })
253 })
254 .collect::<Vec<_>>()
255}