Add support for conflicts-with
This adds a database migration for a new table. Change-Id: I9d5ea4eec89f706435430b90f563b5a0c0fef9e8
This commit is contained in:
parent
7fc7d18db4
commit
4eef0452d5
27
gertty/alembic/versions/3610c2543e07_add_conflicts_table.py
Normal file
27
gertty/alembic/versions/3610c2543e07_add_conflicts_table.py
Normal file
@ -0,0 +1,27 @@
|
||||
"""add conflicts table
|
||||
|
||||
Revision ID: 3610c2543e07
|
||||
Revises: 4388de50824a
|
||||
Create Date: 2016-02-05 16:43:20.047238
|
||||
|
||||
"""
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = '3610c2543e07'
|
||||
down_revision = '4388de50824a'
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
def upgrade():
|
||||
op.create_table('change_conflict',
|
||||
sa.Column('key', sa.Integer(), nullable=False),
|
||||
sa.Column('change1_key', sa.Integer(), sa.ForeignKey('change.key'), index=True),
|
||||
sa.Column('change2_key', sa.Integer(), sa.ForeignKey('change.key'), index=True),
|
||||
sa.PrimaryKeyConstraint('key')
|
||||
)
|
||||
|
||||
|
||||
def downgrade():
|
||||
pass
|
40
gertty/db.py
40
gertty/db.py
@ -82,6 +82,12 @@ change_table = Table(
|
||||
Column('pending_status', Boolean, index=True, nullable=False),
|
||||
Column('pending_status_message', Text),
|
||||
)
|
||||
change_conflict_table = Table(
|
||||
'change_conflict', metadata,
|
||||
Column('key', Integer, primary_key=True),
|
||||
Column('change1_key', Integer, ForeignKey("change.key"), index=True),
|
||||
Column('change2_key', Integer, ForeignKey("change.key"), index=True),
|
||||
)
|
||||
revision_table = Table(
|
||||
'revision', metadata,
|
||||
Column('key', Integer, primary_key=True),
|
||||
@ -366,6 +372,30 @@ class Change(object):
|
||||
owner_name = self.owner.email
|
||||
return owner_name
|
||||
|
||||
@property
|
||||
def conflicts(self):
|
||||
return tuple(set(self.conflicts1 + self.conflicts2))
|
||||
|
||||
def addConflict(self, other):
|
||||
session = Session.object_session(self)
|
||||
if other in self.conflicts1 or other in self.conflicts2:
|
||||
return
|
||||
if self in other.conflicts1 or self in other.conflicts2:
|
||||
return
|
||||
self.conflicts1.append(other)
|
||||
session.flush()
|
||||
session.expire(other, attribute_names=['conflicts2'])
|
||||
|
||||
def delConflict(self, other):
|
||||
session = Session.object_session(self)
|
||||
if other in self.conflicts1:
|
||||
self.conflicts1.remove(other)
|
||||
session.flush()
|
||||
session.expire(other, attribute_names=['conflicts2'])
|
||||
if self in other.conflicts1:
|
||||
other.conflicts1.remove(self)
|
||||
session.flush()
|
||||
session.expire(self, attribute_names=['conflicts2'])
|
||||
|
||||
class Revision(object):
|
||||
def __init__(self, change, number, message, commit, parent,
|
||||
@ -586,6 +616,16 @@ mapper(Topic, topic_table, properties=dict(
|
||||
mapper(ProjectTopic, project_topic_table)
|
||||
mapper(Change, change_table, properties=dict(
|
||||
owner=relationship(Account),
|
||||
conflicts1=relationship(Change,
|
||||
secondary=change_conflict_table,
|
||||
primaryjoin=change_table.c.key==change_conflict_table.c.change1_key,
|
||||
secondaryjoin=change_table.c.key==change_conflict_table.c.change2_key,
|
||||
),
|
||||
conflicts2=relationship(Change,
|
||||
secondary=change_conflict_table,
|
||||
primaryjoin=change_table.c.key==change_conflict_table.c.change2_key,
|
||||
secondaryjoin=change_table.c.key==change_conflict_table.c.change1_key,
|
||||
),
|
||||
revisions=relationship(Revision, backref='change',
|
||||
order_by=revision_table.c.number,
|
||||
cascade='all, delete-orphan'),
|
||||
|
@ -351,29 +351,7 @@ class SyncProjectTask(Task):
|
||||
else:
|
||||
query += ' status:open'
|
||||
queries.append(query)
|
||||
changes = []
|
||||
sortkey = ''
|
||||
done = False
|
||||
offset = 0
|
||||
while not done:
|
||||
query = '&'.join(queries)
|
||||
# We don't actually want to limit to 500, but that's the server-side default, and
|
||||
# if we don't specify this, we won't get a _more_changes flag.
|
||||
q = 'changes/?n=500%s&%s' % (sortkey, query)
|
||||
self.log.debug('Query: %s ' % (q,))
|
||||
responses = sync.get(q)
|
||||
if len(queries) == 1:
|
||||
responses = [responses]
|
||||
done = True
|
||||
for batch in responses:
|
||||
changes += batch
|
||||
if batch and '_more_changes' in batch[-1]:
|
||||
done = False
|
||||
if '_sortkey' in batch[-1]:
|
||||
sortkey = '&N=%s' % (batch[-1]['_sortkey'],)
|
||||
else:
|
||||
offset += len(batch)
|
||||
sortkey = '&start=%s' % (offset,)
|
||||
changes = sync.query(queries)
|
||||
change_ids = [c['id'] for c in changes]
|
||||
with app.db.getSession() as session:
|
||||
# Winnow the list of IDs to only the ones in the local DB.
|
||||
@ -566,6 +544,8 @@ class SyncChangeTask(Task):
|
||||
for remote_commit, remote_revision in remote_change.get('revisions', {}).items():
|
||||
remote_comments_data = sync.get('changes/%s/revisions/%s/comments' % (self.change_id, remote_commit))
|
||||
remote_revision['_gertty_remote_comments_data'] = remote_comments_data
|
||||
remote_conflicts = sync.query(['q=status:open+is:mergeable+conflicts:%s' %
|
||||
remote_change['_number']])
|
||||
fetches = collections.defaultdict(list)
|
||||
parent_commits = set()
|
||||
with app.db.getSession() as session:
|
||||
@ -600,6 +580,28 @@ class SyncChangeTask(Task):
|
||||
change.subject = remote_change['subject']
|
||||
change.updated = dateutil.parser.parse(remote_change['updated'])
|
||||
change.topic = remote_change.get('topic')
|
||||
unseen_conflicts = [x.id for x in change.conflicts]
|
||||
for remote_conflict in remote_conflicts:
|
||||
conflict_id = remote_conflict['id']
|
||||
conflict = session.getChangeByID(conflict_id)
|
||||
if not conflict:
|
||||
self.log.info("Need to sync conflicting change %s for change %s.",
|
||||
conflict_id, change.number)
|
||||
sync.submitTask(SyncChangeTask(conflict_id, priority=self.priority))
|
||||
else:
|
||||
if conflict not in change.conflicts:
|
||||
self.log.info("Added conflict %s for change %s in local DB.",
|
||||
conflict.number, change.number)
|
||||
change.addConflict(conflict)
|
||||
self.results.append(ChangeUpdatedEvent(conflict))
|
||||
if conflict_id in unseen_conflicts:
|
||||
unseen_conflicts.remove(conflict_id)
|
||||
for conflict_id in unseen_conflicts:
|
||||
conflict = session.getChangeByID(conflict_id)
|
||||
self.log.info("Deleted conflict %s for change %s in local DB.",
|
||||
conflict.number, change.number)
|
||||
change.delConflict(conflict)
|
||||
self.results.append(ChangeUpdatedEvent(conflict))
|
||||
repo = gitrepo.get_repo(change.project.name, app.config)
|
||||
new_revision = False
|
||||
for remote_commit, remote_revision in remote_change.get('revisions', {}).items():
|
||||
@ -1290,6 +1292,8 @@ class VacuumDatabaseTask(Task):
|
||||
session.vacuum()
|
||||
|
||||
class Sync(object):
|
||||
_quiet_debug_mode = False
|
||||
|
||||
def __init__(self, app):
|
||||
self.user_agent = 'Gertty/%s %s' % (gertty.version.version_info.release_string(),
|
||||
requests.utils.default_user_agent())
|
||||
@ -1308,16 +1312,17 @@ class Sync(object):
|
||||
self.auth = authclass(
|
||||
self.app.config.username, self.app.config.password)
|
||||
self.submitTask(GetVersionTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncOwnAccountTask(HIGH_PRIORITY))
|
||||
self.submitTask(CheckReposTask(HIGH_PRIORITY))
|
||||
self.submitTask(UploadReviewsTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncProjectListTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectsTask(NORMAL_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectBranchesTask(LOW_PRIORITY))
|
||||
self.submitTask(PruneDatabaseTask(self.app.config.expire_age, LOW_PRIORITY))
|
||||
self.periodic_thread = threading.Thread(target=self.periodicSync)
|
||||
self.periodic_thread.daemon = True
|
||||
self.periodic_thread.start()
|
||||
if not self._quiet_debug_mode:
|
||||
self.submitTask(SyncOwnAccountTask(HIGH_PRIORITY))
|
||||
self.submitTask(CheckReposTask(HIGH_PRIORITY))
|
||||
self.submitTask(UploadReviewsTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncProjectListTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectsTask(NORMAL_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectBranchesTask(LOW_PRIORITY))
|
||||
self.submitTask(PruneDatabaseTask(self.app.config.expire_age, LOW_PRIORITY))
|
||||
self.periodic_thread = threading.Thread(target=self.periodicSync)
|
||||
self.periodic_thread.daemon = True
|
||||
self.periodic_thread.start()
|
||||
|
||||
def periodicSync(self):
|
||||
hourly = time.time()
|
||||
@ -1475,3 +1480,29 @@ class Sync(object):
|
||||
micro = int(parts[2])
|
||||
self.version = (major, minor, micro)
|
||||
self.log.info("Remote version is: %s (parsed as %s)" % (version, self.version))
|
||||
|
||||
def query(self, queries):
|
||||
changes = []
|
||||
sortkey = ''
|
||||
done = False
|
||||
offset = 0
|
||||
while not done:
|
||||
query = '&'.join(queries)
|
||||
# We don't actually want to limit to 500, but that's the server-side default, and
|
||||
# if we don't specify this, we won't get a _more_changes flag.
|
||||
q = 'changes/?n=500%s&%s' % (sortkey, query)
|
||||
self.log.debug('Query: %s' % (q,))
|
||||
responses = self.get(q)
|
||||
if len(queries) == 1:
|
||||
responses = [responses]
|
||||
done = True
|
||||
for batch in responses:
|
||||
changes += batch
|
||||
if batch and '_more_changes' in batch[-1]:
|
||||
done = False
|
||||
if '_sortkey' in batch[-1]:
|
||||
sortkey = '&N=%s' % (batch[-1]['_sortkey'],)
|
||||
else:
|
||||
offset += len(batch)
|
||||
sortkey = '&start=%s' % (offset,)
|
||||
return changes
|
||||
|
@ -485,7 +485,9 @@ class ChangeView(urwid.WidgetWrap):
|
||||
self.depends_on_rows = {}
|
||||
self.needed_by = urwid.Pile([])
|
||||
self.needed_by_rows = {}
|
||||
self.related_changes = urwid.Pile([self.depends_on, self.needed_by])
|
||||
self.conflicts_with = urwid.Pile([])
|
||||
self.conflicts_with_rows = {}
|
||||
self.related_changes = urwid.Pile([self.depends_on, self.needed_by, self.conflicts_with])
|
||||
self.results = mywid.HyperText(u'') # because it scrolls better than a table
|
||||
self.grid = mywid.MyGridFlow([change_info, self.commit_message, votes, self.results],
|
||||
cell_width=80, h_sep=2, v_sep=1, align='left')
|
||||
@ -770,6 +772,16 @@ class ChangeView(urwid.WidgetWrap):
|
||||
self.needed_by, self.needed_by_rows,
|
||||
header='Needed by:')
|
||||
|
||||
# Handle conflicts_with
|
||||
conflicts = {}
|
||||
conflicts.update((c.key, c.subject)
|
||||
for c in change.conflicts
|
||||
if (c.status != 'MERGED' and
|
||||
c.status != 'ABANDONED'))
|
||||
self._updateDependenciesWidget(conflicts,
|
||||
self.conflicts_with, self.conflicts_with_rows,
|
||||
header='Conflicts with:')
|
||||
|
||||
|
||||
def toggleReviewed(self):
|
||||
with self.app.db.getSession() as session:
|
||||
|
Loading…
x
Reference in New Issue
Block a user