Version numbering with SmartGit

525 views Asked by At

Does SmartGit have a way to set and autoincrement version numbers?

I read a little about tags and set this on my initial branch (to "5.7.0"). Will I need to manually set this tag or is there a plugin that will handle versioning for me, behind the scenes?

1

There are 1 answers

0
Roland Smith On BEST ANSWER

Git doesn't care about versions, only commit hashes. :-)

My solution was to write a pair of scripts that I use as a filter. These scripts look for the $Date$ and $Revision$ keywords and extend them with the date and the version (tag) respectively.

In my ~/.gitconfig file I have the following;

[filter "kw"]
    clean = kwclean
    smudge = kwset

These are the scripts (written in Python)

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Author: R.F. Smith <[email protected]>
# $Date: 2013-10-25 23:52:42 +0200 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to kwset.py. This work is published from
# the Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

"""Fill the Date and Revision keywords from the latest git commit and tag and
   subtitutes them in the standard input."""

import os
import sys
import subprocess
import re


def gitdate():
    """Get the date from the latest commit in ISO8601 format.
    """
    args = ['git', 'log',  '-1', '--date=iso']
    dline = [l for l in subprocess.check_output(args).splitlines()
             if l.startswith('Date')]
    try:
        dat = dline[0][5:].strip()
        return ''.join(['$', 'Date: ', dat, ' $'])
    except IndexError:
        raise ValueError('Date not found in git output')


def gitrev():
    """Get the latest tag and use it as the revision number. This presumes the
    habit of using numerical tags. Use the short hash if no tag available.
    """
    args = ['git', 'describe',  '--tags', '--always']
    try:
        with open(os.devnull, 'w') as bb:
            r = subprocess.check_output(args, stderr=bb)[:-1]
    except subprocess.CalledProcessError:
        return ''.join(['$', 'Revision', '$'])
    return ''.join(['$', 'Revision: ', r, ' $'])

def main():
    """Main program.
    """
    dre = re.compile(''.join([r'\$', r'Date:?\$']))
    rre = re.compile(''.join([r'\$', r'Revision:?\$']))
    currp = os.getcwd()
    if not os.path.exists(currp+'/.git'):
        print >> sys.stderr, 'This directory is not controlled by git!'
        sys.exit(1)
    date = gitdate()
    rev = gitrev()
    for line in sys.stdin:
        line = dre.sub(date, line)
        print rre.sub(rev, line),


if __name__ == '__main__':
    main()

and

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Author: R.F. Smith <[email protected]>
# $Date: 2013-07-23 16:14:31 +0200 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to kwclean.py. This work is published from the
# Netherlands. See http://creativecommons.org/publicdomain/zero/1.0/

"""Remove the Date and Revision keyword contents from the standard input."""

import sys
import re

## This is the main program ##
if __name__ == '__main__':
    dre = re.compile(''.join([r'\$', r'Date.*\$']))
    drep = ''.join(['$', 'Date', '$'])
    rre = re.compile(''.join([r'\$', r'Revision.*\$']))
    rrep = ''.join(['$', 'Revision', '$'])
    for line in sys.stdin:
        line = dre.sub(drep, line)
        print rre.sub(rrep, line),

In the .gitattributes file in the repository I specify which files should have their keywords updated, e.g.:

*.py filter=kw

After checking in a new version, I set a version number using git tag. Then I run a script to update all the files that have keywords;

#!/usr/bin/env python2
# -*- coding: utf-8 -*-
#
# Author: R.F. Smith <[email protected]>
# $Date: 2013-10-25 23:52:42 +0200 $
# $Revision: e1e28c9 $
#
# To the extent possible under law, Roland Smith has waived all copyright and
# related or neighboring rights to update-all-keywords.py. This work is
# published from the Netherlands.
# See http://creativecommons.org/publicdomain/zero/1.0/

"""Remove and check out all files under git's control that contain keywords in
the current working directory."""

from __future__ import print_function, division
import os
import mmap
import sys
import subprocess


def checkfor(args):
    """Make sure that a program necessary for using this script is
    available.

    Arguments:
    args -- string or list of strings of commands. A single string may
            not contain spaces.
    """
    if isinstance(args, str):
        if ' ' in args:
            raise ValueError('No spaces in single command allowed.')
        args = [args]
    try:
        with open(os.devnull, 'w') as bb:
            subprocess.check_call(args, stdout=bb, stderr=bb)
    except subprocess.CalledProcessError:
        print("Required program '{}' not found! exiting.".format(args[0]))
        sys.exit(1)


def git_ls_files():
    """Find ordinary files that are controlled by git.

    :returns: A list of files
    """
    args = ['git', 'ls-files']
    flist = subprocess.check_output(args).splitlines()
    return flist
def git_not_checkedin():
    """Find files that are modified but are not checked in.

    :returns: A list of modified files that are not checked in.
    """
    lns = subprocess.check_output(['git', 'status', '-s']).splitlines()
    lns = [l.split()[-1] for l in lns]
    return lns


def keywordfiles(fns):
    """Filter those files that have keywords in them

    :fns: A list of filenames
    :returns: A list for filenames for files that contain keywords.
    """
    # These lines are encoded otherwise they would be mangled if this file
    # is checked in!
    datekw = 'JERhdGU='.decode('base64')
    revkw = 'JFJldmlzaW9u'.decode('base64')
    rv = []
    for fn in fns:
        with open(fn, 'rb') as f:
            try:
                mm = mmap.mmap(f.fileno(), 0, access=mmap.ACCESS_READ)
                if mm.find(datekw) > -1 or mm.find(revkw) > -1:
                    rv.append(fn)
                mm.close()
            except ValueError:
                pass
    return rv


def main(args):
    """Main program.

    :args: command line arguments
    """
    # Check if git is available.
    checkfor(['git', '--version'])
    # Check if .git exists
    if not os.access('.git', os.F_OK):
        print('No .git directory found!')
        sys.exit(1)
    # Get all files that are controlled by git.
    files = git_ls_files()
    # Remove those that aren't checked in
    mod = git_not_checkedin()
    if mod:
        files = [f for f in files if not f in mod]
    if not files:
        print('{}: Only uncommitted changes, nothing to do.'.format(args[0]))
        sys.exit(0)
    files.sort()
    # Find files that have keywords in them
    kwfn = keywordfiles(files)
    if kwfn:
        print('{}: Updating all files.'.format(args[0]))
        for fn in kwfn:
            os.remove(fn)
        args = ['git', 'checkout', '-f'] + kwfn
        subprocess.call(args)
    else:
        print('{}: Nothing to update.'.format(args[0]))


if __name__ == '__main__':
    main(sys.argv)