#!/usr/bin/python
# gitarch.py
#
# Copyright (C) 2012 Jan Stancek <jan@stancek.eu>
#
# 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; either version 2 of the License, or
# (at your option) any later version.
#
# 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-archive in pure python
"""
__author__ = """Jan Stancek"""


import socket
import errno
import os
import sys
import getopt
import traceback
import logging

log = logging
HDR_LEN_SIZE = 4
BUF_SIZE = 4096

def recv_safe(sock, bufSize, flags=0):
    n = 0
    while True:
        try:
            n = sock.recv(bufSize, flags)
            break
        except socket.error, (value, message):
            if value != errno.EINTR and value != errno.EAGAIN:
                raise
            else:
                log.debug('recv_safe: got an EINTR/AGAIN, trying again')
    return n

def send_safe(sock, data):
    n = 0
    while True:
        try:
            n = sock.send(data)
            break
        except socket.error, (value, message):
            if value != errno.EINTR and value != errno.EAGAIN:
                raise
            else:
                log.debug('send_safe: got an EINTR/AGAIN, trying again')
    return n

def send_fully(s, data):
    buflen = len(data)
    n = 0

    while buflen > 0:
        n = send_safe(s, data)
        if (n < 0):
            raise socket.error(0, 'send_safe() < 0')
        data = data[n:]
        buflen = buflen - n
    return n

def recv_fully(s, recvlen):
    data = ''
    n = recvlen

    while len(data) < recvlen:
        part = recv_safe(s, n)
        if part == '':
            data = ''
            break
        data += part
        n -= len(part)
    return data

def recv_git_msg(sock):
    msg_len = 0
    msg = ''
    msg_len_hex = recv_fully(sock, HDR_LEN_SIZE)
    try:
        if len(msg_len_hex) == HDR_LEN_SIZE:
            msg_len = int(msg_len_hex, 16)
            msg = recv_fully(sock, msg_len - HDR_LEN_SIZE)
    except ValueError:
        pass
    return (msg_len, msg)

def prep_git_req(host, basepath, treeish, format):
    op = 'git-upload-archive %s\0host=%s side-band side-band-64k\0' % (basepath, host)
    arg1 = 'argument %s\0' % treeish
    arg2 = 'argument --format=%s\0' % format
    return make_git_msg(op) + make_git_msg(arg1) + \
        make_git_msg(arg2) + make_git_msg('')

def make_git_msg(msg, sideBand=0):
    msglen = len(msg) + HDR_LEN_SIZE
    msglenhex = hex(msglen)[2:]
    msglenhex = msglenhex.zfill(4)
    return '%s%s' % (msglenhex, msg)

def get_archive(outname, host, port, basedir, treeish, format):
    ret = 0
    data_available = True
    log.debug('outname: %s, host: %s, port: %s, basedir: %s, treeish: %s, format: %s',
              outname, host, port, basedir, treeish, format)
    request = prep_git_req(host, basedir, treeish, format)
    log.info('Request for remote side: <%s>', request)

    try:
        outfile = open(outname, 'wb')
        sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        sock.connect((host, port))

        # send our request
        sent = send_fully(sock, request)
        if sent == 0:
            raise socket.error(0, 'Connection reset by peer')

        # server must reply ACK
        msg_len, msg = recv_git_msg(sock)
        if msg:
            msg = msg[:-1]
        log.debug('ACK/NAK: len: %d, <%s>', msg_len, msg)
        if msg_len < 8 or not msg.startswith('ACK'):
            raise socket.error(0, 'Server did not reply with ACK')

        # wait for empty msg
        while msg != '':
            msg_len, msg = recv_git_msg(sock)
            log.debug('got header: len: %d, <%s>', msg_len, msg)

        while True:
            msg_len, msg = recv_git_msg(sock)
            log.debug('got: len: %d', msg_len)

            if msg_len == 0 or msg == '':
                break

            sideband_type = msg[0]
            msg = msg[1:]
            if sideband_type == '\1':
                outfile.write(msg)
            elif sideband_type == '\2':
                log.info('sideband(2): %s', msg)
            elif sideband_type == '\3':
                log.info('sideband(3): %s', msg)

        outfile.flush()
        outfile.close()
        sock.close()
    except socket.error, (value, message):
        log.error('socket.error %s %s', value, message)
        ret = 2
    except IOError, e:
        log.error('ioerror %s', e)
        ret = 3

    log.info('_get_archive ret_code: %d', ret)
    return ret

def get_archive_check(outname, host, port, basedir, treeish):
    ret = 1
    format = outname[-3:]

    if format != 'zip' and format != 'tar':
        return ret

    try:
        log.info('Getting archive %s %s %s %s %s %s', outname, host, port, basedir, treeish, format)
        ret = get_archive(outname, host, port, basedir, treeish, format)

        if ret == 0:
            if format == 'zip':
                ret = os.system('zip -T %s > /dev/null' % outname)
            if format == 'tar':
                ret = os.system('tar -tf %s > /dev/null' % outname)
    except:
        traceback.print_exc()

    log.info('archive test ret_code: %d', ret)
    return ret

def install_from_git(host, port, basedir, treeish, outname, targetdir, verbose = False, force = False):
    log.info('install_from_git %s %s %s %s %s %s',
             host, port, basedir, treeish, outname, targetdir)

    #check to see if the directory already exists and abort
    if os.path.exists(targetdir) and not force:
        log.info('Already Installed: %s' % os.path.basename(targetdir))
        return 0

    log.info('Installing: %s' % os.path.basename(targetdir))
    ret = get_archive_check(outname, host, port, basedir, treeish)
    if ret == 0:
        ret = install_archive(outname, targetdir, verbose)
        if ret != 0:
            ret = 2
    return ret

def install_archive(outname, targetdir, verbose):
    ret = 1
    log.info('Installing archive %s to %s', outname, targetdir)
    if not os.path.exists(targetdir):
        log.debug('Creating dir %s', targetdir)
        os.makedirs(targetdir)
        if not os.path.exists(targetdir):
            return ret

    archtype = outname[-3:]
    if archtype == 'zip':
        ret = install_zip(outname, targetdir, verbose)
    if archtype == 'tar':
        ret = install_tar(outname, targetdir, verbose)
    log.debug('install_archive ret code: %s', ret)
    return ret

def install_zip(outname, targetdir, verbose):
    if verbose:
        quiet_flag = ''
    else:
        quiet_flag = '-q'
    cmd = 'unzip %s -o %s -d %s' % (quiet_flag, outname, targetdir)
    log.debug(cmd)
    ret = os.system(cmd)
    return ret

def install_tar(outname, targetdir, verbose):
    cmd = 'tar -C %s --overwrite xf %s' % (targetdir, outname)
    log.debug(cmd)
    ret = os.system(cmd)
    return ret

user_options = [('help', 'prints help'),
                ('outname', 'output file name, must end with tar or zip'),
                ('hostname', 'target host where git daemon is running'),
                ('port', 'port where git daemon is running'),
                ('basedir', 'location of git repo on host'),
                ('treeish', 'git tree-ish, which identifies some tree'),
                ('debug', 'show debug messages'),
                ('quiet', 'less messages'),
                ]

def usage():
    print 'gitarch.py: git-archive in python'
    print ''
    print 'Common switches:'
    for line in user_options:
        print '    --%s %s' % (line[0], line[1])
    print
    print 'Example: '
    print '  You have your git repo on host (127.0.0.1) at /usr/src/linux-stable'
    print '  You start git daemon:'
    print '      git-daemon --verbose --base-path=/usr/src --enable=upload-archive --export-all /usr/src/linux-stable'
    print '  Using git you would do:'
    print '      git archive --remote=git://127.0.0.1/linux-stable master:scripts > /tmp/a.tar'
    print '  Using gitarch.py:'
    print '      gitarch.py --outname=/tmp/a.tar --hostname=localhost --basedir=/linux-stable --treeish=master:scripts'


def main():
    outname = None
    hostname = None
    port = 9418
    basedir = None
    treeish = None
    debug = False
    verbose = False
    quiet = False

    try:
        opts, args = getopt.getopt(sys.argv[1:],
                                   'hdqv',
                                   ['help',
                                    'outname=',
                                    'hostname=',
                                    'port=',
                                    'basedir=',
                                    'treeish=',
                                    'debug',
                                    'verbose',
                                    'quiet'])
    except getopt.GetoptError, err:
        print str(err)
        usage()
        sys.exit(2)

    for o, a in opts:
        if o in ('-h', '--help'):
            usage()
            sys.exit()
        elif o in ('-d', '--debug'):
            debug = True
        elif o in ('-q', '--quiet'):
            quiet = True
        elif o in ('-v', '--verbose'):
            verbose = True
        elif o in ('--hostname='):
            hostname = a
        elif o in ('--outname='):
            outname = a
        elif o in ('--port='):
            port = a
        elif o in ('--basedir='):
            basedir = a
        elif o in ('--treeish='):
            treeish = a
        else:
            print o, a
            assert False, 'unhandled option'

    if not outname or not hostname or not port or not basedir or not treeish:
        usage()
        sys.exit()

    LOG_FORMAT = '%(asctime)s - %(levelname)s - %(filename)s - ' \
        '%(funcName)s:%(lineno)s - %(message)s'
    if quiet:
        LOG_LEVEL = log.ERROR
    elif verbose:
        LOG_LEVEL = log.INFO
    elif debug:
        LOG_LEVEL = log.DEBUG
    else:
        LOG_LEVEL = log.WARN
        LOG_FORMAT = '%(message)s'

    formatter = logging.Formatter(LOG_FORMAT)
    stdout_handler = logging.StreamHandler(sys.stdout)
    stdout_handler.setFormatter(formatter)
    logger = logging.getLogger('')
    logger.addHandler(stdout_handler)
    logger.setLevel(LOG_LEVEL)

    ret = get_archive_check(outname, hostname, port, basedir, treeish)
    if ret > 255 or ret < 0:
        ret = 1
    sys.exit(ret)

if __name__ == '__main__':
    main()

Back to top
gitarch.py.txt · Last modified: 2015/10/05 16:06 by Jan Stancek
Sitemap Search: