diff options
Diffstat (limited to 'vendor/git2/examples/status.rs')
-rw-r--r-- | vendor/git2/examples/status.rs | 441 |
1 files changed, 441 insertions, 0 deletions
diff --git a/vendor/git2/examples/status.rs b/vendor/git2/examples/status.rs new file mode 100644 index 0000000..4f7bc79 --- /dev/null +++ b/vendor/git2/examples/status.rs @@ -0,0 +1,441 @@ +/* + * libgit2 "status" example - shows how to use the status APIs + * + * Written by the libgit2 contributors + * + * To the extent possible under law, the author(s) have dedicated all copyright + * and related and neighboring rights to this software to the public domain + * worldwide. This software is distributed without any warranty. + * + * You should have received a copy of the CC0 Public Domain Dedication along + * with this software. If not, see + * <http://creativecommons.org/publicdomain/zero/1.0/>. + */ + +#![deny(warnings)] + +use git2::{Error, ErrorCode, Repository, StatusOptions, SubmoduleIgnore}; +use std::str; +use std::time::Duration; +use structopt::StructOpt; + +#[derive(StructOpt)] +struct Args { + arg_spec: Vec<String>, + #[structopt(name = "long", long)] + /// show longer statuses (default) + _flag_long: bool, + /// show short statuses + #[structopt(name = "short", long)] + flag_short: bool, + #[structopt(name = "porcelain", long)] + /// ?? + flag_porcelain: bool, + #[structopt(name = "branch", short, long)] + /// show branch information + flag_branch: bool, + #[structopt(name = "z", short)] + /// ?? + flag_z: bool, + #[structopt(name = "ignored", long)] + /// show ignored files as well + flag_ignored: bool, + #[structopt(name = "opt-modules", long = "untracked-files")] + /// setting for showing untracked files [no|normal|all] + flag_untracked_files: Option<String>, + #[structopt(name = "opt-files", long = "ignore-submodules")] + /// setting for ignoring submodules [all] + flag_ignore_submodules: Option<String>, + #[structopt(name = "dir", long = "git-dir")] + /// git directory to analyze + flag_git_dir: Option<String>, + #[structopt(name = "repeat", long)] + /// repeatedly show status, sleeping inbetween + flag_repeat: bool, + #[structopt(name = "list-submodules", long)] + /// show submodules + flag_list_submodules: bool, +} + +#[derive(Eq, PartialEq)] +enum Format { + Long, + Short, + Porcelain, +} + +fn run(args: &Args) -> Result<(), Error> { + let path = args.flag_git_dir.clone().unwrap_or_else(|| ".".to_string()); + let repo = Repository::open(&path)?; + if repo.is_bare() { + return Err(Error::from_str("cannot report status on bare repository")); + } + + let mut opts = StatusOptions::new(); + opts.include_ignored(args.flag_ignored); + match args.flag_untracked_files.as_ref().map(|s| &s[..]) { + Some("no") => { + opts.include_untracked(false); + } + Some("normal") => { + opts.include_untracked(true); + } + Some("all") => { + opts.include_untracked(true).recurse_untracked_dirs(true); + } + Some(_) => return Err(Error::from_str("invalid untracked-files value")), + None => {} + } + match args.flag_ignore_submodules.as_ref().map(|s| &s[..]) { + Some("all") => { + opts.exclude_submodules(true); + } + Some(_) => return Err(Error::from_str("invalid ignore-submodules value")), + None => {} + } + opts.include_untracked(!args.flag_ignored); + for spec in &args.arg_spec { + opts.pathspec(spec); + } + + loop { + if args.flag_repeat { + println!("\u{1b}[H\u{1b}[2J"); + } + + let statuses = repo.statuses(Some(&mut opts))?; + + if args.flag_branch { + show_branch(&repo, &args.format())?; + } + if args.flag_list_submodules { + print_submodules(&repo)?; + } + + if args.format() == Format::Long { + print_long(&statuses); + } else { + print_short(&repo, &statuses); + } + + if args.flag_repeat { + std::thread::sleep(Duration::new(10, 0)); + } else { + return Ok(()); + } + } +} + +fn show_branch(repo: &Repository, format: &Format) -> Result<(), Error> { + let head = match repo.head() { + Ok(head) => Some(head), + Err(ref e) if e.code() == ErrorCode::UnbornBranch || e.code() == ErrorCode::NotFound => { + None + } + Err(e) => return Err(e), + }; + let head = head.as_ref().and_then(|h| h.shorthand()); + + if format == &Format::Long { + println!( + "# On branch {}", + head.unwrap_or("Not currently on any branch") + ); + } else { + println!("## {}", head.unwrap_or("HEAD (no branch)")); + } + Ok(()) +} + +fn print_submodules(repo: &Repository) -> Result<(), Error> { + let modules = repo.submodules()?; + println!("# Submodules"); + for sm in &modules { + println!( + "# - submodule '{}' at {}", + sm.name().unwrap(), + sm.path().display() + ); + } + Ok(()) +} + +// This function print out an output similar to git's status command in long +// form, including the command-line hints. +fn print_long(statuses: &git2::Statuses) { + let mut header = false; + let mut rm_in_workdir = false; + let mut changes_in_index = false; + let mut changed_in_workdir = false; + + // Print index changes + for entry in statuses + .iter() + .filter(|e| e.status() != git2::Status::CURRENT) + { + if entry.status().contains(git2::Status::WT_DELETED) { + rm_in_workdir = true; + } + let istatus = match entry.status() { + s if s.contains(git2::Status::INDEX_NEW) => "new file: ", + s if s.contains(git2::Status::INDEX_MODIFIED) => "modified: ", + s if s.contains(git2::Status::INDEX_DELETED) => "deleted: ", + s if s.contains(git2::Status::INDEX_RENAMED) => "renamed: ", + s if s.contains(git2::Status::INDEX_TYPECHANGE) => "typechange:", + _ => continue, + }; + if !header { + println!( + "\ +# Changes to be committed: +# (use \"git reset HEAD <file>...\" to unstage) +#" + ); + header = true; + } + + let old_path = entry.head_to_index().unwrap().old_file().path(); + let new_path = entry.head_to_index().unwrap().new_file().path(); + match (old_path, new_path) { + (Some(old), Some(new)) if old != new => { + println!("#\t{} {} -> {}", istatus, old.display(), new.display()); + } + (old, new) => { + println!("#\t{} {}", istatus, old.or(new).unwrap().display()); + } + } + } + + if header { + changes_in_index = true; + println!("#"); + } + header = false; + + // Print workdir changes to tracked files + for entry in statuses.iter() { + // With `Status::OPT_INCLUDE_UNMODIFIED` (not used in this example) + // `index_to_workdir` may not be `None` even if there are no differences, + // in which case it will be a `Delta::Unmodified`. + if entry.status() == git2::Status::CURRENT || entry.index_to_workdir().is_none() { + continue; + } + + let istatus = match entry.status() { + s if s.contains(git2::Status::WT_MODIFIED) => "modified: ", + s if s.contains(git2::Status::WT_DELETED) => "deleted: ", + s if s.contains(git2::Status::WT_RENAMED) => "renamed: ", + s if s.contains(git2::Status::WT_TYPECHANGE) => "typechange:", + _ => continue, + }; + + if !header { + println!( + "\ +# Changes not staged for commit: +# (use \"git add{} <file>...\" to update what will be committed) +# (use \"git checkout -- <file>...\" to discard changes in working directory) +#\ + ", + if rm_in_workdir { "/rm" } else { "" } + ); + header = true; + } + + let old_path = entry.index_to_workdir().unwrap().old_file().path(); + let new_path = entry.index_to_workdir().unwrap().new_file().path(); + match (old_path, new_path) { + (Some(old), Some(new)) if old != new => { + println!("#\t{} {} -> {}", istatus, old.display(), new.display()); + } + (old, new) => { + println!("#\t{} {}", istatus, old.or(new).unwrap().display()); + } + } + } + + if header { + changed_in_workdir = true; + println!("#"); + } + header = false; + + // Print untracked files + for entry in statuses + .iter() + .filter(|e| e.status() == git2::Status::WT_NEW) + { + if !header { + println!( + "\ +# Untracked files +# (use \"git add <file>...\" to include in what will be committed) +#" + ); + header = true; + } + let file = entry.index_to_workdir().unwrap().old_file().path().unwrap(); + println!("#\t{}", file.display()); + } + header = false; + + // Print ignored files + for entry in statuses + .iter() + .filter(|e| e.status() == git2::Status::IGNORED) + { + if !header { + println!( + "\ +# Ignored files +# (use \"git add -f <file>...\" to include in what will be committed) +#" + ); + header = true; + } + let file = entry.index_to_workdir().unwrap().old_file().path().unwrap(); + println!("#\t{}", file.display()); + } + + if !changes_in_index && changed_in_workdir { + println!( + "no changes added to commit (use \"git add\" and/or \ + \"git commit -a\")" + ); + } +} + +// This version of the output prefixes each path with two status columns and +// shows submodule status information. +fn print_short(repo: &Repository, statuses: &git2::Statuses) { + for entry in statuses + .iter() + .filter(|e| e.status() != git2::Status::CURRENT) + { + let mut istatus = match entry.status() { + s if s.contains(git2::Status::INDEX_NEW) => 'A', + s if s.contains(git2::Status::INDEX_MODIFIED) => 'M', + s if s.contains(git2::Status::INDEX_DELETED) => 'D', + s if s.contains(git2::Status::INDEX_RENAMED) => 'R', + s if s.contains(git2::Status::INDEX_TYPECHANGE) => 'T', + _ => ' ', + }; + let mut wstatus = match entry.status() { + s if s.contains(git2::Status::WT_NEW) => { + if istatus == ' ' { + istatus = '?'; + } + '?' + } + s if s.contains(git2::Status::WT_MODIFIED) => 'M', + s if s.contains(git2::Status::WT_DELETED) => 'D', + s if s.contains(git2::Status::WT_RENAMED) => 'R', + s if s.contains(git2::Status::WT_TYPECHANGE) => 'T', + _ => ' ', + }; + + if entry.status().contains(git2::Status::IGNORED) { + istatus = '!'; + wstatus = '!'; + } + if istatus == '?' && wstatus == '?' { + continue; + } + let mut extra = ""; + + // A commit in a tree is how submodules are stored, so let's go take a + // look at its status. + // + // TODO: check for GIT_FILEMODE_COMMIT + let status = entry.index_to_workdir().and_then(|diff| { + let ignore = SubmoduleIgnore::Unspecified; + diff.new_file() + .path_bytes() + .and_then(|s| str::from_utf8(s).ok()) + .and_then(|name| repo.submodule_status(name, ignore).ok()) + }); + if let Some(status) = status { + if status.contains(git2::SubmoduleStatus::WD_MODIFIED) { + extra = " (new commits)"; + } else if status.contains(git2::SubmoduleStatus::WD_INDEX_MODIFIED) + || status.contains(git2::SubmoduleStatus::WD_WD_MODIFIED) + { + extra = " (modified content)"; + } else if status.contains(git2::SubmoduleStatus::WD_UNTRACKED) { + extra = " (untracked content)"; + } + } + + let (mut a, mut b, mut c) = (None, None, None); + if let Some(diff) = entry.head_to_index() { + a = diff.old_file().path(); + b = diff.new_file().path(); + } + if let Some(diff) = entry.index_to_workdir() { + a = a.or_else(|| diff.old_file().path()); + b = b.or_else(|| diff.old_file().path()); + c = diff.new_file().path(); + } + + match (istatus, wstatus) { + ('R', 'R') => println!( + "RR {} {} {}{}", + a.unwrap().display(), + b.unwrap().display(), + c.unwrap().display(), + extra + ), + ('R', w) => println!( + "R{} {} {}{}", + w, + a.unwrap().display(), + b.unwrap().display(), + extra + ), + (i, 'R') => println!( + "{}R {} {}{}", + i, + a.unwrap().display(), + c.unwrap().display(), + extra + ), + (i, w) => println!("{}{} {}{}", i, w, a.unwrap().display(), extra), + } + } + + for entry in statuses + .iter() + .filter(|e| e.status() == git2::Status::WT_NEW) + { + println!( + "?? {}", + entry + .index_to_workdir() + .unwrap() + .old_file() + .path() + .unwrap() + .display() + ); + } +} + +impl Args { + fn format(&self) -> Format { + if self.flag_short { + Format::Short + } else if self.flag_porcelain || self.flag_z { + Format::Porcelain + } else { + Format::Long + } + } +} + +fn main() { + let args = Args::from_args(); + match run(&args) { + Ok(()) => {} + Err(e) => println!("error: {}", e), + } +} |