Просмотр исходного кода

feat(git): add status for git repos

(exa PR) 797: Add status for subdirectories that are git repositories
Christina Sørensen 2 лет назад
Родитель
Сommit
ad01efb8fb
7 измененных файлов с 144 добавлено и 6 удалено
  1. 49 0
      src/fs/feature/git.rs
  2. 6 0
      src/fs/feature/mod.rs
  3. 24 0
      src/fs/fields.rs
  4. 6 4
      src/options/flags.rs
  5. 4 1
      src/options/view.rs
  6. 26 1
      src/output/render/git.rs
  7. 29 0
      src/output/table.rs

+ 49 - 0
src/fs/feature/git.rs

@@ -343,3 +343,52 @@ fn index_status(status: git2::Status) -> f::GitStatus {
         _                                                => f::GitStatus::NotModified,
     }
 }
+
+fn current_branch(repo: &git2::Repository) -> Option<String>{
+    let head = match repo.head() {
+        Ok(head) => Some(head),
+        Err(ref e) if e.code() == git2::ErrorCode::UnbornBranch || e.code() == git2::ErrorCode::NotFound => return None,
+        Err(e) => {
+            error!("Error looking up Git branch: {:?}", e);
+            return None
+        }
+    };
+
+    if let Some(h) = head{
+        if let Some(s) = h.shorthand(){
+            let branch_name = s.to_owned();
+            if branch_name.len() > 10 {
+               return Some(branch_name[..8].to_string()+"..");
+            }
+            return Some(branch_name);
+        }
+    }
+    None
+}
+
+impl f::SubdirGitRepo{
+    pub fn from_path(dir : &Path, status : bool) -> Self{
+
+        let path = &reorient(&dir);
+        let g = git2::Repository::open(path);
+        if let Ok(repo) = g{
+
+            let branch = current_branch(&repo);
+            if !status{
+                return Self{status : f::SubdirGitRepoStatus::GitUnknown, branch};
+            }
+            match repo.statuses(None) {
+                Ok(es) => {
+                    if es.iter().filter(|s| s.status() != git2::Status::IGNORED).any(|_| true){
+                        return Self{status : f::SubdirGitRepoStatus::GitDirty, branch};
+                    }
+                    return Self{status : f::SubdirGitRepoStatus::GitClean, branch};
+                }
+                Err(e) => {
+                    error!("Error looking up Git statuses: {:?}", e)
+                }
+            }
+        }
+        Self::default()
+    }
+}

+ 6 - 0
src/fs/feature/mod.rs

@@ -30,4 +30,10 @@ pub mod git {
             unreachable!();
         }
     }
+
+    impl f::SubdirGitRepo{
+        pub fn from_path(_dir : &Path, _status : bool) -> Self{
+            panic!("Tried to get subdir Git status, but Git support is disabled")
+        }
+    }
 }

+ 24 - 0
src/fs/fields.rs

@@ -259,3 +259,27 @@ impl Default for Git {
         }
     }
 }
+
+#[allow(dead_code)]
+#[derive(PartialEq, Copy, Clone)]
+pub enum SubdirGitRepoStatus{
+    NoRepo,
+    GitClean,
+    GitDirty,
+    GitUnknown
+}
+
+#[derive(Clone)]
+pub struct SubdirGitRepo{
+    pub status : SubdirGitRepoStatus,
+    pub branch : Option<String>
+}
+
+impl Default for SubdirGitRepo{
+    fn default() -> Self {
+        Self{
+            status : SubdirGitRepoStatus::NoRepo,
+            branch : None
+        }
+    }
+}

+ 6 - 4
src/options/flags.rs

@@ -62,9 +62,11 @@ pub static NO_TIME: Arg = Arg { short: None, long: "no-time", takes_value: Takes
 pub static NO_ICONS: Arg = Arg { short: None, long: "no-icons", takes_value: TakesValue::Forbidden };
 
 // optional feature options
-pub static GIT:       Arg = Arg { short: None,       long: "git",               takes_value: TakesValue::Forbidden };
-pub static EXTENDED:  Arg = Arg { short: Some(b'@'), long: "extended",          takes_value: TakesValue::Forbidden };
-pub static OCTAL:     Arg = Arg { short: None,       long: "octal-permissions", takes_value: TakesValue::Forbidden };
+pub static GIT:               Arg = Arg { short: None,       long: "git",                  takes_value: TakesValue::Forbidden };
+pub static GIT_REPOS:         Arg = Arg { short: None,       long: "git-repos",            takes_value: TakesValue::Forbidden };
+pub static GIT_REPOS_NO_STAT: Arg = Arg { short: None,       long: "git-repos-no-status",  takes_value: TakesValue::Forbidden };
+pub static EXTENDED:          Arg = Arg { short: Some(b'@'), long: "extended",             takes_value: TakesValue::Forbidden };
+pub static OCTAL:             Arg = Arg { short: None,       long: "octal-permissions",    takes_value: TakesValue::Forbidden };
 
 
 pub static ALL_ARGS: Args = Args(&[
@@ -80,5 +82,5 @@ pub static ALL_ARGS: Args = Args(&[
     &BLOCKS, &TIME, &ACCESSED, &CREATED, &TIME_STYLE,
     &NO_PERMISSIONS, &NO_FILESIZE, &NO_USER, &NO_TIME, &NO_ICONS,
 
-    &GIT, &EXTENDED, &OCTAL
+    &GIT, &GIT_REPOS, &GIT_REPOS_NO_STAT, &EXTENDED, &OCTAL
 ]);

+ 4 - 1
src/options/view.rs

@@ -199,7 +199,10 @@ impl TableOptions {
 impl Columns {
     fn deduce(matches: &MatchedFlags<'_>) -> Result<Self, OptionsError> {
         let time_types = TimeTypes::deduce(matches)?;
+
         let git = matches.has(&flags::GIT)?;
+        let subdir_git_repos = matches.has(&flags::GIT_REPOS)?;
+        let subdir_git_repos_no_stat = !subdir_git_repos && matches.has(&flags::GIT_REPOS_NO_STAT)?;
 
         let blocks = matches.has(&flags::BLOCKS)?;
         let group  = matches.has(&flags::GROUP)?;
@@ -211,7 +214,7 @@ impl Columns {
         let filesize =    ! matches.has(&flags::NO_FILESIZE)?;
         let user =        ! matches.has(&flags::NO_USER)?;
 
-        Ok(Self { time_types, inode, links, blocks, group, git, octal, permissions, filesize, user })
+        Ok(Self { time_types, inode, links, blocks, group, git, subdir_git_repos, subdir_git_repos_no_stat,octal, permissions, filesize, user })
     }
 }
 

+ 26 - 1
src/output/render/git.rs

@@ -1,4 +1,4 @@
-use ansi_term::{ANSIString, Style};
+use ansi_term::{ANSIString, Style, Color};
 
 use crate::output::cell::{TextCell, DisplayWidth};
 use crate::fs::fields as f;
@@ -16,6 +16,31 @@ impl f::Git {
     }
 }
 
+impl f::SubdirGitRepo {
+    pub fn render(self) -> TextCell {
+        let style = Style::new();
+        let branch_style = match self.branch.as_deref(){
+            Some("master") => style.fg(Color::Green),
+            Some("main") => style.fg(Color::Green),
+            Some(_) => style.fg(Color::Fixed(208)),
+            _ => style,
+        };
+        
+        let branch = branch_style.paint(self.branch.unwrap_or(String::from("-")));
+
+        let s = match self.status {
+            f::SubdirGitRepoStatus::NoRepo => style.paint("- "),
+            f::SubdirGitRepoStatus::GitClean => style.fg(Color::Green).paint("| "),
+            f::SubdirGitRepoStatus::GitDirty => style.bold().fg(Color::Red).paint("- "),
+            f::SubdirGitRepoStatus::GitUnknown => style.paint("- "),
+        };
+
+        TextCell {
+            width: DisplayWidth::from(2 + branch.len()),
+            contents: vec![s,branch].into(),
+        }
+    }
+}
 
 impl f::GitStatus {
     fn render(self, colours: &dyn Colours) -> ANSIString<'static> {

+ 29 - 0
src/output/table.rs

@@ -43,6 +43,8 @@ pub struct Columns {
     pub blocks: bool,
     pub group: bool,
     pub git: bool,
+    pub subdir_git_repos: bool,
+    pub subdir_git_repos_no_stat: bool,
     pub octal: bool,
 
     // Defaults to true:
@@ -113,6 +115,14 @@ impl Columns {
             columns.push(Column::GitStatus);
         }
 
+        if self.subdir_git_repos {
+            columns.push(Column::SubdirGitRepoStatus);
+        }
+
+        if self.subdir_git_repos_no_stat {
+            columns.push(Column::SubdirGitRepoNoStatus);
+        }
+
         columns
     }
 }
@@ -135,6 +145,8 @@ pub enum Column {
     #[cfg(unix)]
     Inode,
     GitStatus,
+    SubdirGitRepoStatus,
+    SubdirGitRepoNoStatus,
     #[cfg(unix)]
     Octal,
 }
@@ -192,6 +204,8 @@ impl Column {
             #[cfg(unix)]
             Self::Inode         => "inode",
             Self::GitStatus     => "Git",
+            Self::SubdirGitRepoStatus => "Repo",
+            Self::SubdirGitRepoNoStatus => "Repo",
             #[cfg(unix)]
             Self::Octal         => "Octal",
         }
@@ -496,6 +510,12 @@ impl<'a, 'f> Table<'a> {
             Column::GitStatus => {
                 self.git_status(file).render(self.theme)
             }
+            Column::SubdirGitRepoStatus => {
+                self.subdir_git_repo(file, true).render()
+            }
+            Column::SubdirGitRepoNoStatus => {
+                self.subdir_git_repo(file, false).render()
+            }
             #[cfg(unix)]
             Column::Octal => {
                 self.octal_permissions(file).render(self.theme.ui.octal)
@@ -524,6 +544,15 @@ impl<'a, 'f> Table<'a> {
             .unwrap_or_default()
     }
 
+    fn subdir_git_repo(&self, file: &File<'_>, status : bool) -> f::SubdirGitRepo {
+        debug!("Getting subdir repo status for path {:?}", file.path);
+
+        if file.is_directory(){
+            return f::SubdirGitRepo::from_path(&file.path, status);
+        }
+        f::SubdirGitRepo::default()
+    }
+
     pub fn render(&self, row: Row) -> TextCell {
         let mut cell = TextCell::default();