2 * libgit2 "status" example - shows how to use the status APIs
4 * Written by the libgit2 contributors
6 * To the extent possible under law, the author(s) have dedicated all copyright
7 * and related and neighboring rights to this software to the public domain
8 * worldwide. This software is distributed without any warranty.
10 * You should have received a copy of the CC0 Public Domain Dedication along
11 * with this software. If not, see
12 * <http://creativecommons.org/publicdomain/zero/1.0/>.
20 extern crate serde_derive
;
23 use std
::time
::Duration
;
25 use git2
::{Repository, Error, StatusOptions, ErrorCode, SubmoduleIgnore}
;
27 #[derive(Deserialize)]
29 arg_spec
: Vec
<String
>,
35 flag_untracked_files
: Option
<String
>,
36 flag_ignore_submodules
: Option
<String
>,
37 flag_git_dir
: Option
<String
>,
39 flag_list_submodules
: bool
,
42 #[derive(Eq, PartialEq)]
43 enum Format { Long, Short, Porcelain }
45 fn run(args
: &Args
) -> Result
<(), Error
> {
46 let path
= args
.flag_git_dir
.clone().unwrap_or_else(|| ".".to_string());
47 let repo
= try
!(Repository
::open(&path
));
49 return Err(Error
::from_str("cannot report status on bare repository"))
52 let mut opts
= StatusOptions
::new();
53 opts
.include_ignored(args
.flag_ignored
);
54 match args
.flag_untracked_files
.as_ref().map(|s
| &s
[..]) {
55 Some("no") => { opts.include_untracked(false); }
56 Some("normal") => { opts.include_untracked(true); }
58 opts
.include_untracked(true).recurse_untracked_dirs(true);
60 Some(_
) => return Err(Error
::from_str("invalid untracked-files value")),
63 match args
.flag_ignore_submodules
.as_ref().map(|s
| &s
[..]) {
64 Some("all") => { opts.exclude_submodules(true); }
65 Some(_
) => return Err(Error
::from_str("invalid ignore-submodules value")),
68 opts
.include_untracked(!args
.flag_ignored
);
69 for spec
in &args
.arg_spec
{
75 println
!("\u{1b}[H\u{1b}[2J");
78 let statuses
= try
!(repo
.statuses(Some(&mut opts
)));
81 try
!(show_branch(&repo
, &args
.format()));
83 if args
.flag_list_submodules
{
84 try
!(print_submodules(&repo
));
87 if args
.format() == Format
::Long
{
88 print_long(&statuses
);
90 print_short(&repo
, &statuses
);
94 std
::thread
::sleep(Duration
::new(10, 0));
101 fn show_branch(repo
: &Repository
, format
: &Format
) -> Result
<(), Error
> {
102 let head
= match repo
.head() {
103 Ok(head
) => Some(head
),
104 Err(ref e
) if e
.code() == ErrorCode
::UnbornBranch
||
105 e
.code() == ErrorCode
::NotFound
=> None
,
106 Err(e
) => return Err(e
),
108 let head
= head
.as_ref().and_then(|h
| h
.shorthand());
110 if format
== &Format
::Long
{
111 println
!("# On branch {}",
112 head
.unwrap_or("Not currently on any branch"));
114 println
!("## {}", head
.unwrap_or("HEAD (no branch)"));
119 fn print_submodules(repo
: &Repository
) -> Result
<(), Error
> {
120 let modules
= try
!(repo
.submodules());
121 println
!("# Submodules");
123 println
!("# - submodule '{}' at {}", sm
.name().unwrap(),
124 sm
.path().display());
129 // This function print out an output similar to git's status command in long
130 // form, including the command-line hints.
131 fn print_long(statuses
: &git2
::Statuses
) {
132 let mut header
= false;
133 let mut rm_in_workdir
= false;
134 let mut changes_in_index
= false;
135 let mut changed_in_workdir
= false;
137 // Print index changes
138 for entry
in statuses
.iter().filter(|e
| e
.status() != git2
::STATUS_CURRENT
) {
139 if entry
.status().contains(git2
::STATUS_WT_DELETED
) {
140 rm_in_workdir
= true;
142 let istatus
= match entry
.status() {
143 s
if s
.contains(git2
::STATUS_INDEX_NEW
) => "new file: ",
144 s
if s
.contains(git2
::STATUS_INDEX_MODIFIED
) => "modified: ",
145 s
if s
.contains(git2
::STATUS_INDEX_DELETED
) => "deleted: ",
146 s
if s
.contains(git2
::STATUS_INDEX_RENAMED
) => "renamed: ",
147 s
if s
.contains(git2
::STATUS_INDEX_TYPECHANGE
) => "typechange:",
152 # Changes to be committed:
153 # (use \"git reset HEAD <file>...\" to unstage)
158 let old_path
= entry
.head_to_index().unwrap().old_file().path();
159 let new_path
= entry
.head_to_index().unwrap().new_file().path();
160 match (old_path
, new_path
) {
161 (Some(old
), Some(new
)) if old
!= new
=> {
162 println
!("#\t{} {} -> {}", istatus
, old
.display(),
166 println
!("#\t{} {}", istatus
, old
.or(new
).unwrap().display());
172 changes_in_index
= true;
177 // Print workdir changes to tracked files
178 for entry
in statuses
.iter() {
179 // With `STATUS_OPT_INCLUDE_UNMODIFIED` (not used in this example)
180 // `index_to_workdir` may not be `None` even if there are no differences,
181 // in which case it will be a `Delta::Unmodified`.
182 if entry
.status() == git2
::STATUS_CURRENT
||
183 entry
.index_to_workdir().is_none() {
187 let istatus
= match entry
.status() {
188 s
if s
.contains(git2
::STATUS_WT_MODIFIED
) => "modified: ",
189 s
if s
.contains(git2
::STATUS_WT_DELETED
) => "deleted: ",
190 s
if s
.contains(git2
::STATUS_WT_RENAMED
) => "renamed: ",
191 s
if s
.contains(git2
::STATUS_WT_TYPECHANGE
) => "typechange:",
197 # Changes not staged for commit:
198 # (use \"git add{} <file>...\" to update what will be committed)
199 # (use \"git checkout -- <file>...\" to discard changes in working directory)
201 ", if rm_in_workdir {"/rm"}
else {""}
);
205 let old_path
= entry
.index_to_workdir().unwrap().old_file().path();
206 let new_path
= entry
.index_to_workdir().unwrap().new_file().path();
207 match (old_path
, new_path
) {
208 (Some(old
), Some(new
)) if old
!= new
=> {
209 println
!("#\t{} {} -> {}", istatus
, old
.display(),
213 println
!("#\t{} {}", istatus
, old
.or(new
).unwrap().display());
219 changed_in_workdir
= true;
224 // Print untracked files
225 for entry
in statuses
.iter().filter(|e
| e
.status() == git2
::STATUS_WT_NEW
) {
229 # (use \"git add <file>...\" to include in what will be committed)
233 let file
= entry
.index_to_workdir().unwrap().old_file().path().unwrap();
234 println
!("#\t{}", file
.display());
238 // Print ignored files
239 for entry
in statuses
.iter().filter(|e
| e
.status() == git2
::STATUS_IGNORED
) {
243 # (use \"git add -f <file>...\" to include in what will be committed)
247 let file
= entry
.index_to_workdir().unwrap().old_file().path().unwrap();
248 println
!("#\t{}", file
.display());
251 if !changes_in_index
&& changed_in_workdir
{
252 println
!("no changes added to commit (use \"git add\" and/or \
253 \"git commit -a\")");
257 // This version of the output prefixes each path with two status columns and
258 // shows submodule status information.
259 fn print_short(repo
: &Repository
, statuses
: &git2
::Statuses
) {
260 for entry
in statuses
.iter().filter(|e
| e
.status() != git2
::STATUS_CURRENT
) {
261 let mut istatus
= match entry
.status() {
262 s
if s
.contains(git2
::STATUS_INDEX_NEW
) => 'A'
,
263 s
if s
.contains(git2
::STATUS_INDEX_MODIFIED
) => 'M'
,
264 s
if s
.contains(git2
::STATUS_INDEX_DELETED
) => 'D'
,
265 s
if s
.contains(git2
::STATUS_INDEX_RENAMED
) => 'R'
,
266 s
if s
.contains(git2
::STATUS_INDEX_TYPECHANGE
) => 'T'
,
269 let mut wstatus
= match entry
.status() {
270 s
if s
.contains(git2
::STATUS_WT_NEW
) => {
271 if istatus
== ' ' { istatus = '?'; } '?'
273 s
if s
.contains(git2
::STATUS_WT_MODIFIED
) => 'M'
,
274 s
if s
.contains(git2
::STATUS_WT_DELETED
) => 'D'
,
275 s
if s
.contains(git2
::STATUS_WT_RENAMED
) => 'R'
,
276 s
if s
.contains(git2
::STATUS_WT_TYPECHANGE
) => 'T'
,
280 if entry
.status().contains(git2
::STATUS_IGNORED
) {
284 if istatus
== '?'
&& wstatus
== '?' { continue }
287 // A commit in a tree is how submodules are stored, so let's go take a
288 // look at its status.
290 // TODO: check for GIT_FILEMODE_COMMIT
291 let status
= entry
.index_to_workdir().and_then(|diff
| {
292 let ignore
= SubmoduleIgnore
::Unspecified
;
293 diff
.new_file().path_bytes()
294 .and_then(|s
| str::from_utf8(s
).ok())
295 .and_then(|name
| repo
.submodule_status(name
, ignore
).ok())
297 if let Some(status
) = status
{
298 if status
.contains(git2
::SUBMODULE_STATUS_WD_MODIFIED
) {
299 extra
= " (new commits)";
300 } else if status
.contains(git2
::SUBMODULE_STATUS_WD_INDEX_MODIFIED
) || status
.contains(git2
::SUBMODULE_STATUS_WD_WD_MODIFIED
) {
301 extra
= " (modified content)";
302 } else if status
.contains(git2
::SUBMODULE_STATUS_WD_UNTRACKED
) {
303 extra
= " (untracked content)";
307 let (mut a
, mut b
, mut c
) = (None
, None
, None
);
308 if let Some(diff
) = entry
.head_to_index() {
309 a
= diff
.old_file().path();
310 b
= diff
.new_file().path();
312 if let Some(diff
) = entry
.index_to_workdir() {
313 a
= a
.or_else(|| diff
.old_file().path());
314 b
= b
.or_else(|| diff
.old_file().path());
315 c
= diff
.new_file().path();
318 match (istatus
, wstatus
) {
319 ('R'
, 'R'
) => println
!("RR {} {} {}{}", a
.unwrap().display(),
320 b
.unwrap().display(), c
.unwrap().display(),
322 ('R'
, w
) => println
!("R{} {} {}{}", w
, a
.unwrap().display(),
323 b
.unwrap().display(), extra
),
324 (i
, 'R'
) => println
!("{}R {} {}{}", i
, a
.unwrap().display(),
325 c
.unwrap().display(), extra
),
326 (i
, w
) => println
!("{}{} {}{}", i
, w
, a
.unwrap().display(), extra
),
330 for entry
in statuses
.iter().filter(|e
| e
.status() == git2
::STATUS_WT_NEW
) {
331 println
!("?? {}", entry
.index_to_workdir().unwrap().old_file()
332 .path().unwrap().display());
337 fn format(&self) -> Format
{
338 if self.flag_short { Format::Short }
339 else if self.flag_porcelain
|| self.flag_z { Format::Porcelain }
340 else { Format::Long }
345 const USAGE
: &'
static str = "
346 usage: status [options] [--] [<spec>..]
349 -s, --short show short statuses
350 --long show longer statuses (default)
352 -b, --branch show branch information
354 --ignored show ignored files as well
355 --untracked-files <opt> setting for showing untracked files [no|normal|all]
356 --ignore-submodules <opt> setting for ignoring submodules [all]
357 --git-dir <dir> git directory to analyze
358 --repeat repeatedly show status, sleeping inbetween
359 --list-submodules show submodules
360 -h, --help show this message
363 let args
= Docopt
::new(USAGE
).and_then(|d
| d
.deserialize())
364 .unwrap_or_else(|e
| e
.exit());
367 Err(e
) => println
!("error: {}", e
),