/usr/bin/git-debcherry is in gitpkg 0.27.
This file is owned by root:root, with mode 0o755.
The actual contents of the file can be viewed below.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 | #!/bin/bash
# git debcherry upstream [head]
function _time() {
if [ "$debug" = 'yes' ]; then
echo "# time=" $(date "+%s.%N") " line=" $1 >&2
fi
}
function usage() {
printf "usage: $0 [options] upstream [head]\n"
printf "\t -d | --debug\t\tprint debugging/profiling info\n"
printf "\t -s | --stat\t\toutput patch statistics to stdout\n"
printf "\t (-o | --output-directory) <dir> \toutput patches to <dir>\n"
}
function patch_id() {
if [ -z "$(git diff-tree $1)" ]; then
echo "empty"
else
local ident=$(git show $1 | git patch-id)
ident=${ident% *}
echo $ident
fi
}
function debug (){
if [ "$debug" = 'yes' ]; then
printf "$@" >&2
fi
}
function _cleanup (){
if [ "$debug" = 'no' ]; then
rm -rf "$tmpdir"
fi
}
function _checkout (){
local ref=$1
git checkout -f $ref >$log 2>&1 || _die "checkout failed"
}
function test_apply (){
local hash=$1
local ref=$2
local want_changes=$3
local ret=0;
debug "test_apply $hash $ref $want_changes\n"
old_head=$(git rev-parse HEAD)
_checkout $ref
if ! git cherry-pick --no-commit $hash 1>/dev/null 2>&1 ; then
debug "cherry-pick failed in test_apply\n"
ret=1
fi
git diff --cached --quiet
local has_changes=$?
if [ "$has_changes" != "$want_changes" ]; then
ret=1
fi
debug "test_apply %d %d %d\n" $has_changes $want_changes $ret
# wipe out any changes
git reset --hard HEAD >$log 2>&1 || _die "reset failed"
_checkout $old_head
return $ret
}
# make a temporary branch.
# usage tmpbranch prefix treeish
function tmpbranch() {
local git_dir=${GIT_DIR-$(pwd)/$(git rev-parse --git-dir)}
local filtered=$(mktemp $git_dir/refs/heads/$1_XXXXXXXXXX)
# git update-ref is too fussy here.
echo $(git rev-parse $2) > $filtered
filtered=${filtered##$git_dir/}
echo $filtered
}
function _die() {
echo $1 >&2
[ -s $log ] && cat $log
exit 1
}
tmpdir=$(mktemp --tmpdir -d git-debcherry.XXXXXX)
log=$tmpdir/log
touch $log
stat_only="no"
patch_dir=""
debug="no"
while : ; do
case $1 in
-h | --help)
usage
exit 0
;;
-d | --debug)
debug="yes"
shift
;;
-s | --stat)
stat_only="yes"
shift
;;
-o | --output-directory)
patch_dir=${2%/}
case $patch_dir in
/*)
;;
*)
patch_dir=$(pwd)/$patch_dir
esac
shift;
shift;
;;
--)
shift
break
;;
-*)
echo "Unknown option $1" >&2
usage
exit 1
;;
*)
break
;;
esac
done
if [ $# -lt 1 ]; then
usage
exit 1
fi
_time $LINENO
upstream=$1
if ! upstream_sha1=$(git rev-parse "$upstream" 2>$log); then
_die "bad or missing ref: $upstream"
fi
head=${2-$(git symbolic-ref HEAD)}
head=${head##refs/heads/}
if ! head_sha1=$(git rev-parse "$head" 2>$log); then
_die "bad or missing ref: $head"
fi
if [ "$stat_only" = "no" -a -n "$patch_dir" -a -e "$patch_dir" ]; then
_die "$patch_dir exists, not overwriting"
fi
declare -A unmerged
orig_git_dir=${GIT_DIR-$(pwd)/$(git rev-parse --git-dir)}
git clone "$orig_git_dir" "$tmpdir"/clone 1>$log || _die "clone failed"
export GIT_WORK_TREE=$tmpdir/clone
export GIT_DIR=$tmpdir/clone/.git
# note that origin here is $orig_git_dir, so the following does
# not do any network access
if git fetch origin refs/notes/commits:refs/notes/commits >/dev/null 2>&1; then
debug "git notes found in the repository\n"
NOTES=true
else
debug "no git notes found in the repository\n"
NOTES=false
fi
tmp_upstream=$(tmpbranch upstream $upstream_sha1)
tmp_head=$(tmpbranch head $head_sha1)
_checkout $tmp_head
trap '_cleanup' EXIT
# Remove any traces of .pc (from quilt) and ./debian on temporary
# branches. This avoids conflicts when dpkg-source tries to apply
# quilt patches. --prune-empty means we are only dealing with commits
# that do somehow touch upstream.
_time $LINENO
{
# git-filter-branch doesn't really understand GIT_WORK_TREE
# git_commit_non_empty_tree provided by git-filter-branch
cd $GIT_WORK_TREE
if ! git filter-branch -f --index-filter \
'git rm --ignore-unmatch --cached -r .pc debian' \
--commit-filter '\
NEW=$(git_commit_non_empty_tree "$@"); \
if $NOTES && [ -n "$NEW" ] && \
git notes show "$GIT_COMMIT" >/dev/null 2>&1 && \
! git notes show "$NEW" >/dev/null 2>&1; then \
git notes copy "$GIT_COMMIT" "$NEW"; \
fi; echo "$NEW"' \
"$tmp_upstream".."$tmp_head" 1>$log 2>&1 ; then
_die "filtering failed"
fi
}
_time $LINENO
# for every commit reachable from head, but not from
# upstream compute its patch-id (essentially sha1 of diff)
# and save a map back to the commit.
while read -r hash ; do
ident=$(patch_id $hash)
unmerged[$ident]=$hash
done < <(git rev-list --no-merges $tmp_upstream..$tmp_head )
_time $LINENO
# now delete any found upstream; note that this only gets exact
# matches, so partial application is not caught here
while read -r ident commit ; do
unset unmerged[$ident]
done < <(git log --patch --no-merges $tmp_upstream| git patch-id)
_time $LINENO
printf 'debcherry fixup patch\n\n' >> $tmpdir/message
debug "Starting test reverts at %s\n" $(git rev-parse HEAD)
initial_upstream=$(git rev-parse $tmp_upstream)
while read -r hash; do
ident=$(patch_id $hash)
if [ -n "${unmerged[$ident]}" ]; then
if [ -z "$(git format-patch --no-binary --stdout -1 $hash | lsdiff)" ]; then
shorthash=$(git rev-parse --short $hash)
message=$(git log --pretty=format:"%s" -1 $hash)
printf "skipping commit $shorthash <$ident>; empty or binary only.\n $message\n\n" >&2
continue
fi
# the patch should cherry pick against head, but produce no
# changes
if ! test_apply $hash HEAD 0 ; then
git --no-pager log --oneline -1 $hash >> $tmpdir/message
printf "\t - extra changes or conflicts\n" >> $tmpdir/message
continue
fi
# the patch should apply to upstream and produce some changes.
# XXX: note that this is a bit heuristic (i.e. wrong). It should
# really check against something like upstream with all of the
# patches so far applied.
if ! test_apply $hash $initial_upstream 1 ; then
git --no-pager log --oneline -1 $hash >> $tmpdir/message
printf "\t - no changes against upstream or conflicts\n" >> $tmpdir/message
continue
fi
if git revert --no-edit $hash 1>/dev/null 2>&1; then
echo "$hash" >> $tmpdir/patch-list
else
git revert --abort
git --no-pager log --oneline -1 $hash >> $tmpdir/message
printf "\t - conflict" >> $tmpdir/message
fi
fi
done < <(git rev-list --no-merges --topo-order $tmp_upstream..$tmp_head)
# this is where we want to build our patch series
SAVED_HEAD=$(git rev-parse HEAD)
_checkout $(git rev-parse "$tmp_upstream")
# group file deletions
DELETED_FILES=$(git diff --diff-filter=D --name-only $tmp_upstream $tmp_head);
if [ -n "$DELETED_FILES" ]; then
git rm $DELETED_FILES
git commit -m'File deletions'
fi
# HEAD is now like upstream, but with files deleted
base=$(git rev-parse HEAD)
git diff $base $SAVED_HEAD | filterdiff -x '[ab]/debian/*' --clean >> $tmpdir/diff
if [ -s $tmpdir/diff ]; then
git apply --whitespace=nowarn --index $tmpdir/diff || \
_die "fatal: apply failed"
git commit -F $tmpdir/message 1>/dev/null 2>&1 || \
_die "fatal: fixup commit failed"
fi
if [ -s $tmpdir/patch-list ]; then
while read -r hash ; do
debug "trying %s\n" $hash
if ! git cherry-pick --no-edit $hash 1>/dev/null 2>&1; then
if [ -z "$(git diff --cached)" ]; then
echo "skipping $hash; empty cherry-pick" >&2
continue
fi
_die "cherry-pick $hash failed"
fi
if $NOTES && git notes show $hash >/dev/null 2>&1; then
git notes copy $hash HEAD;
fi
if [ -z "$(git diff $base)" ]; then
base=$(git rev-parse HEAD);
debug "new base ${base}"
fi
done < <(tac $tmpdir/patch-list)
fi
if [ $stat_only = "yes" ]; then
git log --reverse --oneline --stat "$base"..HEAD
else
if [ -n "$patch_dir" ]; then
mkdir -p "$patch_dir" || _die "mkdir failed";
echo "# exported from git by git-debcherry" > "$patch_dir/series"
if PATCHES=$(git format-patch --no-signature --notes -o "$patch_dir" "$base"..HEAD ); then
if [ -n "$PATCHES" ]; then
echo "$PATCHES" | sed -e "s,$patch_dir/,,g" -e 's, ,\n,g' >> "$patch_dir/series"
else
echo "Warning: no patches exported"
fi
else
_die "git format-patch failed"
fi
else
git format-patch --no-signature --notes --stdout $base..HEAD
fi
fi
_time $LINENO
|