forked from jaylevitt/gibak
/
gibak
executable file
·514 lines (448 loc) · 13.8 KB
/
gibak
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
#!/bin/bash
#
# Copyright (c) 2007 Jean-Francois Richard <jean-francois@richard.name>
# (c) 2008 Mauricio Fernandez <mfp@acm.org>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see
# <http://www.gnu.org/licenses/>.
version="0.3.0"
if ! which git &>/dev/null; then
echo "Please Git" >&2
echo "See http://git.or.cz for more information about Git" >&2
exit 1
fi
#git libexec folder since the git-* commands are no longer in PATH
GITDIR="$(git --exec-path)"
# Autotools defines
MANDIR="/usr/local/share/man"
SCRIPT_NAME=$(basename $0)
ORIG_DIR="$( pwd )"
# Remember that all actions here are to be made in $HOME dir!
cd ~
# We don't want users complaining. by default, top permissions
# "security"
umask 077
# We don't check for just '.git', as the user might have
# mounted/linked .git from somewhere else. Failing on just '-e
# .git' would make it impossible to have the first commit save its
# data to a remote mount. (Since .git has to exist, as a mountpoint)
function __has_initialized(){
test -e .git/objects;
return $?
}
function __abort_on_initialized() {
if __has_initialized; then
echo "You already have git data in your home directory." >&2
echo "Please use '$SCRIPT_NAME rm-all' if you wish to *delete* it." >&2
exit 1
fi
}
function __abort_on_not_initialized() {
if ! __has_initialized; then
echo "You probably did not initialize your home history repository" >&2
echo "Please use '$SCRIPT_NAME init' to initialize it" >&2
exit 1
fi
# further tests
source $GITDIR/git-sh-setup
require_work_tree
}
function __abort_on_no_gpg() {
if ! which gpg >/dev/null; then
echo "You need to install GPG to use this feature" >&2
exit 1
fi
}
function __handle_git_repositories() {
__abort_on_not_initialized
cd_to_toplevel
echo "Managing submodules." >&2
rm -f .gitmodules
local base=.git/git-repositories
mkdir -p $base
find-git-repos -i -z | while read -d $'\0' rep; do
echo " submodule $rep" >&2
rsync -a -F --relative --delete-excluded --delete-after \
"$rep" "$base"
printf '[submodule "%s"]\n\tpath = %s\n\turl= %s\n' \
"$rep" "$rep" "$base/$rep/.git" >> .gitmodules
done
touch .gitmodules
git add -f .gitmodules
git submodule init
}
function init() {
__abort_on_initialized
# I know it's the default... but let's be explicit!
git --shared=umask init
chmod -R u+rwX,go-rwx .git
# Add a gitweb description
echo "git-home-history of $HOME on $(hostname)" > .git/description
# Make sure there is a 'name' field in config... some git scripts
# complain if not
if test "$(getent passwd 2>&1 | grep $USER | cut -d: -f5)" = ""; then
git config user.name "$USER"
fi
# Check for xdg-user-dirs to provide sane defaults in .gitignore
# http://www.freedesktop.org/wiki/Software/xdg-user-dirs
test -f ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs && source ${XDG_CONFIG_HOME:-~/.config}/user-dirs.dirs
XDG_DOWNLOAD_DIR=$( echo ${XDG_DOWNLOAD_DIR#${HOME}} | sed 's:^/*::' )
XDG_VIDEOS_DIR=$( echo ${XDG_VIDEOS_DIR#${HOME}} | sed 's:^/*::' )
XDG_MUSIC_DIR=$( echo ${XDG_MUSIC_DIR#${HOME}} | sed 's:^/*::' )
cat << EOF > .git/hooks/pre-commit
#!/bin/sh
ometastore -x -s -i --sort
git add -f .ometastore
EOF
cat << EOF > .git/hooks/post-checkout
#!/bin/sh
ometastore -v -x -a -i
EOF
test -e .gitignore || cat << EOF > .gitignore
#
# Here are some examples of what you might want to ignore
# in your git-home-history. Feel free to modify.
#
# Example rules start with '##'.
# You can remove the '##' to set the rule.
#
# The rules are read from top to bottom, so a rule can
# "cancel" out a previous one. Be careful.
#
# For more information on the syntax used in this file,
# see "man gitignore" in a terminal or visit
# http://www.kernel.org/pub/software/scm/git/docs/gitignore.html
##/${XDG_DOWNLOAD_DIR:-Download}
##/${XDG_VIDEOS_DIR:-Videos}
##/${XDG_MUSIC_DIR:-Music}
# Notice the '!' below. This tells git to _not_ ignore a file or
# directory, in this case, we do not want to ignore a particular
# directory under Music/, which is ignored according to the rule
# above.
##!/${XDG_MUSIC_DIR:-Music}/Our_Daughter--Flute
# You probably want to ignore all the "dot" files in your home
# directory, since they mostly contain local application state data.
##/.*
# but... some dot files you probably do *not* want ignored are
# listed here:
##!/.bash*
##!/.tcsh*
##!/.zsh*
##!/.emacs
##!/.gnupg
##!/.mail
##!/.maildir
##!/.Maildir
##!/.mail-aliases
##!/.muttrc
##!/.ssh
##!/.vimrc
# We do not want to track the tracking of other files:
#.svn
#CVS
# Please note that all files in a Git project are ignored
# e.g. linux-2.6/ will be entirely ignored if linux-2.6/.git exists.
# Thus it is not necessary to add ".git" here.
# Some editors use some special backup file formats. Ignore them:
##.#*
##*~
EOF
git add -f .gitignore
git commit -q -a -m"Initialized by $SCRIPT_NAME"
chmod u+x .git/hooks/pre-commit
chmod u+x .git/hooks/post-checkout
echo
echo "You might be interested in tweaking the ~/.gitignore file"
echo
echo "Please run '$SCRIPT_NAME commit' to save a first state in your history"
}
function rm_all() {
if __has_initialized; then
echo "Removing all git-home-history data, this may take some time "
# We don't remove '.git', as the user might have
# mounted/linked .git from somewhere else. Removing the link
# would silently break what the user was expecting; removing a
# device mountpoint will fail anyway.
rm -rf .git/* 2>/dev/null
#rm -rf .gitignore 2>/dev/null
else
echo "Nothing to do"
fi
}
function commit() {
__abort_on_not_initialized
local what=$@
local modifier=
echo "Committing to repository, this may take a long time" >&2
if git ls-files --modified --others --exclude-standard | egrep -q '(^|/).gitignore$'; then
echo "Some .gitignore added or modified, determining newly ignored files." >&2
# I don't like myself for using such a sloppy way of
# removing previously-tracked newly-ignored files...
# :( help me! ahhhhrrrggg
# seems like the only way to stop tracking newly-ignored files
#git rm --cached -r -f . >/dev/null
# better way, using ometastore
ometastore -d -i -z | \
xargs -0 git rm --cached -r -f --ignore-unmatch -- 2>/dev/null
fi
echo "Adding new and modified files." >&2
git add -v . \
|| die "Could not complete addition of files to history store!"
__handle_git_repositories
test -n "$what" || modifier="-a"
echo "Committing." >&2
git commit $modifier -m"Committed on $( date +"%a, %d %b %Y %H:%M:%S %z" )" -- $@
echo "Optimizing and compacting repository (might take a while)." >&2
git gc --auto || git gc # the --auto is on newer versions
}
function rm_older_than() {
__abort_on_not_initialized
local time_spec=$@
if ! which git filter-branch &>/dev/null; then
echo "Please install a recent version of Git" >&2
echo "See http://git.or.cz for more information about Git" >&2
exit 1
fi
if test $(git rev-parse "HEAD@{$time_spec}") = "$(git rev-parse HEAD)"; then
local TMOUT=20
echo "You are about to remove *all* commits made before the very last one you made"
echo "Press enter or wait 20 seconds to confirm. Abort with CRTL-C."
read
fi
# Something like that
git filter-branch --parent-filter \
'test $(git rev-parse "HEAD@{$time_spec}") = "$GIT_COMMIT" || cat ' \
HEAD
if test "$?" != "0"; then
die "Please make sure you did '$SCRIPT_NAME commit' before removing old files."
fi
# See git mailing list
# "Trying to use git filter-branch to compress history by removing
# large, obsolete binary files"
git reset --soft # was '--hard' on the post...
rm -rf .git/refs/original/
#vi .git/packed-refs # Use vi to remove the line referring to
# refs/original... No need since we have linear, no tags, nothing
# special
git reflog expire --all --expire-unreachable=0
echo "Committing the removal action"
# Make sure we are able to tell in a commit that on this
# date, a cleanup was made
local removal_date=$( date +"%a, %d %b %Y %H:%M:%S %z" )
local witness_file=.git-home-history-last-removal
local msg="Removed older than '$time_spec' on $removal_date"
echo "$msg" > ${witness_file}
git add $witness_file
git commit -m"$msg" $witness_file
# Finally make sure everything is ok, and remove old stuff
git gc --prune
}
function show() {
__abort_on_not_initialized
local file_to_restore=$1
shift
shift # 'as'
shift # 'of'
local time_spec=$@
echo "Showing: $HOME/$file_to_restore" >&2
GIT_PAGER=cat git show "HEAD@{'$time_spec'}:$file_to_restore"
}
function archive_to() {
__abort_on_not_initialized
__abort_on_no_gpg
local output="$1"
if test "$output" != "-"; then
output="$ORIG_DIR/${1%.git.tar.gpg}.git.tar.gpg"
fi
echo "Saving archive to '$output'" >&2
echo "This may take a long time" >&2
tar -cpf - .git | gpg -c --output "$output"
}
function extract_archive_to() {
__abort_on_no_gpg
local archive="${1%.git}.git"
# Use the path from where the command was run: ORIG_DIR
pushd "$ORIG_DIR" >/dev/null
if test -e "$archive"; then
echo "$(basename $archive) already exists, please move it away" >&2
exit 1
fi
echo "Extracting archive, this may take a long time" >&2
mkdir "$archive" >&2
# We only specify .git to unpack. Else a user could well unpack
# something else and trash his files
gpg -d - | tar --strip-components 1 -C"$archive" -xpf - .git
echo
echo "You can check what files are inside the newly unpacked directory with"
echo "GIT_DIR=\"$archive\" gitk"
echo "GIT_DIR=\"$archive\" git-home-history ls-stored-files"
popd >/dev/null
}
function ls_stored_files() {
__abort_on_not_initialized
if test "$#" -gt "2"; then
shift # as
shift # of
local time_spec="$@"
git ls-tree -r --name-only "HEAD@{$time_spec}"
else
git ls-tree -r --name-only HEAD
fi
}
function eat() {
__abort_on_not_initialized
git add -v -- $@ || die "Could not add '$@' to the history store"
commit $@
git rm -r -f -- $@ || die "Problem removing '$@'"
rm -rf -- $@ # some empty dirs may remain. clean'em up
commit $@
}
case "$1" in
private--init-and-commit)
if ! __has_initialized ; then
init
fi
commit
;;
init)
init
;;
archive-to)
# output can be stdout, using '-' or a file
shift
archive_to $@
;;
extract-archive-to)
# output can only be a file
shift
if test "$#" -gt "1"; then
echo "Please specify one output archive file" >&2
echo "e.g. cat archive | $SCRIPT_NAME extract-archive-to machineA.git" >&2
exit 1
fi
extract_archive_to "$1"
;;
show)
shift
if test "$#" -lt "4"; then
echo "Please specify what to show from the history store, as of when" >&2
echo "e.g. $SCRIPT_NAME show 'myfile.txt' as of 2 days ago" >&2
exit 1
fi
show $@
;;
commit)
commit
;;
eat)
if test "$ORIG_DIR" != "$HOME"; then
echo "Please run this from your home directory" >&2
echo "cd $HOME" >&2
exit 1
fi
shift
if test "$#" -lt "1"; then
echo "Please specify what file or directory to eat" >&2
echo "e.g. $SCRIPT_NAME eat 'myfile.txt'" >&2
exit 1
fi
eat $@
;;
rm-older-than)
shift
if test "$#" = "0"; then
echo "Please add a time specification" >&2
echo "e.g. '1 year ago' or '1979-02-26 18:30:00'" >&2
exit 1
fi
rm_older_than $@
;;
rm-all)
rm_all
;;
ls-changed-files)
echo "These files have been modified:" >&2
git ls-files --exclude-standard --modified
echo "Use '$SCRIPT_NAME commit' to store them" >&2
;;
ls-new-files)
echo "These files are not yet stored:" >&2
git ls-files --exclude-standard --others
echo "Use '$SCRIPT_NAME commit' to store them" >&2
;;
ls-ignored-files)
echo "These files are ignored:" >&2
git ls-files --exclude-standard --ignored --others --directory
;;
ls-newly-ignored-files)
echo "These files have been ignored since the last commit:" >&2
if git ls-files --modified --others --exclude-standard | egrep -q '(^|/).gitignore$'; then
ometastore -d -i | sort
fi
;;
ls-stored-files)
shift
ls_stored_files $@
;;
--help|-h|help)
manfile="${SCRIPT_NAME}.1"
found=
for t in "$MANDIR/man1/$manfile" "$ORIG_DIR/gibak.1"; do
if test -e "$t"; then
man $t
found=1
break
fi
done
if test -z "$found"; then
man gibak
if test "$?" != "0"; then
echo "Could not find manpage" >&2
fi
fi
;;
--version)
echo "gibak version $version"
exit 0
;;
*)
echo "usage: $SCRIPT_NAME <action>"
echo
echo "<action> can be:"
echo " help"
echo " init"
echo " commit"
echo " eat <file_or_dir>"
echo " show <file> as of <time_spec>"
echo " ls-changed-files"
echo " ls-new-files"
echo " ls-ignored-files"
echo " ls-newly-ignored-files"
echo " ls-stored-files [as of <time_spec>]"
echo " archive-to <file>"
echo " extract-archive-to <dir>"
echo " rm-all"
echo " rm-older-than <time_spec>"
echo
echo "<time_spec> examples:"
echo " 5 days ago"
echo " 2 days 2 hours 3 seconds ago"
echo " 1979-02-26 18:30:00"
echo ""
echo "see 'man gibak' for more information"
exit 1
;;
esac
exit 0
# vim: set noexpandtab sw=4: