files: add pre-commit checks on file operations to prevent loosing content while editing when repositories change.

- fixes #5607
This commit is contained in:
Daniel Dourvaris 2020-04-14 13:15:24 +02:00
parent 70b01ee66f
commit 7a360734c5
9 changed files with 138 additions and 4 deletions

View file

@ -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:.*}',

View file

@ -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(

View file

@ -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. */

View file

@ -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']);

View file

@ -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
});
};

View file

@ -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>

View file

@ -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>

View file

@ -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')
});

View file

@ -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">