Skip to content

Commit

Permalink
List all changes related to a build.
Browse files Browse the repository at this point in the history
 - to do this, the column 'parent_changeid' was added to the table changes

Note:
 - I added a tab 'changes' for the build page. I think this should be modified in order to have something more readable.
  • Loading branch information
Xavier Delannoy committed Oct 28, 2014
1 parent c52c2d5 commit e9665b0
Show file tree
Hide file tree
Showing 13 changed files with 514 additions and 8 deletions.
9 changes: 8 additions & 1 deletion master/buildbot/data/changes.py
Expand Up @@ -60,12 +60,18 @@ class ChangesEndpoint(FixerMixin, base.Endpoint):
isCollection = True
pathPatterns = """
/changes
/builds/n:buildid/changes
"""
rootLinkName = 'change'

@defer.inlineCallbacks
def get(self, resultSpec, kwargs):
changes = yield self.master.db.changes.getChanges()
buildid = kwargs.get('buildid')
if buildid is None:
changes = yield self.master.db.changes.getChanges()
else:
changes = yield self.master.db.changes.getChanges4Build(buildid)

changes = [(yield self._fixChange(ch)) for ch in changes]
defer.returnValue(changes)

Expand All @@ -85,6 +91,7 @@ class Change(base.ResourceType):

class EntityType(types.Entity):
changeid = types.Integer()
parent_changeid = types.NoneOk(types.Integer())
author = types.String()
files = types.List(of=types.String())
comments = types.String()
Expand Down
65 changes: 64 additions & 1 deletion master/buildbot/db/changes.py
Expand Up @@ -35,6 +35,20 @@ class ChDict(dict):
class ChangesConnectorComponent(base.DBConnectorComponent):
# Documentation is in developer/db.rst

def getParentChangeid(self, branch, repository, project, codebase):
def thd(conn):
changes_tbl = self.db.model.changes
q = sa.select([changes_tbl.c.changeid],
whereclause=((changes_tbl.c.branch == branch) &
(changes_tbl.c.repository == repository) &
(changes_tbl.c.project == project) &
(changes_tbl.c.codebase == codebase)),
order_by=sa.desc(changes_tbl.c.changeid),
limit=1)
return conn.scalar(q)
d = self.db.pool.do(thd)
return d

This comment has been minimized.

Copy link
@delanne

delanne Oct 28, 2014

Contributor

Is everyone ok with the way the parent change is identified ?


@defer.inlineCallbacks
def addChange(self, author=None, files=None, comments=None, is_dir=None,
revision=None, when_timestamp=None, branch=None,
Expand Down Expand Up @@ -68,6 +82,8 @@ def addChange(self, author=None, files=None, comments=None, is_dir=None,
revision=revision, branch=branch, repository=repository,
codebase=codebase, project=project, _reactor=_reactor)

parent_changeid = yield self.getParentChangeid(branch, repository, project, codebase)

def thd(conn):
# note that in a read-uncommitted database like SQLite this
# transaction does not buy atomicity - other database users may
Expand All @@ -88,7 +104,8 @@ def thd(conn):
repository=repository,
codebase=codebase,
project=project,
sourcestampid=ssid))
sourcestampid=ssid,
parent_changeid=parent_changeid))
changeid = r.inserted_primary_key[0]
if files:
tbl = self.db.model.change_files
Expand Down Expand Up @@ -139,6 +156,51 @@ def thd(conn):
d = self.db.pool.do(thd)
return d

@defer.inlineCallbacks
def getChanges4Build(self, buildid):

This comment has been minimized.

Copy link
@jaredgrubb

jaredgrubb Oct 30, 2014

Member

Please dont abbreviate "For" as "4".

assert buildid > 0
changes = list()

currentBuild = yield self.master.db.builds.getBuild(buildid)
fromChanges, toChanges = dict(), dict()
for ss in (yield self.master.db.sourcestamps.getSourceStamps4build(buildid)):
fromChanges[ss['codebase']] = yield self.master.db.changes.getChangeFromSSid(ss['ssid'])

if currentBuild['number'] > 1:
# Get the Previous Build, and the related sourcestamps
previousBuild = yield self.master.db.builds.getBuildByNumber(currentBuild['builderid'], currentBuild['number'] - 1)
toChanges = dict()
for ss in (yield self.master.db.sourcestamps.getSourceStamps4build(previousBuild['id'])):
toChanges[ss['codebase']] = yield self.master.db.changes.getChangeFromSSid(ss['ssid'])
else:
# If no previous build, then we need to catch all changes
for cb, change in fromChanges.iteritems():
toChanges[ss['codebase']] = {'changeid': None}

# For each codebase, append changes until we match the parent
for cb, change in fromChanges.iteritems():
changes.append(change)
while change['parent_changeid'] != toChanges[cb]['changeid']:
change = yield self.master.db.changes.getChange(change['parent_changeid'])
changes.append(change)
defer.returnValue(changes)

def getChangeFromSSid(self, sourcestampid):
assert sourcestampid >= 0

def thd(conn):
# get the row from the 'changes' table
changes_tbl = self.db.model.changes
q = changes_tbl.select(whereclause=(changes_tbl.c.sourcestampid == sourcestampid))
rp = conn.execute(q)
row = rp.fetchone()
if not row:
return None
# and fetch the ancillary data (files, properties)
return self._chdict_from_change_row_thd(conn, row)
d = self.db.pool.do(thd)
return d

def getChangeUids(self, changeid):
assert changeid >= 0

Expand Down Expand Up @@ -257,6 +319,7 @@ def _chdict_from_change_row_thd(self, conn, ch_row):

chdict = ChDict(
changeid=ch_row.changeid,
parent_changeid=ch_row.parent_changeid,
author=ch_row.author,
files=[], # see below
comments=ch_row.comments,
Expand Down
26 changes: 26 additions & 0 deletions master/buildbot/db/migrate/versions/039_changes_parent.py
@@ -0,0 +1,26 @@
# This file is part of Buildbot. Buildbot is free software: you can
# redistribute it and/or modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation, version 2.
#
# This program is distributed in the hope that it will be useful, but WITHOUT
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
# FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
# details.
#
# You should have received a copy of the GNU General Public License along with
# this program; if not, write to the Free Software Foundation, Inc., 51
# Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
#
# Copyright Buildbot Team Members

import sqlalchemy as sa


def upgrade(migrate_engine):

This comment has been minimized.

Copy link
@delanne

delanne Oct 28, 2014

Contributor

Should we upgrade existing row ?

metadata = sa.MetaData()
metadata.bind = migrate_engine

changes_table = sa.Table('changes', metadata, autoload=True)

parent_changeid = sa.Column('parent_changeid', sa.Integer, sa.ForeignKey('changes.changeid'), nullable=True)
parent_changeid.create(changes_table)
5 changes: 4 additions & 1 deletion master/buildbot/db/model.py
Expand Up @@ -326,7 +326,10 @@ class Model(base.DBConnectorComponent):

# the sourcestamp this change brought the codebase to
sa.Column('sourcestampid', sa.Integer,
sa.ForeignKey('sourcestamps.id'))
sa.ForeignKey('sourcestamps.id')),

# The parent of the change
sa.Column('parent_changeid', sa.Integer, sa.ForeignKey('changes.changeid'), nullable=True),
)

# sourcestamps
Expand Down
28 changes: 28 additions & 0 deletions master/buildbot/db/sourcestamps.py
Expand Up @@ -14,6 +14,7 @@
# Copyright Buildbot Team Members

import base64
import sqlalchemy as sa

from buildbot.db import base
from buildbot.util import epoch2datetime
Expand Down Expand Up @@ -95,6 +96,33 @@ def thd(conn):
return ssdict
return self.db.pool.do(thd)

def getSourceStamps4build(self, buildid):
assert buildid > 0

def thd(conn):
# Get SourceStamps for the build
builds_tbl = self.db.model.builds
reqs_tbl = self.db.model.buildrequests
bsets_tbl = self.db.model.buildsets
bsss_tbl = self.db.model.buildset_sourcestamps
sstamps_tbl = self.db.model.sourcestamps

from_clause = builds_tbl.join(reqs_tbl,
builds_tbl.c.buildrequestid == reqs_tbl.c.id)
from_clause = from_clause.join(bsets_tbl,
reqs_tbl.c.buildsetid == bsets_tbl.c.id)
from_clause = from_clause.join(bsss_tbl,
bsets_tbl.c.id == bsss_tbl.c.buildsetid)
from_clause = from_clause.join(sstamps_tbl,
bsss_tbl.c.sourcestampid == sstamps_tbl.c.id)

q = sa.select([sstamps_tbl]).select_from(from_clause).where(builds_tbl.c.id == buildid)
res = conn.execute(q)
return [self._rowToSsdict_thd(conn, row)
for row in res.fetchall()]

return self.db.pool.do(thd)

def getSourceStamps(self):
def thd(conn):
tbl = self.db.model.sourcestamps
Expand Down
25 changes: 25 additions & 0 deletions master/buildbot/test/fake/fakedb.py
Expand Up @@ -217,6 +217,7 @@ class Change(Row):
codebase=u'',
project=u'proj',
sourcestampid=92,
parent_changeid=None,
)

lists = ('files', 'uids')
Expand Down Expand Up @@ -718,8 +719,11 @@ def addChange(self, author=None, files=None, comments=None, is_dir=None,
revision=revision, branch=branch, repository=repository,
codebase=codebase, project=project, _reactor=_reactor)

parent_changeid = yield self.getParentChangeid(branch, repository, project, codebase)

self.changes[changeid] = ch = dict(
changeid=changeid,
parent_changeid=parent_changeid,
author=author,
comments=comments,
revision=revision,
Expand All @@ -745,6 +749,16 @@ def getLatestChangeid(self):
return defer.succeed(max(self.changes.iterkeys()))
return defer.succeed(None)

def getParentChangeid(self, branch, repository, project, codebase):
if self.changes:
for changeid, change in self.changes.iteritems():
if (change['branch'] == branch and
change['repository'] == repository and
change['repository'] == repository and

This comment has been minimized.

Copy link
@delanne

delanne Oct 28, 2014

Contributor

oups, it should be: change['project'] == project

change['codebase'] == codebase):
return defer.succeed(change['changeid'])
return defer.succeed(None)

def getChange(self, key, no_cache=False):
try:
row = self.changes[key]
Expand Down Expand Up @@ -772,6 +786,9 @@ def getChanges(self):
def getChangesCount(self):
return len(self.changes)

def getChanges4Build(self, buildid):
pass

def _chdict(self, row):
chdict = row.copy()
del chdict['uids']
Expand Down Expand Up @@ -1051,6 +1068,14 @@ def _getSourceStamp_sync(self, ssid):
else:
return None

@defer.inlineCallbacks
def getSourceStamps4build(self, buildid):
build = yield self.db.builds.getBuild(buildid)
breq = yield self.db.buildrequests.getBuildRequest(build['buildrequestid'])
bset = yield self.db.buildsets.getBuildset(breq['buildsetid'])

defer.returnValue([(yield self.getSourceStamp(ssid)) for ssid in bset['sourcestamps']])


class FakeBuildsetsComponent(FakeDBComponent):

Expand Down
3 changes: 3 additions & 0 deletions master/buildbot/test/unit/test_data_changes.py
Expand Up @@ -109,6 +109,7 @@ class Change(interfaces.InterfaceTests, unittest.TestCase):
'comments': u'fix whitespace',
'changeid': 500,
'files': [u'master/buildbot/__init__.py'],
'parent_changeid': None,
'project': u'Buildbot',
'properties': {u'foo': (20, u'Change')},
'repository': u'git://warner',
Expand Down Expand Up @@ -210,6 +211,7 @@ def test_addChange_src_codebase(self):
'comments': u'fix whitespace',
'changeid': 500,
'files': [u'master/buildbot/__init__.py'],
'parent_changeid': None,
'project': u'Buildbot',
'properties': {u'foo': (20, u'Change')},
'repository': u'git://warner',
Expand Down Expand Up @@ -272,6 +274,7 @@ def test_addChange_src_codebaseGenerator(self):
'comments': u'fix whitespace',
'changeid': 500,
'files': [u'master/buildbot/__init__.py'],
'parent_changeid': None,
'project': u'Buildbot',
'properties': {u'foo': (20, u'Change')},
'repository': u'git://warner',
Expand Down

1 comment on commit e9665b0

@jaredgrubb
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are you intending to mean by "parent change"? In this patch it seems to mean ordered in time?

Please sign in to comment.