installed_progs.t: Python checks stdout too, 150 ok
[sunny256-utils.git] / git-delete-old-branches
blob9d6a4eca74026fff51ed50bb44ff9ce70e55dd9e
1 #!/usr/bin/env bash
3 #=======================================================================
4 # git-delete-old-branches
5 # File ID: b2a8aec0-3649-11e6-912b-02010e0a6634
7 # Delete obsolete Git branches after displaying some info about them
9 # Author: Øyvind A. Holm <sunny@sunbase.org>
10 # License: GNU General Public License version 2 or later.
11 #=======================================================================
13 progname=git-delete-old-branches
14 VERSION=0.7.0
16 ARGS="$(getopt -o "\
17 d:\
23 " -l "\
24 dbname:,\
25 help,\
26 load,\
27 once,\
28 quiet,\
29 verbose,\
30 version,\
31 " -n "$progname" -- "$@")"
32 test "$?" = "0" || exit 1
33 eval set -- "$ARGS"
35 opt_dbname=''
36 opt_help=0
37 opt_load=0
38 opt_once=0
39 opt_quiet=0
40 opt_verbose=0
41 while :; do
42 case "$1" in
43 -d|--dbname) opt_dbname="$2"; shift 2 ;;
44 -h|--help) opt_help=1; shift ;;
45 -l|--load) opt_load=1; shift ;;
46 -o|--once) opt_once=1; shift ;;
47 -q|--quiet) opt_quiet=$(($opt_quiet + 1)); shift ;;
48 -v|--verbose) opt_verbose=$(($opt_verbose + 1)); shift ;;
49 --version) echo $progname $VERSION; exit 0 ;;
50 --) shift; break ;;
51 *) echo $progname: Internal error >&2; exit 1 ;;
52 esac
53 done
54 opt_verbose=$(($opt_verbose - $opt_quiet))
56 if test "$opt_help" = "1"; then
57 test $opt_verbose -gt 0 && { echo; echo $progname $VERSION; }
58 cat <<END
60 Delete obsolete Git branches locally and from all remotes. For each
61 branch, display a "git diff --stat" against all local and remote
62 branches with this name, a simplified --graph log, and finally a git log
63 with patch against all branches. Display this in less(1), and ask if all
64 branches with this name should be deleted.
65 The following responses are valid:
67 "y" - Delete all branches with this name locally and from all remotes
68 and mark it as garbage.
69 "n" - Don't delete the branch. If -o/--once is used later, files marked
70 with "n" will not be checked.
71 "d" - Mark branch as "don't know", to be perused later.
72 "q" - Exit the program.
74 Usage: $progname [options] BRANCH [BRANCH [...]]
76 Options:
78 -d FILE, --dbname FILE
79 Store answers in the SQLite database FILE. Can be used to automate
80 deletions later.
81 -h, --help
82 Show this help.
83 -l, --load
84 Store the specified branches in the "refs" database table along with
85 the commit date. This makes it possible to sort the branches by age.
86 Must be used together with the -d/--dbname option. If -v is
87 specified, progress is shown while the branches are stored.
88 -o, --once
89 Don't ask about branches that have been answered "n" to earlier.
90 -q, --quiet
91 Be more quiet. Can be repeated to increase silence.
92 -v, --verbose
93 Increase level of verbosity. Can be repeated.
94 --version
95 Print version information.
97 END
98 exit 0
101 db="$opt_dbname"
102 dbversion=1
104 listbr() {
105 git br -a | rmspcall | grep -E "(^|/)$1\$"
108 sql() {
109 test -z "$db" && return
110 echo "$@" | sqlite3 "$db"
113 add_entry() {
114 sql "
115 INSERT OR REPLACE
116 INTO branches (yn, branch, date)
117 VALUES('$1', '$2', datetime('now'));
121 sql "
122 BEGIN TRANSACTION;
123 CREATE TABLE IF NOT EXISTS branches (
124 yn TEXT
125 CONSTRAINT branches_yn_contents
126 CHECK (yn = 'y' OR yn = 'n' OR yn = 'd' OR yn IS NULL),
127 branch TEXT
128 CONSTRAINT branches_branch_length
129 CHECK (length(branch) > 0)
130 UNIQUE
131 NOT NULL,
132 date TEXT
133 DEFAULT (datetime('now'))
134 CONSTRAINT branches_date_length
135 CHECK (date IS NULL OR length(date) = 19)
136 CONSTRAINT branches_date_valid
137 CHECK (date IS NULL OR datetime(date) IS NOT NULL)
139 CREATE TABLE IF NOT EXISTS meta (
140 key TEXT
141 CONSTRAINT meta_key_length
142 CHECK (length(key) > 0)
143 UNIQUE
144 NOT NULL,
145 value TEXT
147 INSERT OR IGNORE INTO meta (key, value)
148 VALUES('program', 'git-delete-old-branches');
149 INSERT OR IGNORE INTO meta (key, value)
150 VALUES('url',
151 'https://gitlab.com/oyvholm/utils/blob/master/git-delete-old-branches');
152 INSERT OR IGNORE INTO meta (key, value)
153 VALUES('dbversion', '$dbversion');
154 CREATE TABLE IF NOT EXISTS refs (
155 date TEXT
156 CONSTRAINT refs_date_length
157 CHECK (date IS NULL OR length(date) = 19)
158 CONSTRAINT refs_date_valid
159 CHECK (date IS NULL OR datetime(date) IS NOT NULL),
160 branch TEXT
161 CONSTRAINT refs_branch_length
162 CHECK (length(branch) > 0)
163 UNIQUE
164 NOT NULL
166 COMMIT;
169 if test "$opt_load" = "1" -a -z "$opt_dbname"; then
170 echo $progname: -l/--load doesn\'t work without -d/--dbname >&2
171 exit 1
174 currversion=$(sql "SELECT value FROM meta WHERE key = 'dbversion';")
175 if test -n "$currversion"; then
176 if test $currversion -gt $dbversion; then
177 (echo $progname: Database version of $db is too new.
178 echo $progname: The db version is $currversion, and this program can
179 echo $progname: only handle databases up to version $dbversion.) >&2
180 exit 1
184 if test "$opt_load" = "1"; then
185 test $opt_verbose -ge 1 && tput sc >&2
186 for f in "$@"; do
187 s="$(echo "$f" | sed "s/'/''/g")"
188 if test $opt_verbose -ge 1; then
189 tput rc >&2
190 echo -n "$f " >&2
191 tput el >&2
193 GIT_PAGER=cat git log -1 "$f" \
194 --format=tformat:"INSERT INTO refs (date, branch) VALUES (datetime('%cI'), '$s');"
195 done | sqlite3 "$db"
196 test $opt_verbose -ge 1 && tput rc el >&2
197 exit
200 unset LESS
202 for f in "$@"; do
203 s="$(echo "$f" | sed "s/'/''/g")"
204 if test -n "$db" -a "$opt_once" = "1"; then
205 sql "SELECT branch FROM branches WHERE branch = '$s' AND yn = 'n';" |
206 grep -q . && continue
208 if test $opt_verbose -ge 1; then
209 echo
210 echo $progname: Check \"$f\" branches
211 listbr "$f"
213 listbr "$f" | grep -q . || continue
214 clear
216 echo $f
217 echo
218 git diff --stat $(listbr "$f" | perl -pe 's/^/.../;')
219 echo
220 git log -C -C -M -M --abbrev-commit --decorate=short --format=oneline \
221 --graph $(
222 listbr "$f" | perl -pe 's/^/../;'
224 echo
225 git log -C -C -M -M --date-order --date=iso --decorate=short \
226 --format=fuller --graph -c -p $(listbr "$f" | perl -pe 's/^/../;')
227 ) | less -S
228 echo -n "Delete all '$f' branches? " >&2
229 unset valg
230 read valg </dev/tty
231 if test "$valg" = "y"; then
232 git dbr $(listbr "$f")
233 add_entry y "$s"
234 elif test "$valg" = "n"; then
235 add_entry n "$s"
236 elif test "$valg" = "d"; then
237 add_entry d "$s"
238 elif test "$valg" = "q"; then
239 exit 0
241 done