-
Notifications
You must be signed in to change notification settings - Fork 99
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Signed-off-by: Simon Thum <simon.thum@gmx.de>
- Loading branch information
0 parents
commit 673fd51
Showing
2 changed files
with
397 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 |