odfstudio: add upstream maintenance tools

Change-Id: I352b0b0bf55ba845afd6289250060b7af44a26ee
This commit is contained in:
2026-09-16 21:28:39 +02:00
parent 1be4b4318a
commit c2b5ab6b2e
3 changed files with 386 additions and 0 deletions
+251
View File
@@ -0,0 +1,251 @@
#!/usr/bin/env bash
set -u
die() {
echo "ERROR: $*" >&2
exit 1
}
section() {
printf '\n=== %s ===\n' "$1"
}
val() {
[ -n "${1:-}" ] && printf '%s' "$1" || printf '%s' "-"
}
git rev-parse --is-inside-work-tree >/dev/null 2>&1 \
|| die "Not inside a Git repository"
ROOT=$(git rev-parse --show-toplevel)
BRANCH=$(git symbolic-ref --quiet --short HEAD 2>/dev/null || echo "DETACHED")
HEAD_COMMIT=$(git rev-parse --short=12 HEAD)
cd "$ROOT" || exit 1
# ------------------------------------------------------------
section "REPOSITORY"
echo "path : $ROOT"
echo "branch : $BRANCH"
echo "HEAD : $HEAD_COMMIT"
# ------------------------------------------------------------
section "IDENTITY"
GLOBAL_NAME=$(git config --global --get user.name 2>/dev/null || true)
GLOBAL_EMAIL=$(git config --global --get user.email 2>/dev/null || true)
LOCAL_NAME=$(git config --local --get user.name 2>/dev/null || true)
LOCAL_EMAIL=$(git config --local --get user.email 2>/dev/null || true)
EFFECTIVE_NAME=$(git config --get user.name 2>/dev/null || true)
EFFECTIVE_EMAIL=$(git config --get user.email 2>/dev/null || true)
printf 'global : %s <%s>\n' "$(val "$GLOBAL_NAME")" "$(val "$GLOBAL_EMAIL")"
printf 'local : %s <%s>\n' "$(val "$LOCAL_NAME")" "$(val "$LOCAL_EMAIL")"
printf 'effective : %s <%s>\n' "$(val "$EFFECTIVE_NAME")" "$(val "$EFFECTIVE_EMAIL")"
# ------------------------------------------------------------
section "REMOTES"
for remote in $(git remote); do
FETCH_URL=$(git remote get-url "$remote" 2>/dev/null || true)
PUSH_URL=$(git remote get-url --push "$remote" 2>/dev/null || true)
echo
echo "[$remote]"
echo " fetch : $(val "$FETCH_URL")"
echo " push : $(val "$PUSH_URL")"
done
# ------------------------------------------------------------
section "SUBMODULES"
if [ ! -f .gitmodules ]; then
echo "(none)"
else
git config -f .gitmodules \
--get-regexp '^submodule\..*\.path$' 2>/dev/null |
while read -r key path; do
name=${key#submodule.}
name=${name%.path}
declared=$(
git config -f .gitmodules \
--get "submodule.$name.url" 2>/dev/null || true
)
resolved=$(
git config \
--get "submodule.$name.url" 2>/dev/null || true
)
commit=$(
git ls-tree HEAD "$path" 2>/dev/null |
awk '{print $3}'
)
echo
echo "[$name]"
echo " path : $path"
echo " declared : $(val "$declared")"
echo " resolved : $(val "$resolved")"
echo " commit : $(val "$commit")"
done
fi
# ------------------------------------------------------------
section "SSH ALIASES"
SSH_CONFIG="$HOME/.ssh/config"
if [ ! -f "$SSH_CONFIG" ]; then
echo "(no ~/.ssh/config)"
else
awk '
/^Host[ \t]+/ {
host=$0
sub(/^[ \t]*Host[ \t]+/, "", host)
print ""
print "[" host "]"
next
}
/^[ \t]*(HostName|User|Port|IdentityFile|IdentitiesOnly)[ \t]+/ {
key=$1
value=$2
printf " %-15s %s\n", key ":", value
}
' "$SSH_CONFIG"
fi
# ------------------------------------------------------------
section "SSH BY REMOTE"
for remote in $(git remote); do
url=$(git remote get-url "$remote" 2>/dev/null || true)
case "$url" in
ssh://*)
;;
*@*:*)
;;
*)
echo
echo "[$remote]"
echo " protocol : non-SSH"
continue
;;
esac
# Extract SSH destination from URL.
if [[ "$url" =~ ^ssh://([^/@]+@)?([^/:]+)(:([0-9]+))?/.*$ ]]; then
url_user=${BASH_REMATCH[1]%@}
url_host=${BASH_REMATCH[2]}
url_port=${BASH_REMATCH[4]}
elif [[ "$url" =~ ^([^@]+)@([^:]+):.*$ ]]; then
url_user=${BASH_REMATCH[1]}
url_host=${BASH_REMATCH[2]}
url_port=""
else
echo
echo "[$remote]"
echo " unable to parse: $url"
continue
fi
SSH_OUTPUT=$(ssh -G "$url_host" 2>/dev/null || true)
cfg_user=$(printf '%s\n' "$SSH_OUTPUT" |
awk '$1=="user" {print $2; exit}')
cfg_host=$(printf '%s\n' "$SSH_OUTPUT" |
awk '$1=="hostname" {print $2; exit}')
cfg_port=$(printf '%s\n' "$SSH_OUTPUT" |
awk '$1=="port" {print $2; exit}')
cfg_keys=$(printf '%s\n' "$SSH_OUTPUT" |
awk '$1=="identityfile" {print $2}')
effective_user=${url_user:-$cfg_user}
effective_port=${url_port:-$cfg_port}
echo
echo "[$remote]"
echo " URL : $url"
echo " URL user : $(val "$url_user")"
echo " URL host : $(val "$url_host")"
echo " URL port : $(val "$url_port")"
echo
echo " SSH cfg user : $(val "$cfg_user")"
echo " SSH cfg host : $(val "$cfg_host")"
echo " SSH cfg port : $(val "$cfg_port")"
while read -r key; do
[ -n "$key" ] && echo " identity : $key"
done <<< "$cfg_keys"
echo
echo " effective user: $(val "$effective_user")"
echo " effective host: $(val "$cfg_host")"
echo " effective port: $(val "$effective_port")"
if [ -n "$url_user" ] &&
[ -n "$cfg_user" ] &&
[ "$url_user" != "$cfg_user" ]; then
echo " NOTICE : URL user overrides SSH config user"
fi
if [ -n "$url_port" ] &&
[ -n "$cfg_port" ] &&
[ "$url_port" != "$cfg_port" ]; then
echo " NOTICE : URL port overrides SSH config port"
fi
done
# ------------------------------------------------------------
section "SSH AGENT"
if ssh-add -l >/dev/null 2>&1; then
ssh-add -l
else
echo "INFO: no SSH agent available or no key loaded"
fi
# ------------------------------------------------------------
section "OBSERVATIONS"
OBS=0
if [ -n "$LOCAL_NAME$LOCAL_EMAIL" ] &&
{ [ "$LOCAL_NAME" != "$GLOBAL_NAME" ] ||
[ "$LOCAL_EMAIL" != "$GLOBAL_EMAIL" ]; }; then
echo "NOTICE: local Git identity overrides global identity"
OBS=$((OBS + 1))
fi
if [ -f .gitmodules ]; then
while read -r key url; do
case "$url" in
../*|./*)
echo "INFO: relative submodule URL: $key -> $url"
OBS=$((OBS + 1))
;;
esac
done < <(
git config -f .gitmodules \
--get-regexp '^submodule\..*\.url$' 2>/dev/null
)
fi
if [ "$OBS" -eq 0 ]; then
echo "No notable configuration difference detected."
fi
echo
+68
View File
@@ -0,0 +1,68 @@
#!/usr/bin/env bash
set -euo pipefail
ODF_BRANCH="${1:-main}"
UPSTREAM_BRANCH="${2:-upstream/master}"
git rev-parse --git-dir >/dev/null 2>&1 || {
echo "ERROR: not inside a Git repository" >&2
exit 1
}
git rev-parse --verify "$ODF_BRANCH" >/dev/null
git rev-parse --verify "$UPSTREAM_BRANCH" >/dev/null
BASE="$(git merge-base "$ODF_BRANCH" "$UPSTREAM_BRANCH")"
ODF_HEAD="$(git rev-parse "$ODF_BRANCH")"
UPSTREAM_HEAD="$(git rev-parse "$UPSTREAM_BRANCH")"
TMPDIR_WORK="$(mktemp -d)"
trap 'rm -rf "$TMPDIR_WORK"' EXIT
ODF_FILES="$TMPDIR_WORK/odfstudio-files.txt"
UPSTREAM_FILES="$TMPDIR_WORK/upstream-files.txt"
SHARED_FILES="$TMPDIR_WORK/shared-files.txt"
git diff --name-only "$BASE".."$ODF_BRANCH" | sort -u > "$ODF_FILES"
git diff --name-only "$BASE".."$UPSTREAM_BRANCH" | sort -u > "$UPSTREAM_FILES"
comm -12 "$ODF_FILES" "$UPSTREAM_FILES" > "$SHARED_FILES"
ODF_COMMITS="$(git rev-list --count "$UPSTREAM_BRANCH".."$ODF_BRANCH")"
UPSTREAM_COMMITS="$(git rev-list --count "$ODF_BRANCH".."$UPSTREAM_BRANCH")"
ODF_FILE_COUNT="$(wc -l < "$ODF_FILES")"
UPSTREAM_FILE_COUNT="$(wc -l < "$UPSTREAM_FILES")"
SHARED_FILE_COUNT="$(wc -l < "$SHARED_FILES")"
echo "ODFStudio upstream synchronization report"
echo "========================================="
echo
printf "BASE %s\n" "$BASE"
printf "ODFStudio %s (%s)\n" "$ODF_HEAD" "$ODF_BRANCH"
printf "Upstream %s (%s)\n" "$UPSTREAM_HEAD" "$UPSTREAM_BRANCH"
echo
echo "Commits"
printf " ODFStudio %8d\n" "$ODF_COMMITS"
printf " Upstream %8d\n" "$UPSTREAM_COMMITS"
echo
echo "Modified files since common base"
printf " ODFStudio %8d\n" "$ODF_FILE_COUNT"
printf " Upstream %8d\n" "$UPSTREAM_FILE_COUNT"
printf " Shared %8d\n" "$SHARED_FILE_COUNT"
echo
echo "Upstream activity on shared files"
echo "---------------------------------"
if [[ ! -s "$SHARED_FILES" ]]; then
echo "No shared modified files."
exit 0
fi
while IFS= read -r file; do
count="$(git rev-list --count "$BASE".."$UPSTREAM_BRANCH" -- "$file")"
printf "%5d %s\n" "$count" "$file"
done < "$SHARED_FILES" | sort -nr
+67
View File
@@ -0,0 +1,67 @@
#!/usr/bin/env bash
set -euo pipefail
ODF_BRANCH="${1:-main}"
UPSTREAM_BRANCH="${2:-upstream/master}"
git rev-parse --git-dir >/dev/null 2>&1 || {
echo "ERROR: not inside a Git repository" >&2
exit 1
}
echo "ODFStudio upstream sync status"
echo "=============================="
echo
printf "Repository : %s\n" "$(git rev-parse --show-toplevel)"
printf "Branch : %s\n" "$(git branch --show-current)"
printf "HEAD : %s\n" "$(git rev-parse --short=12 HEAD)"
printf "ODFStudio : %s\n" "$(git rev-parse --short=12 "$ODF_BRANCH")"
printf "Upstream : %s\n" "$(git rev-parse --short=12 "$UPSTREAM_BRANCH")"
printf "Base : %s\n" "$(git merge-base "$ODF_BRANCH" "$UPSTREAM_BRANCH" | cut -c1-12)"
echo
echo "Working tree"
echo "------------"
if git diff --quiet && git diff --cached --quiet; then
echo "Tracked files : clean"
else
echo "Tracked files : MODIFIED"
git status --short
fi
UNTRACKED="$(git ls-files --others --exclude-standard)"
if [[ -z "$UNTRACKED" ]]; then
echo "Untracked : none"
else
echo "Untracked :"
printf '%s\n' "$UNTRACKED" | sed 's/^/ /'
fi
echo
echo "Remotes"
echo "-------"
git remote -v | awk '$3 == "(fetch)" {print}'
echo
echo "Divergence"
echo "----------"
printf "ODFStudio-only commits : "
git rev-list --count "$UPSTREAM_BRANCH".."$ODF_BRANCH"
printf "Upstream-only commits : "
git rev-list --count "$ODF_BRANCH".."$UPSTREAM_BRANCH"
echo
echo "Sync readiness"
echo "--------------"
if ! git diff --quiet || ! git diff --cached --quiet; then
echo "NOT READY: tracked working tree is modified."
exit 1
fi
echo "READY: tracked working tree is clean."