#!/bin/bash # # Copyright (c) 2007 Jean-Francois Richard # (c) 2008 Mauricio Fernandez # # 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 # . 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 ~ # 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-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 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 " echo echo " can be:" echo " help" echo " init" echo " commit" echo " eat " echo " show as of " echo " ls-changed-files" echo " ls-new-files" echo " ls-ignored-files" echo " ls-newly-ignored-files" echo " ls-stored-files [as of ]" echo " archive-to " echo " extract-archive-to " echo " rm-all" echo " rm-older-than " echo echo " 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: