├── .gitignore ├── README ├── config.cfg ├── daemon.py ├── gitParser.py ├── gitPushNotify.py └── gitPushNotifyDaemon.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | /.idea -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | Try to create python daemon... 2 | 3 | Class Daemon: http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ 4 | 5 | $vim /etc/rc.local 6 | python /home/fisher/python/bin/gitPushNotify/gitPushNotifyDaemon.py start >> /tmp/gitPushNotifyDaemon.log 2>&1 7 | -------------------------------------------------------------------------------- /config.cfg: -------------------------------------------------------------------------------- 1 | [git] 2 | repository: /home/lenin/python/gitPushNotify 3 | branch: origin/master 4 | 5 | [daemon] 6 | timeout: 60 7 | 8 | [notify] 9 | useNotifySend: true 10 | useNotifySound: true 11 | -------------------------------------------------------------------------------- /daemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | #http://www.jejik.com/articles/2007/02/a_simple_unix_linux_daemon_in_python/ 3 | 4 | import sys, os, time, atexit 5 | from signal import SIGTERM 6 | 7 | class Daemon: 8 | """ 9 | A generic daemon class. 10 | 11 | Usage: subclass the Daemon class and override the run() method 12 | """ 13 | def __init__(self, pidfile, stdin='/dev/null', stdout='/dev/null', stderr='/dev/null'): 14 | self.stdin = stdin 15 | self.stdout = stdout 16 | self.stderr = stderr 17 | self.pidfile = pidfile 18 | 19 | def daemonize(self): 20 | """ 21 | do the UNIX double-fork magic, see Stevens' "Advanced 22 | Programming in the UNIX Environment" for details (ISBN 0201563177) 23 | http://www.erlenstar.demon.co.uk/unix/faq_2.html#SEC16 24 | """ 25 | try: 26 | pid = os.fork() 27 | if pid > 0: 28 | # exit first parent 29 | sys.exit(0) 30 | except OSError, e: 31 | sys.stderr.write("fork #1 failed: %d (%s)\n" % (e.errno, e.strerror)) 32 | sys.exit(1) 33 | 34 | # decouple from parent environment 35 | os.chdir("/") 36 | os.setsid() 37 | os.umask(0) 38 | 39 | # do second fork 40 | try: 41 | pid = os.fork() 42 | if pid > 0: 43 | # exit from second parent 44 | sys.exit(0) 45 | except OSError, e: 46 | sys.stderr.write("fork #2 failed: %d (%s)\n" % (e.errno, e.strerror)) 47 | sys.exit(1) 48 | 49 | # redirect standard file descriptors 50 | sys.stdout.flush() 51 | sys.stderr.flush() 52 | si = file(self.stdin, 'r') 53 | so = file(self.stdout, 'a+') 54 | se = file(self.stderr, 'a+', 0) 55 | os.dup2(si.fileno(), sys.stdin.fileno()) 56 | os.dup2(so.fileno(), sys.stdout.fileno()) 57 | os.dup2(se.fileno(), sys.stderr.fileno()) 58 | 59 | # write pidfile 60 | atexit.register(self.delpid) 61 | pid = str(os.getpid()) 62 | file(self.pidfile,'w+').write("%s\n" % pid) 63 | 64 | def delpid(self): 65 | os.remove(self.pidfile) 66 | 67 | def start(self): 68 | """ 69 | Start the daemon 70 | """ 71 | # Check for a pidfile to see if the daemon already runs 72 | try: 73 | pf = file(self.pidfile,'r') 74 | pid = int(pf.read().strip()) 75 | pf.close() 76 | except IOError: 77 | pid = None 78 | 79 | if pid: 80 | message = "pidfile %s already exist. Daemon already running?\n" 81 | sys.stderr.write(message % self.pidfile) 82 | sys.exit(1) 83 | 84 | # Start the daemon 85 | self.daemonize() 86 | self.run() 87 | 88 | def stop(self): 89 | """ 90 | Stop the daemon 91 | """ 92 | # Get the pid from the pidfile 93 | try: 94 | pf = file(self.pidfile,'r') 95 | pid = int(pf.read().strip()) 96 | pf.close() 97 | except IOError: 98 | pid = None 99 | 100 | if not pid: 101 | message = "pidfile %s does not exist. Daemon not running?\n" 102 | sys.stderr.write(message % self.pidfile) 103 | return # not an error in a restart 104 | 105 | # Try killing the daemon process 106 | try: 107 | while 1: 108 | os.kill(pid, SIGTERM) 109 | time.sleep(0.1) 110 | except OSError, err: 111 | #FIX for Ru_ru locale 112 | # Anton Fischer 113 | #err = str(err) 114 | #if err.find("No such process") > 0: 115 | if os.path.exists(self.pidfile): 116 | os.remove(self.pidfile) 117 | #else: 118 | # print str(err) 119 | # sys.exit(1) 120 | 121 | def restart(self): 122 | """ 123 | Restart the daemon 124 | """ 125 | self.stop() 126 | self.start() 127 | 128 | def run(self): 129 | """ 130 | You should override this method when you subclass Daemon. It will be called after the process has been 131 | daemonized by start() or restart(). 132 | """ -------------------------------------------------------------------------------- /gitParser.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __author__ = 'Anton Fischer ' 5 | __date__ = '$30.08.2011 23:33:33$' 6 | 7 | class GitParser: 8 | """ 9 | Class for raw git console output 10 | """ 11 | def __init__(self, rawText = ''): 12 | self.rawText = rawText 13 | self.listChanges = [] 14 | 15 | def setRawText(self, text): 16 | """ 17 | Raw text setter 18 | """ 19 | self.rawText = text 20 | return self 21 | 22 | def getChangesList(self): 23 | listSource = self.rawText.split('\n') 24 | listGroup = {} 25 | i = 0 26 | for string in listSource: 27 | if string != '': 28 | if listGroup.has_key(i): 29 | listGroup[i].append(string) 30 | else : 31 | listGroup[i] = [string] 32 | else: 33 | i += 1 34 | 35 | listChanges = [] 36 | for group in listGroup.values(): 37 | if len(group) < 4: 38 | continue 39 | change = { 40 | 'id': group[0], 41 | 'author': group[1], 42 | 'email': group[2], 43 | 'time': group[3], 44 | 'message': group[4], 45 | 'files': [], 46 | } 47 | for i in range(5, len(group)): 48 | change['files'].append(self._getFileNameStatus(group[i])) 49 | listChanges.append(change) 50 | return listChanges 51 | 52 | def _getFileNameStatus(self, text): 53 | """ 54 | ":000000 100644 0000000... bd725c6... A daemon.py" -> "A daemon.py" 55 | """ 56 | tmp = text.split("\t") 57 | return tmp[0].split().pop() + ' ' + tmp[1] 58 | -------------------------------------------------------------------------------- /gitPushNotify.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __author__ = 'Anton Fischer ' 5 | __date__ = '$30.08.2011 23:33:33$' 6 | 7 | import subprocess, os, pynotify, logging, tempfile, ConfigParser, sys 8 | from datetime import datetime 9 | import gitParser 10 | 11 | class GitPushNotify: 12 | useNotifySend = True 13 | useNotifySound = False #no yet 14 | repositoryPath = None 15 | repositoryBranch = 'origin/master' 16 | daemonTimeout = 60 #sec 17 | 18 | def __init__(self): 19 | # log init 20 | logging.basicConfig(filename = tempfile.gettempdir() + '/gitPushNotify.log', 21 | level = logging.DEBUG, 22 | format = '%(asctime)s %(levelname)s: %(message)s', 23 | datefmt = '%Y-%m-%d %I:%M:%S') 24 | logging.info('Daemon start') 25 | 26 | # read config 27 | config = ConfigParser.ConfigParser() 28 | configPath = os.path.dirname(__file__) + '/config.cfg' 29 | if os.path.exists(configPath): 30 | config.read(configPath) 31 | else: 32 | self.fireNotify('File "config.cfg" does not exist. Daemon stopped.') 33 | sys.exit(2) 34 | 35 | # -- repository path 36 | if config.has_option('git', 'repository'): 37 | self.repositoryPath = config.get('git', 'repository') 38 | else: 39 | message = 'Is not define repository path in "config.cfg". Daemon stopped.' 40 | self.fireNotify(message) 41 | logging.error(message) 42 | sys.exit(2) 43 | 44 | # -- repository branch 45 | if config.has_option('git', 'branch'): 46 | self.repositoryBranch = config.get('git', 'branch') 47 | 48 | # -- daemon timeout 49 | if config.has_option('daemon', 'timeout'): 50 | self.daemonTimeout = config.getint('daemon', 'timeout') 51 | 52 | # -- use notify-send 53 | if config.has_option('notify', 'useNotifySend'): 54 | self.useNotifySend = config.getboolean('notify', 'useNotifySend') 55 | 56 | # -- use notify-sound 57 | if config.has_option('notify', 'useNotifySound'): 58 | self.useNotifySound = config.getboolean('notify', 'useNotifySound') 59 | 60 | # start notify 61 | self.fireNotify('Start!') 62 | 63 | def fireNotify(self, msg = '', title = 'GitPushNotify'): 64 | """ 65 | Fire notify action 66 | """ 67 | logging.info('Called fireNotify()') 68 | if (self.useNotifySend): 69 | #commands.getstatusoutput('notify-send -u "%s" -i "%s" "%s" "%s"' % (level, icon, title, msg)) 70 | if pynotify.init('icon-summary-body'): 71 | pynotify.Notification(title, msg, self.getSystemIcon()).show() 72 | else: 73 | print 'Notify not supported. You need to install python-notify package first.' 74 | 75 | if (self.useNotifySound): 76 | # play sound 77 | pass 78 | 79 | def getSystemIcon(self): 80 | """ 81 | Get system icon path 82 | example: notification-power-disconnected 83 | """ 84 | return '' 85 | 86 | def getLastCheckTime(self): 87 | """ 88 | To simplicity, time of last modification of the current file is used as the time of last checking 89 | """ 90 | lastCheckTime = os.path.getmtime(__file__) 91 | return datetime.fromtimestamp(lastCheckTime) 92 | 93 | def setLastCheckTime(self, time = None): 94 | """ 95 | Set time of last checking -> touch file 96 | """ 97 | #commands.getoutput('touch ' + __file__) 98 | #os.system('touch ' + __file__) 99 | subprocess.check_output('touch ' + __file__, shell=True) 100 | return self 101 | 102 | def getRepositoryPath(self): 103 | """ 104 | Get repository path 105 | """ 106 | return self.repositoryPath 107 | 108 | def getDaemonTimeout(self): 109 | """ 110 | Get daemon checking timeout 111 | """ 112 | return self.daemonTimeout 113 | 114 | def check(self, lastCheckTime = None, repositoryPath = None): 115 | """ 116 | Get git log as string 117 | """ 118 | logging.info('Called check()') 119 | if (not lastCheckTime): 120 | lastCheckTime = self.getLastCheckTime() 121 | 122 | if (not repositoryPath): 123 | repositoryPath = self.getRepositoryPath() 124 | 125 | #sourceOutput = commands.getoutput( 126 | #sourceOutput = os.system( 127 | sourceOutput = subprocess.check_output( 128 | 'cd ' + repositoryPath + ' &&'\ 129 | + ' git fetch' + ' &&'\ 130 | + ' git whatchanged ' + self.repositoryBranch + ' -10 --date=raw --date-order --pretty=format:"%H %n%cn %n%ce %n%ct %n%s"', 131 | shell=True 132 | ) 133 | logging.debug('sourceOutput: %s', sourceOutput) 134 | parser = gitParser.GitParser(sourceOutput) 135 | listChanges = parser.getChangesList() 136 | message = '' 137 | countCommits = 0 138 | 139 | logging.info('Count commits: %s', len(listChanges)) 140 | for item in listChanges: 141 | commitTime = datetime.fromtimestamp(int(item['time'])) 142 | if (commitTime >= lastCheckTime): 143 | message += '...\n' + commitTime.strftime('%x %X') + '\n' + item['author'] + ' <' + item['email']\ 144 | + '>\n' + item['message'] + '\n' 145 | countCommits += 1 146 | 147 | if (countCommits > 0): 148 | logging.info('Count new commits: %s', countCommits) 149 | message += '...\n%s new commit(s)\n\n' % countCommits 150 | 151 | self.fireNotify(message) 152 | self.setLastCheckTime() 153 | 154 | logging.info('End check()') 155 | return self 156 | 157 | if __name__ == '__main__': 158 | c = GitPushNotify() 159 | c.check() 160 | -------------------------------------------------------------------------------- /gitPushNotifyDaemon.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | 4 | __author__ = 'Anton Fischer ' 5 | __date__ = '$30.08.2011 23:33:33$' 6 | 7 | import sys, time, tempfile 8 | import daemon, gitPushNotify 9 | 10 | class GitPushNotifyDaemon(daemon.Daemon): 11 | def run(self): 12 | c = gitPushNotify.GitPushNotify() 13 | while True: 14 | c.check() 15 | time.sleep(c.getDaemonTimeout()) 16 | 17 | if __name__ == '__main__': 18 | pidFile = tempfile.gettempdir() + '/daemonGitPushNotify.pid' 19 | daemon = GitPushNotifyDaemon(pidFile) 20 | if len(sys.argv) == 2: 21 | if 'start' == sys.argv[1]: 22 | print 'Daemon starting..' 23 | daemon.start() 24 | print 'Daemon started!' 25 | elif 'stop' == sys.argv[1]: 26 | print 'Daemon stopping..' 27 | daemon.stop() 28 | print 'Daemon stopped!' 29 | elif 'restart' == sys.argv[1]: 30 | print 'Daemon restarting..' 31 | daemon.restart() 32 | print 'Daemon restarted!' 33 | else: 34 | print 'Unknown command' 35 | sys.exit(2) 36 | sys.exit(0) 37 | else: 38 | print 'usage: %s start|stop|restart' % sys.argv[0] 39 | sys.exit(2) --------------------------------------------------------------------------------