files: add pre-commit checks on file operations to prevent loosing content while editing when repositories change.
- fixes #5607
This commit is contained in:
parent
70b01ee66f
commit
7a360734c5
9 changed files with 138 additions and 4 deletions
|
|
@ -155,6 +155,10 @@ def includeme(config):
|
|||
name='repo_file_authors',
|
||||
pattern='/{repo_name:.*?[^/]}/authors/{commit_id}/{f_path:.*}', repo_route=True)
|
||||
|
||||
config.add_route(
|
||||
name='repo_files_check_head',
|
||||
pattern='/{repo_name:.*?[^/]}/check_head/{commit_id}/{f_path:.*}',
|
||||
repo_route=True)
|
||||
config.add_route(
|
||||
name='repo_files_remove_file',
|
||||
pattern='/{repo_name:.*?[^/]}/remove_file/{commit_id}/{f_path:.*}',
|
||||
|
|
|
|||
|
|
@ -1111,6 +1111,42 @@ class RepoFilesView(RepoAppView):
|
|||
|
||||
return self._get_template_context(c)
|
||||
|
||||
@LoginRequired()
|
||||
@HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
route_name='repo_files_check_head', request_method='POST',
|
||||
renderer='json_ext', xhr=True)
|
||||
def repo_files_check_head(self):
|
||||
self.load_default_context()
|
||||
|
||||
commit_id, f_path = self._get_commit_and_path()
|
||||
_branch_name, _sha_commit_id, is_head = \
|
||||
self._is_valid_head(commit_id, self.rhodecode_vcs_repo)
|
||||
|
||||
new_path = self.request.POST.get('path')
|
||||
operation = self.request.POST.get('operation')
|
||||
path_exist = ''
|
||||
|
||||
if new_path and operation in ['create', 'upload']:
|
||||
new_f_path = os.path.join(f_path.lstrip('/'), new_path)
|
||||
try:
|
||||
commit_obj = self.rhodecode_vcs_repo.get_commit(commit_id)
|
||||
# NOTE(dan): construct whole path without leading /
|
||||
file_node = commit_obj.get_node(new_f_path)
|
||||
if file_node is not None:
|
||||
path_exist = new_f_path
|
||||
except EmptyRepositoryError:
|
||||
pass
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
return {
|
||||
'branch': _branch_name,
|
||||
'sha': _sha_commit_id,
|
||||
'is_head': is_head,
|
||||
'path_exists': path_exist
|
||||
}
|
||||
|
||||
@LoginRequired()
|
||||
@HasRepoPermissionAnyDecorator('repository.write', 'repository.admin')
|
||||
@view_config(
|
||||
|
|
|
|||
|
|
@ -2532,6 +2532,10 @@ h3.files_location{
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.upload-form {
|
||||
margin-top: 46px;
|
||||
}
|
||||
|
||||
.location-path {
|
||||
width: -moz-available; /* WebKit-based browsers will ignore this. */
|
||||
width: -webkit-fill-available; /* Mozilla-based browsers will ignore this. */
|
||||
|
|
|
|||
|
|
@ -204,6 +204,7 @@ function registerRCRoutes() {
|
|||
pyroutes.register('repo_file_download:legacy', '/%(repo_name)s/rawfile/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_file_history', '/%(repo_name)s/history/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_file_authors', '/%(repo_name)s/authors/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_files_check_head', '/%(repo_name)s/check_head/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_files_remove_file', '/%(repo_name)s/remove_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_files_delete_file', '/%(repo_name)s/delete_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
pyroutes.register('repo_files_edit_file', '/%(repo_name)s/edit_file/%(commit_id)s/%(f_path)s', ['repo_name', 'commit_id', 'f_path']);
|
||||
|
|
|
|||
|
|
@ -521,3 +521,80 @@ var showAuthors = function(elem, annotate) {
|
|||
return FileEditor;
|
||||
});
|
||||
|
||||
|
||||
var checkFileHead = function($editForm, commit_id, f_path, operation) {
|
||||
function getFormData($form){
|
||||
var unindexed_array = $form.serializeArray();
|
||||
var indexed_array = {};
|
||||
|
||||
$.map(unindexed_array, function(n, i){
|
||||
indexed_array[n['name']] = n['value'];
|
||||
});
|
||||
|
||||
return indexed_array;
|
||||
}
|
||||
|
||||
$editForm.on('submit', function (e) {
|
||||
|
||||
var validHead = $editForm.find('#commit_btn').data('validHead');
|
||||
if (validHead === true){
|
||||
return true
|
||||
}
|
||||
|
||||
// no marker, we do "checks"
|
||||
e.preventDefault();
|
||||
var formData = getFormData($editForm);
|
||||
var new_path = formData['filename'];
|
||||
|
||||
var success = function(data) {
|
||||
|
||||
if (data['is_head'] === true && data['path_exists'] === "") {
|
||||
|
||||
$editForm.find('#commit_btn').data('validHead', true);
|
||||
$editForm.find('#commit_btn').val('Committing...');
|
||||
$editForm.submit();
|
||||
|
||||
} else {
|
||||
var message = '';
|
||||
var urlTmpl = '<a target="_blank" href="{0}">here</a>';
|
||||
$editForm.find('#commit_btn').val('Commit aborted');
|
||||
|
||||
if (operation === 'edit') {
|
||||
var new_url = urlTmpl.format(pyroutes.url('repo_files_edit_file', {"repo_name": templateContext.repo_name, "commit_id": data['branch'], "f_path": f_path}));
|
||||
message = _gettext('File `{0}` has a newer version available, or has been removed. Click {1} to see the latest version.'.format(f_path, new_url));
|
||||
} else if (operation === 'delete') {
|
||||
var new_url = urlTmpl.format(pyroutes.url('repo_files_remove_file', {"repo_name": templateContext.repo_name, "commit_id": data['branch'], "f_path": f_path}));
|
||||
message = _gettext('File `{0}` has a newer version available, or has been removed. Click {1} to see the latest version.'.format(f_path, new_url));
|
||||
} else if (operation === 'create') {
|
||||
if (data['path_exists'] !== "") {
|
||||
message = _gettext('There is an existing path `{0}` at this commit.'.format(data['path_exists']));
|
||||
} else {
|
||||
var new_url = urlTmpl.format(pyroutes.url('repo_files_add_file', {"repo_name": templateContext.repo_name, "commit_id": data['branch'], "f_path": f_path, "filename": new_path}));
|
||||
message = _gettext('There is a later version of file tree available. Click {0} to create a file at the latest tree.'.format(new_url));
|
||||
}
|
||||
}
|
||||
|
||||
var payload = {
|
||||
message: {
|
||||
message: message,
|
||||
level: 'warning',
|
||||
force: true
|
||||
}
|
||||
};
|
||||
$.Topic('/notifications').publish(payload);
|
||||
}
|
||||
};
|
||||
|
||||
// lock button
|
||||
$editForm.find('#commit_btn').attr('disabled', 'disabled');
|
||||
$editForm.find('#commit_btn').val('Checking commit...');
|
||||
|
||||
var postData = {'csrf_token': CSRF_TOKEN, 'path': new_path, 'operation': operation};
|
||||
ajaxPOST(pyroutes.url('repo_files_check_head',
|
||||
{'repo_name': templateContext.repo_name, 'commit_id': commit_id, 'f_path': f_path}),
|
||||
postData, success);
|
||||
|
||||
return false
|
||||
|
||||
});
|
||||
};
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@
|
|||
</div>
|
||||
</li>
|
||||
<li class="location-path">
|
||||
<input class="file-name-input input-small" type="text" value="" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
|
||||
<input class="file-name-input input-small" type="text" value="${request.GET.get('filename')}" name="filename" id="filename" placeholder="${_('Filename e.g example.py, or docs/readme.md')}">
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
@ -50,7 +50,6 @@
|
|||
|
||||
<div class="table">
|
||||
<div>
|
||||
|
||||
<div id="codeblock" class="codeblock">
|
||||
<div class="editor-items">
|
||||
<div class="editor-action active show-editor pull-left" onclick="fileEditor.showEditor(); return false">
|
||||
|
|
@ -108,6 +107,10 @@
|
|||
|
||||
$('#filename').focus();
|
||||
|
||||
var commit_id = "${c.commit.raw_id}";
|
||||
var f_path = "${c.f_path}";
|
||||
|
||||
checkFileHead($('#eform'), commit_id, f_path, 'create')
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@
|
|||
</div>
|
||||
</div>
|
||||
<div class="pull-left">
|
||||
${h.submit('commit',_('Commit changes'),class_="btn btn-small btn-danger-action")}
|
||||
${h.submit('commit_btn',_('Commit changes'),class_="btn btn-small btn-danger-action")}
|
||||
</div>
|
||||
</div>
|
||||
${h.end_form()}
|
||||
|
|
@ -82,6 +82,10 @@
|
|||
|
||||
fileEditor = new FileEditor('#editor');
|
||||
|
||||
var commit_id = "${c.commit.raw_id}";
|
||||
var f_path = "${c.f_path}";
|
||||
|
||||
checkFileHead($('#eform'), commit_id, f_path, 'delete');
|
||||
});
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -117,6 +117,11 @@ $(document).ready(function() {
|
|||
// on entering the new filename set mode, from given extension
|
||||
setCodeMirrorModeFromInput(modes_select, filename_selector, fileEditor.cm, null);
|
||||
|
||||
var commit_id = "${c.commit.raw_id}";
|
||||
var f_path = "${c.f_path}";
|
||||
|
||||
checkFileHead($('#eform'), commit_id, f_path, 'edit')
|
||||
|
||||
});
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -106,7 +106,7 @@
|
|||
|
||||
<div class="file-upload-transaction-wrapper" style="display: none">
|
||||
<div class="file-upload-transaction">
|
||||
<h3>${_('Commiting...')}</h3>
|
||||
<h3>${_('Committing...')}</h3>
|
||||
<p>${_('Please wait while the files are being uploaded')}</p>
|
||||
<p class="error" style="display: none">
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue