Add support for cherry-picking to a branch
Change-Id: I50fc591226f15505daf1bb5d954be4a9e00a39b5
This commit is contained in:
parent
01b575d702
commit
de17a501e7
24
gertty/db.py
24
gertty/db.py
@ -300,6 +300,15 @@ class Revision(object):
|
||||
session.flush()
|
||||
return c
|
||||
|
||||
def createPendingCherryPick(self, *args, **kw):
|
||||
session = Session.object_session(self)
|
||||
args = [self] + list(args)
|
||||
c = PendingCherryPick(*args, **kw)
|
||||
self.pending_cherry_picks.append(c)
|
||||
session.add(c)
|
||||
session.flush()
|
||||
return c
|
||||
|
||||
def getPendingMessage(self):
|
||||
for m in self.messages:
|
||||
if m.pending:
|
||||
@ -366,7 +375,7 @@ class PendingCherryPick(object):
|
||||
mapper(Account, account_table)
|
||||
mapper(Project, project_table, properties=dict(
|
||||
branches=relationship(Branch, backref='project',
|
||||
order_by=branch_table.c.key),
|
||||
order_by=branch_table.c.name),
|
||||
changes=relationship(Change, backref='project',
|
||||
order_by=change_table.c.number),
|
||||
unreviewed_changes=relationship(Change,
|
||||
@ -415,6 +424,7 @@ mapper(Revision, revision_table, properties=dict(
|
||||
comment_table.c.draft==True),
|
||||
order_by=(comment_table.c.line,
|
||||
comment_table.c.created)),
|
||||
pending_cherry_picks=relationship(PendingCherryPick, backref='revision'),
|
||||
))
|
||||
mapper(Message, message_table, properties=dict(
|
||||
author=relationship(Account)))
|
||||
@ -424,8 +434,7 @@ mapper(Label, label_table)
|
||||
mapper(PermittedLabel, permitted_label_table)
|
||||
mapper(Approval, approval_table, properties=dict(
|
||||
reviewer=relationship(Account)))
|
||||
mapper(PendingCherryPick, pending_cherry_pick_table, properties=dict(
|
||||
revision=relationship(Revision)))
|
||||
mapper(PendingCherryPick, pending_cherry_pick_table)
|
||||
|
||||
class Database(object):
|
||||
def __init__(self, app):
|
||||
@ -539,6 +548,12 @@ class DatabaseSession(object):
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return None
|
||||
|
||||
def getPendingCherryPick(self, key):
|
||||
try:
|
||||
return self.session().query(PendingCherryPick).filter_by(key=key).one()
|
||||
except sqlalchemy.orm.exc.NoResultFound:
|
||||
return None
|
||||
|
||||
def getChanges(self, query, unreviewed=False):
|
||||
self.database.log.debug("Search query: %s" % query)
|
||||
q = self.session().query(Change).filter(self.search.parse(query))
|
||||
@ -611,6 +626,9 @@ class DatabaseSession(object):
|
||||
def getPendingStatusChanges(self):
|
||||
return self.session().query(Change).filter_by(pending_status=True).all()
|
||||
|
||||
def getPendingCherryPicks(self):
|
||||
return self.session().query(PendingCherryPick).all()
|
||||
|
||||
def getAccountByID(self, id, name=None, username=None, email=None):
|
||||
try:
|
||||
account = self.session().query(Account).filter_by(id=id).one()
|
||||
|
@ -48,6 +48,7 @@ TOGGLE_HIDDEN_COMMENTS = 'toggle hidden comments'
|
||||
ABANDON_CHANGE = 'abandon change'
|
||||
RESTORE_CHANGE = 'restore change'
|
||||
REBASE_CHANGE = 'rebase change'
|
||||
CHERRY_PICK_CHANGE = 'cherry-pick change'
|
||||
REFRESH = 'refresh'
|
||||
EDIT_TOPIC = 'edit topic'
|
||||
# Project list screen:
|
||||
@ -89,6 +90,7 @@ DEFAULT_KEYMAP = {
|
||||
ABANDON_CHANGE: 'ctrl a',
|
||||
RESTORE_CHANGE: 'ctrl e',
|
||||
REBASE_CHANGE: 'ctrl b',
|
||||
CHERRY_PICK_CHANGE: 'ctrl x',
|
||||
REFRESH: 'ctrl r',
|
||||
EDIT_TOPIC: 'ctrl t',
|
||||
|
||||
|
@ -17,7 +17,9 @@ import collections
|
||||
import logging
|
||||
import math
|
||||
import os
|
||||
import re
|
||||
import threading
|
||||
import urllib
|
||||
import urlparse
|
||||
import json
|
||||
import time
|
||||
@ -125,6 +127,49 @@ class SyncProjectListTask(Task):
|
||||
p = remote[name]
|
||||
session.createProject(name, description=p.get('description', ''))
|
||||
|
||||
class SyncSubscribedProjectBranchesTask(Task):
|
||||
def __repr__(self):
|
||||
return '<SyncSubscribedProjectBranchesTask>'
|
||||
|
||||
def run(self, sync):
|
||||
app = sync.app
|
||||
with app.db.getSession() as session:
|
||||
for p in session.getProjects(subscribed=True):
|
||||
sync.submitTask(SyncProjectBranchesTask(p.name, self.priority))
|
||||
|
||||
class SyncProjectBranchesTask(Task):
|
||||
branch_re = re.compile(r'refs/heads/(.*)')
|
||||
|
||||
def __init__(self, project_name, priority=NORMAL_PRIORITY):
|
||||
super(SyncProjectBranchesTask, self).__init__(priority)
|
||||
self.project_name = project_name
|
||||
|
||||
def __repr__(self):
|
||||
return '<SyncProjectBranchesTask %s>' % (self.project_name,)
|
||||
|
||||
def run(self, sync):
|
||||
app = sync.app
|
||||
remote = sync.get('projects/%s/branches/' % urllib.quote_plus(self.project_name))
|
||||
remote_branches = set()
|
||||
for x in remote:
|
||||
m = self.branch_re.match(x['ref'])
|
||||
if m:
|
||||
remote_branches.add(m.group(1))
|
||||
with app.db.getSession() as session:
|
||||
local = {}
|
||||
project = session.getProjectByName(self.project_name)
|
||||
for branch in project.branches:
|
||||
local[branch.name] = branch
|
||||
local_branches = set(local.keys())
|
||||
|
||||
for name in local_branches-remote_branches:
|
||||
self.log.debug("Delete branch %s from project %s" % (name, project.name))
|
||||
session.delete(local[name])
|
||||
|
||||
for name in remote_branches-local_branches:
|
||||
self.log.debug("Add branch %s to project %s" % (name, project.name))
|
||||
project.createBranch(name)
|
||||
|
||||
class SyncSubscribedProjectsTask(Task):
|
||||
def __repr__(self):
|
||||
return '<SyncSubscribedProjectsTask>'
|
||||
@ -537,6 +582,8 @@ class UploadReviewsTask(Task):
|
||||
sync.submitTask(RebaseChangeTask(c.key, self.priority))
|
||||
for c in session.getPendingStatusChanges():
|
||||
sync.submitTask(ChangeStatusTask(c.key, self.priority))
|
||||
for c in session.getPendingCherryPicks():
|
||||
sync.submitTask(SendCherryPickTask(c.key, self.priority))
|
||||
for m in session.getPendingMessages():
|
||||
sync.submitTask(UploadReviewTask(m.key, self.priority))
|
||||
|
||||
@ -603,6 +650,28 @@ class ChangeStatusTask(Task):
|
||||
data)
|
||||
sync.submitTask(SyncChangeTask(change.id, priority=self.priority))
|
||||
|
||||
class SendCherryPickTask(Task):
|
||||
def __init__(self, cp_key, priority=NORMAL_PRIORITY):
|
||||
super(SendCherryPickTask, self).__init__(priority)
|
||||
self.cp_key = cp_key
|
||||
|
||||
def __repr__(self):
|
||||
return '<SendCherryPickTask %s>' % (self.cp_key,)
|
||||
|
||||
def run(self, sync):
|
||||
app = sync.app
|
||||
with app.db.getSession() as session:
|
||||
cp = session.getPendingCherryPick(self.cp_key)
|
||||
data = dict(message=cp.message,
|
||||
destination=cp.branch)
|
||||
session.delete(cp)
|
||||
# Inside db session for rollback
|
||||
ret = sync.post('changes/%s/revisions/%s/cherrypick' %
|
||||
(cp.revision.change.id, cp.revision.commit),
|
||||
data)
|
||||
if ret and 'id' in ret:
|
||||
sync.submitTask(SyncChangeTask(ret['id'], priority=self.priority))
|
||||
|
||||
class UploadReviewTask(Task):
|
||||
def __init__(self, message_key, priority=NORMAL_PRIORITY):
|
||||
super(UploadReviewTask, self).__init__(priority)
|
||||
@ -662,6 +731,7 @@ class Sync(object):
|
||||
self.submitTask(UploadReviewsTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncProjectListTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectsTask(HIGH_PRIORITY))
|
||||
self.submitTask(SyncSubscribedProjectBranchesTask(LOW_PRIORITY))
|
||||
self.periodic_thread = threading.Thread(target=self.periodicSync)
|
||||
self.periodic_thread.daemon = True
|
||||
self.periodic_thread.start()
|
||||
@ -735,6 +805,14 @@ class Sync(object):
|
||||
headers = {'Content-Type': 'application/json;charset=UTF-8',
|
||||
'User-Agent': self.user_agent})
|
||||
self.log.debug('Received: %s' % (r.text,))
|
||||
ret = None
|
||||
if r.text and len(r.text)>4:
|
||||
try:
|
||||
ret = json.loads(r.text[4:])
|
||||
except Exception:
|
||||
self.log.exception("Unable to parse result %s from post to %s" %
|
||||
(r.text, url))
|
||||
return ret
|
||||
|
||||
def put(self, path, data):
|
||||
url = self.url(path)
|
||||
|
@ -65,7 +65,7 @@ class AbandonRestoreDialog(urwid.WidgetWrap):
|
||||
button_columns = urwid.Columns(button_widgets, dividechars=2)
|
||||
rows = []
|
||||
self.entry = urwid.Edit(edit_text=text, multiline=True)
|
||||
rows.append(urwid.Text(u"%s message: " % action))
|
||||
rows.append(urwid.Text(u"%s message:" % action))
|
||||
rows.append(self.entry)
|
||||
rows.append(urwid.Divider())
|
||||
rows.append(button_columns)
|
||||
@ -73,6 +73,36 @@ class AbandonRestoreDialog(urwid.WidgetWrap):
|
||||
fill = urwid.Filler(pile, valign='top')
|
||||
super(AbandonRestoreDialog, self).__init__(urwid.LineBox(fill, '%s Change' % (action,)))
|
||||
|
||||
class CherryPickDialog(urwid.WidgetWrap):
|
||||
signals = ['save', 'cancel']
|
||||
def __init__(self, change):
|
||||
save_button = mywid.FixedButton('Propose Change')
|
||||
cancel_button = mywid.FixedButton('Cancel')
|
||||
urwid.connect_signal(save_button, 'click',
|
||||
lambda button:self._emit('save'))
|
||||
urwid.connect_signal(cancel_button, 'click',
|
||||
lambda button:self._emit('cancel'))
|
||||
button_widgets = [('pack', save_button),
|
||||
('pack', cancel_button)]
|
||||
button_columns = urwid.Columns(button_widgets, dividechars=2)
|
||||
rows = []
|
||||
self.entry = urwid.Edit(edit_text=change.revisions[-1].message, multiline=True)
|
||||
self.branch_buttons = []
|
||||
rows.append(urwid.Text(u"Branch:"))
|
||||
for branch in change.project.branches:
|
||||
b = mywid.FixedRadioButton(self.branch_buttons, branch.name,
|
||||
state=(branch.name == change.branch))
|
||||
rows.append(b)
|
||||
rows.append(urwid.Divider())
|
||||
rows.append(urwid.Text(u"Commit message:"))
|
||||
rows.append(self.entry)
|
||||
rows.append(urwid.Divider())
|
||||
rows.append(button_columns)
|
||||
pile = urwid.Pile(rows)
|
||||
fill = urwid.Filler(pile, valign='top')
|
||||
super(CherryPickDialog, self).__init__(urwid.LineBox(fill,
|
||||
'Propose Change to Branch'))
|
||||
|
||||
class ReviewDialog(urwid.WidgetWrap):
|
||||
signals = ['save', 'cancel']
|
||||
def __init__(self, revision_row):
|
||||
@ -378,6 +408,8 @@ class ChangeView(urwid.WidgetWrap):
|
||||
"Refresh this change"),
|
||||
(key(keymap.EDIT_TOPIC),
|
||||
"Edit the topic of this change"),
|
||||
(key(keymap.CHERRY_PICK_CHANGE),
|
||||
"Propose this change to another branch"),
|
||||
]
|
||||
|
||||
for k in self.app.config.reviewkeys.values():
|
||||
@ -762,6 +794,9 @@ class ChangeView(urwid.WidgetWrap):
|
||||
if keymap.EDIT_TOPIC in commands:
|
||||
self.editTopic()
|
||||
return None
|
||||
if keymap.CHERRY_PICK_CHANGE in commands:
|
||||
self.cherryPickChange()
|
||||
return None
|
||||
if r in self.app.config.reviewkeys:
|
||||
self.reviewKey(self.app.config.reviewkeys[r])
|
||||
return None
|
||||
@ -819,6 +854,36 @@ class ChangeView(urwid.WidgetWrap):
|
||||
self.app.backScreen()
|
||||
self.refresh()
|
||||
|
||||
def cherryPickChange(self):
|
||||
with self.app.db.getSession() as session:
|
||||
change = session.getChange(self.change_key)
|
||||
dialog = CherryPickDialog(change)
|
||||
urwid.connect_signal(dialog, 'cancel', self.app.backScreen)
|
||||
urwid.connect_signal(dialog, 'save', lambda button:
|
||||
self.doCherryPickChange(dialog))
|
||||
self.app.popup(dialog,
|
||||
relative_width=50, relative_height=75,
|
||||
min_width=60, min_height=20)
|
||||
|
||||
|
||||
def doCherryPickChange(self, dialog):
|
||||
cp_key = None
|
||||
with self.app.db.getSession() as session:
|
||||
change = session.getChange(self.change_key)
|
||||
branch = None
|
||||
for button in dialog.branch_buttons:
|
||||
if button.state:
|
||||
branch = button.get_label()
|
||||
message = dialog.entry.edit_text
|
||||
self.app.log.debug("Creating pending cherry-pick of %s to %s" %
|
||||
(change.revisions[-1].commit, branch))
|
||||
cp = change.revisions[-1].createPendingCherryPick(branch, message)
|
||||
cp_key = cp.key
|
||||
self.app.sync.submitTask(
|
||||
sync.SendCherryPickTask(cp_key, sync.HIGH_PRIORITY))
|
||||
self.app.backScreen()
|
||||
self.refresh()
|
||||
|
||||
def editTopic(self):
|
||||
dialog = EditTopicDialog(self.app, self.topic)
|
||||
urwid.connect_signal(dialog, 'save',
|
||||
|
Loading…
x
Reference in New Issue
Block a user