Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
branch: master
Fetching contributors…

Cannot retrieve contributors at this time

executable file 514 lines (449 sloc) 14.098 kB
#!/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
# 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 $HOME
# 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 "$(git --exec-path)/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 init --shared=umask
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
if [[ "\$SKIP_OMETASTORE" == "" ]]; then
ometastore -x -s -i --sort
git add -f .ometastore
fi
EOF
cat << EOF > .git/hooks/post-checkout
#!/bin/sh
if [[ "\$SKIP_OMETASTORE" == "" ]]; then
ometastore -v -x -a -i
fi
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 --ignore-errors .
__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:
Jump to Line
Something went wrong with that request. Please try again.