Skip to content

Instantly share code, notes, and snippets.

@joepvd
Last active December 14, 2024 15:07
Show Gist options
  • Save joepvd/35ec4a4b77b7f3321f48ae279c78c63f to your computer and use it in GitHub Desktop.
Save joepvd/35ec4a4b77b7f3321f48ae279c78c63f to your computer and use it in GitHub Desktop.
git-update

git update

Do the right thing when updating branches

Author: joepvd
Date: 2024-12-14
Copyright: GPLv2
Version: 0.2
Manual section:1

SYNOPSIS

git update

DESCRIPTION

For automatically rebasing current branch to freshly fetched origin branch, and rebase local branches with upstream changes. Optionally, a configuration file can be used for a list of read-only checkouts for relevant branches.

If you're daring: `sh git config --global alias.pull git-update `

Features (any repo)

  • fetches upstream origin
  • ensures the local main branch is current with the upstream main branch
  • ensures the currently checked out branch gets the changes from its originating branch

Features configured repo

In addition to the general features, it is possible to configure read only checkouts in a different directory. Take this example:

It: * ensures all openshift-$VERSION branches are up to date with their origin

counterpart
  • ensures the read-only worktree checkouts under ~/ocp-ro/$VERSION are up to date with origin.
  • can run git update in read only worktree, and updates the real repository with all relevant branches, and all the read only worktree checkouts.

Example config is in /usr/share/git-update/git-update.yaml. Copy to ~/.config/git-update.yaml and edit.

This makes use of the [worktree](https://git-scm.com/docs/git-worktree) feature of git. The result is a directory ~/ocp-ro, where for each relevant version, a complete and current checkout of the origin state of that version is.

The purpose of these checkouts is to easily query and compare facts between branches.

These checkouts are headless, as the local knowledge of the remote state is checked out. Files and directories in the worktree checkouts are set to be readonly, to ensure the character of "upstream truth" is maintained.

#!/usr/bin/env bash
set -euo pipefail
shopt -s extglob
main() {
repo_path="$(realpath "$(dirname "$(git rev-parse --git-common-dir)")")"
reponame="$(basename "$repo_path")"
current_branch="$(git rev-parse --abbrev-ref HEAD)"
main_branch="$(get_main_branch)"
cd "$repo_path"
fetch_origin
update_branch "$main_branch" "origin/$main_branch"
config="$(get_config "$reponame")"
if [[ -n "$config" ]]; then
update_worktrees
fi
# Rebase current branch on most recent originating branch
git checkout --quiet "$current_branch"
if [[ "$current_branch" != "$main_branch" ]]; then
o="$(originating_branch)"
[[ -n "$o" ]] && update_branch "$current_branch" "$o"
fi
}
originating_branch() {
local i res
i=0
res=""
while [[ -z "$res" ]] && ((i < 100 )); do
res=$(git branch -r --list 'origin/*' --contains "HEAD~$i" | awk '{print $1; exit}')
i=$((i+1))
done
echo "$res"
}
get_main_branch() {
if git rev-parse --verify origin/main >/dev/null 2>&1; then
echo main
elif git rev-parse --verify origin/master >/dev/null 2>&1; then
echo master
else
echo "No main/master branch detected">/dev/stderr
exit 1
fi
}
fetch_origin() {
git diff --quiet || {
echo branch not clean. exiting >/dev/stderr
exit 1
}
echo "Fetching origin" >/dev/stderr
git fetch --quiet origin
}
update_branch() {
local local_branch remote_branch local_head remote_head
local_branch="$1"
remote_branch="$2"
git checkout --quiet "$local_branch"
local_head="$(git rev-parse --short HEAD)"
remote_head="$(git log --pretty=format:%h -n 1 "$remote_branch")"
if [[ "$local_head" == "$remote_head" ]]; then
echo "No updates for $local_branch" >/dev/stderr
else
echo "rebasing branch $remote_branch onto $local_branch: $local_head..$remote_head" >/dev/stderr
git rebase --quiet "$remote_branch"
fi
}
get_config() {
local reponame="$1"
# shellcheck disable=SC2016
yq --arg name "$reponame" '
.[]
| select((.repo | split("/").[-1] ) == $name )
' ~/.config/git-update.yaml || true
}
update_worktrees() {
worktree_dir="$(jq -r .worktree_dir <<<"$config")"
eval worktree_dir="$worktree_dir" # expand ~
mkdir -p "$worktree_dir"
shopt -s nullglob
chmod --recursive +w "$worktree_dir"/* 2>/dev/null || true
trap 'chmod --recursive -w "$worktree_dir"/*' EXIT
while read -r branch dir; do
update_branch "$branch" "origin/$branch"
(
echo "Updating worktree $worktree_dir/$dir" >/dev/stderr
if [[ -d "$worktree_dir/$dir" ]]; then
cd "$worktree_dir/$dir"
git checkout --quiet "origin/$branch"
else
git worktree add "$worktree_dir/$dir" "origin/$branch"
fi
)
done < <(
jq -r '.checkouts[] | .branch + " " + .dir' <<<"$config"
)
}
main
Name: {{{ git_dir_name }}}
Version: 0.2
Release: 0%{?dist}
Summary: Display data in pretty table in terminal
License: GPLv2
URL: https://github.com/joepvd/git-update
VCS: {{{ git_dir_vcs }}}
Source: {{{ git_dir_pack }}}
BuildRequires: python3-docutils
Requires: git
Requires: jq
Requires: yq
%global debug_package %{nil}
%description
Do the right thing when updating branches
%prep
{{{ git_dir_setup_macro }}}
%build
rst2man README.rst | gzip >git-update.1.gz
%install
mkdir -p %{buildroot}/usr/bin
install -m 755 git-update %{buildroot}/usr/bin/git-update
mkdir -p %{buildroot}/usr/share/man/man1
install -m 644 git-update.1.gz %{buildroot}/usr/share/man/man1/git-update.1.gz
mkdir -p %{buildroot}/usr/share/git-update
install -m 644 git-update.yaml %{buildroot}/usr/share/git-update/git-update.yaml
%files
/usr/bin/git-update
/usr/share/man/man1/git-update.1.gz
/usr/share/git-update/git-update.yaml
%license
%doc
%changelog
{{{ git_dir_changelog }}}
- repo: ~/src/github.com/openshift-eng/ocp-build-data
worktree_dir: ~/ocp-ro
checkouts:
- branch: openshift-3.11
dir: "3.11"
- branch: openshift-4.12
dir: "4.12"
- branch: openshift-4.13
dir: "4.13"
- branch: openshift-4.14
dir: "4.14"
- branch: openshift-4.15
dir: "4.15"
- branch: openshift-4.16
dir: "4.16"
- branch: openshift-4.17
dir: "4.17"
- branch: openshift-4.18
dir: "4.18"
- branch: openshift-4.19
dir: "4.19"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment