6
6
7
7
8
8
import os
9
+ import re
9
10
10
11
import common
11
12
14
15
FILE_NOT_FOUND = 2
15
16
16
17
# Possible status in which a Git file can be in.
18
+ # (There are actually many more, but these seem to be the only ones relevant
19
+ # to Gitless.)
20
+ # TODO(sperezde): just have gitpylib's status return the status code and let
21
+ # Gitless figure out the rest by itself.
17
22
TRACKED_UNMODIFIED = 3
18
23
TRACKED_MODIFIED = 4
19
24
UNTRACKED = 5
25
30
DELETED_ASSUME_UNCHANGED = 10
26
31
IN_CONFLICT = 11
27
32
IGNORED = 12
28
- IGNORED_STAGED = 13
29
33
# the file was a tracked file that was modified after being staged.
30
- MODIFIED_MODIFIED = 14
31
- ADDED_MODIFIED = 15 # file is a new file that was added and then modified.
34
+ MODIFIED_MODIFIED = 13
35
+ ADDED_MODIFIED = 14 # file is a new file that was added and then modified.
32
36
33
37
34
38
def of_file (fp ):
@@ -43,20 +47,29 @@ def of_file(fp):
43
47
"""
44
48
fp = common .real_case (fp )
45
49
46
- ok , out , unused_err = common .git_call (
50
+ ok , out_ls_files , unused_err = common .git_call (
47
51
'ls-files -tvco --error-unmatch "%s"' % fp )
48
52
if not ok :
49
53
# The file doesn't exist.
50
54
return FILE_NOT_FOUND
55
+ return _status_file (fp )
51
56
52
- return _status_from_output (out [0 ], fp )
53
57
54
-
55
- def au_files ():
56
- """Gets all assumed unchanged files. Paths are relative to the repo dir."""
58
+ def au_files (relative_to_cwd = False ):
59
+ """Gets all assumed unchanged files.
60
+
61
+ Args:
62
+ relative_to_cwd: if True then only those au files under the cwd are
63
+ reported. If False, all au files in the repository are reported. (Defaults
64
+ to False.)
65
+ """
57
66
out , unused_err = common .safe_git_call (
58
- 'ls-files -v --full-name %s' % common .repo_dir ())
67
+ 'ls-files -v {}' .format (
68
+ '--full-name "{}"' .format (
69
+ common .repo_dir ()) if not relative_to_cwd else '' ))
59
70
ret = []
71
+ # There could be dups in the output from ls-files if, for example, there are
72
+ # files in conflict.
60
73
for f_out in common .remove_dups (out .splitlines (), lambda x : x [2 :]):
61
74
if f_out [0 ] == 'h' :
62
75
ret .append (f_out [2 :])
@@ -71,53 +84,101 @@ def of_repo():
71
84
status is the status of the file (TRACKED_UNMODIFIED, TRACKED_MODIFIED,
72
85
UNTRACKED, ASSUME_UNCHANGED, STAGED, etc -- see above).
73
86
"""
74
- unused_ok , out , unused_err = common . git_call ( 'ls-files -tvco' )
87
+ return _status_cwd ( )
75
88
76
- for f_out in common .remove_dups (out .splitlines (), lambda x : x [2 :]):
77
- # output is 'S filename' where S is a character representing the status of
78
- # the file.
79
- fp = f_out [2 :]
80
- yield (_status_from_output (f_out [0 ], fp ), fp )
81
-
82
-
83
- def _status_from_output (s , fp ):
84
- if s == '?' :
85
- # We need to see if it is an ignored file.
86
- out , unused_err = common .safe_git_call ('status --porcelain "%s"' % fp )
87
- if not len (out ):
88
- return IGNORED
89
- return UNTRACKED
90
- elif s == 'h' :
91
- return ASSUME_UNCHANGED if os .path .exists (fp ) else DELETED_ASSUME_UNCHANGED
92
- elif s == 'H' :
93
- # We need to use status --porcelain to figure out whether it's deleted,
94
- # modified or not.
95
- out , unused_err = common .safe_git_call ('status --porcelain "%s"' % fp )
96
- if not len (out ):
89
+
90
+ # Private functions.
91
+
92
+
93
+ def _status_cwd ():
94
+ status_codes = _status_porcelain (os .getcwd ())
95
+ au_fps = set (au_files (relative_to_cwd = True ))
96
+ for au_fp in au_fps :
97
+ if au_fp not in status_codes :
98
+ status_codes [au_fp ] = None
99
+ all_fps_under_cwd = common .get_all_fps_under_cwd ()
100
+ for fp_under_cwd in all_fps_under_cwd :
101
+ if fp_under_cwd not in status_codes :
102
+ status_codes [fp_under_cwd ] = None
103
+ for s_fp , s in status_codes .iteritems ():
104
+ status = _status_from_output (s , s_fp in au_fps , s_fp )
105
+ yield (status , s_fp )
106
+
107
+
108
+ def _status_file (fp ):
109
+ s = _status_porcelain (fp ).get (fp , None )
110
+ return _status_from_output (s , _is_au_file (fp ), fp )
111
+
112
+
113
+ def _is_au_file (fp ):
114
+ """True if the given fp corresponds to an assume unchanged file.
115
+
116
+ Args:
117
+ fp: the filepath to check (fp must be a file not a dir).
118
+ """
119
+ out , unused_err = common .safe_git_call (
120
+ 'ls-files -v --full-name "{}"' .format (fp ))
121
+ ret = False
122
+ if out :
123
+ f_out = common .remove_dups (out .splitlines (), lambda x : x [2 :])
124
+ if len (f_out ) != 1 :
125
+ raise Exception ('Unexpected output of ls-files: {}' .format (out ))
126
+ ret = f_out [0 ][0 ] == 'h'
127
+ return ret
128
+
129
+
130
+ def _status_porcelain (pathspec ):
131
+ """Executes the status porcelain command with the given pathspec.
132
+
133
+ Ignored and untracked files are reported.
134
+
135
+ Returns:
136
+ A dict of fp -> status code. All fps are relative to the cwd.
137
+ """
138
+ def sanitize_fp (unsanitized_fp ):
139
+ ret = unsanitized_fp .strip ()
140
+ if ret .startswith ('"' ) and ret .endswith ('"' ):
141
+ ret = ret [1 :- 1 ]
142
+ # The paths outputted by status are relative to the repodir, we need to make
143
+ # them relative to the cwd.
144
+ ret = os .path .relpath (
145
+ os .path .join (common .repo_dir (), ret ), os .getcwd ())
146
+ return ret
147
+
148
+ out_status , unused_err = common .safe_git_call (
149
+ 'status --porcelain -u --ignored "{}"' .format (pathspec ))
150
+ ret = {}
151
+ for f_out_status in out_status .splitlines ():
152
+ # Output is in the form <status> <file path>.
153
+ # <status> is 2 chars long.
154
+ ret [sanitize_fp (f_out_status [3 :])] = f_out_status [:2 ]
155
+ return ret
156
+
157
+
158
+ def _status_from_output (s , is_au , fp ):
159
+ if not s :
160
+ if is_au :
161
+ if not os .path .exists (fp ):
162
+ return DELETED_ASSUME_UNCHANGED
163
+ return ASSUME_UNCHANGED
164
+ else :
97
165
return TRACKED_UNMODIFIED
98
- # Output is in the form <status> <name>. We are only interested in the
99
- # status part.
100
- s = out .strip ().split ()[0 ]
101
- if s == 'M' :
102
- return TRACKED_MODIFIED
103
- elif s == 'A' :
104
- # It could be ignored and staged.
105
- out , unused_err = common .safe_git_call (
106
- 'ls-files -ic --exclude-standard "%s"' % fp )
107
- if len (out ):
108
- return IGNORED_STAGED
109
- return STAGED
110
- elif s == 'D' :
111
- return DELETED
112
- elif s == 'AD' :
113
- return DELETED_STAGED
114
- elif s == 'MM' :
115
- return MODIFIED_MODIFIED
116
- elif s == 'AM' :
117
- return ADDED_MODIFIED
118
- raise Exception (
119
- "Failed to get status of file %s, out %s, status %s" % (fp , out , s ))
120
- elif s == 'M' :
166
+ if s == '??' :
167
+ return UNTRACKED
168
+ elif s == '!!' :
169
+ return IGNORED
170
+ elif s == ' M' :
171
+ return TRACKED_MODIFIED
172
+ elif s == 'A ' :
173
+ return STAGED
174
+ elif s == ' D' :
175
+ return DELETED
176
+ elif s == 'AD' :
177
+ return DELETED_STAGED
178
+ elif s == 'MM' :
179
+ return MODIFIED_MODIFIED
180
+ elif s == 'AM' :
181
+ return ADDED_MODIFIED
182
+ elif s == 'AA' or s == 'M ' or s == 'DD' or 'U' in s :
121
183
return IN_CONFLICT
122
-
123
- raise Exception ("Failed to get status of file %s, status %s" % (fp , s ))
184
+ raise Exception ('Failed to get status of file {}, s is "{}"' .format (fp , s ))
0 commit comments