##// END OF EJS Templates
todos: all todos needs to be resolved for merge to happen....
todos: all todos needs to be resolved for merge to happen. This will prevent the outdated todos beeing automatically marked as solved becuase of bigger diff changes. It's better to mark commits quickly as resolved instead of potentially have unresolved todos hidden because of invlidation logic.

File last commit:

r1282:90601d74 default
r1342:44fc3039 default
Show More
pullrequest.mako
593 lines | 20.5 KiB | application/x-mako | MakoHtmlLexer
templating: use .mako as extensions for template files.
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)}
${self.breadcrumbs()}
</div>
${h.secure_form(url('pullrequest', repo_name=c.repo_name), method='post', id='pull_request_form')}
<div class="box pr-summary">
<div class="summary-details block-left">
<div class="form">
<!-- 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, )}
<span class="help-block">
${_('Write a short description on this pull request')}
</span>
</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">
<strong>${_('Origin repository')}:</strong>
${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">
${h.submit('save',_('Submit Pull Request'),class_="btn")}
</div>
<div id="pr_open_message"></div>
</div>
</div>
<div class="pr-spacing-container"></div>
</div>
</div>
</div>
<div>
<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">
${h.text('user', class_='ac-input', placeholder=_('Add reviewer'))}
<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 targetRepoName = '${c.repo_name}';
var $pullRequestForm = $('#pull_request_form');
var $sourceRepo = $('#source_repo', $pullRequestForm);
var $targetRepo = $('#target_repo', $pullRequestForm);
var $sourceRef = $('#source_ref', $pullRequestForm);
var $targetRef = $('#target_ref', $pullRequestForm);
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;
if (selectedRef === undefined) {
id = element.val();
refData = element.val().split(':');
} 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');
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({
url: pyroutes.url('pullrequest_repo_destinations', {'repo_name': targetRepoName}),
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 prButtonLockChecks = {
'compare': false,
'reviewers': false
};
var prButtonLock = function(lockEnabled, msg, scope) {
scope = scope || 'all';
if (scope == 'all'){
prButtonLockChecks['compare'] = !lockEnabled;
prButtonLockChecks['reviewers'] = !lockEnabled;
} else if (scope == 'compare') {
prButtonLockChecks['compare'] = !lockEnabled;
} else if (scope == 'reviewers'){
prButtonLockChecks['reviewers'] = !lockEnabled;
}
var checksMeet = prButtonLockChecks.compare && prButtonLockChecks.reviewers;
if (lockEnabled) {
$('#save').attr('disabled', 'disabled');
}
else if (checksMeet) {
$('#save').removeAttr('disabled');
}
if (msg) {
$('#pr_open_message').html(msg);
}
};
var loadRepoRefDiffPreview = function() {
var sourceRepo = $sourceRepo.eq(0).val();
var sourceRef = $sourceRef.eq(0).val().split(':');
var targetRepo = $targetRepo.eq(0).val();
var targetRef = $targetRef.eq(0).val().split(':');
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
if (sourceRef.length !== 3 || targetRef.length !== 3) {
prButtonLock(true, "${_('Please select origin and destination')}");
return;
}
var url = pyroutes.url('compare_url', url_data);
// 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) {
alert(
"Error while processing request.\nError code {0} ({1}).".format(
data.status, data.statusText));
})
.done(function(data) {
loadRepoRefDiffPreview._currentRequest = null;
$('#pull_request_overview').html(data);
var commitElements = $(data).find('tr[commit_id]');
var prTitleAndDesc = getTitleAndDescription(sourceRef[1],
commitElements, 5);
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) {
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)}";
}
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');
}
});
};
/**
Generate Title and Description for a PullRequest.
In case of 1 commits, the title and description is that one commit
in case of multiple commits, we iterate on them with max N number of commits,
and build description in a form
- commitN
- commitN+1
...
Title is then constructed from branch names, or other references,
replacing '-' and '_' into spaces
* @param sourceRef
* @param elements
* @param limit
* @returns {*[]}
*/
var getTitleAndDescription = function(sourceRef, elements, limit) {
var title = '';
var desc = '';
$.each($(elements).get().reverse().slice(0, limit), function(idx, value) {
var rawMessage = $(value).find('td.td-description .message').data('messageRaw');
desc += '- ' + rawMessage.split('\n')[0].replace(/\n+$/, "") + '\n';
});
// only 1 commit, use commit message as title
if (elements.length == 1) {
title = $(elements[0]).find('td.td-description .message').data('messageRaw').split('\n')[0];
}
else {
// use reference name
title = sourceRef.replace(/-/g, ' ').replace(/_/g, ' ').capitalizeFirstLetter();
}
return [title, desc]
};
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);
}
}
};
};
var initTargetRefs = function(refsData, selectedRef){
Select2Box($targetRef, {
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
$('#target_repo_desc').html(
"<strong>${_('Destination repository')}</strong>: {0}".format(repoData['description'])
);
// generate dynamic select2 for refs.
initTargetRefs(repoData['refs']['select2_refs'],
repoData['refs']['selected_ref']);
};
var sourceRefSelect2 = Select2Box(
$sourceRef, {
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);
}),
formatResult: formatResult
});
sourceRefSelect2.initRef();
sourceRepoSelect2.initRepo(defaultSourceRepo, true);
targetRepoSelect2.initRepo(defaultTargetRepo, false);
$sourceRef.on('change', function(e){
loadRepoRefDiffPreview();
loadDefaultReviewers();
});
$targetRef.on('change', function(e){
loadRepoRefDiffPreview();
loadDefaultReviewers();
});
$targetRepo.on('change', function(e){
var repoName = $(this).val();
calculateContainerWidth();
$targetRef.select2('destroy');
$('#target_ref_loading').show();
$.ajax({
url: pyroutes.url('pullrequest_repo_refs',
{'repo_name': targetRepoName, '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));
}
})
});
var loadDefaultReviewers = function() {
if (loadDefaultReviewers._currentRequest) {
loadDefaultReviewers._currentRequest.abort();
}
$('.calculate-reviewers').show();
prButtonLock(true, null, 'reviewers');
var url = pyroutes.url('repo_default_reviewers_data', {'repo_name': targetRepoName});
var sourceRepo = $sourceRepo.eq(0).val();
var sourceRef = $sourceRef.eq(0).val().split(':');
var targetRepo = $targetRepo.eq(0).val();
var targetRef = $targetRef.eq(0).val().split(':');
url += '?source_repo=' + sourceRepo;
url += '&source_ref=' + sourceRef[2];
url += '&target_repo=' + targetRepo;
url += '&target_ref=' + targetRef[2];
loadDefaultReviewers._currentRequest = $.get(url)
.done(function(data) {
loadDefaultReviewers._currentRequest = null;
// reset && add the reviewer based on selected repo
$('#review_members').html('');
for (var i = 0; i < data.reviewers.length; i++) {
var reviewer = data.reviewers[i];
addReviewMember(
reviewer.user_id, reviewer.firstname,
reviewer.lastname, reviewer.username,
reviewer.gravatar_link, reviewer.reasons);
}
$('.calculate-reviewers').hide();
prButtonLock(false, null, 'reviewers');
});
};
prButtonLock(true, "${_('Please select origin and destination')}", 'all');
// 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:
// in case we have a pre-selected value, use it now
$sourceRef.select2('val', '${c.default_source_ref}');
loadRepoRefDiffPreview();
loadDefaultReviewers();
% endif
ReviewerAutoComplete('user');
});
</script>
</%def>