#!/usr/bin/python -tt
# vim: ai ts=4 sts=4 et sw=4
#
# Copyright (C) 2010, 2011, 2012, 2013, 2014 Intel, Inc.
#
#    This program is free software; you can redistribute it and/or
#    modify it under the terms of the GNU General Public License
#    as published by the Free Software Foundation; version 2 of the License.
#
#    This program is distributed in the hope that it will be useful,
#    but WITHOUT ANY WARRANTY; without even the implied warranty of
#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
#    GNU General Public License for more details.
#
#    You should have received a copy of the GNU General Public License
#    along with this program; if not, write to the Free Software
#    Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
#
"""
Git module inherited from GitRepository and self customed
"""

import os, sys
import shutil

from common import runner
from common.utils import retry
from common.utils import Workdir

from gbp.git.repository import GitRepository
from gbp.git.repository import GitRepositoryError
import gbp.log as log
from gbp.git.args import GitArgs

class GitError(Exception):
    """Local exception."""
    pass

class Git(GitRepository):
    """The Git class wrappered from GitRepository"""
    def __init__(self, path):
        GitRepository.__init__(self, path)
        log.setup('auto', log.DEBUG)
        self._git_dir = os.path.join(path, '.git')

    def _exec_git(self, command, args=[]):
        """Exec a git command and return the output
        """

        if self._git_dir:
            cmd = ['git --git-dir=%s ' % self._git_dir, command] + args
        else:
            cmd = ['git ', command] + args

        cmdln = ' '.join(cmd)
        #print('run command: %s' % cmdln)

        with Workdir(self.path):
            ret, outs = runner.show(cmdln)

        #if ret:
        #    raise errors.GitError("command error for: %s" % cmdln)

        return ret, outs

    def get_tag(self, tag_name):
        """Get tag info though tag name"""
        outs, err, ret = self._git_inout('show',
                                         ['-s', '--pretty=format:###',
                                         tag_name])
        if ret:
            raise GitError('Error running git show -s '\
                           '--pretty=format:### %s: %s' % (tag_name, err))
        tag = {}
        msg = ''
        msgstub = False
        for line in outs.splitlines():
            if not line or not line.strip():
                continue
            if msgstub:
                if line.startswith('###') or line.startswith(
                        '-----BEGIN PGP SIGNATURE-----'):
                    tag['message'] = msg.strip()
                    break
                else:
                    msg += '%s\n' % line
            else:
                if line.startswith('Tagger: '):
                    tag['author'] = line[len('Tagger: '):line.index(' <')]
                    tag['email'] = line[line.index(' <')+2:line.index('>')]
                    msgstub = True

        try:
            tag['commitid'] = self.rev_parse('%s^{commit}' % tag_name)
            tag['tagrevision'] = self.rev_parse('%s' % tag_name)
        except GitRepositoryError, err:
            raise GitError('Error running rev parse %s^{commit}: %s' % \
                            (tag_name, str(err)))

        return tag

    def branch_contains(self, cmitid):
        """shows only the branches that contain the named commit(commitid)"""
        outs, _, ret = self._git_inout('branch',
                                       ['-a', '--contains', cmitid])
        if not ret:
            branches = []
            for branch in outs.splitlines():
                if branch.find('remotes/origin/HEAD ->') != -1:
                    continue
                branch = branch.strip().lstrip('* ')
                if branch.startswith('remotes/origin/'):
                    branch = branch[len('remotes/origin/'):]
                if branch not in branches:
                    branches.append(branch)
            print 'git branch contains %s' % branches
            return branches

    def create_overwrite_commit(self, branch, commit):
        ret, outs = self._exec_git('log', ['-n1', '--pretty=%H','origin/%s' %(branch), '--'])
        if ret != 0:
            print('not found commit of %s' % branch)
            return (False, '')
        deprecated_commit = outs.strip()
        ret, outs = self._exec_git('diff', ['--stat', '--exit-code', deprecated_commit, commit])
        if ret == 0:
            print('%s branch is same as %s, no create overwrite commit' % (branch, commit))
            return (True, '')
        ret, outs = self._exec_git('log', ['-n1', '--pretty=%T', commit])
        if ret != 0:
            print('not found tree object of %s' % commit)
            return (False, '')
        tree_object = outs.strip()
        msg = '"[SCM] %s branch is overwritten by %s"' % (branch, commit)
        ret, outs = self._exec_git('commit-tree', [tree_object, '-p', commit, '-p', deprecated_commit, '-m', msg])
        if ret != 0:
            print('commit-tree failed(tree:%s, parent: %s %s)' % (tree_object, commit, deprecated_commit))
            return (False, '')
        overwrite_commit = outs.strip()
        return (True, overwrite_commit)

    def _get_branches(self):
        """Return the branches list, current working branch is the first
        element.
        """
        branches = []
        for line in self._exec_git('branch', ['--no-color'])[1].splitlines():
            br = line.strip().split()[-1]

            if line.startswith('*'):
                current_branch = br

            branches.append(br)

        return (current_branch, branches)

    def get_branches(self):
        if not self.cur_branch or not self.branches:  #pylint: disable=access-member-before-definition
            self.cur_branch, self.branches = \
                self._get_branches()

        return (self.cur_branch, self.branches)

    def has_branch(self, br, remote=False):
        """Check if the repository has branch 'br'
          @param remote: only liste remote branches
        """

        if remote:
            options = [ '--no-color', '-r' ]

            for line in self._exec_git('branch', options)[1].splitlines():
                rbr = line.strip().split()[-1]
                if br == rbr:
                    return True

            return False

        else:
            return (br in self.get_branches()[1])

    def remove_tag_from_remote(self, remote, tag):
        """Remove tag from the remote
          @param remote: complete url
        """

        args = GitArgs(remote, ':' + tag)
        self._git_command("push", args.args)

    def get_commit_info_from_id(self, commitid):
        data =  {"commit_id": None, \
                "committer": None, \
                "commit_date": None, \
                "commit_message": None}
        try:
            t_outs, t_err, t_code = self._git_inout('log', \
                ['-1', '--date=iso', \
                 '--format=%H\n%aE\n%ad\n%s\n%b', \
                 commitid])
            log_list = t_outs.rstrip().split('\n')
            data["commit_id"]      = log_list[0]
            data["committer"]      = log_list[1]
            data["commit_date"]    = log_list[2]
            data["commit_message"] = log_list[3]
        except Exception as err:
            print repr(err)
        return data

    def get_tag_info(self, tag):
        data = {"tag_name": None,
                "tag_rev": None,
                "tagger": None,
                "tag_date": None,
                "tag_message": None}
        try:
            t_outs, t_err, t_code = self._git_inout('for-each-ref', ['--sort=-taggerdate', '--count=1', \
                '--format=%(refname)%0a%(objectname)%0a%(taggeremail)%0a%(taggerdate:iso)%0a%(subject)', \
                tag])
            log_list = t_outs.rstrip().split('\n')
            data["tag_name"]    = log_list[0]
            data["tag_rev"]     = log_list[1]
            data["tagger"]      = log_list[2].replace('<','').replace('>','')
            data["tag_date"]    = log_list[3]
            data["tag_message"] = log_list[4]
        except Exception as err:
            print repr(err)
        return data

def _update_gitproject(localdir, gitpath=None):
    """Fetch latest code to local dir"""

    print '\nUpdating local git: %s' % localdir
    try:
        localgit = Git(localdir)

        repo_fail = True
        if localgit.get_remote_repos():
            repo_origin = localgit.get_remote_repos().get('origin')[0]
            if repo_origin.endswith('.git'):
                repo_origin = repo_origin[:-4]
            if repo_origin == gitpath:
                repo_fail = False
        if gitpath and repo_fail:
            shutil.rmtree(localdir)
            return False

        print 'OK. We have local git repo and trying to update it'
        sys.stdout.flush()
        if localgit.bare:
            localgit.fetch(tags=True, all_remotes=True)
            # Current gbp does not support --prune option, Call it directly.
            args = GitArgs('--quiet')
            args.add_true(True, '--prune')
            args.add_true(True, '--all')
            localgit._git_command('fetch', args.args)
        else:
            localgit.fetch(tags=True)
            try:
                localgit.pull()
            except GitRepositoryError, err:
                print('pull exception: ', err)
                return True
    except GitRepositoryError, gre:
        print('git execption: ', gre)
        shutil.rmtree(localdir)
        return False

    return True

@retry()
def _clone_gitproject(giturl, gerritprj, localdir, bare=False, git_cache_dir=None):
    """Clone gerrit project from remote to local dir"""

    result = True

    # fetch latest code if local dir already exists
    if os.path.isdir(localdir) and \
            _update_gitproject(localdir, gitpath="%s/%s" %(giturl, gerritprj)):
        return True

    try:
        if git_cache_dir is None:
            cache_dir = os.path.join(os.getenv('GIT_CACHE_DIR'), gerritprj) + '.git'
        else:
            cache_dir = os.path.join(git_cache_dir, gerritprj) + '.git'

        print 'No local repo or updating it failed... Use .git instead into %s' % cache_dir
        sys.stdout.flush()
        if os.path.isdir(cache_dir):
            # use local cache repo as reference to clone
            gitcmd = 'git clone %s/%s --reference %s %s %s' % \
                   (giturl, gerritprj, cache_dir,
                    localdir, '--mirror' if bare else '')
        else:
            gitcmd = 'git clone %s/%s %s %s' % \
                   (giturl, gerritprj, localdir,
                    '--mirror' if bare else '')

        print 'Cloning it with gitcmd: %s' % gitcmd
        sys.stdout.flush()
        if runner.show(gitcmd)[0] != 0:
            result = False
    except (TypeError, AttributeError, OSError), err:
        result = False
        print '\nExcept occur when clone gerrit project'
        print err

    if not result:
        print '\nClone gerrit project Failed.'
        shutil.rmtree(localdir, ignore_errors=True)

    return result

def clone_gitproject(gerritprj, localdir, giturl=None, bare=False, gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None, git_cache_dir=None):
    """Clone gerrit project from remote to local dir"""
    if not giturl:
        if not gerrit_hostname or not gerrit_username or not gerrit_sshport:
            giturl = 'ssh://%s@%s:%s' % (os.getenv('GERRIT_USERNAME'),
                                        os.getenv('GERRIT_HOSTNAME'),
                                         os.getenv('GERRIT_SSHPORT'))
        else:
            giturl = 'ssh://%s@%s:%s' % (gerrit_username,gerrit_hostname,gerrit_sshport)


    return _clone_gitproject(giturl, gerritprj, localdir, bare, git_cache_dir)


def fetch_change(gerritprj, localdir, refspec, giturl=None, bare=False, gerrit_hostname=None, gerrit_username=None, gerrit_sshport=None, git_cache_dir=None):
    """Fecth and checkout change to local dir"""

    if not giturl:
        if not gerrit_hostname or not gerrit_username or not gerrit_sshport:
            giturl = 'ssh://%s@%s:%s/%s' % (os.getenv('GERRIT_USERNAME'),
                                           os.getenv('GERRIT_HOSTNAME'),
                                           os.getenv('GERRIT_SSHPORT'),
                                           gerritprj)
        else:
            giturl = 'ssh://%s@%s:%s/%s' % (gerrit_username,gerrit_hostname,gerrit_sshport,gerritprj)

    git = Git.create(localdir, bare)
    git.fetch(repo=giturl, refspec=refspec)
    git.checkout('FETCH_HEAD')

