pullrequest.mako
544 lines
| 19.9 KiB
| application/x-mako
|
MakoHtmlLexer
r1282 | <%inherit file="/base/base.mako"/> | |||
r2816 | <%namespace name="dt" file="/data_table/_dt_elements.mako"/> | |||
r1282 | ||||
<%def name="title()"> | ||||
${c.repo_name} ${_('New pull request')} | ||||
</%def> | ||||
r3589 | <%def name="breadcrumbs_links()"></%def> | |||
r1282 | ||||
<%def name="menu_bar_nav()"> | ||||
${self.menu_items(active='repositories')} | ||||
</%def> | ||||
<%def name="menu_bar_subnav()"> | ||||
${self.repo_menu(active='showpullrequest')} | ||||
</%def> | ||||
<%def name="main()"> | ||||
<div class="box"> | ||||
r2105 | ${h.secure_form(h.route_path('pullrequest_create', repo_name=c.repo_name, _query=request.GET.mixed()), id='pull_request_form', request=request)} | |||
r1786 | ||||
r1282 | <div class="box pr-summary"> | |||
<div class="summary-details block-left"> | ||||
r1786 | ||||
<div class="pr-details-title"> | ||||
r3905 | ${_('New pull request')} | |||
r1786 | </div> | |||
<div class="form" style="padding-top: 10px"> | ||||
r1282 | <!-- fields --> | |||
<div class="fields" > | ||||
<div class="field"> | ||||
<div class="label"> | ||||
<label for="pullrequest_title">${_('Title')}:</label> | ||||
</div> | ||||
<div class="input"> | ||||
${h.text('pullrequest_title', c.default_title, class_="medium autogenerated-title")} | ||||
</div> | ||||
r4102 | <p class="help-block"> | |||
Start the title with WIP: to prevent accidental merge of Work In Progress pull request before it's ready. | ||||
</p> | ||||
r1282 | </div> | |||
<div class="field"> | ||||
<div class="label label-textarea"> | ||||
<label for="pullrequest_desc">${_('Description')}:</label> | ||||
</div> | ||||
r4102 | <div class="textarea text-area"> | |||
r2903 | <input id="pr-renderer-input" type="hidden" name="description_renderer" value="${c.visual.default_renderer}"> | |||
r2816 | ${dt.markup_form('pullrequest_desc')} | |||
r1282 | </div> | |||
</div> | ||||
<div class="field"> | ||||
<div class="label label-textarea"> | ||||
r2816 | <label for="commit_flow">${_('Commit flow')}:</label> | |||
r1282 | </div> | |||
## TODO: johbo: Abusing the "content" class here to get the | ||||
## desired effect. Should be replaced by a proper solution. | ||||
##ORG | ||||
<div class="content"> | ||||
r1769 | <strong>${_('Source repository')}:</strong> | |||
r1282 | ${c.rhodecode_db_repo.description} | |||
</div> | ||||
<div class="content"> | ||||
${h.hidden('source_repo')} | ||||
${h.hidden('source_ref')} | ||||
</div> | ||||
##OTHER, most Probably the PARENT OF THIS FORK | ||||
<div class="content"> | ||||
## filled with JS | ||||
<div id="target_repo_desc"></div> | ||||
</div> | ||||
<div class="content"> | ||||
${h.hidden('target_repo')} | ||||
${h.hidden('target_ref')} | ||||
<span id="target_ref_loading" style="display: none"> | ||||
${_('Loading refs...')} | ||||
</span> | ||||
</div> | ||||
</div> | ||||
<div class="field"> | ||||
<div class="label label-textarea"> | ||||
<label for="pullrequest_submit"></label> | ||||
</div> | ||||
<div class="input"> | ||||
<div class="pr-submit-button"> | ||||
r2806 | <input id="pr_submit" class="btn" name="save" type="submit" value="${_('Submit Pull Request')}"> | |||
r1282 | </div> | |||
<div id="pr_open_message"></div> | ||||
</div> | ||||
</div> | ||||
<div class="pr-spacing-container"></div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
<div> | ||||
r1786 | ## AUTHOR | |||
<div class="reviewers-title block-right"> | ||||
<div class="pr-details-title"> | ||||
${_('Author of this pull request')} | ||||
</div> | ||||
</div> | ||||
<div class="block-right pr-details-content reviewers"> | ||||
<ul class="group_members"> | ||||
<li> | ||||
r4026 | ${self.gravatar_with_user(c.rhodecode_user.email, 16, tooltip=True)} | |||
r1786 | </li> | |||
</ul> | ||||
</div> | ||||
## REVIEW RULES | ||||
r1769 | <div id="review_rules" style="display: none" class="reviewers-title block-right"> | |||
<div class="pr-details-title"> | ||||
${_('Reviewer rules')} | ||||
</div> | ||||
<div class="pr-reviewer-rules"> | ||||
## review rules will be appended here, by default reviewers logic | ||||
</div> | ||||
</div> | ||||
## REVIEWERS | ||||
r1282 | <div class="reviewers-title block-right"> | |||
<div class="pr-details-title"> | ||||
${_('Pull request reviewers')} | ||||
<span class="calculate-reviewers"> - ${_('loading...')}</span> | ||||
</div> | ||||
</div> | ||||
<div id="reviewers" class="block-right pr-details-content reviewers"> | ||||
## members goes here, filled via JS based on initial selection ! | ||||
<input type="hidden" name="__start__" value="review_members:sequence"> | ||||
<ul id="review_members" class="group_members"></ul> | ||||
<input type="hidden" name="__end__" value="review_members:sequence"> | ||||
<div id="add_reviewer_input" class='ac'> | ||||
<div class="reviewer_ac"> | ||||
r1678 | ${h.text('user', class_='ac-input', placeholder=_('Add reviewer or reviewer group'))} | |||
r1282 | <div id="reviewers_container"></div> | |||
</div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
</div> | ||||
<div class="box"> | ||||
<div> | ||||
## overview pulled by ajax | ||||
<div id="pull_request_overview"></div> | ||||
</div> | ||||
</div> | ||||
${h.end_form()} | ||||
</div> | ||||
<script type="text/javascript"> | ||||
r2816 | $(function(){ | |||
var defaultSourceRepo = '${c.default_repo_data['source_repo_name']}'; | ||||
var defaultSourceRepoData = ${c.default_repo_data['source_refs_json']|n}; | ||||
var defaultTargetRepo = '${c.default_repo_data['target_repo_name']}'; | ||||
var defaultTargetRepoData = ${c.default_repo_data['target_refs_json']|n}; | ||||
r1282 | ||||
r2816 | var $pullRequestForm = $('#pull_request_form'); | |||
var $pullRequestSubmit = $('#pr_submit', $pullRequestForm); | ||||
var $sourceRepo = $('#source_repo', $pullRequestForm); | ||||
var $targetRepo = $('#target_repo', $pullRequestForm); | ||||
var $sourceRef = $('#source_ref', $pullRequestForm); | ||||
var $targetRef = $('#target_ref', $pullRequestForm); | ||||
r1282 | ||||
r2816 | var sourceRepo = function() { return $sourceRepo.eq(0).val() }; | |||
var sourceRef = function() { return $sourceRef.eq(0).val().split(':') }; | ||||
r1769 | ||||
r2816 | var targetRepo = function() { return $targetRepo.eq(0).val() }; | |||
var targetRef = function() { return $targetRef.eq(0).val().split(':') }; | ||||
r1769 | ||||
r2816 | var calculateContainerWidth = function() { | |||
var maxWidth = 0; | ||||
var repoSelect2Containers = ['#source_repo', '#target_repo']; | ||||
$.each(repoSelect2Containers, function(idx, value) { | ||||
$(value).select2('container').width('auto'); | ||||
var curWidth = $(value).select2('container').width(); | ||||
if (maxWidth <= curWidth) { | ||||
maxWidth = curWidth; | ||||
} | ||||
$.each(repoSelect2Containers, function(idx, value) { | ||||
$(value).select2('container').width(maxWidth + 10); | ||||
}); | ||||
}); | ||||
}; | ||||
r1282 | ||||
r2816 | var initRefSelection = function(selectedRef) { | |||
return function(element, callback) { | ||||
// translate our select2 id into a text, it's a mapping to show | ||||
// simple label when selecting by internal ID. | ||||
var id, refData; | ||||
if (selectedRef === undefined || selectedRef === null) { | ||||
id = element.val(); | ||||
refData = element.val().split(':'); | ||||
r2555 | ||||
r2816 | if (refData.length !== 3){ | |||
refData = ["", "", ""] | ||||
} | ||||
} else { | ||||
id = selectedRef; | ||||
refData = selectedRef.split(':'); | ||||
} | ||||
r1282 | ||||
r2816 | var text = refData[1]; | |||
if (refData[0] === 'rev') { | ||||
text = text.substring(0, 12); | ||||
} | ||||
r1282 | ||||
r2816 | var data = {id: id, text: text}; | |||
callback(data); | ||||
}; | ||||
}; | ||||
r1282 | ||||
r2995 | var formatRefSelection = function(data, container, escapeMarkup) { | |||
r2816 | var prefix = ''; | |||
r2995 | var refData = data.id.split(':'); | |||
r2816 | if (refData[0] === 'branch') { | |||
prefix = '<i class="icon-branch"></i>'; | ||||
} | ||||
else if (refData[0] === 'book') { | ||||
prefix = '<i class="icon-bookmark"></i>'; | ||||
} | ||||
else if (refData[0] === 'tag') { | ||||
prefix = '<i class="icon-tag"></i>'; | ||||
} | ||||
r1282 | ||||
r2995 | var originalOption = data.element; | |||
return prefix + escapeMarkup(data.text); | ||||
};formatSelection: | ||||
r1282 | ||||
r2816 | // custom code mirror | |||
var codeMirrorInstance = $('#pullrequest_desc').get(0).MarkupForm.cm; | ||||
r1282 | ||||
r2816 | reviewersController = new ReviewersController(); | |||
r1769 | ||||
r2816 | var queryTargetRepo = function(self, query) { | |||
// cache ALL results if query is empty | ||||
var cacheKey = query.term || '__'; | ||||
var cachedData = self.cachedDataSource[cacheKey]; | ||||
r1282 | ||||
r2816 | if (cachedData) { | |||
query.callback({results: cachedData.results}); | ||||
} else { | ||||
$.ajax({ | ||||
r3330 | url: pyroutes.url('pullrequest_repo_targets', {'repo_name': templateContext.repo_name}), | |||
r2816 | data: {query: query.term}, | |||
dataType: 'json', | ||||
type: 'GET', | ||||
success: function(data) { | ||||
self.cachedDataSource[cacheKey] = data; | ||||
query.callback({results: data.results}); | ||||
}, | ||||
error: function(data, textStatus, errorThrown) { | ||||
alert( | ||||
"Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | ||||
} | ||||
}); | ||||
} | ||||
}; | ||||
r1282 | ||||
r2816 | var queryTargetRefs = function(initialData, query) { | |||
var data = {results: []}; | ||||
// filter initialData | ||||
$.each(initialData, function() { | ||||
var section = this.text; | ||||
var children = []; | ||||
$.each(this.children, function() { | ||||
if (query.term.length === 0 || | ||||
this.text.toUpperCase().indexOf(query.term.toUpperCase()) >= 0 ) { | ||||
children.push({'id': this.id, 'text': this.text}) | ||||
} | ||||
}); | ||||
data.results.push({'text': section, 'children': children}) | ||||
}); | ||||
query.callback({results: data.results}); | ||||
}; | ||||
r1282 | ||||
r2816 | var loadRepoRefDiffPreview = function() { | |||
r1282 | ||||
r2816 | var url_data = { | |||
'repo_name': targetRepo(), | ||||
'target_repo': sourceRepo(), | ||||
'source_ref': targetRef()[2], | ||||
'source_ref_type': 'rev', | ||||
'target_ref': sourceRef()[2], | ||||
'target_ref_type': 'rev', | ||||
'merge': true, | ||||
'_': Date.now() // bypass browser caching | ||||
}; // gather the source/target ref and repo here | ||||
r1282 | ||||
r2816 | if (sourceRef().length !== 3 || targetRef().length !== 3) { | |||
prButtonLock(true, "${_('Please select source and target')}"); | ||||
return; | ||||
} | ||||
var url = pyroutes.url('repo_compare', url_data); | ||||
r1282 | ||||
r2816 | // lock PR button, so we cannot send PR before it's calculated | |||
prButtonLock(true, "${_('Loading compare ...')}", 'compare'); | ||||
if (loadRepoRefDiffPreview._currentRequest) { | ||||
loadRepoRefDiffPreview._currentRequest.abort(); | ||||
} | ||||
r1282 | ||||
r2816 | loadRepoRefDiffPreview._currentRequest = $.get(url) | |||
.error(function(data, textStatus, errorThrown) { | ||||
if (textStatus !== 'abort') { | ||||
alert( | ||||
"Error while processing request.\nError code {0} ({1}).".format( | ||||
data.status, data.statusText)); | ||||
} | ||||
r1282 | ||||
r2816 | }) | |||
.done(function(data) { | ||||
loadRepoRefDiffPreview._currentRequest = null; | ||||
$('#pull_request_overview').html(data); | ||||
var commitElements = $(data).find('tr[commit_id]'); | ||||
r2480 | ||||
r2816 | var prTitleAndDesc = getTitleAndDescription( | |||
sourceRef()[1], commitElements, 5); | ||||
r1769 | ||||
r2816 | var title = prTitleAndDesc[0]; | |||
var proposedDescription = prTitleAndDesc[1]; | ||||
r1282 | ||||
r2816 | var useGeneratedTitle = ( | |||
$('#pullrequest_title').hasClass('autogenerated-title') || | ||||
$('#pullrequest_title').val() === ""); | ||||
r1282 | ||||
r2816 | if (title && useGeneratedTitle) { | |||
// use generated title if we haven't specified our own | ||||
$('#pullrequest_title').val(title); | ||||
$('#pullrequest_title').addClass('autogenerated-title'); | ||||
r1282 | ||||
r2816 | } | |||
var useGeneratedDescription = ( | ||||
!codeMirrorInstance._userDefinedValue || | ||||
codeMirrorInstance.getValue() === ""); | ||||
r1282 | ||||
r2816 | if (proposedDescription && useGeneratedDescription) { | |||
// set proposed content, if we haven't defined our own, | ||||
// or we don't have description written | ||||
codeMirrorInstance._userDefinedValue = false; // reset state | ||||
codeMirrorInstance.setValue(proposedDescription); | ||||
} | ||||
r1282 | ||||
r2816 | // refresh our codeMirror so events kicks in and it's change aware | |||
codeMirrorInstance.refresh(); | ||||
r1282 | ||||
r2816 | var msg = ''; | |||
if (commitElements.length === 1) { | ||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}"; | ||||
} else { | ||||
msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}"; | ||||
} | ||||
r1282 | ||||
r2816 | msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url); | |||
r1282 | ||||
r2816 | if (commitElements.length) { | |||
var commitsLink = '<a href="#pull_request_overview"><strong>{0}</strong></a>'.format(commitElements.length); | ||||
prButtonLock(false, msg.replace('__COMMITS__', commitsLink), 'compare'); | ||||
} | ||||
else { | ||||
prButtonLock(true, "${_('There are no commits to merge.')}", 'compare'); | ||||
} | ||||
r1282 | ||||
r2816 | }); | |||
}; | ||||
r1282 | ||||
r2816 | var Select2Box = function(element, overrides) { | |||
var globalDefaults = { | ||||
dropdownAutoWidth: true, | ||||
containerCssClass: "drop-menu", | ||||
dropdownCssClass: "drop-menu-dropdown" | ||||
}; | ||||
r1282 | ||||
r2816 | var initSelect2 = function(defaultOptions) { | |||
var options = jQuery.extend(globalDefaults, defaultOptions, overrides); | ||||
element.select2(options); | ||||
}; | ||||
r1282 | ||||
r2816 | return { | |||
initRef: function() { | ||||
var defaultOptions = { | ||||
minimumResultsForSearch: 5, | ||||
formatSelection: formatRefSelection | ||||
}; | ||||
r1282 | ||||
r2816 | initSelect2(defaultOptions); | |||
}, | ||||
r1282 | ||||
r2816 | initRepo: function(defaultValue, readOnly) { | |||
var defaultOptions = { | ||||
initSelection : function (element, callback) { | ||||
var data = {id: defaultValue, text: defaultValue}; | ||||
callback(data); | ||||
} | ||||
}; | ||||
r1282 | ||||
r2816 | initSelect2(defaultOptions); | |||
r1282 | ||||
r2816 | element.select2('val', defaultSourceRepo); | |||
if (readOnly === true) { | ||||
element.select2('readonly', true); | ||||
} | ||||
} | ||||
}; | ||||
}; | ||||
r1282 | ||||
r2816 | var initTargetRefs = function(refsData, selectedRef) { | |||
r2555 | ||||
r2816 | Select2Box($targetRef, { | |||
placeholder: "${_('Select commit reference')}", | ||||
query: function(query) { | ||||
queryTargetRefs(refsData, query); | ||||
}, | ||||
initSelection : initRefSelection(selectedRef) | ||||
}).initRef(); | ||||
r1282 | ||||
r2816 | if (!(selectedRef === undefined)) { | |||
$targetRef.select2('val', selectedRef); | ||||
} | ||||
}; | ||||
r1282 | ||||
r2816 | var targetRepoChanged = function(repoData) { | |||
// generate new DESC of target repo displayed next to select | ||||
var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']}); | ||||
$('#target_repo_desc').html( | ||||
"<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink) | ||||
); | ||||
r1282 | ||||
r2816 | // generate dynamic select2 for refs. | |||
initTargetRefs(repoData['refs']['select2_refs'], | ||||
repoData['refs']['selected_ref']); | ||||
r1282 | ||||
r2816 | }; | |||
r1282 | ||||
r2816 | var sourceRefSelect2 = Select2Box($sourceRef, { | |||
placeholder: "${_('Select commit reference')}", | ||||
query: function(query) { | ||||
var initialData = defaultSourceRepoData['refs']['select2_refs']; | ||||
queryTargetRefs(initialData, query) | ||||
}, | ||||
initSelection: initRefSelection() | ||||
} | ||||
); | ||||
r1282 | ||||
r2816 | var sourceRepoSelect2 = Select2Box($sourceRepo, { | |||
query: function(query) {} | ||||
}); | ||||
r1282 | ||||
r2816 | var targetRepoSelect2 = Select2Box($targetRepo, { | |||
cachedDataSource: {}, | ||||
query: $.debounce(250, function(query) { | ||||
queryTargetRepo(this, query); | ||||
}), | ||||
formatResult: formatRepoResult | ||||
}); | ||||
r1282 | ||||
r2816 | sourceRefSelect2.initRef(); | |||
r1282 | ||||
r2816 | sourceRepoSelect2.initRepo(defaultSourceRepo, true); | |||
r1282 | ||||
r2816 | targetRepoSelect2.initRepo(defaultTargetRepo, false); | |||
r1282 | ||||
r2816 | $sourceRef.on('change', function(e){ | |||
loadRepoRefDiffPreview(); | ||||
reviewersController.loadDefaultReviewers( | ||||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
}); | ||||
r1282 | ||||
r2816 | $targetRef.on('change', function(e){ | |||
loadRepoRefDiffPreview(); | ||||
reviewersController.loadDefaultReviewers( | ||||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
}); | ||||
r1282 | ||||
r2816 | $targetRepo.on('change', function(e){ | |||
var repoName = $(this).val(); | ||||
calculateContainerWidth(); | ||||
$targetRef.select2('destroy'); | ||||
$('#target_ref_loading').show(); | ||||
r1282 | ||||
r2816 | $.ajax({ | |||
url: pyroutes.url('pullrequest_repo_refs', | ||||
{'repo_name': templateContext.repo_name, 'target_repo_name':repoName}), | ||||
data: {}, | ||||
dataType: 'json', | ||||
type: 'GET', | ||||
success: function(data) { | ||||
$('#target_ref_loading').hide(); | ||||
targetRepoChanged(data); | ||||
loadRepoRefDiffPreview(); | ||||
}, | ||||
error: function(data, textStatus, errorThrown) { | ||||
alert("Error while fetching entries.\nError code {0} ({1}).".format(data.status, data.statusText)); | ||||
} | ||||
}) | ||||
r1282 | ||||
r2816 | }); | |||
r2806 | ||||
r2816 | $pullRequestForm.on('submit', function(e){ | |||
// Flush changes into textarea | ||||
codeMirrorInstance.save(); | ||||
prButtonLock(true, null, 'all'); | ||||
r4231 | $pullRequestSubmit.val(_gettext('Please wait creating pull request...')); | |||
r2816 | }); | |||
r1282 | ||||
r2816 | prButtonLock(true, "${_('Please select source and target')}", 'all'); | |||
r1282 | ||||
r2816 | // auto-load on init, the target refs select2 | |||
calculateContainerWidth(); | ||||
targetRepoChanged(defaultTargetRepoData); | ||||
r1282 | ||||
r2816 | $('#pullrequest_title').on('keyup', function(e){ | |||
$(this).removeClass('autogenerated-title'); | ||||
}); | ||||
r1282 | ||||
r2816 | % if c.default_source_ref: | |||
// in case we have a pre-selected value, use it now | ||||
$sourceRef.select2('val', '${c.default_source_ref}'); | ||||
// diff preview load | ||||
loadRepoRefDiffPreview(); | ||||
// default reviewers | ||||
reviewersController.loadDefaultReviewers( | ||||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
% endif | ||||
ReviewerAutoComplete('#user'); | ||||
}); | ||||
r1282 | </script> | |||
</%def> | ||||