hgsync / HgMon.py

#!/usr/bin/env python

"""HgMon.py -- Monitoring directory change and sync project directories upon
request.

If a file named 'SYNC!' is in the project directory, the script will try to
read into the file, parse out user name and commit message, then commit the
project, then sync (hg push) to other directories of the same project. If any
conflict is found, it will create a branch named by 'date-time-conflictdir',
record the conflicts to the branch, then branch to default and update the
previous pushed content.

If a file named 'LOCKED!' is in the project directory, the script will ignore
the directory, no change will be pushed or synchronized to the project.

Content format of 'SYNC!' is 'User Message String', e.g.,
    Joel Fixed a bug.

The first word in the message is treated as user name, other strings are treated
as commit messages.

"""

import os, re, shutil, filecmp, yaml
import logging, logging.config
from time import sleep
from datetime import datetime
from hgcmd import init, commit, autosync

def make_project_dir(project_dir):
    "Make sure project directory actually exists and create one if it doesn't."
    if os.path.exists(project_dir):
        if not os.path.isdir(project_dir): 
            try: shutil.rmtree(project_dir)
            except:
                print "Unable to remove non-project directory %s" % project_dir
            try:
                os.makedirs(project_dir)
            except:
                print "Unable to create project directory %s" % project_dir
    else:   # path not exist
        try: os.makedirs(project_dir)
        except:
            print "Unable to create project directory %s" % project_dir

def parse_flag(chflag):
    "change flag file -> user, comment"
    with open(chflag, 'r') as fh:
        user, comment = tuple(
            fh.readline().strip().split(' ', 1))
    return user, comment
    
def hg_modify_config(project_dir):
    "Writes config to do auto update after hg push."
    hgrc_src = 'hgrc.template'
    hgignore_src = 'hgignore.template'
    hgrc_dst = os.path.join(project_dir, '.hg', 'hgrc')
    hgignore_dst = os.path.join(project_dir, '.hgignore')
    configpairs = [(hgrc_src, hgrc_dst), (hgignore_src, hgignore_dst)]
    for src, dst in configpairs:
        if os.path.exists(src) and not os.path.exists(dst):
            shutil.copy(src, dst)
        elif os.path.exists(src) and os.path.exists(dst):
            if not filecmp.cmp(src, dst):
                os.remove(dst)
                shutil.copy(src, dst)
        else: pass

def load_project_config(configfile):
    "config_file -> dir group list"
    with open(configfile) as f:
        y = yaml.load(f)
    grouplist = []
    for entry in y:
        for group in entry.itervalues():
            for k, v in group.iteritems():
                grouplist.append(set(map(lambda x: os.path.join(x, k),
                                         v.split())))
    return grouplist

def main():
    logging.config.fileConfig('logconfig.ini')
    log = logging.getLogger('SyncLog')
    configfile = 'config.yaml'
    fn_syncflag = 'SYNC!'       # Command sync
    fn_lockflag = 'LOCKED!'     # Status locked
    fn_statflag = 'DOING'       # Status synchronizing

    while True:
        grouplist = load_project_config(configfile)
        for group in grouplist:
            for project_dir in group:
                make_project_dir(project_dir)
                fd_syncflag = os.path.join(project_dir, fn_syncflag)
                fd_hgflag = os.path.join(project_dir, '.hg')
                fd_lockflag = os.path.join(project_dir, fn_lockflag)
                if os.path.exists(fd_lockflag): continue
                if not os.path.exists(fd_hgflag):
                    init(project_dir)
                hg_modify_config(project_dir)
                if os.path.exists(fd_syncflag):
                    sleep(0.2)
                    user, comment = parse_flag(fd_syncflag)
                    os.remove(fd_syncflag)
                    fd_statflag = os.path.join(project_dir, fn_statflag)
                    sf = open(fd_statflag, 'w')
                    sf.close()
                    log.info("%s changed %s" % (user, project_dir))
                    log.info("Message: '%s'" % comment)
                    os.environ['HGUSER'] = user
                    log.info("Commiting changes for %s" % project_dir)
                    commit(project_dir, comment)
                    syncgroup = group.copy()
                    autosync(project_dir, syncgroup)
                    os.remove(fd_statflag)
                    log.info("Sync completed!")
        sleep(10)

if __name__ == '__main__':
    main()
Tip: Filter by directory path e.g. /media app.js to search for public/media/app.js.
Tip: Use camelCasing e.g. ProjME to search for ProjectModifiedEvent.java.
Tip: Filter by extension type e.g. /repo .js to search for all .js files in the /repo directory.
Tip: Separate your search with spaces e.g. /ssh pom.xml to search for src/ssh/pom.xml.
Tip: Use ↑ and ↓ arrow keys to navigate and return to view the file.
Tip: You can also navigate files with Ctrl+j (next) and Ctrl+k (previous) and view the file with Ctrl+o.
Tip: You can also navigate files with Alt+j (next) and Alt+k (previous) and view the file with Alt+o.