#!/usr/bin/bash

# gp_setup
# This is part of the Mer git-packaging suite
# It sets up a git repo to use git-pkg and can use
# existing packaging

. ${GP_LIB:=/usr/share/gitpkg}/gp_common

trap cleanup EXIT

usage() {
    cat <<EOF
    git pkg
       Used to setup an existing git repo for git-pkg

       $DISTRO is set either in ~/.gitpkg, the GITPKG_DISTRO
       environment variable or using --distro=<DISTRO>. If not
       specified 'mer' is used.

EOF
    # keep the usage msg near the code:
    usage_pkg
    usage_existing
}

confirm_continue() {
    echo "Press <enter> to continue or ^C"
    read dummy
}

unpack_tarball() {
    rm -rf *
    case $1 in
	*xz )
	    xzcat $1 | tar --transform 's_[^/]*/__' -xf -
	    ;;
	* )
	    tar --transform 's_[^/]*/__' -xf $1
	    ;;
    esac
}

usage_existing() {
cat <<EOF
    --pkgdir=<packaging dir> 
    [--pristine] --base-on=<tag>|--unpack-to=<tag>
    [--patch-branch=<branch/tag>]
    [--ver=<ver>[-<rel>]]

       Takes an existing set of packaging and applies it to some git repo.
       This can be used in 3 ways:
         1) with an upstream git and some packaging/patches
         2) with an upstream tarball and some packaging/patches
         3) with an upstream git, an upstream tarball and some packaging/patches
            (eg where upstream does some post-processing after a git
            checkout)

       --base-on=<basetag>
                   The existing tag to base the packaging on.
                   Typically the upstream release tag.

                   If --pristine is used then it looks in <packaging
                   dir> for a tarball and uses that as the basis for
                   the build.

       --unpack-to=<basetag>
                  Uses an empty git (implies --pristine); looks in
                  <packaging dir> for a tarball and uses that as the
                  basis for the build.  Unpacks the tarball as an
                  initial commit and applies the given tag to the
                  unpacked tarball.

       --ver=<ver>[-<rel>]
                  If the upstream <basetag> is not the X.Y.Z version
                  then specify the version and optionally the release.
                  (Could come from spec/.changes?)

       --pkgdir=<packaging dir>
                  is the source dir for the spec/yaml/changes/patches
                  This could be a 'checked out' osc dir.
                  Any patches mentioned in the spec file and found in
                  pkgdir are applied at <tag> in the order they appear
                  in the spec file.

       --fuzz=<fuzz>
                  Can be useful if the patches found in pkgdir don't
                  apply cleanly.

       --use-all-packaging
                  Use this flag if pkgdir has been carefully prepared
                  to remove unwanted patches and tarballs but still
                  contains .gz / .bz2 / .xz files which would
                  otherwise be discarded

       --pristine This allows an upstream git tree to be used in
                  conjunction with pristine-tar. It is useful when
                  upstream modify the source which is tagged in
                  git. Upstream modifications are preserved as an
                  initial commit which ensures that when mer-master is
                  checked-out it contains the same information as the
                  pristine tarball. For build purposes the actual
                  pristine tarball is used together with any
                  patches. Typically use with --base-on to specify a
                  suitable tag.

EOF
}

use_existing() {

    gp_type="git"
    unpack=
    FUZZ="--fuzz=2"
    # better option parsing welcomed

    while [[ $1 ]]; do
	case $1 in
	    -V)
		_V=1
		_MKPKG_OPTS="-V"
		shift ;;
	    --pristine )
		gp_type="pristine-tar"
		shift ;;
	    --base-on* )
		basetag=${1#*=}
		# if tag is empty, complain
		shift ;;
	    --unpack-to=* )
		# Would be nice to default to tag=version
		unpack=true
		basetag=${1#*=}
		# Make a git repo if there isn't one
		if ! [[ -d .git ]]; then git init; fi
		shift ;;
	    --pkgdir=* )
		pkgdir=${1#*=}
		shift ;;
	    --ver=* )
		# Would be nice to default to tag=version-1
		verrel=${1#*=}
		force_rel=${verrel##*-}
		force_ver=${verrel%-*}
		shift ;;
	    --fuzz=* )
		# patch application fuzz
		FUZZ="--fuzz=${1#*=}"
		shift ;;
	    --use-all-packaging )
		USE_ALL_PKG=true
		shift ;;
	    * )
		fatal "Error $1 not recognised";;
	esac
    done

    # If --unpack-to didn't make one then complain if we're not in a git repo
    [[ -d .git ]] || {
	fatal "This is not a git repository; use gp_setup when you've cloned or created a normal src repo that is ready for packaging".
    }

    [[ $pkgdir ]] || { fatal "You must supply --pkgdir when using --auto"; }
    [[ -d $pkgdir ]] || { fatal "Supplied --pkgdir '$pkgdir' is not a directory"; }
    pkgdir=$(cd $pkgdir;pwd)
    # Determine version/release numbers
    get_info_from_packaging $pkgdir

    if [[ "$ver" ]]; then 
	# When version is found and basetag is not set set version to basetag
	if [[ ! "$basetag" ]]; then
	    basetag=$ver
	fi
    else
	# If version is not found one must define base tag.
	[[ $basetag ]] || { fatal "basetag not supplied. You must use either --base-on= or --unpack-to= "; }
    fi

    name=${name:-unnamed}
    rel=${force_rel:-${rel:-1}}
    ver=${force_ver:-${ver:-$basetag}}
    ver_pattern='^[0-9.]*$'
    if ! [[ $ver =~ $ver_pattern ]]; then
	echo "WARNING: Version is set to $ver which is not a simple X.Y.Z version"
	confirm_continue
    fi
    if [[ $(echo $ver | tr -dc '[:digit:]') != $(echo $basetag | tr -dc '[:digit:]') ]]; then
	echo "WARNING: Version is $ver and basetag is $basetag : the numbers don't match"
	confirm_continue
    fi

    src_tag=$DISTRO-${ver}
    patches_tag=$DISTRO-${ver}-${rel}
    pkg_tag=pkg-$DISTRO-${ver}-${rel}

    pbranch="${DISTRO}-master"

    if ! [[ -d .git ]]; then
	fatal "This is not a git repository"
    fi

    # Verify there's only a single spec or yaml file.
    numspec=$(ls $pkgdir/*spec 2>/dev/null | wc -l)
    numyaml=$(ls $pkgdir/*yaml 2>/dev/null | wc -l)
    if [[ $numspec -gt 1 ]] || [[ $numyaml -gt 1 ]]; then
	fatal "gp_setup doesn't support packaging with >1 .spec or .yaml file"
    fi

    # Without spec file there isn't packaging.
    if [[ "$numspec" -eq "0" ]]; then
	fatal "No spec file found from '$pkgdir/'"
    fi

    # ... and record it
    specfile=$(cd $pkgdir; ls *.spec 2>/dev/null)
    yamlfile=$(cd $pkgdir; ls *.yaml 2>/dev/null)

    # Guess the tarball
    tarball=$(cd $pkgdir; find * -name '*t*bz2' -o -name '*t*gz' -o -name '*t*xz')
    tarball=${tarball:-$name.tar.bz2}
    log "Using tarball: $tarball"

    # If this is an unpack then it is pristine-tar with no
    # git tree so unpack the tarball as a starting point
    if [[ $unpack ]] ; then
	echo "Unpack so setting pristine-tar : Unpacking tarball as initial commit"
	gp_type="pristine-tar"
	unpack_tarball $pkgdir/$tarball
	git add .
	git commit -q -m"Initial commit from $tarball"
	# Create a tag for the tarball - it's OK to do it now in unpack mode
	git_tag_cleanly $basetag
    fi

    # Branch from the appropriate tag
    git checkout -q -f $basetag
    # We tag now since gp_mkpkg needs it
    git_tag_cleanly $src_tag

    # Ensure the patches branch is rooted at basetag
    git branch -f $pbranch
    branch_cleanup_list+=" $pbranch"
    git checkout $pbranch

    # Pristine tar approach needs some extra work
    # 1. Commit the tarball in packaging to pristine tar
    # 2. Replace the checked-out tree with the tarball
    # 3. Commit any deltas (empty if needed)
    # 4. Ready for patches
    if [[ $gp_type == "pristine-tar" ]] ; then
	echo "Pristine tar: removing all files and unpacking tarball to record any delta to $basetag"
	pristine-tar commit $pkgdir/$tarball $basetag
	branch_cleanup_list+=" pristine-tar"
	unpack_tarball $pkgdir/$tarball
	git add .; git ls-files --deleted | xargs --no-run-if-empty git rm
	git commit -q --allow-empty -m"pristine-tar-delta: Import any changes from the released tarball into the $DISTRO source tree ready for local patches"
	have_patches=1
    fi

    # Apply the patches mentioned in the spec file in the order they appear
    # We'll probably need a --fuzz option at some point
    # Keep the dir clean and we should abort/warn when patches don't apply
    for patch in $(cd $pkgdir; grep Patch.*: *spec | cut -f2- -d:); do
	log Applying $patch
	patch=$(basename $patch)
	if ! patch $FUZZ --batch -p1 --no-backup-if-mismatch -s < $pkgdir/$patch; then
	    log "Failed to apply -p1 - trying -p0"
	    if ! patch $FUZZ --batch -p0 --no-backup-if-mismatch -s < $pkgdir/$patch; then
		echo "Patch $pkgdir/$patch seemed to apply badly - fixup in a different terminal and press return to commit it and continue"
		read dummy
	    fi
	fi
	git add --all . # Ensure we get any added/removed files too
	# Use the filename as a comment - remove any initial '0003-' numbering
	git commit -q -am"$(basename $patch .patch | sed 's/^[0-9]*-//')"
	have_patches=1
    done

    # Only do this next section if we actually found patches to apply
    if [[ $have_patches ]] ; then
	# We need to tag the patch branch head
	git_tag_cleanly $patches_tag

        # Pristine tar needs to skip the pristine-tar-delta patch
	if [[ $gp_type == "pristine-tar" ]] ; then
	    tag1="${pbranch}~$(($(count_commits $basetag $pbranch ) - 1))"
	else
	    tag1=$src_tag
	fi

        # We now have all we need to create the _src file
	_src="$gp_type:$tarball:$src_tag:$patches_tag"
    else
	# No patches variant of _src
	_src="$gp_type:$tarball:$src_tag"
    fi

    prepare_pkg_branch
    ( cd rpm

        # Copy all the packaging files in (assuming no hidden files)
	cp $pkgdir/* .
        # Clean up the tarball we found and any patches which are now
        # autogenerated
	# Allow --use-all-packaging to override complex cases
	if [[ ! $USE_ALL_PKG ]] ; then
	    rm -f *t*gz *t*bz2 *t*xz
	    rm -f *patch
	fi

        # Provide patch information for yaml/spec
	if [[ -e $yamlfile ]]; then
	    if [[ $gp_type != "pristine-tar" ]] ; then
		echo "SetupOptions: -q -n src"  >> $yamlfile
	    fi
	    gp_mkpkg --no-checkout --no-tarball $_MKPKG_OPTS || fatal "gp_mkpkg failed"

	    specify -s -N -n $yamlfile
	else
	    if [[ $gp_type == "pristine-tar" ]] ; then
		gp_mkpkg -n --no-tarball  --no-src $_MKPKG_OPTS || fatal "gp_mkpkg failed"
	    else
		gp_mkpkg -n --no-tarball $_MKPKG_OPTS || fatal "gp_mkpkg failed"
	    fi
	fi

        # Everything else is normal packaging
	git add * >/dev/null 2>&1

        # And finally a commit
	git commit -q -m"Initial git package of $basetag as $pbranch"
	git_tag_cleanly $pkg_tag

        # Comment on what we've created
	log "Used '$(cat _src)' for _src."
    )
}

usage_pkg(){
cat <<EOF
   git pkg --manual [--distro=<DISTRO>] \
           [--pkgdir=<packaging dir>]

   The --manual mode is used when you want to work on the packaging
   before doing a commit.

    --manual
       Used in an existing git repo this creates a pkg branch
       called pkg-$DISTRO with a suitable .gitignore and a _src
       file.
       The user must then add packaging files to this branch and
       manually create tags. --pkgdir can be used to copy in some
       packaging

       [--pkgdir=<packaging dir>]
          Any files in this dir are copied into the rpm dir

          This could be a 'checked out' osc dir.

EOF
}

do_pkg() {
    [[ -d .git ]] || {
	fatal "This is not a git repository; Initialize the repository with 'git init'".
    }

    pkgdir=
    while [[ $1 ]]; do
	case $1 in
	    --pkgdir=* )
		pkgdir=${1#*=}
		[[ -d $pkgdir ]] || { fatal "Supplied --pkgdir '$pkgdir' is not a directory"; }
		pkgdir=$(cd $pkgdir;pwd)
		shift ;;
	    * )
		fatal "Error $1 not recognised" ;;
	esac
    done

    prepare_pkg_branch

    cd rpm

    if [[ $pkgdir ]]; then
	cp $pkgdir/* .
	tarball=$(find * -name '*t*bz2' -o -name '*t*gz' -o -name '*t*xz')
    fi
    tarball=${tarball:-name-version.tar.bz2}

    cat <<EOF

Now you have an rpm/ dir with ${pkg_branch} branch

You should create suitable packaging in this dir.
See https://wiki.merproject.org/wiki/Git_pkg for more help.
EOF
}

# Create an rpm dir and an orphaned packaging branch
prepare_pkg_branch() {
    # Verify there's no packaging branch already
    if git show-ref --verify --quiet refs/heads/${pkg_branch}; then
	fatal "${pkg_branch} branch exists already"
    fi

    ensure_rpm_subdir
    cp .git/HEAD rpm/.git/HEAD
    (cd rpm
        # Now create the orphaned pkg-* branch
	branch_cleanup_list+=" $pkg_branch"
	git checkout --orphan ${pkg_branch}
	git rm -q -rf .
	git commit --allow-empty -m"Initial packaging branch (empty)"

	# Add a dummy _src file
	echo ${_src:-git:pkg.tar.bz2:srctag[:patchestag]} > _src
	git add _src
    )
}

case $1 in
    --usage | --help )
	usage ;;
    --manual )
	shift
	do_pkg "$@" ;;
    * )
	use_existing "$@" ;;
esac

trap - EXIT
