diff options
Diffstat (limited to 'git-update-agent')
-rw-r--r-- | git-update-agent | 320 |
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 "$@" |