pullrequest.mako
542 lines
| 19.2 KiB
| application/x-mako
|
MakoHtmlLexer
r1282 | <%inherit file="/base/base.mako"/> | |||
<%def name="title()"> | ||||
${c.repo_name} ${_('New pull request')} | ||||
</%def> | ||||
<%def name="breadcrumbs_links()"> | ||||
${_('New pull request')} | ||||
</%def> | ||||
<%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"> | ||||
<div class="title"> | ||||
${self.repo_page_title(c.rhodecode_db_repo)} | ||||
</div> | ||||
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 | ||||
${self.breadcrumbs()} | ||||
r1282 | <div class="box pr-summary"> | |||
<div class="summary-details block-left"> | ||||
r1786 | ||||
<div class="pr-details-title"> | ||||
${_('Pull request summary')} | ||||
</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> | ||||
</div> | ||||
<div class="field"> | ||||
<div class="label label-textarea"> | ||||
<label for="pullrequest_desc">${_('Description')}:</label> | ||||
</div> | ||||
<div class="textarea text-area editor"> | ||||
${h.textarea('pullrequest_desc',size=30, )} | ||||
r1470 | <span class="help-block">${_('Write a short description on this pull request')}</span> | |||
r1282 | </div> | |||
</div> | ||||
<div class="field"> | ||||
<div class="label label-textarea"> | ||||
<label for="pullrequest_desc">${_('Commit flow')}:</label> | ||||
</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> | ||||
${self.gravatar_with_user(c.rhodecode_user.email, 16)} | ||||
</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"> | ||||
$(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}; | ||||
var $pullRequestForm = $('#pull_request_form'); | ||||
r2806 | var $pullRequestSubmit = $('#pr_submit', $pullRequestForm); | |||
r1282 | var $sourceRepo = $('#source_repo', $pullRequestForm); | |||
var $targetRepo = $('#target_repo', $pullRequestForm); | ||||
var $sourceRef = $('#source_ref', $pullRequestForm); | ||||
var $targetRef = $('#target_ref', $pullRequestForm); | ||||
r1769 | var sourceRepo = function() { return $sourceRepo.eq(0).val() }; | |||
var sourceRef = function() { return $sourceRef.eq(0).val().split(':') }; | ||||
var targetRepo = function() { return $targetRepo.eq(0).val() }; | ||||
var targetRef = function() { return $targetRef.eq(0).val().split(':') }; | ||||
r1282 | 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); | ||||
}); | ||||
}); | ||||
}; | ||||
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; | ||||
r2555 | if (selectedRef === undefined || selectedRef === null) { | |||
r1282 | id = element.val(); | |||
refData = element.val().split(':'); | ||||
r2555 | ||||
if (refData.length !== 3){ | ||||
refData = ["", "", ""] | ||||
} | ||||
r1282 | } else { | |||
id = selectedRef; | ||||
refData = selectedRef.split(':'); | ||||
} | ||||
var text = refData[1]; | ||||
if (refData[0] === 'rev') { | ||||
text = text.substring(0, 12); | ||||
} | ||||
var data = {id: id, text: text}; | ||||
callback(data); | ||||
}; | ||||
}; | ||||
var formatRefSelection = function(item) { | ||||
var prefix = ''; | ||||
var refData = item.id.split(':'); | ||||
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>'; | ||||
} | ||||
var originalOption = item.element; | ||||
return prefix + item.text; | ||||
}; | ||||
// custom code mirror | ||||
var codeMirrorInstance = initPullRequestsCodeMirror('#pullrequest_desc'); | ||||
r1769 | reviewersController = new ReviewersController(); | |||
r1282 | var queryTargetRepo = function(self, query) { | |||
// cache ALL results if query is empty | ||||
var cacheKey = query.term || '__'; | ||||
var cachedData = self.cachedDataSource[cacheKey]; | ||||
if (cachedData) { | ||||
query.callback({results: cachedData.results}); | ||||
} else { | ||||
$.ajax({ | ||||
r1769 | url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': templateContext.repo_name}), | |||
r1282 | 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)); | ||||
} | ||||
}); | ||||
} | ||||
}; | ||||
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}); | ||||
}; | ||||
var loadRepoRefDiffPreview = function() { | ||||
var url_data = { | ||||
r1769 | 'repo_name': targetRepo(), | |||
'target_repo': sourceRepo(), | ||||
'source_ref': targetRef()[2], | ||||
r1282 | 'source_ref_type': 'rev', | |||
r1769 | 'target_ref': sourceRef()[2], | |||
r1282 | 'target_ref_type': 'rev', | |||
'merge': true, | ||||
'_': Date.now() // bypass browser caching | ||||
}; // gather the source/target ref and repo here | ||||
r1769 | if (sourceRef().length !== 3 || targetRef().length !== 3) { | |||
prButtonLock(true, "${_('Please select source and target')}"); | ||||
r1282 | return; | |||
} | ||||
r1957 | var url = pyroutes.url('repo_compare', url_data); | |||
r1282 | ||||
// lock PR button, so we cannot send PR before it's calculated | ||||
prButtonLock(true, "${_('Loading compare ...')}", 'compare'); | ||||
if (loadRepoRefDiffPreview._currentRequest) { | ||||
loadRepoRefDiffPreview._currentRequest.abort(); | ||||
} | ||||
loadRepoRefDiffPreview._currentRequest = $.get(url) | ||||
.error(function(data, textStatus, errorThrown) { | ||||
r2480 | if (textStatus !== 'abort') { | |||
alert( | ||||
"Error while processing request.\nError code {0} ({1}).".format( | ||||
data.status, data.statusText)); | ||||
} | ||||
r1282 | }) | |||
.done(function(data) { | ||||
loadRepoRefDiffPreview._currentRequest = null; | ||||
$('#pull_request_overview').html(data); | ||||
r1769 | ||||
r1282 | var commitElements = $(data).find('tr[commit_id]'); | |||
r1769 | var prTitleAndDesc = getTitleAndDescription( | |||
sourceRef()[1], commitElements, 5); | ||||
r1282 | ||||
var title = prTitleAndDesc[0]; | ||||
var proposedDescription = prTitleAndDesc[1]; | ||||
var useGeneratedTitle = ( | ||||
$('#pullrequest_title').hasClass('autogenerated-title') || | ||||
$('#pullrequest_title').val() === ""); | ||||
if (title && useGeneratedTitle) { | ||||
// use generated title if we haven't specified our own | ||||
$('#pullrequest_title').val(title); | ||||
$('#pullrequest_title').addClass('autogenerated-title'); | ||||
} | ||||
var useGeneratedDescription = ( | ||||
!codeMirrorInstance._userDefinedDesc || | ||||
codeMirrorInstance.getValue() === ""); | ||||
if (proposedDescription && useGeneratedDescription) { | ||||
// set proposed content, if we haven't defined our own, | ||||
// or we don't have description written | ||||
codeMirrorInstance._userDefinedDesc = false; // reset state | ||||
codeMirrorInstance.setValue(proposedDescription); | ||||
} | ||||
var msg = ''; | ||||
if (commitElements.length === 1) { | ||||
r1945 | msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 1)}"; | |||
r1282 | } else { | |||
r1945 | msg = "${_ungettext('This pull request will consist of __COMMITS__ commit.', 'This pull request will consist of __COMMITS__ commits.', 2)}"; | |||
r1282 | } | |||
msg += ' <a id="pull_request_overview_url" href="{0}" target="_blank">${_('Show detailed compare.')}</a>'.format(url); | ||||
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'); | ||||
} | ||||
}); | ||||
}; | ||||
var Select2Box = function(element, overrides) { | ||||
var globalDefaults = { | ||||
dropdownAutoWidth: true, | ||||
containerCssClass: "drop-menu", | ||||
dropdownCssClass: "drop-menu-dropdown" | ||||
}; | ||||
var initSelect2 = function(defaultOptions) { | ||||
var options = jQuery.extend(globalDefaults, defaultOptions, overrides); | ||||
element.select2(options); | ||||
}; | ||||
return { | ||||
initRef: function() { | ||||
var defaultOptions = { | ||||
minimumResultsForSearch: 5, | ||||
formatSelection: formatRefSelection | ||||
}; | ||||
initSelect2(defaultOptions); | ||||
}, | ||||
initRepo: function(defaultValue, readOnly) { | ||||
var defaultOptions = { | ||||
initSelection : function (element, callback) { | ||||
var data = {id: defaultValue, text: defaultValue}; | ||||
callback(data); | ||||
} | ||||
}; | ||||
initSelect2(defaultOptions); | ||||
element.select2('val', defaultSourceRepo); | ||||
if (readOnly === true) { | ||||
element.select2('readonly', true); | ||||
} | ||||
} | ||||
}; | ||||
}; | ||||
r2555 | var initTargetRefs = function(refsData, selectedRef) { | |||
r1282 | Select2Box($targetRef, { | |||
r2555 | placeholder: "${_('Select commit reference')}", | |||
r1282 | query: function(query) { | |||
queryTargetRefs(refsData, query); | ||||
}, | ||||
initSelection : initRefSelection(selectedRef) | ||||
}).initRef(); | ||||
if (!(selectedRef === undefined)) { | ||||
$targetRef.select2('val', selectedRef); | ||||
} | ||||
}; | ||||
var targetRepoChanged = function(repoData) { | ||||
// generate new DESC of target repo displayed next to select | ||||
r2469 | var prLink = pyroutes.url('pullrequest_new', {'repo_name': repoData['name']}); | |||
r1282 | $('#target_repo_desc').html( | |||
r2484 | "<strong>${_('Target repository')}</strong>: {0}. <a href=\"{1}\">Switch base, and use as source.</a>".format(repoData['description'], prLink) | |||
r1282 | ); | |||
// generate dynamic select2 for refs. | ||||
initTargetRefs(repoData['refs']['select2_refs'], | ||||
repoData['refs']['selected_ref']); | ||||
}; | ||||
r1769 | var sourceRefSelect2 = Select2Box($sourceRef, { | |||
r1282 | placeholder: "${_('Select commit reference')}", | |||
query: function(query) { | ||||
var initialData = defaultSourceRepoData['refs']['select2_refs']; | ||||
queryTargetRefs(initialData, query) | ||||
}, | ||||
initSelection: initRefSelection() | ||||
} | ||||
); | ||||
var sourceRepoSelect2 = Select2Box($sourceRepo, { | ||||
query: function(query) {} | ||||
}); | ||||
var targetRepoSelect2 = Select2Box($targetRepo, { | ||||
cachedDataSource: {}, | ||||
query: $.debounce(250, function(query) { | ||||
queryTargetRepo(this, query); | ||||
}), | ||||
r2774 | formatResult: formatRepoResult | |||
r1282 | }); | |||
sourceRefSelect2.initRef(); | ||||
sourceRepoSelect2.initRepo(defaultSourceRepo, true); | ||||
targetRepoSelect2.initRepo(defaultTargetRepo, false); | ||||
$sourceRef.on('change', function(e){ | ||||
loadRepoRefDiffPreview(); | ||||
r1769 | reviewersController.loadDefaultReviewers( | |||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
r1282 | }); | |||
$targetRef.on('change', function(e){ | ||||
loadRepoRefDiffPreview(); | ||||
r1769 | reviewersController.loadDefaultReviewers( | |||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
r1282 | }); | |||
$targetRepo.on('change', function(e){ | ||||
var repoName = $(this).val(); | ||||
calculateContainerWidth(); | ||||
$targetRef.select2('destroy'); | ||||
$('#target_ref_loading').show(); | ||||
$.ajax({ | ||||
url: pyroutes.url('pullrequest_repo_refs', | ||||
r1769 | {'repo_name': templateContext.repo_name, 'target_repo_name':repoName}), | |||
r1282 | 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)); | ||||
} | ||||
}) | ||||
}); | ||||
r2806 | $pullRequestForm.on('submit', function(e){ | |||
prButtonLock(true, null, 'all'); | ||||
}); | ||||
r1769 | prButtonLock(true, "${_('Please select source and target')}", 'all'); | |||
r1282 | ||||
// auto-load on init, the target refs select2 | ||||
calculateContainerWidth(); | ||||
targetRepoChanged(defaultTargetRepoData); | ||||
$('#pullrequest_title').on('keyup', function(e){ | ||||
$(this).removeClass('autogenerated-title'); | ||||
}); | ||||
% if c.default_source_ref: | ||||
r1769 | // in case we have a pre-selected value, use it now | |||
$sourceRef.select2('val', '${c.default_source_ref}'); | ||||
r2555 | // diff preview load | |||
r1769 | loadRepoRefDiffPreview(); | |||
r2555 | // default reviewers | |||
r1769 | reviewersController.loadDefaultReviewers( | |||
sourceRepo(), sourceRef(), targetRepo(), targetRef()); | ||||
r1282 | % endif | |||
r1678 | ReviewerAutoComplete('#user'); | |||
r1282 | }); | |||
</script> | ||||
</%def> | ||||