예제 #1
0
class ProjectWatcher(BaseModel):
    __orz_table__ = "codedouban_watches"
    user_id = ModelField(as_key=ModelField.KeyType.DESC)
    project_id = ModelField(as_key=ModelField.KeyType.DESC)

    class OrzMeta:
        id2str = True
예제 #2
0
class Activity(BaseModel):
    __orz_table__ = "center_activities"
    title = ModelField(as_key=ModelField.KeyType.DESC)
    description = ModelField()
    type = ModelField(as_key=ModelField.KeyType.ONLY_INDEX)
    creator_id = ModelField(as_key=ModelField.KeyType.ONLY_INDEX)
    created_at = ModelField(auto_now_create=True)

    def to_dict(self):
        return {
            'id': self.id,
            'title': self.title,
            'description': self.description,
            'created_at': self.created_at.strftime('%Y-%m-%dT%H:%M:%S+0800')
        }

    @property
    def rendered_description(self):
        from vilya.libs.text import render_markdown
        description = self.description
        if description:
            description = render_desc(self.type, description)
            return render_markdown(description)
        return ''

    @property
    def short_description(self):
        if self.description:
            return trunc_utf8(self.description, 80)
        return ''

    @property
    def url(self):
        return 'activities/%s' % self.id
예제 #3
0
class ProjectHook(BaseModel):
    __orz_table__ = "codedouban_hooks"
    hook_id = ModelField(as_key=ModelField.KeyType.DESC)
    url = ModelField()
    project_id = ModelField(as_key=ModelField.KeyType.DESC)

    class OrzMeta:
        id2str = True
예제 #4
0
파일: nteam.py 프로젝트: jackfrued/code-1
class TeamProjectRelationship(BaseModel):
    __orz_table__ = "team_project_relationship"
    team_id = ModelField(as_key=ModelField.KeyType.DESC)
    project_id = ModelField(as_key=ModelField.KeyType.DESC)
    created_at = ModelField(auto_now_create=True)

    class OrzMeta:
        id2str = True

    def __repr__(self):
        return '<TeamProjectRelationship %s, %s, %s>' % (self.id, self.team_id,
                                                         self.project_id)
예제 #5
0
class GroupUser(BaseModel):
    __orz_table__ = "group_users"
    group_id = ModelField(as_key=ModelField.KeyType.DESC)
    user_id = ModelField(as_key=ModelField.KeyType.DESC)
    created_at = ModelField(auto_now_create=True)

    @property
    def group(self):
        return TeamGroup.get(id=self.group_id)

    @property
    def user(self):
        from vilya.models.user import User
        return User(self.user_id)

    def to_dict(self):
        return dict(group_name=self.group.name, user_name=self.user_id)
예제 #6
0
class ProjectGroup(BaseModel):
    __orz_table__ = "projects_groups"
    project_id = ModelField(as_key=ModelField.KeyType.DESC)
    group_id = ModelField(as_key=ModelField.KeyType.DESC)
    created_at = ModelField(auto_now_create=True)

    @property
    def group(self):
        return TeamGroup.get(id=self.group_id)

    @property
    def project(self):
        from vilya.models.project import CodeDoubanProject
        return CodeDoubanProject.get(self.project_id)

    def to_dict(self):
        return dict(group_name=self.group.name, project_name=self.project.name)
예제 #7
0
파일: nteam.py 프로젝트: jackfrued/code-1
class TeamUserRelationship(BaseModel):
    __orz_table__ = "team_user_relationship"
    team_id = ModelField(as_key=ModelField.KeyType.DESC)
    user_id = ModelField(as_key=ModelField.KeyType.DESC)
    identity = ModelField(as_key=ModelField.KeyType.DESC)
    created_at = ModelField(auto_now_create=True)

    class OrzMeta:
        id2str = True

    def __repr__(self):
        return '<TeamUserRelationship %s, %s, %s>' % (self.id, self.team_id,
                                                      self.user_id)

    @property
    def is_owner(self):
        return self.identity == TEAM_OWNER

    @property
    def is_member(self):
        return self.identity == TEAM_MEMBER
예제 #8
0
class User2(BaseModel):
    __orz_table__ = "users"
    name = ModelField(as_key=ModelField.KeyType.DESC)
    password = ModelField()
    role = ModelField()
    created_at = ModelField(auto_now_create=True)

    class OrzMeta:
        id2str = True

    def __repr__(self):
        return '<u %s, %s>' % (self.id, self.name)

    @property
    def is_intern(self):
        return int(self.role) == USER_ROLE_INTERN

    def validate_password(self, password):
        return check_password(password, self.password)

    @classmethod
    def is_exists(cls, name):
        return cls.get(name=name)

    @classmethod
    def add(cls, name, password):
        encoded_password = make_password(password, hasher=PASSWORD_METHOD)
        return cls.create(name=name,
                          password=encoded_password,
                          role=USER_ROLE_DEFAULT)

    def set_role(self, intern=False):
        self.role = USER_ROLE_INTERN if intern else USER_ROLE_STAFF
        self.save()

    def has_role(self):
        if self.role is not None and \
                int(self.role) != USER_ROLE_DEFAULT:
            return True
예제 #9
0
파일: mirror.py 프로젝트: jackfrued/code-1
class CodeDoubanMirror(BaseModel):
    __orz_table__ = "codedouban_mirror"
    url = ModelField()
    state = ModelField()
    project_id = ModelField(ModelField.KeyType.ASC)
    with_proxy = ModelField(default=MIRROR_NOT_PROXY)
    frequency = ModelField(default=0)

    class OrzMeta:
        order_combs = (("-id", ), )

    def __str__(self):
        return "Mirror: %s " % self.url

    @classmethod
    def validate(cls, repo_url):
        return check_git_url(repo_url)

    @classmethod
    def add(cls, url, state, project_id, with_proxy=MIRROR_NOT_PROXY):
        return cls.create(project_id=project_id,
                          url=url,
                          state=state,
                          with_proxy=with_proxy)

    @classmethod
    def get_by_project_id(cls, project_id):
        return cls.get(project_id=project_id)

    def update_state(self, state):
        self.state = state
        self.save()

    @property
    def is_clone_completed(self):
        return self.state == MIRROR_STATE_CLONED
예제 #10
0
class TeamGroup(BaseModel):
    __orz_table__ = "team_groups"
    name = ModelField(as_key=ModelField.KeyType.DESC)
    team_id = ModelField(as_key=ModelField.KeyType.DESC)
    description = ModelField()
    permission = ModelField(as_key=ModelField.KeyType.DESC, default=PERM_PULL)
    creator_id = ModelField()
    created_at = ModelField(auto_now_create=True)

    @property
    def permission_text(self):
        return PERM_TEXT.get(self.permission, 'pull')

    def add_user(self, **kw):
        kw['group_id'] = self.id
        return GroupUser.create(**kw)

    def add_project(self, **kw):
        kw['group_id'] = self.id
        return ProjectGroup.create(**kw)

    def remove_user(self, **kw):
        kw['group_id'] = self.id
        u = GroupUser.get(**kw)
        if u:
            u.delete()

    def remove_project(self, **kw):
        kw['group_id'] = self.id
        u = ProjectGroup.get(**kw)
        if u:
            u.delete()

    @property
    def url(self):
        return self.team.url + 'groups/%s/' % self.name

    @property
    def team(self):
        from vilya.models.team import Team
        return Team.get(self.team_id)

    @property
    def full_name(self):
        return "%s/%s" % (self.team.uid, self.name)

    @property
    def members(self):
        from vilya.models.user import User
        rs = GroupUser.gets(group_id=self.id)
        return [User(r.user_id) for r in rs]

    @property
    def projects(self):
        from vilya.models.project import CodeDoubanProject
        rs = ProjectGroup.gets(group_id=self.id)
        return filter(None, [CodeDoubanProject.get(r.project_id) for r in rs])

    def is_member(self, user_id):
        r = GroupUser.get(group_id=self.id, user_id=user_id)
        return True if r else False

    def to_dict(self):
        return dict(name=self.name, team_id=self.team_id)

    @classmethod
    def translate_perm(cls, perm):
        r = PERM_PULL
        if perm == 'push':
            r = PERM_PUSH
        elif perm == 'merge':
            r = PERM_MERGE
        elif perm == 'admin':
            r = PERM_ADMIN
        return r
예제 #11
0
class Pull2(BaseModel, PropsMixin, PullMixin):
    __orz_table__ = "pullreq"
    from_project = ModelField(as_key=ModelField.KeyType.DESC)
    from_branch = ModelField(as_key=ModelField.KeyType.DESC)
    to_project = ModelField(as_key=ModelField.KeyType.DESC)
    to_branch = ModelField(as_key=ModelField.KeyType.DESC)
    merged = ModelField(as_key=ModelField.KeyType.DESC)
    ticket_id = ModelField(as_key=ModelField.KeyType.DESC)

    class OrzMeta:
        id2str = True
        cache_ver = "v1"

    def get_uuid(self):
        return '/pull/%s' % self.id

    def after_create(self):
        self.init_pull()
        self.sync()

    @classmethod
    def get(cls, **kwargs):
        r = super(Pull2, cls).get(**kwargs)
        if r:
            r.init_pull()
            r.sync()
        return r

    @property
    @cache(MC_KEY_PULL_MERGE_BASE %
           ('{self.id}', '{self.from_sha}', '{self.to_sha}'))
    def merge_base(self):
        return self._get_merge_base()

    @property
    @cache(MC_KEY_PULL_COMMITS %
           ('{self.id}', '{self.from_sha}', '{self.to_sha}'))
    def commits(self):
        return self._get_commits()

    @property
    def ticket(self):
        if not self.ticket_id:
            return None
        ticket = Ticket.get_by_projectid_and_ticketnumber(
            self.to_proj.id, self.ticket_id)
        return ticket

    @property
    def full_url(self):
        return "%s/%s/pull/%s/" % (DOMAIN, self.to_proj.name, self.ticket_id)

    def get_commits_by_status(self, status):
        if status == 'closed':
            return self.merged_commits
        return self.commits

    def get_commits(self):
        return self.commits

    def get_commits_shas(self):
        return [c.sha for c in self.commits]

    def get_format_patch(self):
        text = self.to_proj.repo.get_patch_file(self.from_sha, self.merge_base)
        return text

    def get_diff_tree(self):
        text = self.to_proj.repo.get_diff_file(self.from_sha, self.merge_base)
        return text

    # TODO: cache
    def get_diff_length(self, ref=None, from_ref=None, paths=None):
        ref = ref or self.from_sha
        from_ref = from_ref or self.merge_base
        return self.repo.get_diff_length(ref=ref,
                                         from_ref=from_ref,
                                         paths=paths)

    def get_contexts(self, ref, path, line_start, line_end):
        return self.repo.get_contexts(ref, path, line_start, line_end)

    @classmethod
    def get_by_proj_and_ticket(cls, proj_id, tid):
        return cls.get(to_project=proj_id, ticket_id=tid)

    @classmethod
    def get_by_ticket(cls, ticket):
        proj_id = ticket.project_id
        tid = ticket.ticket_id
        return cls.get(to_project=proj_id, ticket_id=tid)

    @classmethod
    def get_by_from_and_to(cls, from_project, from_branch, to_project,
                           to_branch):
        rs = cls.gets(from_project=from_project,
                      from_branch=from_branch,
                      to_project=to_project,
                      to_branch=to_branch)
        return rs

    @classmethod
    # backward compatibility
    def get_max_ticket_id(cls, proj_id):
        rs = store.execute(
            "select max(ticket_id) "
            "from pullreq where to_project=%s", (proj_id, ))
        r = rs and rs[0]
        if r and r[0]:
            return int(r[0])

    @cache(MC_KEY_PULL_IS_MERGABLE %
           ('{self.id}', '{self.from_sha}', '{self.to_sha}'))
    def is_auto_mergable(self):
        return self.repo.can_merge()

    def can_fastforward(self):
        return self.repo.can_fastforward()

    # TODO: remove this
    def remove_branch_if_temp(self):
        if self.from_ref.startswith('patch_tmp'):
            try:
                self.to_proj.repo.delete_branch(self.from_ref)
            except AssertionError as err:
                logging.debug("Unable to remove_temp_branch(%s): %s" %
                              (self.from_ref, err))

    def is_temp_pull(self):
        if self.from_ref.startswith('patch_tmp'):
            return True

    def _save_merged(self, operator, from_sha, to_sha, merge_commit_sha):
        self.merged = "%s..%s" % (from_sha, to_sha)
        self.save()
        merged_time = datetime.now()
        self.merge_by = operator
        self.merge_time = merged_time.strftime('%Y-%m-%d %H:%M:%S')
        # FIXME: pullreq without ticket_id?
        if self.ticket_id:
            ticket = Ticket.get_by_projectid_and_ticketnumber(
                self.to_proj.id, self.ticket_id)
            if ticket:
                # add commits to ticket
                ticket.add_commits(merge_commit_sha, operator)
                TicketNode.add_merge(ticket.id, operator, merged_time)

    def _get_merge_by(self):
        return self.props.get('merge_by')

    def _set_merge_by(self, merge_by):
        self.set_props_item('merge_by', merge_by)

    merge_by = property(_get_merge_by, _set_merge_by)

    def _get_merge_time(self):
        return self.props.get('merge_time')

    def _set_merge_time(self, merge_time):
        self.set_props_item('merge_time', merge_time)

    def _base_repo_as_dict(self):
        # TODO: wrap to project repo
        d = self._repo_as_dict(self.to_proj)
        commit = self.to_commit
        ts = commit.author_timestamp if commit else 0
        d['repo']['pushed_at'] = datetime.fromtimestamp(
            float(ts)).strftime('%Y-%m-%dT%H:%M:%S')
        d['label'] = "%s:%s" % (self.to_proj.name, self.to_ref)
        d['sha'] = commit.sha if commit else None
        d['ref'] = self.to_ref

        return d

    def _head_repo_as_dict(self):
        d = self._repo_as_dict(self.from_proj)
        if not d['repo']:
            return d

        commit = self.from_commit
        if commit:
            ts = commit.author_timestamp if commit else 0
            d['repo']['pushed_at'] = datetime.fromtimestamp(
                float(ts)).strftime('%Y-%m-%dT%H:%M:%S')
            d['label'] = "%s:%s" % (self.from_proj.name, self.from_ref)
            d['sha'] = commit.sha if commit else None
            d['ref'] = self.from_ref

        return d

    def _repo_as_dict(self, proj):
        if not proj:
            return {'repo': {}}

        d = {}
        d['repo'] = {}
        d['repo']['id'] = proj.id
        d['repo']['name'] = proj.name
        d['repo']['clone_url'] = "%s/%s.git" % (DOMAIN, proj.name)
        d['repo']['html_url'] = "%s/%s" % (DOMAIN, proj.name)

        d['repo']['name'] = proj.name
        d['repo']['summary'] = proj.summary
        d['repo'][
            'fork'] = True if proj.fork_from and proj.fork_from > 0 else False
        d['repo']['created_at'] = proj.time.strftime('%Y-%m-%dT%H:%M:%S')
        d['repo']['author'] = {'login': proj.owner_id}

        return d

    def as_dict(self):
        if not (self.ticket_id and self.to_proj):
            return None

        ticket = Ticket.get_by_projectid_and_ticketnumber(
            self.to_proj.id, self.ticket_id)
        if not ticket:
            return None
        d = {}
        d['url'] = "%s/api/%s/pull/%s" % (DOMAIN, self.to_proj.name,
                                          self.ticket_id)
        d['html_url'] = "%s/%s/pull/%s/" % (DOMAIN, self.to_proj.name,
                                            self.ticket_id)
        d['number'] = self.ticket_id  # or ticket_id as name is better?
        d['title'] = ticket.title
        d['description'] = ticket.description
        d['merged'] = self.merged and True or False
        d['created_at'] = ticket.time.strftime('%Y-%m-%dT%H:%M:%S')
        if ticket.closed:
            d['closed_at'] = ticket.closed.strftime('%Y-%m-%dT%H:%M:%S')
            d['state'] = 'closed'
        else:
            d['closed_at'] = None
            d['state'] = 'open'

        if d['merged']:
            # No merged time recored, use closed_at instead
            d['merged_at'] = d['closed_at']
        else:
            d['merged_at'] = None

        d['author'] = {}
        d['author']['login'] = ticket.author
        d['ticket_id'] = self.ticket_id
        d['from_proj'] = self.from_proj and self.from_proj.name or ''
        d['to_proj'] = self.to_proj and self.to_proj.name or ''
        d['to_proj_id'] = self.to_proj and self.to_proj.id or -1
        d['creator'] = ticket.author
        d['base'] = self._base_repo_as_dict()
        d['head'] = self._head_repo_as_dict()
        commits = self.get_commits_by_status(d['state'])
        d['commit_id'] = [c.sha for c in commits]

        return d

    merge_time = property(_get_merge_time, _set_merge_time)

    def get_merged_commit_shas(self):
        commits_shas = []
        for ticket_commits in self.ticket.get_commits():
            commits_shas += [str(c) for c in ticket_commits.commits.split(',')]
        commits_shas = [c for c in commits_shas if c]
        return commits_shas

    def get_merged_commits(self):
        commits = []
        repo = self.to_proj.repo
        commits = filter(None, (repo.get_commit(str(c))
                                for c in self.get_merged_commit_shas()))
        commits = sorted(commits, key=lambda x: x.committer_time, reverse=True)
        if self.merged:
            # exclude last merge commit
            return commits[1:]
        else:
            return commits

    def get_delta_commits(self):
        """
        补充pull request创建之后再提交的commits
        """
        saved = self.get_merged_commit_shas()
        nowcommits = self.get_commits_shas()
        delta_commits = [c for c in nowcommits if c not in saved]
        if delta_commits:
            delta_commits = [
                self.to_proj.repo.get_commit(str(d)) for d in delta_commits
            ]
            delta_commits = filter(None, delta_commits)
            return delta_commits
        return []

    @classmethod
    def open(cls, from_project, from_branch, to_project, to_branch):
        return PullMixin(from_project.id, from_branch, to_project.id,
                         to_branch)
예제 #12
0
class TicketComment(BaseModel):
    __orz_table__ = "codedouban_ticket_comment"
    author = ModelField()
    # content = ModelField()
    ticket_id = ModelField(as_key=ModelField.KeyType.DESC)
    time = ModelField(as_key=ModelField.KeyType.DESC, auto_now_create=True)

    @property
    def content(self):
        return bdb.get(BDB_TICKET_COMMENT_CONTENT_KEY % self.id)

    @property
    def uid(self):
        return PULL_COMMENT_UID_PATTERN % self.id

    def as_dict(self):
        return {
            'content': self.content,
            'author': self.author,
            'date': self.time.strftime('%Y-%m-%dT%H:%M:%S'),
            'id': self.id,
            'ticket_id': self.ticket_id
        }

    @classmethod
    def add(cls, ticket_id, content, author):
        comment = cls.create(ticket_id=ticket_id,
                             extra_args=content,
                             author=author)
        return comment

    def after_create(self, extra_args):
        from vilya.models.ticket import Ticket
        comment = self
        TicketNode.add_comment(comment)
        content = extra_args
        bdb.set(BDB_TICKET_COMMENT_CONTENT_KEY % self.id, content)
        ticket = Ticket.get(self.ticket_id)
        # TODO: 将Feed全部迁移到新的系统后,取消signal发送
        codereview_signal.send(comment,
                               content=content,
                               ticket=ticket,
                               author=self.author,
                               comment=comment)
        dispatch('codereview',
                 data={
                     'comment': comment,
                     'ticket': ticket,
                     'sender': self.author,
                 })

    def update(self, content):
        bdb.set(BDB_TICKET_COMMENT_CONTENT_KEY % self.id, content)
        self.time = datetime.now()
        self.save()

    def after_delete(self):
        bdb.delete(BDB_TICKET_COMMENT_CONTENT_KEY % self.id)
        node = TicketNode.get_by_comment(self)
        if node:
            node.delete()

    @classmethod
    def gets_by_ticketid(cls, ticket_id):
        return cls.gets(ticket_id=ticket_id)
예제 #13
0
class TicketNode(BaseModel):
    __orz_table__ = "ticket_nodes"
    author = ModelField()
    type = ModelField(as_key=ModelField.KeyType.DESC)
    type_id = ModelField(as_key=ModelField.KeyType.DESC)
    ticket_id = ModelField(as_key=ModelField.KeyType.DESC)
    created_at = ModelField(as_key=ModelField.KeyType.ASC,
                            auto_now_create=True)

    class OrzMeta:
        id2str = False
        order_combs = (("created_at", ), )

    @property
    def commit(self):
        if self.type == TICKET_NODE_TYPE_COMMIT:
            return TicketCommits.get(id=self.type_id)

    @property
    def comment(self):
        if self.type == TICKET_NODE_TYPE_COMMENT:
            return TicketComment.get(id=self.type_id)

    @property
    def codereview(self):
        if self.type == TICKET_NODE_TYPE_LINECOMMENT:
            return PullLineComment.get(self.type_id)

    @classmethod
    def gets_by_ticket_id(cls, id):
        return cls.gets(ticket_id=id, order_by='created_at')

    @classmethod
    def get_by_type_id(cls, type, id):
        return cls.get(type=type, type_id=id)

    @classmethod
    def add(cls, type, type_id, author, ticket_id, created_at):
        return cls.create(type=type,
                          type_id=type_id,
                          author=author,
                          ticket_id=ticket_id,
                          created_at=created_at)

    @classmethod
    def add_commit(cls, commit):
        id = commit.id
        ticket_id = commit.ticket_id
        author = commit.author
        created_at = commit.time
        type = TICKET_NODE_TYPE_COMMIT
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def add_comment(cls, comment):
        id = comment.id
        ticket_id = comment.ticket_id
        author = comment.author
        created_at = comment.time
        type = TICKET_NODE_TYPE_COMMENT
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def add_codereview(cls, codereview):
        id = codereview.id
        ticket_id = codereview.target_id
        author = codereview.author
        created_at = codereview.created_at
        type = TICKET_NODE_TYPE_LINECOMMENT
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def add_merge(cls, ticket_id, author, created_at):
        type = TICKET_NODE_TYPE_MERGE
        id = 0
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def add_open(cls, ticket_id, author, created_at):
        type = TICKET_NODE_TYPE_OPEN
        id = 0
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def add_close(cls, ticket_id, author, created_at):
        type = TICKET_NODE_TYPE_CLOSE
        id = 0
        return cls.add(type, id, author, ticket_id, created_at)

    @classmethod
    def get_by_comment(cls, comment):
        type = TICKET_NODE_TYPE_COMMENT
        return cls.get_by_type_id(type, comment.id)

    @classmethod
    def get_by_codereview(cls, codereview):
        type = TICKET_NODE_TYPE_LINECOMMENT
        return cls.get_by_type_id(type, codereview.id)

    # 用于migrate
    @classmethod
    def get_by_codereview_id(cls, codereview_id):
        type = TICKET_NODE_TYPE_LINECOMMENT
        return cls.get_by_type_id(type, codereview_id)

    @classmethod
    def get_by_commit(cls, commit):
        type = TICKET_NODE_TYPE_COMMIT
        return cls.get_by_type_id(type, commit.id)

    def delete_type(self):
        if self.type == TICKET_NODE_TYPE_COMMENT:
            comment = self.comment
            comment.delete()
        elif self.type == TICKET_NODE_TYPE_COMMIT:
            commit = self.commit
            commit.delete()
        elif self.type == TICKET_NODE_TYPE_LINECOMMENT:
            codereview = self.codereview
            codereview.delete()
예제 #14
0
class TicketCommits(BaseModel):
    __orz_table__ = "codedouban_ticket_commits"
    author = ModelField()
    commits = ModelField()
    ticket_id = ModelField(as_key=ModelField.KeyType.DESC)
    time = ModelField(as_key=ModelField.KeyType.DESC, auto_now_create=True)

    @property
    def ticket(self):
        from vilya.models.ticket import Ticket
        return Ticket.get(self.ticket_id)

    @property
    def project(self):
        return CodeDoubanProject.get(self.ticket.project_id)

    @classmethod
    def commit_as_dict(cls, proj, sha):
        d = {}
        d['sha'] = sha
        commit = proj.repo.get_commit(sha)
        d['commit'] = {}
        d['commit']['html_url'] = "%s/%s/commits/%s" % (DOMAIN, proj.name, sha)
        d['commit']['sha'] = sha
        d['commit']['message'] = commit.message
        d['commit']['date'] = commit.committer_time.strftime(
            '%Y-%m-%dT%H:%M:%S')
        d['commit']['author'] = dict(name=commit.author.name,
                                     email=commit.author.email)
        d['commit']['committer'] = dict(name=commit.committer.name,
                                        email=commit.committer.email)
        d['commit']['tree'] = dict(html_url="%s/%s/tree/%s" %
                                   (DOMAIN, proj.name, sha),
                                   sha=sha)

        parent = [
            dict(sha=commit.parent,
                 html_url="%s/%s/commits/%s" %
                 (DOMAIN, proj.name, commit.parent))
        ] if commit.parent else []
        d['parent'] = parent

        files = []
        for delta in commit.diff.deltas:
            files.append(
                dict(type=delta.status_text, filepath=delta.new_file_path))
        d['commit']['files'] = files

        return d

    def as_dict(self):
        l = []
        for sha in self.commits.split(','):
            d = TicketCommits.commit_as_dict(self.project, sha)
            l.append(d)
        return l

    @classmethod
    def add(cls, ticket_id, commits, author):
        return cls.create(ticket_id=ticket_id, commits=commits, author=author)

    @classmethod
    def gets_by_ticketid(cls, ticket_id):
        return cls.gets(ticket_id=ticket_id)

    def after_create(self):
        commit = self
        TicketNode.add_commit(commit)