5
5
use std:: error:: Error ;
6
6
use std:: process;
7
7
8
- const PPERROR : & str = "Unexpected end of data while parsing Git output" ;
8
+ struct GitStatus {
9
+ branch : Option < String > ,
10
+ ahead : i64 ,
11
+ behind : i64 ,
12
+
13
+ staged : i64 ,
14
+ modified : i64 ,
15
+ deleted : i64 ,
16
+ unmerged : i64 ,
17
+ untracked : i64 ,
18
+ }
9
19
10
20
fn color ( c : i32 ) {
11
21
if c >= 0 {
@@ -23,78 +33,83 @@ fn bold(b: bool) {
23
33
}
24
34
}
25
35
26
- fn main ( ) -> Result < ( ) , Box < Error > > {
27
- let output = process:: Command :: new ( "git" )
28
- . args ( & [ "status" , "--porcelain=v2" , "-z" , "--branch" , "--untracked-files=all" ] )
29
- . stdin ( process:: Stdio :: null ( ) )
30
- . stderr ( process:: Stdio :: null ( ) )
31
- . output ( ) ?;
32
- if !output. status . success ( ) {
33
- // We're most likely not in a Git repo
34
- return Ok ( ( ) )
35
- }
36
- let status = String :: from_utf8 ( output. stdout )
37
- . ok ( ) . ok_or ( "Invalid UTF-8 while decoding Git output" ) ?;
38
-
39
- // Details on the current branch
40
- let mut branch = None ;
41
- let mut ahead = 0 ;
42
- let mut behind = 0 ;
43
-
44
- // File counters
45
- let mut staged = 0 ;
46
- let mut modified = 0 ;
47
- let mut deleted = 0 ;
48
- let mut unmerged = 0 ;
49
- let mut untracked = 0 ;
36
+ fn parse_porcelain2 ( data : String ) -> Option < GitStatus > {
37
+ let mut status = GitStatus {
38
+ branch : None ,
39
+ ahead : 0 ,
40
+ behind : 0 ,
50
41
42
+ staged : 0 ,
43
+ modified : 0 ,
44
+ deleted : 0 ,
45
+ unmerged : 0 ,
46
+ untracked : 0 ,
47
+ } ;
51
48
// Simple parser for the porcelain v2 format
52
- for entry in status . split ( '\0' ) {
49
+ for entry in data . split ( '\0' ) {
53
50
let mut entry = entry. split ( ' ' ) ;
54
51
match entry. next ( ) {
55
52
// Header lines
56
53
Some ( "#" ) => {
57
- match entry. next ( ) . ok_or ( PPERROR ) ? {
54
+ match entry. next ( ) ? {
58
55
"branch.head" => {
59
- let head = entry. next ( ) . ok_or ( PPERROR ) ?;
56
+ let head = entry. next ( ) ?;
60
57
if head != "(detached)" {
61
- branch = Some ( head) ;
58
+ status . branch = Some ( String :: from ( head) ) ;
62
59
}
63
60
} ,
64
61
"branch.ab" => {
65
- let a = entry. next ( ) . ok_or ( PPERROR ) ?;
66
- let b = entry. next ( ) . ok_or ( PPERROR ) ?;
67
- ahead = a. parse :: < i64 > ( ) ?. abs ( ) ;
68
- behind = b. parse :: < i64 > ( ) ?. abs ( ) ;
62
+ let a = entry. next ( ) ?;
63
+ let b = entry. next ( ) ?;
64
+ status . ahead = a. parse :: < i64 > ( ) . ok ( ) ?. abs ( ) ;
65
+ status . behind = b. parse :: < i64 > ( ) . ok ( ) ?. abs ( ) ;
69
66
} ,
70
67
_ => { } ,
71
68
}
72
69
} ,
73
70
// File entries
74
71
Some ( "1" ) | Some ( "2" ) => {
75
- let mut xy = entry. next ( ) . ok_or ( PPERROR ) ?. chars ( ) ;
76
- let x = xy. next ( ) . ok_or ( PPERROR ) ?;
77
- let y = xy. next ( ) . ok_or ( PPERROR ) ?;
72
+ let mut xy = entry. next ( ) ?. chars ( ) ;
73
+ let x = xy. next ( ) ?;
74
+ let y = xy. next ( ) ?;
78
75
if x != '.' {
79
- staged += 1 ;
76
+ status . staged += 1 ;
80
77
}
81
78
match y {
82
- 'M' => modified += 1 ,
83
- 'D' => deleted += 1 ,
79
+ 'M' => status . modified += 1 ,
80
+ 'D' => status . deleted += 1 ,
84
81
_ => { } ,
85
82
}
86
83
}
87
- Some ( "u" ) => unmerged += 1 ,
88
- Some ( "?" ) => untracked += 1 ,
84
+ Some ( "u" ) => status . unmerged += 1 ,
85
+ Some ( "?" ) => status . untracked += 1 ,
89
86
_ => { } ,
90
87
}
91
88
}
89
+ Some ( status)
90
+ }
91
+
92
+ fn main ( ) -> Result < ( ) , Box < Error > > {
93
+ let output = process:: Command :: new ( "git" )
94
+ . args ( & [ "status" , "--porcelain=v2" , "-z" , "--branch" , "--untracked-files=all" ] )
95
+ . stdin ( process:: Stdio :: null ( ) )
96
+ . stderr ( process:: Stdio :: null ( ) )
97
+ . output ( ) ?;
98
+ if !output. status . success ( ) {
99
+ // We're most likely not in a Git repo
100
+ return Ok ( ( ) )
101
+ }
102
+ let status = String :: from_utf8 ( output. stdout )
103
+ . ok ( ) . ok_or ( "Invalid UTF-8 while decoding Git output" ) ?;
104
+
105
+ let status = parse_porcelain2 ( status)
106
+ . ok_or ( "Error while parsing Git output" ) ?;
92
107
93
108
print ! ( "(" ) ;
94
109
95
110
color ( 15 ) ;
96
111
bold ( true ) ;
97
- if let Some ( branch) = branch {
112
+ if let Some ( branch) = status . branch {
98
113
print ! ( "{}" , branch) ;
99
114
} else {
100
115
// Detached head
@@ -104,35 +119,35 @@ fn main() -> Result<(), Box<Error>> {
104
119
color ( -1 ) ;
105
120
106
121
// Divergence with remote branch
107
- if ahead != 0 {
108
- print ! ( "↑{}" , ahead) ;
122
+ if status . ahead != 0 {
123
+ print ! ( "↑{}" , status . ahead) ;
109
124
}
110
- if behind != 0 {
111
- print ! ( "↓{}" , behind) ;
125
+ if status . behind != 0 {
126
+ print ! ( "↓{}" , status . behind) ;
112
127
}
113
128
114
- if untracked + modified + deleted + unmerged + staged > 0 {
129
+ if status . untracked + status . modified + status . deleted + status . unmerged + status . staged > 0 {
115
130
print ! ( "|" ) ;
116
131
}
117
- if untracked != 0 {
132
+ if status . untracked != 0 {
118
133
color ( 2 ) ;
119
- print ! ( "+{}" , untracked) ;
134
+ print ! ( "+{}" , status . untracked) ;
120
135
}
121
- if modified != 0 {
136
+ if status . modified != 0 {
122
137
color ( 5 ) ;
123
- print ! ( "~{}" , modified) ;
138
+ print ! ( "~{}" , status . modified) ;
124
139
}
125
- if deleted != 0 {
140
+ if status . deleted != 0 {
126
141
color ( 1 ) ;
127
- print ! ( "-{}" , deleted) ;
142
+ print ! ( "-{}" , status . deleted) ;
128
143
}
129
- if unmerged != 0 {
144
+ if status . unmerged != 0 {
130
145
color ( 3 ) ;
131
- print ! ( "x{}" , unmerged) ;
146
+ print ! ( "x{}" , status . unmerged) ;
132
147
}
133
- if staged != 0 {
148
+ if status . staged != 0 {
134
149
color ( 4 ) ;
135
- print ! ( "•{}" , staged) ;
150
+ print ! ( "•{}" , status . staged) ;
136
151
}
137
152
138
153
color ( -1 ) ;
0 commit comments