Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bash_completion
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ _umpf_completion()
"")
COMPREPLY=( $( compgen -W "${completion_cmds[*]} help" -- $cur ) )
;;
diff|show|tag|tig|build)
diff|show|tag|tig|build|push|pull)
local -a refs
refs=( $( compgen -W "$( git for-each-ref --format='%(refname:short)' refs/tags refs/heads refs/remotes)" -- $cur ) )
if [ ${#refs[@]} -eq 0 ]; then
Expand Down
41 changes: 41 additions & 0 deletions doc/getting-started.rst
Original file line number Diff line number Diff line change
Expand Up @@ -447,6 +447,47 @@ Or tell umpf to rebase onto a new *umpf-base* when creating a fresh *utag*::
# umpf-topic-range: 8bae5bbec8cb4599c141405e9755b7c0e42e064f..19cdc2b857e662a38c712b41ce610000a5ddc6ae
# umpf-end

Synchronizing umpf topic branch
-------------------------------

Due to Git's distributed nature, checked out topic branches can get
out-of-sync. To compare local topic branches against those referenced
in a *utag*, ``umpf pull`` can be used::

umpf pull --dry-run 5.0/special-customer-release/20190311-1
umpf: Using series from commit message...
* [new branch] 02fb74aa381080855a57080138b29ecc96586788 -> v5.0/topic/most-fixes
! [rejected] f0693b782dd026f2adc4d3c336d9ac6dfb352a73 -> v5.0/topic/more-fixes (non-fast-forward)

Following options are supported:

- ``--dry-run``: compare the branches, but stop short of actually updating
them
- ``--force``: reset local branches that are not checked-out to the
``umpf-hashinfo`` in the ``utag``
- ``--update``: only update existing branches

The counterpart to publish topic branches to a remote after creating a new
``utag`` is ``umpf push``:

umpf --dry-run --remote=downstream push 5.0/special-customer-release/20190311-1
umpf: Using series from commit message...
To ssh://downstream
* [new branch] 02fb74aa381080855a57080138b29ecc96586788 -> v5.0/topic/most-fixes
! [rejected] f0693b782dd026f2adc4d3c336d9ac6dfb352a73 -> v5.0/topic/more-fixes (non-fast-forward)
error: failed to push some refs to 'ssh:/downstream'

It supports the same options as ``umpf pull``, but instead of doing local
changes, it operates on the specified remote.

``umpf push`` is especially useful when multiple developers are creating
`utags` for the same project in parallel. Each developer will initially
only push their `utag` to the common repository. Once the changes
introduced by a `utag` are accepted, all topic branches can be force
updated on the remote to this most recent `utag`, possibly via
a server-side pull-request post-merge hook running, e.g.::

umpf push --remote=downstream --force .../linux/patches/series.inc

Overview
--------
Expand Down
133 changes: 132 additions & 1 deletion umpf
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ IDENTICAL=false
STABLE=false
DEFAULT=false
FORCE=false
DRYRUN=false
UPDATE=false
VERBOSE=false
VERSION_SEPARATOR=-
Expand Down Expand Up @@ -182,6 +183,8 @@ usage() {
--nix with format-patch: write patch series nix
-h, --help
-f, --force
--dry-run with push/pull: Do everything except actually send
the updates.
--flags specify/override umpf-flags
-i, --identical use exact commit hashes, not tip of branches
-s, --stable create a 'stable' tag from a branch based on an
Expand All @@ -198,6 +201,7 @@ usage() {
specified, it's interpreted as
<topic>=[<remote>/]<topic>
-u, --update with --patchdir: update existing patches in <path>
with push/pull: update only existing branches
-v, --version <version> with tag: overwrite version number [default: 1]

Commands:
Expand All @@ -222,6 +226,8 @@ usage() {
build <umpf> build an umerge from another umpf
distribute <commit-ish> push patches not yet in any topic branch
upstream
push [<umpf>] push topic branches to the given remote
pull [<umpf>] pull topic branches into the local repository

continue continue a previously interrupted umpf command
abort abort a previously started umpf command
Expand Down Expand Up @@ -249,7 +255,7 @@ setup() {
fi

o="fhilsub:n:p:r:v:"
l="auto-rerere,bb,nix,flags:,default,force,help,identical,stable,update,base:,name:,patchdir:,relative:,override:,remote:,local,version:"
l="auto-rerere,bb,nix,flags:,default,dry-run,force,help,identical,stable,update,base:,name:,patchdir:,relative:,override:,remote:,local,version:"
if ! args="$(getopt -n umpf -o "${o}" -l "${l}" -- "${@}")"; then
usage
exit 1
Expand Down Expand Up @@ -278,6 +284,9 @@ setup() {
-f|--force)
FORCE=true
;;
--dry-run)
DRYRUN=true
;;
--flags)
FLAGS="${1}"
shift
Expand Down Expand Up @@ -1880,6 +1889,128 @@ do_distribute() {
run_distribute
}

### namespace: push ###

push_topic() {
echo "${content}" >> "${STATE}/topic-names"
}

push_hashinfo() {
echo "${content}" >> "${STATE}/topics"
}

push_release() {
echo "${content}" >> "${STATE}/tagname"
}

push_topic_range() {
[ ! -e "${STATE}/tagname" ] && return
[ -e "${STATE}/tagrev-flat" ] && abort "more than one 'topic-range' after 'release'!"

echo "${content##*..}" > "${STATE}/tagrev-flat"
}

### command: push ###

resolve_remote_tag() {
local remote="${1}" tag="${2}"
# Annotated tags return two lines with the latter being derefenced
# by ^{}, which is why we use tail -n1 here
${GIT} ls-remote -q --tags "${remote}" "${tag}" 2>/dev/null | \
sed 's/\s\+.*$//' | tail -n1
}

resolve_commitish() {
${GIT} rev-parse --revs-only "${@}" 2>/dev/null
}

shorten_commitish() {
resolve_commitish --short "${@}"
}

do_push () {
local remote
local -a opts args branches branch_names
local -A topics

if [ -z "${GIT_REMOTE}" ]; then
info "Git remote must be specified. Cannot continue."
exit 1
fi

if [ "${GIT_REMOTE}" = "refs/heads/" ]; then
remote="${GIT_DIR}"
else
remote=${GIT_REMOTE%/}
fi

prepare_persistent push "${@}"
parse_series push "${STATE}/series"

local tagname="$(<"${STATE}/tagname")"
local tagrevf="$(<"${STATE}/tagrev-flat")"
mapfile -t branches < "${STATE}/topics"
mapfile -t branch_names < "${STATE}/topic-names"

# resolve_remote_tag aborts early without fetching everything
local rtagrev="$(resolve_remote_tag "${remote}" "${tagname}")"
if [ -z "${rtagrev}" ]; then
abort "${remote}${remote:+/}refs/tags/${tagname} not found"
fi

# Fetch the remote tag including history to speed up resolving remote
# branches and get --force-with-lease to work
git fetch --quiet --no-tags "${remote}" "${rtagrev}" 2>/dev/null
local rtagrevf="$(${GIT} rev-parse --revs-only "FETCH_HEAD^" 2>/dev/null)"
if [ "${rtagrevf}" != "${tagrevf}" ]; then
abort "${remote}${remote:+/}refs/tags/${tagname} has unexpected" \
'commit hash "'"$(shorten_commitish "${rtagrevf}")"'"' \
'instead of "' "$(shorten_commitish "${tagrevf}")" '"'
fi

for i in "${!branch_names[@]}"; do
local branch="${branch_names[$i]}"
local rbranchrev="$(resolve_commitish "${GIT_REMOTE}${branch}")"

if ! $UPDATE || [ -n "${rbranchrev}" ]; then
topics[${branch}]="$(resolve_commitish "${branches[$i]}")"
fi
done

if ${FORCE}; then
if [ "${GIT_REMOTE}" = "refs/heads/" ]; then
# Otherwise, we'll always get rejected for "stale info"
opts+=("--force")
else
opts+=("--force-with-lease")
fi
fi

if ${DRYRUN}; then
opts+=("--dry-run")
fi

for topic in "${!topics[@]}"; do
args+=("${topics[$topic]}:refs/heads/${topic}")
done

if [ -z "${#args[@]}" ]; then
info "No branches to push"
cleanup
return
fi

${GIT} push "${opts[@]}" ${remote} -- "${args[@]}"

cleanup
}

### command: pull ###

do_pull () {
GIT_REMOTE=refs/heads/ do_push "$@"
}

### command: continue ###

do_continue() {
Expand Down