]> git.proxmox.com Git - cargo.git/blob - vendor/git2-0.6.8/examples/status.rs
New upstream version 0.23.0
[cargo.git] / vendor / git2-0.6.8 / examples / status.rs
1 /*
2 * libgit2 "status" example - shows how to use the status APIs
3 *
4 * Written by the libgit2 contributors
5 *
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.
9 *
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/>.
13 */
14
15 #![deny(warnings)]
16
17 extern crate git2;
18 extern crate docopt;
19 #[macro_use]
20 extern crate serde_derive;
21
22 use std::str;
23 use std::time::Duration;
24 use docopt::Docopt;
25 use git2::{Repository, Error, StatusOptions, ErrorCode, SubmoduleIgnore};
26
27 #[derive(Deserialize)]
28 struct Args {
29 arg_spec: Vec<String>,
30 flag_short: bool,
31 flag_porcelain: bool,
32 flag_branch: bool,
33 flag_z: bool,
34 flag_ignored: bool,
35 flag_untracked_files: Option<String>,
36 flag_ignore_submodules: Option<String>,
37 flag_git_dir: Option<String>,
38 flag_repeat: bool,
39 flag_list_submodules: bool,
40 }
41
42 #[derive(Eq, PartialEq)]
43 enum Format { Long, Short, Porcelain }
44
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));
48 if repo.is_bare() {
49 return Err(Error::from_str("cannot report status on bare repository"))
50 }
51
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); }
57 Some("all") => {
58 opts.include_untracked(true).recurse_untracked_dirs(true);
59 }
60 Some(_) => return Err(Error::from_str("invalid untracked-files value")),
61 None => {}
62 }
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")),
66 None => {}
67 }
68 opts.include_untracked(!args.flag_ignored);
69 for spec in &args.arg_spec {
70 opts.pathspec(spec);
71 }
72
73 loop {
74 if args.flag_repeat {
75 println!("\u{1b}[H\u{1b}[2J");
76 }
77
78 let statuses = try!(repo.statuses(Some(&mut opts)));
79
80 if args.flag_branch {
81 try!(show_branch(&repo, &args.format()));
82 }
83 if args.flag_list_submodules {
84 try!(print_submodules(&repo));
85 }
86
87 if args.format() == Format::Long {
88 print_long(&statuses);
89 } else {
90 print_short(&repo, &statuses);
91 }
92
93 if args.flag_repeat {
94 std::thread::sleep(Duration::new(10, 0));
95 } else {
96 return Ok(())
97 }
98 }
99 }
100
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),
107 };
108 let head = head.as_ref().and_then(|h| h.shorthand());
109
110 if format == &Format::Long {
111 println!("# On branch {}",
112 head.unwrap_or("Not currently on any branch"));
113 } else {
114 println!("## {}", head.unwrap_or("HEAD (no branch)"));
115 }
116 Ok(())
117 }
118
119 fn print_submodules(repo: &Repository) -> Result<(), Error> {
120 let modules = try!(repo.submodules());
121 println!("# Submodules");
122 for sm in &modules {
123 println!("# - submodule '{}' at {}", sm.name().unwrap(),
124 sm.path().display());
125 }
126 Ok(())
127 }
128
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;
136
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;
141 }
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:",
148 _ => continue,
149 };
150 if !header {
151 println!("\
152 # Changes to be committed:
153 # (use \"git reset HEAD <file>...\" to unstage)
154 #");
155 header = true;
156 }
157
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(),
163 new.display());
164 }
165 (old, new) => {
166 println!("#\t{} {}", istatus, old.or(new).unwrap().display());
167 }
168 }
169 }
170
171 if header {
172 changes_in_index = true;
173 println!("#");
174 }
175 header = false;
176
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() {
184 continue
185 }
186
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:",
192 _ => continue,
193 };
194
195 if !header {
196 println!("\
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)
200 #\
201 ", if rm_in_workdir {"/rm"} else {""});
202 header = true;
203 }
204
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(),
210 new.display());
211 }
212 (old, new) => {
213 println!("#\t{} {}", istatus, old.or(new).unwrap().display());
214 }
215 }
216 }
217
218 if header {
219 changed_in_workdir = true;
220 println!("#");
221 }
222 header = false;
223
224 // Print untracked files
225 for entry in statuses.iter().filter(|e| e.status() == git2::STATUS_WT_NEW) {
226 if !header {
227 println!("\
228 # Untracked files
229 # (use \"git add <file>...\" to include in what will be committed)
230 #");
231 header = true;
232 }
233 let file = entry.index_to_workdir().unwrap().old_file().path().unwrap();
234 println!("#\t{}", file.display());
235 }
236 header = false;
237
238 // Print ignored files
239 for entry in statuses.iter().filter(|e| e.status() == git2::STATUS_IGNORED) {
240 if !header {
241 println!("\
242 # Ignored files
243 # (use \"git add -f <file>...\" to include in what will be committed)
244 #");
245 header = true;
246 }
247 let file = entry.index_to_workdir().unwrap().old_file().path().unwrap();
248 println!("#\t{}", file.display());
249 }
250
251 if !changes_in_index && changed_in_workdir {
252 println!("no changes added to commit (use \"git add\" and/or \
253 \"git commit -a\")");
254 }
255 }
256
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',
267 _ => ' ',
268 };
269 let mut wstatus = match entry.status() {
270 s if s.contains(git2::STATUS_WT_NEW) => {
271 if istatus == ' ' { istatus = '?'; } '?'
272 }
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',
277 _ => ' ',
278 };
279
280 if entry.status().contains(git2::STATUS_IGNORED) {
281 istatus = '!';
282 wstatus = '!';
283 }
284 if istatus == '?' && wstatus == '?' { continue }
285 let mut extra = "";
286
287 // A commit in a tree is how submodules are stored, so let's go take a
288 // look at its status.
289 //
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())
296 });
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)";
304 }
305 }
306
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();
311 }
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();
316 }
317
318 match (istatus, wstatus) {
319 ('R', 'R') => println!("RR {} {} {}{}", a.unwrap().display(),
320 b.unwrap().display(), c.unwrap().display(),
321 extra),
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),
327 }
328 }
329
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());
333 }
334 }
335
336 impl Args {
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 }
341 }
342 }
343
344 fn main() {
345 const USAGE: &'static str = "
346 usage: status [options] [--] [<spec>..]
347
348 Options:
349 -s, --short show short statuses
350 --long show longer statuses (default)
351 --porcelain ??
352 -b, --branch show branch information
353 -z ??
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
361 ";
362
363 let args = Docopt::new(USAGE).and_then(|d| d.deserialize())
364 .unwrap_or_else(|e| e.exit());
365 match run(&args) {
366 Ok(()) => {}
367 Err(e) => println!("error: {}", e),
368 }
369 }