Completed
Push — master ( 12ec71...118fc7 )
by Tomaz
02:23
created

GitCommitSensor._prepare_local_repository()   B

Complexity

Conditions 5

Size

Total Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 5
c 2
b 0
f 0
dl 0
loc 26
rs 8.0894
1
#!/usr/bin/env python
2
3
# Requirements
4
# pip install gitpython
5
# Also requires git CLI tool to be installed.
6
7
import os
8
import datetime
9
10
from git.repo import Repo
11
12
from st2reactor.sensor.base import PollingSensor
13
14
15
class GitCommitSensor(PollingSensor):
16
    def __init__(self, sensor_service, config=None, poll_interval=5):
17
        super(GitCommitSensor, self).__init__(sensor_service=sensor_service,
18
                                              config=config,
19
                                              poll_interval=poll_interval)
20
21
        self._logger = self._sensor_service.get_logger(__name__)
22
        self._git_repositories = []
23
        self._trigger_name = 'head_sha_monitor'
24
        self._trigger_pack = 'git'
25
        self._trigger_ref = '.'.join([self._trigger_pack, self._trigger_name])
26
27
    def setup(self):
28
        git_opts = self._config
29
30
        # update internal variable which specifies interval to dispatch poll
31
        interval = git_opts.get('poll_interval', None)
32
        if interval is not None:
33
            self.set_poll_interval(interval)
34
35
        for opts in git_opts['repositories']:
36
            try:
37
                repo_local, repo_remote = self._prepare_local_repository(opts)
38
                self._git_repositories.append({
39
                    'local': repo_local,
40
                    'remote': repo_remote,
41
                    'old_head': None,
42
                })
43
            except Exception as e:
44
                # Whan an exception is occurred during preparation,
45
                # this only output error message and ignores it to poll events.
46
                self._logger.exception(str(e))
47
48
    def poll(self):
49
        for repo in self._git_repositories:
50
            # Fetch new commits
51
            try:
52
                pulled = repo['remote'].pull()
53
                if pulled:
54
                    self._logger.debug('Pulled info from remote repo. %s', pulled[0].commit)
55
                else:
56
                    self._logger.debug('Nothing pulled from remote repo.')
57
            except:
58
                self._logger.exception('Failed git pull from remote repo.')
59
60
            head = repo['local'].commit()
61
            head_sha = head.hexsha
62
63
            if not repo['old_head']:
64
                repo['old_head'] = head_sha
65
66
                # There is exactly one commit. Kick off a trigger.
67
                if len(repo['local'].heads) == 1:
68
                    self._dispatch_trigger(head, repo['local'])
69
            elif head_sha != repo['old_head']:
70
                try:
71
                    self._dispatch_trigger(head, repo['local'])
72
                except Exception:
73
                    self._logger.exception('Failed dispatching trigger.')
74
                else:
75
                    repo['old_head'] = head_sha
76
77
    def cleanup(self):
78
        pass
79
80
    def add_trigger(self, trigger):
81
        pass
82
83
    def update_trigger(self, trigger):
84
        pass
85
86
    def remove_trigger(self, trigger):
87
        pass
88
89
    def _dispatch_trigger(self, commit, repo):
90
        trigger = self._trigger_ref
91
        payload = {}
92
        payload['branch'] = repo.active_branch.name
93
        payload['revision'] = str(commit)
94
        payload['author'] = commit.author.name
95
        payload['author_email'] = commit.author.email
96
        payload['authored_date'] = self._to_date(commit.authored_date)
97
        payload['author_tz_offset'] = commit.author_tz_offset
98
        payload['commit_message'] = getattr(commit, 'message', None)
99
        payload['committer'] = commit.committer.name
100
        payload['committer_email'] = commit.committer.email
101
        payload['committed_date'] = self._to_date(commit.committed_date)
102
        payload['committer_tz_offset'] = commit.committer_tz_offset
103
        payload['repository_url'] = repo.config_reader().get_value('remote "origin"', 'url')
104
        self._logger.debug('Found new commit. Dispatching trigger: %s', payload)
105
        self._sensor_service.dispatch(trigger, payload)
106
107
    def _to_date(self, ts_epoch):
108
        return datetime.datetime.fromtimestamp(ts_epoch).strftime('%Y-%m-%dT%H:%M:%SZ')
109
110
    def _prepare_local_repository(self, opts):
111
        url = opts['url']
112
        branch = opts['branch']
113
114
        if url is None:
115
            raise Exception('Remote git URL not set.')
116
117
        # set local repository path to be cloned
118
        repo_name = url[url.rindex('/') + 1:]
119
        default_clone_dir = os.path.join(os.path.dirname(__file__), 'clones', repo_name)
120
        local_path = opts.get('local_clone_path', default_clone_dir)
121
122
        # create a directory to store cloned repositories
123
        if not os.path.exists(os.path.dirname(local_path)):
124
            os.mkdir(os.path.dirname(local_path), 0755)
125
126
        if os.path.exists(local_path):
127
            repo_local = Repo.init(local_path)
128
        else:
129
            try:
130
                repo_local = Repo.clone_from(url, local_path, branch=branch)
131
            except Exception as e:
132
                raise Exception('Unable to clone remote repo from %s [%s]: %s' %
133
                                (url, branch, str(e)))
134
135
        return repo_local, repo_local.remote('origin')
136