summaryrefslogtreecommitdiff
path: root/git-update-agent
diff options
context:
space:
mode:
Diffstat (limited to 'git-update-agent')
-rw-r--r--git-update-agent320
1 files changed, 320 insertions, 0 deletions
diff --git a/git-update-agent b/git-update-agent
new file mode 100644
index 0000000..0a75161
--- /dev/null
+++ b/git-update-agent
@@ -0,0 +1,320 @@
+#!/bin/sh
+
+# GLOBAL VARIABLES:
+# - AGENT: Name of the project which is receiving updates (for display purposes)
+# - UPSTREAM_BRANCH: The branch in the upstream repo used for updates (defaults to remote's head if set)
+# - REMOTE: Name of the remote used for updates
+# - REMOTE_URL: Name of URL used for synchronization
+
+# USAGE:
+#
+# Initialize a project:
+# git-update-agent --init https://git.mydomain.com
+#
+# Update:
+# git-update-agent
+
+CMD_PROMPT="[#] "
+PROGRAM="${0##*/}"
+
+die () {
+ if [[ $# -eq 0 ]]; then
+ echo -n "$PROGRAM: " 1>&2
+ cat <&0 1>&2
+ elif [[ $# -eq 1 ]]; then
+ echo "$PROGRAM: $1" 1>&2
+ fi
+
+ exit 1
+}
+
+depends() {
+ if ! type "$1" >/dev/null; then
+ die "\"$1\" must be installed and in your \$PATH"
+ fi
+}
+
+cmd() {
+ echo "$CMD_PROMPT $*"
+ "$@"
+}
+
+stash() {
+ if ! git \
+ -c "user.email=git-updater@localhost" \
+ -c "user.name=git-updater" \
+ stash save --include-untracked &>/dev/null
+ then
+
+ die <<_EOF
+Could not stash in $(pwd)
+Please stash/commit manually if you need to keep your changes. If you would like to destroy all changes, run:
+cd $(pwd)
+git reset --hard origin/main
+_EOF
+
+ fi
+}
+
+current_version() {
+ git rev-parse -q --verify HEAD
+}
+
+die_no_upstream_found() {
+ die <<_EOF
+origin/HEAD is used as the reference for updates
+origin/HEAD is not set and UPSTREAM_BRANCH override not provided
+
+You can set the upstream branch with:
+git remote set-head <name> <branch>
+_EOF
+}
+
+upstream_branch() {
+ local upstream_branch
+ local remote
+
+ remote="$1"
+
+
+ if [[ -z "$UPSTREAM_BRANCH" ]]; then
+ upstream_branch="$(git symbolic-ref "refs/remotes/$remote/HEAD" 2>/dev/null)"
+ upstream_branch="${upstream_branch#refs/remotes/$remote/}"
+ else
+ upstream_branch="$UPSTREAM_BRANCH"
+ fi
+
+ echo "$upstream_branch"
+}
+
+local_branch() {
+ local branch
+
+ branch="$(git symbolic-ref HEAD)"
+ branch="${branch#refs/heads/}"
+
+ echo "$branch"
+}
+
+merge() {
+ local STASHED
+
+ local remote
+ local remote_branch
+ local local_branch
+
+ remote="$1"
+ remote_branch="$2"
+ local_branch="$3"
+
+ local remote_ref
+ remote_ref="$remote/$remote_branch"
+
+
+ if [[ -n "$(git status --untracked-files=all --porcelain 2>/dev/null)" ]]; then
+ # Abort rebase or merge if initiated
+ git merge --abort &>/dev/null
+ git rebase --abort &>/dev/null
+
+ stash
+ STASHED="1"
+ fi
+
+ # Attempt fetch to REMOTE/UPSTREAM_BRANCH
+ if ! git fetch --tags --force "$remote" \
+ "refs/heads/$remote_branch:refs/remotes/$remote_ref"
+ then
+ die "Cannot fetch from $remote_ref from $local_branch"
+ fi
+
+ # Try fast-forward merge
+ git merge --no-edit --ff "$remote_ref" \
+ --strategy=recursive \
+ --strategy-option=ours \
+ --strategy-option=ignore-all-space \
+ >/dev/null
+
+ if [[ -n "$STASHED" && -z "$NO_STASH_POP" ]]; then
+ git stash pop &>/dev/null
+ fi
+}
+
+remote() {
+ local remote
+
+ if [[ -z "$REMOTE" ]]; then
+ remote="origin"
+ else
+ remote="$REMOTE"
+ fi
+
+ echo "$remote"
+}
+
+cd_or_die() {
+ cd "$1" || die "Cannot enter directory $1. Exiting."
+}
+
+die_no_git_dir() {
+ die <<_EOF
+Cannot find a .git directory in $(pwd). Try initializing repo with:
+$PROGRAM --init-with https://git.example.com
+
+To automatically create a git repo, consider setting "REMOTE_URL"
+_EOF
+}
+
+init_if_empty() {
+ if [[ ! -d ".git" ]]; then
+
+ if [[ -z "$REMOTE_URL" ]]; then
+ die_no_git_dir
+ else
+ init_repo "$REMOTE_URL"
+ fi
+
+ fi
+}
+
+update() {
+ local dir
+ local previous_version
+ local next_version
+
+ dir="$1"
+ cd_or_die "$dir"
+
+ previous_version="$(current_version)"
+
+ if [[ -z "$AGENT" ]]; then
+ AGENT="$(basename "$(pwd)")"
+ fi
+
+ depends git
+
+ init_if_empty
+
+ local remote
+ local remote_branch
+ local local_branch
+
+ remote="$(remote)"
+
+ remote_branch="$(upstream_branch "$remote")"
+
+ if [[ -z "$remote_branch" ]]; then
+ die_no_upstream_found
+ fi
+
+ local_branch="$(local_branch)"
+
+ merge "$remote" "$remote_branch" "$local_branch"
+
+ next_version="$(current_version)"
+
+ if [[ "$previous_version" == "" ]]; then
+ echo "Initialized $AGENT with \"$previous_version\""
+ elif [[ "$previous_version" != "$next_version" ]]; then
+ echo "Updated $AGENT from version \"$previous_version\" to \"$next_version\""
+ else
+ echo "$AGENT is already up to date."
+ fi
+}
+
+print_usage() {
+cat <<_EOF
+Usage: ${PROGRAM} [OPTIONS]...
+ -h, --help output this help info
+ -d, --dir update in directory
+ --init initialize a project with the given URL
+_EOF
+}
+
+init_repo() {
+ local remote
+ local remote_url
+ local upstream_branch
+ local remote_ref
+
+ remote_url="$1"
+ remote="$(remote)"
+
+ DEFAULT_BRANCH="main"
+
+ trap '{ rm -rf .git; exit 1; }' EXIT
+
+ set -e
+ git init &>/dev/null
+ git branch -M "$DEFAULT_BRANCH"
+ git config remote.origin.url "$remote_url"
+ git config remote.origin.fetch "+refs/heads/*:refs/remotes/$remote/*"
+ git fetch --force --tags "$remote" &>/dev/null
+ git remote set-head "$remote" --auto >/dev/null
+
+ upstream_branch="$(upstream_branch "$remote")"
+
+ if [[ -z "$upstream_branch" ]]; then
+ die_no_upstream_found
+ fi
+
+ remote_ref="$remote/$upstream_branch"
+
+ git merge --ff --no-edit "$remote_ref"
+
+ git branch --set-upstream-to="$remote_ref" "$DEFAULT_BRANCH"
+
+ git reset --hard "$remote_ref" >/dev/null
+
+ trap - EXIT
+
+ set +e
+}
+
+git_update_agent() {
+ local dir
+ local option
+ local remote_url
+
+ while [[ $# -gt 0 ]]; do
+ case "$1" in
+ -h|-\?|--help|--usage) print_usage; exit 0 ;;
+ --dir)
+
+ shift
+
+ if [[ $# -eq 0 ]]; then
+ die "--dir requires an option"
+ fi
+
+ dir="$1"
+
+ shift
+ ;;
+ --init-with)
+
+ shift
+
+ if [[ $# -eq 0 ]]; then
+ die "--init requires an option"
+ fi
+
+ remote_url="$1"
+
+ shift
+ ;;
+ *) die "$(print_usage)" ;;
+ esac
+ done
+
+ if [[ -z "$dir" ]]; then
+ dir="."
+ fi
+
+ if [[ -z "$remote_url" ]]; then
+ update "$dir"
+ else
+ init_repo "$remote_url"
+ fi
+}
+
+git_update_agent "$@"