Permalink
Browse files

initial commit

Signed-off-by: Simon Thum <simon.thum@gmx.de>
  • Loading branch information...
simonthum committed May 20, 2012
0 parents commit 673fd51fcb437f54c372516fbcf0beb9eeca1c26
Showing with 397 additions and 0 deletions.
  1. +101 −0 README.md
  2. +296 −0 git-sync
101 README.md
@@ -0,0 +1,101 @@
+# git-sync
+
+sychronize tracking repositories
+
+2012 by Simon Thum
+
+This scrips intends to sync near-automatically via git
+in "tracking" repositories where a nice history is not
+as crucial as having one.
+
+Licensed under CC0
+
+## One more git sync script? Seriously?
+
+Unlike the myriad of scripts to do just that already available,
+it follows the KISS principle: It is small, requires nothing but
+git and bash, but does not even try to shield you from git.
+
+It is ultimately intended for git-savy people. Tested on msysgit and a
+real bash. In case you know bash scripting, it will probably make your
+eyes bleed, but for some reason it works.
+
+### What does it do?
+
+It will likely get from you from a dull normal git repo with trivial
+changes to an updated dull normal git repo equal to origin. No more,
+no less.
+
+The intent is to do everything that's needed to sync
+automatically, and resort to manual intervention as soon
+as something non-trivial occurs. It is designed to be safe
+in that `git-sync` will likely refuse to do anything not known to
+be safe.
+
+### How am I supposed to use it?
+
+Just call `git-sync` inside your average joe's repository (not in the
+middle of a rebase, git-am, merge or whatever, not detached, no
+untracked files) and everything will likely just work. If you don't
+push in an intertwined manner, `git-sync` is virtually guaranteed to work.
+
+Care has been taken that any kind of problem, pre-existing or not,
+results in clear error messages and non-zero return code, but of
+course no guarantee can be given.
+
+## How does it work?
+
+The flow is roughly:
+1. sanity checks
+ You don't want to do this in the middle of a rebase
+2. Check for new files
+ exit if there are, unless allowed in config
+2. check for auto-commitable changes
+3. perform auto-commit
+ one more check for leftover changes / general tidyness
+4. fetch upstream
+5. Relate upstream to ours
+ ahead -> push
+ behind -> pull
+ diverged -> rebase, then push
+6. At exit, check sync state once more
+
+On the first invocation, `git-sync` will ask you to whitelist the
+current branch for sync using git config. This has to be done once for
+every copy.
+
+## Options
+
+There are two `git config`-based options for tailoring your sync:
+
+ branch.$branch_name.syncNewFiles (bool)
+
+Tells git-sync to invoke auto-commit even if new (untracked)
+files are present. Normally you have to commit those yourself.
+
+ branch.$branch_name.autocommitscript (string)
+
+A string which is being eval'ed by this script to perform an
+auto-commit. Here you can run a commit script which should not
+leave any uncommited state. The default will commit modified or
+all files with a more or less useful message.
+
+By default commit is done using:
+
+ git add -u ; git commit -m "changes from $(uname -n) on $(date)"
+
+Or if you enable `syncNewFiles`:
+
+ git add -A ; git commit -m \"changes from $(uname -n) on $(date)\";"
+
+# License
+
+I declare this work to be useable under the provisions of the CC0 license.
+
+http://creativecommons.org/publicdomain/zero/1.0/
+
+Attribution is appreciated, but not required.
+
+# Thanks
+
+Thanks go to all the people behind git.
296 git-sync
@@ -0,0 +1,296 @@
+#!/bin/bash
+#
+# git-sync
+#
+# sychronize tracking repositories
+#
+# 2012 by Simon Thum
+# Licensed as: CC0
+#
+# This scrips intends to sync via git near-automatically
+# in "tracking" repositories where a nice history is not
+# crucial as having one.
+#
+# Unlike the myriad of scripts to do just that already available,
+# it follows the KISS principle: It is small, requires nothing but
+# git and bash, but does not even try to shield you from git.
+#
+# It will likely get from you from a dull normal git repo with trivial
+# changes to an updated dull normal git repo equal to origin. No more,
+# no less. The intent is to do everything that's needed to sync
+# automatically, and resort to manual intervention as soon
+# as something non-trivial occurs. It is designed to be safe
+# in that it will likely refuse to do anything not known to
+# be safe.
+
+
+# command used to auto-commit file modifications
+DEFAULT_AUTOCOMMIT_CMD="git add -u ; git commit -m \"changes from $(uname -n) on $(date)\";"
+
+# command used to auto-commit all changes
+ALL_AUTOCOMMIT_CMD="git add -A ; git commit -m \"changes from $(uname -n) on $(date)\";"
+
+
+# AUTOCOMMIT_CMD="echo \"Please commit or stash pending changes\"; exit 1;"
+# TODO mode for stash push & pop
+
+
+
+#
+# utility functions, some adapted from git bash completion
+#
+
+# echo the git dir
+__gitdir()
+{
+ if [ "true" = "$(git rev-parse --is-inside-work-tree)" ]; then
+ git rev-parse --git-dir 2>/dev/null
+ fi
+}
+
+# echos repo state
+git_repo_state ()
+{
+ local g="$(__gitdir)"
+ if [ -n "$g" ]; then
+ if [ -f "$g/rebase-merge/interactive" ]; then
+ echo "REBASE-i"
+ elif [ -d "$g/rebase-merge" ]; then
+ echo "REBASE-m"
+ else
+ if [ -d "$g/rebase-apply" ]; then
+ echo "AM/REBASE"
+ elif [ -f "$g/MERGE_HEAD" ]; then
+ echo "MERGING"
+ elif [ -f "$g/CHERRY_PICK_HEAD" ]; then
+ echo "CHERRY-PICKING"
+ elif [ -f "$g/BISECT_LOG" ]; then
+ echo "BISECTING"
+ fi
+ fi
+ if [ "true" = "$(git rev-parse --is-inside-git-dir 2>/dev/null)" ]; then
+ if [ "true" = "$(git rev-parse --is-bare-repository 2>/dev/null)" ]; then
+ echo "|BARE"
+ else
+ echo "|GIT_DIR"
+ fi
+ elif [ "true" = "$(git rev-parse --is-inside-work-tree 2>/dev/null)" ]; then
+ git diff --no-ext-diff --quiet --exit-code || echo "|DIRTY"
+# if [ -n "${GIT_PS1_SHOWSTASHSTATE-}" ]; then
+# git rev-parse --verify refs/stash >/dev/null 2>&1 && s="$"
+# fi
+#
+# if [ -n "${GIT_PS1_SHOWUNTRACKEDFILES-}" ]; then
+# if [ -n "$(git ls-files --others --exclude-standard)" ]; then
+# u="%"
+# fi
+# fi
+#
+# if [ -n "${GIT_PS1_SHOWUPSTREAM-}" ]; then
+# __git_ps1_show_upstream
+# fi
+ fi
+ else
+ echo "NOGIT"
+ fi
+}
+
+# check if we only have untouched, modified or (if configured) new files
+check_initial_file_state()
+{
+ local syncNew="$(git config --get --bool branch.$branch_name.syncNewFiles)"
+ if [ "true" == "$syncNew" ]; then
+ # allow for new files
+ if [ ! -z "$(git status --porcelain | grep -E '^[^ \?][^M\?] *')" ]; then
+ echo "NonNewOrModified"
+ fi
+ else
+ # also bail on new files
+ if [ ! -z "$(git status --porcelain | grep -E '^[^ ][^M] *')" ]; then
+ echo "NotOnlyModified"
+ fi
+ fi
+}
+
+# look for local changes
+# used to decide if autocommit should be invoked
+local_changes()
+{
+ if [ ! -z "$(git status --short | grep -E '^[^U\?][^U\?]*')" ]; then
+ echo "LocalChanges"
+ fi
+}
+
+# determine sync state of repository, i.e. how the remote relates to our HEAD
+sync_state()
+{
+ local count="$(git rev-list --count --left-right $remote_name/$branch_name...HEAD)"
+
+ case "$count" in
+ "") # no upstream
+ echo "noUpstream"
+ false
+ ;;
+ "0 0")
+ echo "equal"
+ true
+ ;;
+ "0 "*)
+ echo "ahead"
+ true
+ ;;
+ *" 0")
+ echo "behind"
+ true
+ ;;
+ *)
+ echo "diverged"
+ true
+ ;;
+ esac
+}
+
+# exit, issue warning if not in sync
+exit_assuming_sync() {
+ if [ "equal" == "$(sync_state)" ] ; then
+ echo "git-sync: In sync, all fine."
+ exit 0;
+ else
+ echo "git-sync: Synchronization FAILED! You should definitely check your repository carefully!"
+ echo "(Possibly, a concurrent push to $remote_name screwed it?)"
+ exit 3
+ fi
+}
+
+#
+# Here git-sync actually starts
+#
+
+# first some sanity checks
+rstate="$(git_repo_state)"
+if [[ -z "$rstate" || "|DIRTY" = "$rstate" ]]; then
+ echo "git-sync: Preparing for sync. Repo in $(__gitdir)"
+elif [[ "NOGIT" = "$rstate" ]] ; then
+ echo "git-sync: No git repository detected. Exiting."
+ exit 128 # matches git's error code
+else
+ echo "git-sync: git repo state considered unsafe for sync: $(git_repo_state)"
+ exit 2
+fi
+
+# determine the current branch (thanks to stackoverflow)
+branch_name=$(git symbolic-ref -q HEAD)
+branch_name=${branch_name##refs/heads/}
+
+if [ -z "$branch_name" ] ; then
+ echo "git-sync: Syncing is only possible on a branch."
+ git status
+ exit 2
+fi
+
+# check if current branch is configured
+if [ "true" != "$(git config --get --bool branch.$branch_name.sync)" ] ; then
+ echo
+ echo "git-sync: Please use"
+ echo
+ echo " git config --bool branch.$branch_name.sync true"
+ echo
+ echo "to whitelist branch $branch_name for synchronization."
+ echo "Branch $branch_name has to have a same-named remote branch"
+ echo "for this script to work."
+ echo
+ echo "(If you don't know what this means, you should change that"
+ echo "before relying on this script. You have been warned.)"
+ echo
+ exit 1
+fi
+
+# while at it, determine the remote to operate on
+remote_name=$(git config --get branch.$branch_name.remote)
+
+echo "git-sync: using $remote_name/$branch_name"
+
+# check for intentionally unhandled file states
+if [ ! -z "$(check_initial_file_state)" ] ; then
+ echo "git-sync: There are changed files you should probably handle manually."
+ git status
+ exit 1
+fi
+
+# check if we have to commit local changes, if yes, do so
+if [ ! -z "$(local_changes)" ]; then
+ autocommit_cmd=""
+ config_autocommit_cmd="$(git config --get branch.$branch_name.autocommitscript)"
+
+ # discern the three ways to auto-commit
+ if [ ! -z "$config_autocommit_cmd" ]; then
+ autocommit_cmd="$config_autocommit_cmd"
+ elif [ "true" == "$(git config --get --bool branch.$branch_name.syncNewFiles)" ]; then
+ autocommit_cmd=${ALL_AUTOCOMMIT_CMD}
+ else
+ autocommit_cmd=${DEFAULT_AUTOCOMMIT_CMD}
+ fi
+
+ echo "git-sync: Commiting local chages using ${autocommit_cmd}"
+ eval $autocommit_cmd
+
+ # after autocommit, we should be clean
+ rstate="$(git_repo_state)"
+ if [[ ! -z "$rstate" ]]; then
+ echo "git-sync: Auto-commit left uncommited changes. Cannot continue."
+ exit 1
+ fi
+fi
+
+# fetch remote to get to the current sync state
+# TODO make fetching/pushing optional
+echo "fetching from $remote_name"
+git fetch $remote_name
+if [ $? != 0 ] ; then
+ echo "git-sync: git fetch $remote_name returned non-zero. Cannot continue."
+ exit 3
+fi
+
+case "$(sync_state)" in
+"noUpstream")
+ echo "git-sync: Strange state, you're on your own. Good luck."
+ exit 2
+ ;;
+"equal")
+ exit_assuming_sync
+ ;;
+"ahead")
+ echo "git-sync: Pushing changes"
+ git push
+ if [ $? == 0 ]; then
+ exit_assuming_sync
+ else
+ echo "git-sync: git push returned non-zero. Possibly a connection failure."
+ exit 3
+ fi
+ ;;
+"behind")
+ echo "git-sync: we are behind, fast-forwarding."
+ # TODO this should be with fetch's --dry-run
+ git pull --ff-only
+ if [ $? == 0 ]; then
+ exit_assuming_sync
+ else
+ echo "git pull --ff-only returned non-zero ($?). Exiting."
+ exit 2
+ fi
+ ;;
+"diverged")
+ echo "git-sync: We have diverged. Trying to rebase..."
+ git rebase $remote_name/$branch_name
+ if [[ $? == 0 && -z "$(git_repo_state)" && "ahead" == "$(sync_state)" ]] ; then
+ echo "git-sync: Rebasing went fine, pushing..."
+ git push
+ exit_assuming_sync
+ else
+ echo "git-sync: Rebasing failed, likely there are conflicting changes."
+ exit 1
+ fi
+ # TODO: save master, if rebasing fails, make a branch of old master
+ ;;
+esac

0 comments on commit 673fd51

Please sign in to comment.