[Python-Dev] Checkin Utility

M.-A. Lemburg mal@lemburg.com
Wed, 28 Jun 2000 19:22:40 +0200


This is a multi-part message in MIME format.
--------------4A599D73348784E859687D01
Content-Type: text/plain; charset=us-ascii
Content-Transfer-Encoding: 7bit

I thought you might have some use for the attached utility. It's
a simple script which helps with checking in patches which I've
been using for a while now. It also hides the CVS options and
parameters away as far as possible, so that CVS knowledge is
not really needed to complete the task.

-- 
Marc-Andre Lemburg
______________________________________________________________________
Business:                                      http://www.lemburg.com/
Python Pages:                           http://www.lemburg.com/python/
--------------4A599D73348784E859687D01
Content-Type: text/python; charset=us-ascii;
 name="checkin.py"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline;
 filename="checkin.py"

#!/usr/local/bin/python -u
"""
    Tools for (interactive) CVS checkins.

    (c) Marc-Andre Lemburg, mal@lemburg.com, All Rights Reserved.

    Permission to use, copy, modify, and distribute this software and its
    documentation for any purpose and without fee or royalty is hereby granted,
    provided that the above copyright notice appear in all copies and that
    both that copyright notice and this permission notice appear in
    supporting documentation or portions thereof, including modifications,
    that you make.

    THE AUTHOR MARC-ANDRE LEMBURG DISCLAIMS ALL WARRANTIES WITH REGARD TO
    THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
    FITNESS, IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL,
    INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING
    FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
    NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION
    WITH THE USE OR PERFORMANCE OF THIS SOFTWARE !

"""#"

import string,os,exceptions,tempfile,sys

__version__ = '0.3'

### Configuration

# CVS Options which will always be used
CVSOPTIONS = '-z3'

# Path of the cvs tool
CVS = "/usr/bin/cvs"

# Default name of patcher
default_author = 'Marc-Andre Lemburg'
default_email = 'mal@lemburg.com'

# Check the settings
if not os.path.exists(CVS):
    CVS = 'cvs'

# Debug ?
__debug__ = 0

### Errors

class CVSError(exceptions.StandardError):
    pass

### Low Level interface

def cvs(command, flags=(), files=(), options=(), test=0):

    if options:
        options = '-' + string.join(options, ' -')
    else:
        options = ''
    if flags:
        flags = '-' + string.join(flags, ' -')
    else:
        flags = ''
    cmd = '%s %s %s %s %s %s' % (CVS,
                                 CVSOPTIONS,
                                 options,
                                 command,
                                 flags,
                                 string.join(files, ' '))
    if test:
        print '* exec "%s"' % cmd
        return ''
    try:
        p = os.popen(cmd)
    except os.error,why:
        raise CVSError, why
    return p.read()

def check_files(files=('.',), modify=0):

    options = options=('q',)
    if not modify:
        options = options + ('n',)
    output = cvs('update', files=files, options=options)
    lines = string.split(output, '\n')
    added_files = []
    modified_files = []
    updated_files = []
    patched_files = []
    for line in lines:
        if not line:
            continue
        try:
            marker, filename = string.split(line)
        except ValueError:
            if __debug__:
                print '* could not parse: "%s"' % line
            continue
        if marker == '?':
            list = added_files
        elif marker == 'M':
            list = modified_files
        elif marker == 'U':
            list = updated_files
        elif marker == 'P':
            list = patched_files
        else:
            if __debug__:
                print '* unkown marker: "%s" for file "%s"' % \
                      (marker, filename)
            continue
        list.append(filename)
    return added_files, modified_files, updated_files, patched_files

def update_files(files=('.',), verbose=0):

    return check_files(files, modify=1)

def print_check_results(a, m, u, p):

    if a:
        print 'Added to work dir:'
        for filename in a:
            print '  ',filename
    if m:
        print 'Modified in work dir:'
        for filename in m:
            print '  ',filename
    if u:
        print 'Updated in repository:'
        for filename in u:
            print '  ',filename
    if p:
        print 'Patched in repository:'
        for filename in p:
            print '  ',filename

def checkin_files(files, message='', rev=''):

    options = ('Q',)
    flags = ()
    if message:
        tempname = tempfile.mktemp()
        open(tempname, 'w').write(message)
        flags = flags + ('F %s' % tempname,)
    else:
        tempname = ''
    if rev:
        flags = flags + ('r %s' % rev,)
    try:
        if not __debug__:
            output = cvs('commit', files=files, options=options, flags=flags)
        else:
            output = cvs('commit', files=files, options=options, flags=flags,
                         test=1)
            print message
        if output:
            if __debug__:
                print '* commit output:'
                print output
    finally:
        os.unlink(tempname)

def checkin_file(file, message='', rev=''):

    return checkin_files((file,), message, rev)
    
def add_files(files):

    if not __debug__:
        output = cvs('add', files=files, options=('Q',))
    else:
        output = cvs('add', files=files, options=('Q',), test=1)
    if output:
        if __debug__:
            print '* add output:'
            print output

def add_file(file):

    return add_files((file,))

def multiline_input(prompt='', term='.', linesep='\n'):

    lines = []
    while 1:
        line = raw_input(prompt)
        if not lines and \
           not line:
            return ''
        if line == term or \
           lines and lines[-1] == line:
            return string.join(lines, linesep)
        lines.append(line)

def format_message(message, author, email=''):

    if email:
        header = '%s <%s>:\n' % (author, email)
    elif author:
        header = '%s:\n' % (author)
    else:
        header = ''
    msg = header + message
    return string.strip(msg)
    
if __name__ == '__main__':

    # -f cmd line switch
    if len(sys.argv) > 1 and sys.argv[1] == '-f':
        force = 1
    else:
        force = 0

    print '='*72
    print 'CVS Checkin Utility'
    print '-'*72
    print
    print 'Checking work dir...',
    a,m,u,p = check_files()
    if not force and \
       (u or p):
        print
        print '* Files changed in the repository since the last update.'
        print '* Please update before proceeding with the checkin or run'
        print '* this script with switch -f.'
        print
        print 'For your information, these files were found to be out'
        print 'of sync:'
        print
        print_check_results((),(),u,p)
        sys.exit(1)
    print 'done.'
    print
    if not a and not m:
        print '* No files need to be checked in.'
        print
        sys.exit(1)

    print 'These files will be checked in:'
    print
    print_check_results(a,m,u,p)

    print
    print '='*72
    print 'Please enter the name and email of the patch author:'
    print
    author = raw_input('Name: [%s] ' % default_author) or default_author
    email = raw_input('EMail: [%s] ' % default_email) or default_email

    print
    print '='*72
    print 'Please enter the commit messages for each file below...'
    print
    print '[Emtpy entries will be skipped, comments must be terminated by'
    print ' line containing a single dot or two consecutive empty lines]'

    if a:
        print
        print '# New Files:'
        for file in a:
            print
            print '%s' % file
            print '-'*72
            message = multiline_input(term='.')
            print '-'*72
            if not message:
                print '>>> (commit skipped)'
                continue
            print '>>> Adding to CVS Repository...',
            message = format_message(message, author, email)
            add_file(file)
            checkin_file(file, message)
            print 'done.'

    if m:
        print
        print '# Modified Files:'
        for file in m:
            print
            print '%s' % file
            print '-'*72
            message = multiline_input(term='.')
            print '-'*72
            if not message:
                print '>>> (commit skipped)'
                continue
            print '>>> Committing to CVS Repository...',
            message = format_message(message, author, email)
            checkin_file(file, message)
            print 'done.'

    print
    print 'Done. Thanks :-)'

--------------4A599D73348784E859687D01--