Thread changes
Use the dependency information of the most recent revisions to create a threaded view of changes. Also, use a more efficient query to collect the children of a change in the change view. Change-Id: I1cbef9fe7c4f1822b8541e5b4d0e7d2fe2e180a9
This commit is contained in:
parent
84e072e172
commit
b6f20ee70b
@ -142,6 +142,10 @@ commentlinks:
|
|||||||
# of the default side-by-side:
|
# of the default side-by-side:
|
||||||
# diff-view: unified
|
# diff-view: unified
|
||||||
|
|
||||||
|
# Dependent changes are displayed as "threads" in the change list by
|
||||||
|
# default. To disable this behavior, uncomment the following line:
|
||||||
|
# thread-changes: false
|
||||||
|
|
||||||
# Uncomment the following lines to Hide comments by default that match
|
# Uncomment the following lines to Hide comments by default that match
|
||||||
# certain criteria. You can toggle their display with 't'. Currently
|
# certain criteria. You can toggle their display with 't'. Currently
|
||||||
# the only supported criterion is "author".
|
# the only supported criterion is "author".
|
||||||
|
@ -110,6 +110,7 @@ class ConfigSchema(object):
|
|||||||
'change-list-query': str,
|
'change-list-query': str,
|
||||||
'diff-view': str,
|
'diff-view': str,
|
||||||
'hide-comments': self.hide_comments,
|
'hide-comments': self.hide_comments,
|
||||||
|
'thread-changes': bool,
|
||||||
})
|
})
|
||||||
return schema
|
return schema
|
||||||
|
|
||||||
@ -200,6 +201,8 @@ class Config(object):
|
|||||||
for h in self.config.get('hide-comments', []):
|
for h in self.config.get('hide-comments', []):
|
||||||
self.hide_comments.append(re.compile(h['author']))
|
self.hide_comments.append(re.compile(h['author']))
|
||||||
|
|
||||||
|
self.thread_changes = self.config.get('thread-changes', True)
|
||||||
|
|
||||||
def getServer(self, name=None):
|
def getServer(self, name=None):
|
||||||
for server in self.config['servers']:
|
for server in self.config['servers']:
|
||||||
if name is None or name == server['name']:
|
if name is None or name == server['name']:
|
||||||
|
@ -606,8 +606,10 @@ class DatabaseSession(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def getRevisionsByParent(self, parent):
|
def getRevisionsByParent(self, parent):
|
||||||
|
if isinstance(parent, basestring):
|
||||||
|
parent = (parent,)
|
||||||
try:
|
try:
|
||||||
return self.session().query(Revision).filter_by(parent=parent).all()
|
return self.session().query(Revision).filter(Revision.parent.in_(parent)).all()
|
||||||
except sqlalchemy.orm.exc.NoResultFound:
|
except sqlalchemy.orm.exc.NoResultFound:
|
||||||
return []
|
return []
|
||||||
|
|
||||||
|
@ -745,11 +745,10 @@ class ChangeView(urwid.WidgetWrap):
|
|||||||
|
|
||||||
# Handle needed-by
|
# Handle needed-by
|
||||||
children = {}
|
children = {}
|
||||||
for revision in change.revisions:
|
children.update((r.change.key, r.change.subject)
|
||||||
children.update((r.change.key, r.change.subject)
|
for r in session.getRevisionsByParent([revision.commit for revision in change.revisions])
|
||||||
for r in session.getRevisionsByParent(revision.commit)
|
if (r.change.status != 'MERGED' and
|
||||||
if (r.change.status != 'MERGED' and
|
r == r.change.revisions[-1]))
|
||||||
r == r.change.revisions[-1]))
|
|
||||||
self._updateDependenciesWidget(children,
|
self._updateDependenciesWidget(children,
|
||||||
self.needed_by, self.needed_by_rows,
|
self.needed_by, self.needed_by_rows,
|
||||||
header='Needed by:')
|
header='Needed by:')
|
||||||
|
@ -23,6 +23,29 @@ from gertty import sync
|
|||||||
from gertty.view import change as view_change
|
from gertty.view import change as view_change
|
||||||
import gertty.view
|
import gertty.view
|
||||||
|
|
||||||
|
|
||||||
|
class ThreadStack(object):
|
||||||
|
def __init__(self):
|
||||||
|
self.stack = []
|
||||||
|
|
||||||
|
def push(self, change, children):
|
||||||
|
self.stack.append([change, children])
|
||||||
|
|
||||||
|
def pop(self):
|
||||||
|
change = None
|
||||||
|
while self.stack:
|
||||||
|
if self.stack[-1][1]:
|
||||||
|
# handle children at the tip
|
||||||
|
return self.stack[-1][1].pop(0)
|
||||||
|
else:
|
||||||
|
# current tip has no children, walk up
|
||||||
|
self.stack.pop()
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def countChildren(self):
|
||||||
|
return [len(x[1]) for x in self.stack]
|
||||||
|
|
||||||
class ChangeRow(urwid.Button):
|
class ChangeRow(urwid.Button):
|
||||||
change_focus_map = {None: 'focused',
|
change_focus_map = {None: 'focused',
|
||||||
'unreviewed-change': 'focused-unreviewed-change',
|
'unreviewed-change': 'focused-unreviewed-change',
|
||||||
@ -60,7 +83,11 @@ class ChangeRow(urwid.Button):
|
|||||||
else:
|
else:
|
||||||
style = 'unreviewed-change'
|
style = 'unreviewed-change'
|
||||||
self.row_style.set_attr_map({None: style})
|
self.row_style.set_attr_map({None: style})
|
||||||
self.subject.set_text(change.subject)
|
if hasattr(change, '_subject'):
|
||||||
|
subject = change._subject
|
||||||
|
else:
|
||||||
|
subject = change.subject
|
||||||
|
self.subject.set_text(subject)
|
||||||
self.number.set_text(str(change.number))
|
self.number.set_text(str(change.number))
|
||||||
self.project.set_text(change.project.name.split('/')[-1])
|
self.project.set_text(change.project.name.split('/')[-1])
|
||||||
self.owner.set_text(change.owner_name)
|
self.owner.set_text(change.owner_name)
|
||||||
@ -176,6 +203,15 @@ class ChangeListView(urwid.WidgetWrap):
|
|||||||
change_list = reversed(lst)
|
change_list = reversed(lst)
|
||||||
else:
|
else:
|
||||||
change_list = lst
|
change_list = lst
|
||||||
|
if self.app.config.thread_changes:
|
||||||
|
change_list = self._threadChanges(change_list)
|
||||||
|
new_rows = []
|
||||||
|
if len(self.listbox.body):
|
||||||
|
focus_pos = self.listbox.focus_position
|
||||||
|
focus_row = self.listbox.body[focus_pos]
|
||||||
|
else:
|
||||||
|
focus_pos = 0
|
||||||
|
focus_row = None
|
||||||
for change in change_list:
|
for change in change_list:
|
||||||
row = self.change_rows.get(change.key)
|
row = self.change_rows.get(change.key)
|
||||||
if not row:
|
if not row:
|
||||||
@ -187,14 +223,78 @@ class ChangeListView(urwid.WidgetWrap):
|
|||||||
else:
|
else:
|
||||||
row.update(change, self.categories)
|
row.update(change, self.categories)
|
||||||
unseen_keys.remove(change.key)
|
unseen_keys.remove(change.key)
|
||||||
|
new_rows.append(row)
|
||||||
i += 1
|
i += 1
|
||||||
|
self.listbox.body[:] = new_rows
|
||||||
|
if focus_row in self.listbox.body:
|
||||||
|
pos = self.listbox.body.index(focus_row)
|
||||||
|
else:
|
||||||
|
pos = min(focus_pos, len(self.listbox.body)-1)
|
||||||
|
self.listbox.body.set_focus(pos)
|
||||||
if lst:
|
if lst:
|
||||||
self.header.update(self.categories)
|
self.header.update(self.categories)
|
||||||
for key in unseen_keys:
|
for key in unseen_keys:
|
||||||
row = self.change_rows[key]
|
row = self.change_rows[key]
|
||||||
self.listbox.body.remove(row)
|
|
||||||
del self.change_rows[key]
|
del self.change_rows[key]
|
||||||
|
|
||||||
|
def _threadChanges(self, changes):
|
||||||
|
ret = []
|
||||||
|
stack = ThreadStack()
|
||||||
|
children = {}
|
||||||
|
commits = {}
|
||||||
|
orphans = changes[:]
|
||||||
|
for change in changes:
|
||||||
|
for revision in change.revisions:
|
||||||
|
commits[revision.commit] = change
|
||||||
|
for change in changes:
|
||||||
|
revision = change.revisions[-1]
|
||||||
|
parent = commits.get(revision.parent, None)
|
||||||
|
if parent:
|
||||||
|
if parent.revisions[-1].commit != revision.parent:
|
||||||
|
# Our parent is an outdated revision. This could
|
||||||
|
# cause a cycle, so skip. This change will not
|
||||||
|
# appear in the thread, but will still appear in
|
||||||
|
# the list. TODO: use color to indicate it
|
||||||
|
# depends on an outdated change.
|
||||||
|
continue
|
||||||
|
if change in orphans:
|
||||||
|
orphans.remove(change)
|
||||||
|
v = children.get(parent, [])
|
||||||
|
v.append(change)
|
||||||
|
children[parent] = v
|
||||||
|
if orphans:
|
||||||
|
change = orphans.pop(0)
|
||||||
|
else:
|
||||||
|
change = None
|
||||||
|
while change:
|
||||||
|
prefix = ''
|
||||||
|
stack_children = stack.countChildren()
|
||||||
|
for i, nchildren in enumerate(stack_children):
|
||||||
|
if nchildren:
|
||||||
|
if i+1 == len(stack_children):
|
||||||
|
prefix += u'\u251c'
|
||||||
|
else:
|
||||||
|
prefix += u'\u2502'
|
||||||
|
else:
|
||||||
|
if i+1 == len(stack_children):
|
||||||
|
prefix += u'\u2514'
|
||||||
|
else:
|
||||||
|
prefix += u' '
|
||||||
|
if i+1 == len(stack_children):
|
||||||
|
prefix += u'\u2500'
|
||||||
|
else:
|
||||||
|
prefix += u' '
|
||||||
|
subject = '%s%s' % (prefix, change.subject)
|
||||||
|
change._subject = subject
|
||||||
|
ret.append(change)
|
||||||
|
if change in children:
|
||||||
|
stack.push(change, children[change])
|
||||||
|
change = stack.pop()
|
||||||
|
if (not change) and orphans:
|
||||||
|
change = orphans.pop(0)
|
||||||
|
assert len(ret) == len(changes)
|
||||||
|
return ret
|
||||||
|
|
||||||
def clearChangeList(self):
|
def clearChangeList(self):
|
||||||
for key, value in self.change_rows.iteritems():
|
for key, value in self.change_rows.iteritems():
|
||||||
self.listbox.body.remove(value)
|
self.listbox.body.remove(value)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user