/src/backend.rs

https://github.com/matheuslessarodrigues/verco · Rust · 199 lines · 178 code · 21 blank · 0 comment · 21 complexity · bbcbc69b430b93f6878fff3d47ee96b5 MD5 · raw file

  1. use std::{
  2. path::PathBuf,
  3. process::{Child, Command, Stdio},
  4. sync::Arc,
  5. };
  6. use crate::mode::{fuzzy_matches, FilterEntry};
  7. pub mod git;
  8. pub mod hg;
  9. pub mod plastic;
  10. pub type BackendResult<T> = std::result::Result<T, String>;
  11. #[derive(Clone, PartialEq, Eq, PartialOrd, Ord)]
  12. pub enum FileStatus {
  13. Modified,
  14. Added,
  15. Deleted,
  16. Renamed,
  17. Untracked,
  18. Copied,
  19. Unmerged,
  20. Missing,
  21. Ignored,
  22. Clean,
  23. Unknown(String),
  24. }
  25. impl FileStatus {
  26. pub const fn max_len() -> usize {
  27. 9
  28. }
  29. pub fn as_str(&self) -> &str {
  30. match self {
  31. Self::Modified => "modified",
  32. Self::Added => "added",
  33. Self::Deleted => "deleted",
  34. Self::Renamed => "renamed",
  35. Self::Untracked => "untracked",
  36. Self::Copied => "copied",
  37. Self::Unmerged => "unmerged",
  38. Self::Missing => "missing",
  39. Self::Ignored => "ignored",
  40. Self::Clean => "clean",
  41. Self::Unknown(status) => {
  42. if status.len() > Self::max_len() {
  43. &status[..Self::max_len()]
  44. } else {
  45. status
  46. }
  47. }
  48. }
  49. }
  50. }
  51. pub struct StatusInfo {
  52. pub header: String,
  53. pub entries: Vec<RevisionEntry>,
  54. }
  55. pub struct RevisionInfo {
  56. pub message: String,
  57. pub entries: Vec<RevisionEntry>,
  58. }
  59. #[derive(Clone)]
  60. pub struct RevisionEntry {
  61. pub selected: bool,
  62. pub name: String,
  63. pub status: FileStatus,
  64. }
  65. impl RevisionEntry {
  66. pub fn new(name: String, status: FileStatus) -> Self {
  67. Self {
  68. selected: false,
  69. name,
  70. status,
  71. }
  72. }
  73. }
  74. impl FilterEntry for RevisionEntry {
  75. fn fuzzy_matches(&self, pattern: &str) -> bool {
  76. fuzzy_matches(&self.name, pattern)
  77. }
  78. }
  79. pub struct LogEntry {
  80. pub graph: String,
  81. pub hash: String,
  82. pub date: String,
  83. pub author: String,
  84. pub refs: String,
  85. pub message: String,
  86. }
  87. impl FilterEntry for LogEntry {
  88. fn fuzzy_matches(&self, pattern: &str) -> bool {
  89. fuzzy_matches(&self.message, pattern)
  90. || fuzzy_matches(&self.refs, pattern)
  91. || fuzzy_matches(&self.author, pattern)
  92. || fuzzy_matches(&self.date, pattern)
  93. || fuzzy_matches(&self.hash, pattern)
  94. }
  95. }
  96. pub struct BranchEntry {
  97. pub name: String,
  98. pub checked_out: bool,
  99. }
  100. impl FilterEntry for BranchEntry {
  101. fn fuzzy_matches(&self, pattern: &str) -> bool {
  102. fuzzy_matches(&self.name, pattern)
  103. }
  104. }
  105. pub struct TagEntry {
  106. pub name: String,
  107. }
  108. impl FilterEntry for TagEntry {
  109. fn fuzzy_matches(&self, pattern: &str) -> bool {
  110. fuzzy_matches(&self.name, pattern)
  111. }
  112. }
  113. pub trait Backend: 'static + Send + Sync {
  114. fn status(&self) -> BackendResult<StatusInfo>;
  115. fn commit(&self, message: &str, entries: &[RevisionEntry]) -> BackendResult<()>;
  116. fn discard(&self, entries: &[RevisionEntry]) -> BackendResult<()>;
  117. fn diff(&self, revision: Option<&str>, entries: &[RevisionEntry]) -> BackendResult<String>;
  118. fn resolve_taking_ours(&self, entries: &[RevisionEntry]) -> BackendResult<()>;
  119. fn resolve_taking_theirs(&self, entries: &[RevisionEntry]) -> BackendResult<()>;
  120. fn log(&self, start: usize, len: usize) -> BackendResult<(usize, Vec<LogEntry>)>;
  121. fn checkout(&self, revision: &str) -> BackendResult<()>;
  122. fn merge(&self, revision: &str) -> BackendResult<()>;
  123. fn fetch(&self) -> BackendResult<()>;
  124. fn pull(&self) -> BackendResult<()>;
  125. fn push(&self) -> BackendResult<()>;
  126. fn revision_details(&self, revision: &str) -> BackendResult<RevisionInfo>;
  127. fn branches(&self) -> BackendResult<Vec<BranchEntry>>;
  128. fn new_branch(&self, name: &str) -> BackendResult<()>;
  129. fn delete_branch(&self, name: &str) -> BackendResult<()>;
  130. fn tags(&self) -> BackendResult<Vec<TagEntry>>;
  131. fn new_tag(&self, name: &str) -> BackendResult<()>;
  132. fn delete_tag(&self, name: &str) -> BackendResult<()>;
  133. }
  134. pub struct Process(Child);
  135. impl Process {
  136. pub fn spawn(command_name: &str, args: &[&str]) -> BackendResult<Self> {
  137. let mut command = Command::new(command_name);
  138. command.args(args);
  139. command.stdin(Stdio::null());
  140. command.stdout(Stdio::piped());
  141. command.stderr(Stdio::piped());
  142. match command.spawn() {
  143. Ok(child) => Ok(Self(child)),
  144. Err(error) => Err(format!(
  145. "could not spawn process '{}': {}",
  146. command_name, error
  147. )),
  148. }
  149. }
  150. pub fn wait(self) -> BackendResult<String> {
  151. let output = match self.0.wait_with_output() {
  152. Ok(output) => output,
  153. Err(error) => return Err(format!("could not wait for process: {}", error)),
  154. };
  155. let stdout = String::from_utf8_lossy(&output.stdout);
  156. if output.status.success() {
  157. Ok(stdout.into())
  158. } else {
  159. let stderr = String::from_utf8_lossy(&output.stderr);
  160. let mut error = String::new();
  161. error.push_str(&stdout);
  162. error.push('\n');
  163. error.push_str(&stderr);
  164. Err(error)
  165. }
  166. }
  167. }
  168. pub fn backend_from_current_repository() -> Option<(PathBuf, Arc<dyn Backend>)> {
  169. if let Some((root, git)) = git::Git::try_new() {
  170. Some((root, Arc::new(git)))
  171. } else if let Some((root, hg)) = hg::Hg::try_new() {
  172. Some((root, Arc::new(hg)))
  173. } else if let Some((root, plastic)) = plastic::Plastic::try_new() {
  174. Some((root, Arc::new(plastic)))
  175. } else {
  176. None
  177. }
  178. }