Test commit
[cogito/jonas.git] / cg-status
blobe3bd08158406563d6b284af367418499c2766458
1 #!/usr/bin/env bash
3 # Show status of the repository and your working tree
4 # Copyright (c) Petr Baudis, 2005
5 # Copyright (c) Pavel Roskin 2005
7 # The output includes the list of branches and merge status.
8 # Current branch is marked with ">", remote branches are marked with "R".
9 # Branches with shelved local changes (currently produced only by
10 # `cg-switch -l`) are marked with "s".
12 # Then, the files in the working tree are printed out. The output has
13 # the following format:
15 # <status flag> <file>
17 # where '<status flag>' can be one of the following:
19 # ?::
20 # '<file>' is not known to Cogito. See the IGNORING section below.
21 # A::
22 # '<file>' has been added.
23 # D::
24 # '<file>' has been deleted.
25 # !::
26 # '<file>' is gone from your working copy but not deleted by `cg-rm`.
27 # M::
28 # '<file>' has been touched or modified.
29 # m::
30 # '<file>' has been touched or modified, but will not be automatically
31 # committed the next time you call `cg-commit`. This is used during a
32 # merge to mark files which contained local changes before the merge.
34 # OPTIONS
35 # -------
36 # If neither -g or -w is passed, both is shown; otherwise, only the
37 # corresponding parts are shown.
39 # -g:: Show the GIT repository information
40 # Show the GIT repository information.
42 # -n:: Do not show status flags
43 # Do not show status flags. This is probably useful only when you filter
44 # the flags for a single specific flag using the '-s' option.
46 # -s STATUS:: Limit to files matching the STATUS flags
47 # Show only files with the given status flag, e.g. '-s D'. You can list
48 # multiple flags ('-s MmA') to filter for all of them. You can prepend
49 # '^' to the STATUS argument to invert the filter - only items with flags
50 # NOT listed in the STATUS string will be printed out.
52 # -S:: Do not squash directories
53 # By default, cg-status will not list full contents of untracked
54 # directories but only their name. This option will make it show the
55 # all untracked files inside as well.
57 # -w:: Show working files
58 # Show the working tree file list.
60 # -x:: Disable file exclusion
61 # Don't exclude any files from listing.
63 # DIRPATH::
64 # Path to the directory to use as the base for the working tree
65 # file list (instead of the current directory).
67 # NOTES
68 # -----
69 # If a file has been removed with `cg-rm` without using the `-f` option
70 # to remove it physically from the tree it will be reported as both being
71 # deleted and unknown. The reason for this is that the file is internally
72 # marked as deleted and thus also untracked. After next commit it will only
73 # be reported as being untracked.
75 # IGNORING
76 # --------
77 # You can declare some files to be ignored: this means that they will
78 # not be listed as unknown in `cg-status`, `cg-clean` will not remove
79 # them (unless '-x' is passed), and `cg-init` and `cg-add -a` will
80 # not add them. However, the moment you explicitly tell Cogito about
81 # them using `cg-add`, Cogito will stop ignoring them; it will commit
82 # any modifications in them, etc.: the concept is the same as e.g. in CVS.
83 # Typically, autogenerated and backup files are marked as ignored.
85 # Which files to ignore is determined by lists of exclude patterns
86 # stored in various files. There is one pattern per line and the
87 # patterns are classic shell glob patterns (with '*' and '?' wildcards).
88 # The pattern can be prefixed by '!' to unignore matching files.
89 # If the pattern does not contain a slash, it is applied in all
90 # directories; otherwise, only to the given path in the tree; use
91 # leading slash to denote the tree root.
93 # For example, consider the following:
95 # -------------------------------------------------------------------
96 # .*
97 # !.gitignore
98 # !/.list
99 # -------------------------------------------------------------------
101 # This will ignore all hidden files except '.gitignore' in all
102 # directories and the '.list' file in project root.
104 # When collecting the ignore patterns, first the default ignore
105 # patterns are loaded from '/usr/share/cogito/default-exclude'
106 # (or a slightly different path depending on your installation prefix).
107 # Then, '.git/info/exclude' from your working copy is loaded. At last,
108 # during the actual tree traversal '.gitignore' in each visited directory
109 # is loaded for the time of its traversal.
111 # FILES
112 # -----
113 # $GIT_DIR/info/exclude::
114 # The list of ignore patterns; the list itself is not version-tracked
115 # and is local to this particular clone.
117 # '.gitignore'::
118 # '.gitignore' in the working tree will be also scanned for ignore
119 # patterns. Contrary to the exclude file, it is usually version-tracked.
121 # BUGS
122 # ----
123 # One known bug is that when you `cg-add` a new file and then delete it
124 # but do not call `cg-rm`, it will not be listed in `cg-status` output,
125 # but from the merging point of view there will still be "local changes"
126 # and `cg-diff` will show a diff.
128 # Testsuite: Marginal (part of t9202-merge-on-dirty)
130 USAGE="cg-status [-g] [[-n] -s STATUS] [-w] [-x] [DIRPATH]"
131 _git_wc_unneeded=1
133 . "${COGITO_LIB}"cg-Xlib || exit 1
136 should_show_flag() {
137 [[ -z "$flagfilter" || "$flagfilter" == *"$1"* ]]
141 gitstatus=
142 workstatus=
143 exclude=yes
144 flagfilter=
145 noflags=
146 squashdirs=squashdirs
147 while optparse; do
148 if optparse -g; then
149 gitstatus=1
150 elif optparse -n; then
151 noflags=1
152 elif optparse -s=; then
153 flagfilter="$OPTARG"
154 echo "$flagfilter" | grep -qx '[a-zA-Z?!^]*' \
155 || die "invalid -s status flag"
156 if [ x"${flagfilter:0:1}" = x"^" ]; then
157 flagfilter="$(echo "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ?!" | tr -d "${flagfilter:1}")"
159 elif optparse -S; then
160 squashdirs=nosquashdirs
161 elif optparse -w; then
162 workstatus=1
163 elif optparse -x; then
164 exclude=no
165 else
166 optfail
168 done
169 if [ ! "$gitstatus" ] && [ ! "$workstatus" ]; then
170 gitstatus=1
171 workstatus=1
173 if [ "$_git_no_wc" ]; then
174 workstatus=
178 mstatedir="$_git/cg-merge-state"
181 if [ "$gitstatus" ]; then
182 [ -s "$_git/branch-name" ] && echo "Branch (informal): $(cat "$_git/branch-name")"
184 if [ -s "$_git/head-name" ]; then
185 headsha1=$(cat "$_git/$(git-symbolic-ref HEAD)")
186 echo "Seeked from head: $(cat "$_git/head-name")"
187 echo "Seeked at commit: $headsha1"
188 echo
191 # -name * will prevent listing hidden heads
192 maxlen="$(git-for-each-ref --format="%(refname)" refs/heads | column_width "refs/heads/")"
193 maxlen=$((maxlen + 1))
194 maxloglen=$(tput cols)
195 # 5 status flags + 2 times separating spaces = 7
196 maxloglen=$((maxloglen - maxlen - 7))
198 #maxsha1len=$((maxloglen - 40))
199 #[ $maxsha1len -lt 12 ] && maxsha1len=12
200 #[ $maxsha1len -gt 40 ] && maxsha1len=40
201 maxsha1len=12
203 sha1digits="$maxsha1len"
204 if [ $maxsha1len -lt 40 ]; then
205 trimmark="~"
206 maxsha1len=$((maxsha1len+1))
207 else
208 trimmark=
210 maxloglen=$((maxloglen - maxsha1len))
212 [ $maxlen -gt 1 ] && echo "Heads:"
214 git-for-each-ref --format="%(refname) %(objectname) %(subject)" refs/heads |
215 while read head sha1 headlog; do
216 headname="${head#refs/heads/}"
217 headsha1="${sha1:0:$sha1digits}"
218 [ "$headname" = "cg-seek-point" ] && continue
219 cf=" "; rf=" "; sf=" ";
220 [ "$headname" = "$_git_head" ] && cf=">"
221 [ -s "$_git/branches/$headname" ] && rf="R"
222 # ..cg-shelve deprecated as of 2006-11-19
223 exists_ref "refs/heads/.cg-shelve-$headname" && sf="s"
224 exists_ref "refs/shelves/$headname" && sf="s"
225 columns_print " $sf$rf$cf" - "$headname" $maxlen "$headsha1$trimmark" $((maxsha1len + 1)) "$headlog" m$maxloglen
226 done
228 if [ -s "$mstatedir/merging" ]; then
229 tmp="$(cat "$mstatedir/merging")"
230 echo
231 echo "Merging: $(cat "$mstatedir/merging") ($(cat "$mstatedir/merging-sym"))"
232 echo "Merge base: $(cat "$mstatedir/merge-base")"
233 [ -s "$mstatedir/squashing" ] && echo "Squash-merge."
235 if [ -s "$mstatedir/commit-ignore" ]; then
236 echo "Files not to be committed now (contained local changes before the merge):"
237 sed 's/^/ /' "$mstatedir/commit-ignore"
240 if [ -s "$_git/blocked" ]; then
241 echo
242 echo "Changes recording BLOCKED:"
243 sed 's/^/ /' "$_git/blocked"
246 if [ ! -s "$_git/$(git-symbolic-ref HEAD)" ]; then
247 echo
248 echo "Before initial commit."
251 if [ "$_git_no_wc" ]; then
252 echo
253 echo "Repository without attached working copy."
259 if [ "$gitstatus" ] && [ "$workstatus" ]; then
260 echo
265 if [ "$workstatus" ]; then
266 git-update-index --refresh > /dev/null
268 basepath="$_git_relpath"
269 [ "${ARGS[0]}" ] && basepath="$(echo "$_git_relpath${ARGS[0]}" | normpath)"
271 should_show_flag '?' &&
272 list_untracked_files $exclude $squashdirs | tr '\0' '\n' |
273 if [ "$basepath" ]; then
274 while IFS=$'' read path; do
275 [ x"${path#$basepath}" != x"$path" ] &&
276 echo "${path#$_git_relpath}"
277 done
278 else
280 fi |
281 sed 's,^,? ,'
283 if [ ! -s "$_git/$(git-symbolic-ref HEAD)" ]; then
284 # Initial commit
285 should_show_flag 'A' && git-ls-files | sed 's,^,A ,'
286 else
287 commitignore=
288 [ -s "$mstatedir/commit-ignore" ] && commitignore=1
290 git-diff-index HEAD -- "${basepath:-.}" | cut -f5- -d' ' |
291 while IFS=$'\t' read -r mode file; do
292 if [ "$mode" = D ]; then
293 [ "$(git-diff-files -- "$file")" ] && continue # "!"
294 elif [ "$mode" = M ] && [ "$commitignore" ]; then
295 fgrep -qx "$file" "$mstatedir/commit-ignore" && mode=m
297 should_show_flag "$mode" && echo "$mode ${file#$_git_relpath}"
298 done
300 git-diff-files -- "${basepath:-.}" | cut -f5- -d' ' |
301 while IFS=$'\t' read -r mode file; do
302 if [ "$mode" = D ]; then
303 should_show_flag "!" && echo "! ${file#$_git_relpath}"
305 done
307 fi | if [ "$noflags" ]; then
308 sed 's/^. //'
309 else