diff --git a/assets/src/bundles/vault/vault-create-tasks.js b/assets/src/bundles/vault/vault-create-tasks.js
index 420b92cf..591d139d 100644
--- a/assets/src/bundles/vault/vault-create-tasks.js
+++ b/assets/src/bundles/vault/vault-create-tasks.js
@@ -1,155 +1,155 @@
/**
* Copyright (C) 2018-2019 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, csrfPost, htmlAlert} from 'utils/functions';
const alertStyle = {
'position': 'fixed',
'left': '1rem',
'bottom': '1rem',
'z-index': '100000'
};
export async function vaultRequest(objectType, swhid) {
let vaultUrl;
if (objectType === 'directory') {
vaultUrl = Urls.api_1_vault_cook_flat(swhid);
} else {
- vaultUrl = Urls.api_1_vault_cook_gitfast(swhid);
+ vaultUrl = Urls.api_1_vault_cook_git_bare(swhid);
}
// check if object has already been cooked
const response = await fetch(vaultUrl);
const data = await response.json();
// object needs to be cooked
if (data.exception === 'NotFoundExc' || data.status === 'failed') {
// if last cooking has failed, remove previous task info from localStorage
// in order to force the recooking of the object
swh.vault.removeCookingTaskInfo([swhid]);
$(`#vault-cook-${objectType}-modal`).modal('show');
// object has been cooked and should be in the vault cache,
// it will be asked to cook it again if it is not
} else if (data.status === 'done') {
$(`#vault-fetch-${objectType}-modal`).modal('show');
} else {
const cookingServiceDownAlert =
$(htmlAlert('danger',
'Archive cooking service is currently experiencing issues. ' +
'Please try again later.',
true));
cookingServiceDownAlert.css(alertStyle);
$('body').append(cookingServiceDownAlert);
}
}
async function addVaultCookingTask(objectType, cookingTask) {
const swhidsContext = swh.webapp.getSwhIdsContext();
cookingTask.origin = swhidsContext[objectType].context.origin;
cookingTask.path = swhidsContext[objectType].context.path;
cookingTask.browse_url = swhidsContext[objectType].swhid_with_context_url;
if (!cookingTask.browse_url) {
cookingTask.browse_url = swhidsContext[objectType].swhid_url;
}
let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks) {
vaultCookingTasks = [];
}
if (vaultCookingTasks.find(val => {
return val.bundle_type === cookingTask.bundle_type &&
val.swhid === cookingTask.swhid;
}) === undefined) {
let cookingUrl;
if (cookingTask.bundle_type === 'flat') {
cookingUrl = Urls.api_1_vault_cook_flat(cookingTask.swhid);
} else {
- cookingUrl = Urls.api_1_vault_cook_gitfast(cookingTask.swhid);
+ cookingUrl = Urls.api_1_vault_cook_git_bare(cookingTask.swhid);
}
if (cookingTask.email) {
cookingUrl += '?email=' + cookingTask.email;
}
try {
const response = await csrfPost(cookingUrl);
handleFetchError(response);
vaultCookingTasks.push(cookingTask);
localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
$('#vault-cook-directory-modal').modal('hide');
$('#vault-cook-revision-modal').modal('hide');
const cookingTaskCreatedAlert =
$(htmlAlert('success',
'Archive cooking request successfully submitted. ' +
`Go to the Downloads page ` +
'to get the download link once it is ready.',
true));
cookingTaskCreatedAlert.css(alertStyle);
$('body').append(cookingTaskCreatedAlert);
} catch (_) {
$('#vault-cook-directory-modal').modal('hide');
$('#vault-cook-revision-modal').modal('hide');
const cookingTaskFailedAlert =
$(htmlAlert('danger',
'Archive cooking request submission failed.',
true));
cookingTaskFailedAlert.css(alertStyle);
$('body').append(cookingTaskFailedAlert);
}
}
}
function validateEmail(email) {
const re = /^(([^<>()[\]\\.,;:\s@"]+(\.[^<>()[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
return re.test(String(email).toLowerCase());
}
export function cookDirectoryArchive(swhid) {
const email = $('#swh-vault-directory-email').val().trim();
if (!email || validateEmail(email)) {
const cookingTask = {
'bundle_type': 'flat',
'swhid': swhid,
'email': email,
'status': 'new'
};
addVaultCookingTask('directory', cookingTask);
} else {
$('#invalid-email-modal').modal('show');
}
}
export async function fetchDirectoryArchive(directorySwhid) {
$('#vault-fetch-directory-modal').modal('hide');
const vaultUrl = Urls.api_1_vault_cook_flat(directorySwhid);
const response = await fetch(vaultUrl);
const data = await response.json();
swh.vault.fetchCookedObject(data.fetch_url);
}
export function cookRevisionArchive(revisionId) {
const email = $('#swh-vault-revision-email').val().trim();
if (!email || validateEmail(email)) {
const cookingTask = {
- 'bundle_type': 'gitfast',
+ 'bundle_type': 'git_bare',
'swhid': revisionId,
'email': email,
'status': 'new'
};
addVaultCookingTask('revision', cookingTask);
} else {
$('#invalid-email-modal').modal('show');
}
}
export async function fetchRevisionArchive(revisionSwhid) {
$('#vault-fetch-directory-modal').modal('hide');
- const vaultUrl = Urls.api_1_vault_cook_gitfast(revisionSwhid);
+ const vaultUrl = Urls.api_1_vault_cook_git_bare(revisionSwhid);
const response = await fetch(vaultUrl);
const data = await response.json();
swh.vault.fetchCookedObject(data.fetch_url);
}
diff --git a/assets/src/bundles/vault/vault-table-row.ejs b/assets/src/bundles/vault/vault-table-row.ejs
index e40d2e5d..01299387 100644
--- a/assets/src/bundles/vault/vault-table-row.ejs
+++ b/assets/src/bundles/vault/vault-table-row.ejs
@@ -1,56 +1,57 @@
<%#
Copyright (C) 2020 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
%>
<% if (cookingTask.object_type === 'directory') { %>
<% if (cookingTask.status === 'done') { %>
<% } %>
diff --git a/assets/src/bundles/vault/vault-ui.js b/assets/src/bundles/vault/vault-ui.js
index facf4539..ffe03eb8 100644
--- a/assets/src/bundles/vault/vault-ui.js
+++ b/assets/src/bundles/vault/vault-ui.js
@@ -1,258 +1,258 @@
/**
* Copyright (C) 2018-2019 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
import {handleFetchError, handleFetchErrors, csrfPost} from 'utils/functions';
import vaultTableRowTemplate from './vault-table-row.ejs';
const progress =
`
;`;
const pollingInterval = 5000;
let checkVaultId;
function updateProgressBar(progressBar, cookingTask) {
if (cookingTask.status === 'new') {
progressBar.css('background-color', 'rgba(128, 128, 128, 0.5)');
} else if (cookingTask.status === 'pending') {
progressBar.css('background-color', 'rgba(0, 0, 255, 0.5)');
} else if (cookingTask.status === 'done') {
progressBar.css('background-color', '#5cb85c');
} else if (cookingTask.status === 'failed') {
progressBar.css('background-color', 'rgba(255, 0, 0, 0.5)');
progressBar.css('background-image', 'none');
}
progressBar.text(cookingTask.progress_message || cookingTask.status);
if (cookingTask.status === 'new' || cookingTask.status === 'pending') {
progressBar.addClass('progress-bar-animated');
} else {
progressBar.removeClass('progress-bar-striped');
}
}
let recookTask;
// called when the user wants to download a cooked archive
export async function fetchCookedObject(fetchUrl) {
recookTask = null;
// first, check if the link is still available from the vault
const response = await fetch(fetchUrl);
// link is still alive, proceed to download
if (response.ok) {
$('#vault-fetch-iframe').attr('src', fetchUrl);
// link is dead
} else {
// get the associated cooking task
const vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
for (let i = 0; i < vaultCookingTasks.length; ++i) {
if (vaultCookingTasks[i].fetch_url === fetchUrl) {
recookTask = vaultCookingTasks[i];
break;
}
}
// display a modal asking the user if he wants to recook the archive
$('#vault-recook-object-modal').modal('show');
}
}
// called when the user wants to recook an archive
// for which the download link is not available anymore
export async function recookObject() {
if (recookTask) {
// stop cooking tasks status polling
clearTimeout(checkVaultId);
// build cook request url
let cookingUrl;
if (recookTask.bundle_type === 'flat') {
cookingUrl = Urls.api_1_vault_cook_flat(recookTask.swhid);
} else {
- cookingUrl = Urls.api_1_vault_cook_gitfast(recookTask.swhid);
+ cookingUrl = Urls.api_1_vault_cook_git_bare(recookTask.swhid);
}
if (recookTask.email) {
cookingUrl += '?email=' + recookTask.email;
}
try {
// request archive cooking
const response = await csrfPost(cookingUrl);
handleFetchError(response);
// update task status
recookTask.status = 'new';
const vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
for (let i = 0; i < vaultCookingTasks.length; ++i) {
if (vaultCookingTasks[i].swhid === recookTask.swhid) {
vaultCookingTasks[i] = recookTask;
break;
}
}
// save updated tasks to local storage
localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
// hide recook archive modal
$('#vault-recook-object-modal').modal('hide');
// restart cooking tasks status polling
await checkVaultCookingTasks();
} catch (_) {
// something went wrong
$('#vault-recook-object-modal').modal('hide');
await checkVaultCookingTasks();
}
}
}
async function checkVaultCookingTasks() {
const vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks || vaultCookingTasks.length === 0) {
$('.swh-vault-table tbody tr').remove();
checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
return;
}
const cookingTaskRequests = [];
const tasks = {};
const currentObjectIds = [];
for (let i = 0; i < vaultCookingTasks.length; ++i) {
const cookingTask = vaultCookingTasks[i];
if (typeof cookingTask.object_type !== 'undefined') {
// Legacy cooking task, upgrade it to the new schema
if (cookingTask.object_type === 'directory') {
cookingTask.swhid = `swh:1:dir:${cookingTask.object_id}`;
cookingTask.bundle_type = 'flat';
cookingTask.fetch_url = Urls.api_1_vault_fetch_flat(cookingTask.swhid);
} else if (cookingTask.object_type === 'revision') {
cookingTask.swhid = `swh:1:rev:${cookingTask.object_id}`;
- cookingTask.bundle_type = 'gitfast';
- cookingTask.fetch_url = Urls.api_1_vault_fetch_gitfast(cookingTask.swhid);
+ cookingTask.bundle_type = 'git_bare';
+ cookingTask.fetch_url = Urls.api_1_vault_fetch_git_bare(cookingTask.swhid);
} else {
// Log to the console + Sentry
console.error(`Unexpected cookingTask.object_type: ${cookingTask.object_type}`);
// Ignore it for now and hope a future version will fix it
continue;
}
delete cookingTask.object_type;
delete cookingTask.object_id;
}
currentObjectIds.push(cookingTask.swhid);
tasks[cookingTask.swhid] = cookingTask;
let cookingUrl;
if (cookingTask.bundle_type === 'flat') {
cookingUrl = Urls.api_1_vault_cook_flat(cookingTask.swhid);
} else {
- cookingUrl = Urls.api_1_vault_cook_gitfast(cookingTask.swhid);
+ cookingUrl = Urls.api_1_vault_cook_git_bare(cookingTask.swhid);
}
if (cookingTask.status !== 'done' && cookingTask.status !== 'failed') {
cookingTaskRequests.push(fetch(cookingUrl));
}
}
$('.swh-vault-table tbody tr').each((i, row) => {
const swhid = $(row).find('.vault-object-info').data('swhid');
if ($.inArray(swhid, currentObjectIds) === -1) {
$(row).remove();
}
});
try {
const responses = await Promise.all(cookingTaskRequests);
handleFetchErrors(responses);
const cookingTasks = await Promise.all(responses.map(r => r.json()));
const table = $('#vault-cooking-tasks tbody');
for (let i = 0; i < cookingTasks.length; ++i) {
const cookingTask = tasks[cookingTasks[i].swhid];
cookingTask.status = cookingTasks[i].status;
cookingTask.fetch_url = cookingTasks[i].fetch_url;
cookingTask.progress_message = cookingTasks[i].progress_message;
}
for (let i = 0; i < vaultCookingTasks.length; ++i) {
const cookingTask = vaultCookingTasks[i];
const rowTask = $(`#vault-task-${CSS.escape(cookingTask.swhid)}`);
if (!rowTask.length) {
let browseUrl = cookingTask.browse_url;
if (!browseUrl) {
browseUrl = Urls.browse_swhid(cookingTask.swhid);
}
const progressBar = $.parseHTML(progress)[0];
const progressBarContent = $(progressBar).find('.progress-bar');
updateProgressBar(progressBarContent, cookingTask);
table.prepend(vaultTableRowTemplate({
browseUrl: browseUrl,
cookingTask: cookingTask,
progressBar: progressBar,
Urls: Urls,
swh: swh
}));
} else {
const progressBar = rowTask.find('.progress-bar');
updateProgressBar(progressBar, cookingTask);
const downloadLink = rowTask.find('.vault-dl-link');
if (cookingTask.status === 'done') {
downloadLink[0].innerHTML =
'';
} else {
downloadLink[0].innerHTML = '';
}
}
}
localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
} catch (error) {
console.log('Error when fetching vault cooking tasks:', error);
}
}
export function removeCookingTaskInfo(tasksToRemove) {
let vaultCookingTasks = JSON.parse(localStorage.getItem('swh-vault-cooking-tasks'));
if (!vaultCookingTasks) {
return;
}
vaultCookingTasks = $.grep(vaultCookingTasks, task => {
return $.inArray(task.swhid, tasksToRemove) === -1;
});
localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultCookingTasks));
}
export function initUi() {
$('#vault-tasks-toggle-selection').change(event => {
$('.vault-task-toggle-selection').prop('checked', event.currentTarget.checked);
});
$('#vault-remove-tasks').click(() => {
clearTimeout(checkVaultId);
const tasksToRemove = [];
$('.swh-vault-table tbody tr').each((i, row) => {
const taskSelected = $(row).find('.vault-task-toggle-selection').prop('checked');
if (taskSelected) {
const swhid = $(row).find('.vault-object-info').data('swhid');
tasksToRemove.push(swhid);
$(row).remove();
}
});
removeCookingTaskInfo(tasksToRemove);
$('#vault-tasks-toggle-selection').prop('checked', false);
checkVaultId = setTimeout(checkVaultCookingTasks, pollingInterval);
});
checkVaultCookingTasks();
window.onfocus = () => {
clearTimeout(checkVaultId);
checkVaultCookingTasks();
};
}
diff --git a/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.git.tar b/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.git.tar
new file mode 100644
index 00000000..13265a3f
Binary files /dev/null and b/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.git.tar differ
diff --git a/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz b/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz
deleted file mode 100644
index aa70433e..00000000
Binary files a/cypress/fixtures/swh:1:rev:1c480a4573d2a003fc2630c21c2b25829de49972.gitfast.gz and /dev/null differ
diff --git a/cypress/integration/vault.spec.js b/cypress/integration/vault.spec.js
index fc3768b2..69d9f5fe 100644
--- a/cypress/integration/vault.spec.js
+++ b/cypress/integration/vault.spec.js
@@ -1,541 +1,541 @@
/**
* Copyright (C) 2019-2021 The Software Heritage developers
* See the AUTHORS file at the top-level directory of this distribution
* License: GNU Affero General Public License version 3, or any later version
* See top-level LICENSE file for more information
*/
const progressbarColors = {
'new': 'rgba(128, 128, 128, 0.5)',
'pending': 'rgba(0, 0, 255, 0.5)',
'done': 'rgb(92, 184, 92)'
};
function checkVaultCookingTask(objectType) {
cy.contains('button', 'Download')
.click();
cy.contains('.dropdown-item', objectType)
.click();
cy.wait('@checkVaultCookingTask');
}
function getVaultItemList() {
return JSON.parse(window.localStorage.getItem('swh-vault-cooking-tasks'));
}
function updateVaultItemList(vaultItems) {
window.localStorage.setItem('swh-vault-cooking-tasks', JSON.stringify(vaultItems));
}
// Mocks API response : /api/1/vault/(:bundleType)/(:swhid)
-// bundleType : {'flat', 'gitfast'}
+// bundleType : {'flat', 'git_bare'}
function genVaultCookingResponse(bundleType, swhid, status, message, fetchUrl) {
return {
'bundle_type': bundleType,
'id': 1,
'progress_message': message,
'status': status,
'swhid': swhid,
'fetch_url': fetchUrl
};
};
// Tests progressbar color, status
// And status in localStorage
function testStatus(taskId, color, statusMsg, status) {
cy.get(`.swh-vault-table #vault-task-${CSS.escape(taskId)}`)
.should('be.visible')
.find('.progress-bar')
.should('be.visible')
.and('have.css', 'background-color', color)
.and('contain', statusMsg)
.then(() => {
// Vault item with object_id as taskId should exist in localStorage
const currentVaultItems = getVaultItemList();
const vaultItem = currentVaultItems.find(obj => obj.swhid === taskId);
assert.isNotNull(vaultItem);
assert.strictEqual(vaultItem.status, status);
});
}
describe('Vault Cooking User Interface Tests', function() {
before(function() {
const dirInfo = this.origin[0].directory[0];
this.directory = `swh:1:dir:${dirInfo.id}`;
this.directoryUrl = this.Urls.browse_origin_directory() +
`?origin_url=${this.origin[0].url}&path=${dirInfo.path}`;
this.vaultDirectoryUrl = this.Urls.api_1_vault_cook_flat(this.directory);
this.vaultFetchDirectoryUrl = this.Urls.api_1_vault_fetch_flat(this.directory);
this.revisionId = this.origin[1].revisions[0];
this.revision = `swh:1:rev:${this.revisionId}`;
this.revisionUrl = this.Urls.browse_revision(this.revisionId);
- this.vaultRevisionUrl = this.Urls.api_1_vault_cook_gitfast(this.revision);
- this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_gitfast(this.revision);
+ this.vaultRevisionUrl = this.Urls.api_1_vault_cook_git_bare(this.revision);
+ this.vaultFetchRevisionUrl = this.Urls.api_1_vault_fetch_git_bare(this.revision);
const release = this.origin[1].release;
this.releaseUrl = this.Urls.browse_release(release.id) + `?origin_url=${this.origin[1].url}`;
this.vaultReleaseDirectoryUrl = this.Urls.api_1_vault_cook_flat(`swh:1:dir:${release.directory}`);
});
beforeEach(function() {
// For some reason, this gets reset if we define it in the before() hook,
// so we need to define it here
this.vaultItems = [
{
- 'bundle_type': 'gitfast',
+ 'bundle_type': 'git_bare',
'swhid': this.revision,
'email': '',
'status': 'done',
- 'fetch_url': `/api/1/vault/gitfast/${this.revision}/raw/`,
+ 'fetch_url': `/api/1/vault/git-bare/${this.revision}/raw/`,
'progress_message': null
}
];
this.legacyVaultItems = [
{
'object_type': 'revision',
'object_id': this.revisionId,
'email': '',
'status': 'done',
'fetch_url': `/api/1/vault/revision/${this.revisionId}/gitfast/raw/`,
'progress_message': null
}
];
this.genVaultDirCookingResponse = (status, message = null) => {
return genVaultCookingResponse('flat', this.directory, status,
message, this.vaultFetchDirectoryUrl);
};
this.genVaultRevCookingResponse = (status, message = null) => {
- return genVaultCookingResponse('gitfast', this.revision, status,
+ return genVaultCookingResponse('git_bare', this.revision, status,
message, this.vaultFetchRevisionUrl);
};
});
it('should report an error when vault service is experiencing issues', function() {
// Browse a directory
cy.visit(this.directoryUrl);
// Stub responses when requesting the vault API to simulate
// an internal server error
cy.intercept(this.vaultDirectoryUrl, {
body: {'exception': 'APIError'},
statusCode: 500
}).as('checkVaultCookingTask');
cy.contains('button', 'Download')
.click();
// Check error alert is displayed
cy.get('.alert-danger')
.should('be.visible')
.should('contain', 'Archive cooking service is currently experiencing issues.');
});
it('should report an error when a cooking task creation failed', function() {
// Browse a directory
cy.visit(this.directoryUrl);
// Stub responses when requesting the vault API to simulate
// a task can not be created
cy.intercept('GET', this.vaultDirectoryUrl, {
body: {'exception': 'NotFoundExc'}
}).as('checkVaultCookingTask');
cy.intercept('POST', this.vaultDirectoryUrl, {
body: {'exception': 'ValueError'},
statusCode: 500
}).as('createVaultCookingTask');
cy.contains('button', 'Download')
.click();
// Create a vault cooking task through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@createVaultCookingTask');
// Check error alert is displayed
cy.get('.alert-danger')
.should('be.visible')
.should('contain', 'Archive cooking request submission failed.');
});
it('should display previous cooking tasks', function() {
updateVaultItemList(this.vaultItems);
cy.visit(this.Urls.browse_vault());
cy.contains(`#vault-task-${CSS.escape(this.revision)} button`, 'Download')
.click();
});
it('should display and upgrade previous cooking tasks from the legacy format', function() {
updateVaultItemList(this.legacyVaultItems);
cy.visit(this.Urls.browse_vault());
// Check it is displayed
cy.contains(`#vault-task-${CSS.escape(this.revision)} button`, 'Download')
.then(() => {
// Check the LocalStorage was upgraded
expect(getVaultItemList()).to.deep.equal(this.vaultItems);
});
});
it('should create a directory cooking task and report the success', function() {
// Browse a directory
cy.visit(this.directoryUrl);
// Stub response to the vault API to simulate archive download
cy.intercept('GET', this.vaultFetchDirectoryUrl, {
fixture: `${this.directory}.tar.gz`,
headers: {
'Content-disposition': `attachment; filename=${this.directory}.tar.gz`,
'Content-Type': 'application/gzip'
}
}).as('fetchCookedArchive');
// Stub responses when checking vault task status
const checkVaulResponses = [
{'exception': 'NotFoundExc'},
this.genVaultDirCookingResponse('new'),
this.genVaultDirCookingResponse('pending', 'Processing...'),
this.genVaultDirCookingResponse('done')
];
// trick to override the response of an intercepted request
// https://github.com/cypress-io/cypress/issues/9302
cy.intercept('GET', this.vaultDirectoryUrl, req => req.reply(checkVaulResponses.shift()))
.as('checkVaultCookingTask');
// Stub responses when requesting the vault API to simulate
// a task has been created
cy.intercept('POST', this.vaultDirectoryUrl, {
body: this.genVaultDirCookingResponse('new')
}).as('createVaultCookingTask');
cy.contains('button', 'Download')
.click();
cy.window().then(win => {
const swhIdsContext = win.swh.webapp.getSwhIdsContext();
const browseDirectoryUrl = swhIdsContext.directory.swhid_with_context_url;
// Create a vault cooking task through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@createVaultCookingTask');
// Check success alert is displayed
cy.get('.alert-success')
.should('be.visible')
.should('contain', 'Archive cooking request successfully submitted.');
// Go to Downloads page
cy.visit(this.Urls.browse_vault());
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.directory, progressbarColors['new'], 'new', 'new');
});
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.directory, progressbarColors['pending'], 'Processing...', 'pending');
});
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.directory, progressbarColors['done'], 'done', 'done');
});
cy.get(`#vault-task-${CSS.escape(this.directory)} .vault-origin a`)
.should('contain', this.origin[0].url)
.should('have.attr', 'href', `${this.Urls.browse_origin()}?origin_url=${this.origin[0].url}`);
cy.get(`#vault-task-${CSS.escape(this.directory)} .vault-object-info a`)
.should('have.text', this.directory)
.should('have.attr', 'href', browseDirectoryUrl);
cy.get(`#vault-task-${CSS.escape(this.directory)} .vault-dl-link button`)
.click();
cy.wait('@fetchCookedArchive').then((xhr) => {
assert.isNotNull(xhr.response.body);
});
});
});
it('should create a revision cooking task and report its status', function() {
cy.adminLogin();
// Browse a revision
cy.visit(this.revisionUrl);
// Stub response to the vault API indicating to simulate archive download
cy.intercept({url: this.vaultFetchRevisionUrl}, {
- fixture: `${this.revision}.gitfast.gz`,
+ fixture: `${this.revision}.git.tar`,
headers: {
- 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`,
+ 'Content-disposition': `attachment; filename=${this.revision}.git.tar`,
'Content-Type': 'application/gzip'
}
}).as('fetchCookedArchive');
// Stub responses when checking vault task status
const checkVaultResponses = [
{'exception': 'NotFoundExc'},
this.genVaultRevCookingResponse('new'),
this.genVaultRevCookingResponse('pending', 'Processing...'),
this.genVaultRevCookingResponse('done')
];
// trick to override the response of an intercepted request
// https://github.com/cypress-io/cypress/issues/9302
cy.intercept('GET', this.vaultRevisionUrl, req => req.reply(checkVaultResponses.shift()))
.as('checkVaultCookingTask');
// Stub responses when requesting the vault API to simulate
// a task has been created
cy.intercept('POST', this.vaultRevisionUrl, {
body: this.genVaultRevCookingResponse('new')
}).as('createVaultCookingTask');
// Create a vault cooking task through the GUI
checkVaultCookingTask('as git');
cy.window().then(win => {
const swhIdsContext = win.swh.webapp.getSwhIdsContext();
const browseRevisionUrl = swhIdsContext.revision.swhid_url;
// Create a vault cooking task through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@createVaultCookingTask');
// Check success alert is displayed
cy.get('.alert-success')
.should('be.visible')
.should('contain', 'Archive cooking request successfully submitted.');
// Go to Downloads page
cy.visit(this.Urls.browse_vault());
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.revision, progressbarColors['new'], 'new', 'new');
});
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.revision, progressbarColors['pending'], 'Processing...', 'pending');
});
cy.wait('@checkVaultCookingTask').then(() => {
testStatus(this.revision, progressbarColors['done'], 'done', 'done');
});
cy.get(`#vault-task-${CSS.escape(this.revision)} .vault-origin`)
.should('have.text', 'unknown');
cy.get(`#vault-task-${CSS.escape(this.revision)} .vault-object-info a`)
.should('have.text', this.revision)
.should('have.attr', 'href', browseRevisionUrl);
cy.get(`#vault-task-${CSS.escape(this.revision)} .vault-dl-link button`)
.click();
cy.wait('@fetchCookedArchive').then((xhr) => {
assert.isNotNull(xhr.response.body);
});
});
});
it('should create a directory cooking task from the release view', function() {
// Browse a directory
cy.visit(this.releaseUrl);
// Stub responses when checking vault task status
const checkVaultResponses = [
{'exception': 'NotFoundExc'},
this.genVaultDirCookingResponse('new')
];
// trick to override the response of an intercepted request
// https://github.com/cypress-io/cypress/issues/9302
cy.intercept('GET', this.vaultReleaseDirectoryUrl, req => req.reply(checkVaultResponses.shift()))
.as('checkVaultCookingTask');
// Stub responses when requesting the vault API to simulate
// a task has been created
cy.intercept('POST', this.vaultReleaseDirectoryUrl, {
body: this.genVaultDirCookingResponse('new')
}).as('createVaultCookingTask');
cy.contains('button', 'Download')
.click();
// Create a vault cooking task through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@createVaultCookingTask');
// Check success alert is displayed
cy.get('.alert-success')
.should('be.visible')
.should('contain', 'Archive cooking request successfully submitted.');
});
it('should offer to recook an archive if no longer available for download', function() {
updateVaultItemList(this.vaultItems);
// Send 404 when fetching vault item
cy.intercept({url: this.vaultFetchRevisionUrl}, {
statusCode: 404,
body: {
'exception': 'NotFoundExc',
'reason': `Revision with ID '${this.revision}' not found.`
},
headers: {
'Content-Type': 'json'
}
}).as('fetchCookedArchive');
cy.visit(this.Urls.browse_vault())
.get(`#vault-task-${CSS.escape(this.revision)} .vault-dl-link button`)
.click();
cy.wait('@fetchCookedArchive').then(() => {
cy.intercept('POST', this.vaultRevisionUrl, {
body: this.genVaultRevCookingResponse('new')
}).as('createVaultCookingTask');
cy.intercept(this.vaultRevisionUrl, {
body: this.genVaultRevCookingResponse('new')
}).as('checkVaultCookingTask');
cy.get('#vault-recook-object-modal > .modal-dialog')
.should('be.visible')
.contains('button:visible', 'Ok')
.click();
cy.wait('@checkVaultCookingTask')
.then(() => {
testStatus(this.revision, progressbarColors['new'], 'new', 'new');
});
});
});
it('should remove selected vault items', function() {
updateVaultItemList(this.vaultItems);
cy.visit(this.Urls.browse_vault())
.get(`#vault-task-${CSS.escape(this.revision)}`)
.find('input[type="checkbox"]')
.click({force: true});
cy.contains('button', 'Remove selected tasks')
.click();
cy.get(`#vault-task-${CSS.escape(this.revision)}`)
.should('not.exist');
});
it('should offer to immediately download a directory tarball if already cooked', function() {
// Browse a directory
cy.visit(this.directoryUrl);
// Stub response to the vault API to simulate archive download
cy.intercept({url: this.vaultFetchDirectoryUrl}, {
fixture: `${this.directory}.tar.gz`,
headers: {
'Content-disposition': `attachment; filename=${this.directory}.tar.gz`,
'Content-Type': 'application/gzip'
}
}).as('fetchCookedArchive');
// Stub responses when requesting the vault API to simulate
// the directory tarball has already been cooked
cy.intercept(this.vaultDirectoryUrl, {
body: this.genVaultDirCookingResponse('done')
}).as('checkVaultCookingTask');
// Create a vault cooking task through the GUI
cy.contains('button', 'Download')
.click();
// Start archive download through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@fetchCookedArchive');
});
- it('should offer to immediately download a revision gitfast archive if already cooked', function() {
+ it('should offer to immediately download a bare revision git archive if already cooked', function() {
cy.adminLogin();
// Browse a directory
cy.visit(this.revisionUrl);
// Stub response to the vault API to simulate archive download
cy.intercept({url: this.vaultFetchRevisionUrl}, {
- fixture: `${this.revision}.gitfast.gz`,
+ fixture: `${this.revision}.git.tar`,
headers: {
- 'Content-disposition': `attachment; filename=${this.revision}.gitfast.gz`,
+ 'Content-disposition': `attachment; filename=${this.revision}.git.tar`,
'Content-Type': 'application/gzip'
}
}).as('fetchCookedArchive');
// Stub responses when requesting the vault API to simulate
// the directory tarball has already been cooked
cy.intercept(this.vaultRevisionUrl, {
body: this.genVaultRevCookingResponse('done')
}).as('checkVaultCookingTask');
checkVaultCookingTask('as git');
// Start archive download through the GUI
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.click();
cy.wait('@fetchCookedArchive');
});
it('should offer to recook an object if previous vault task failed', function() {
cy.visit(this.directoryUrl);
// Stub responses when requesting the vault API to simulate
// the last cooking of the directory tarball has failed
cy.intercept(this.vaultDirectoryUrl, {
body: this.genVaultDirCookingResponse('failed')
}).as('checkVaultCookingTask');
cy.contains('button', 'Download')
.click();
// Check that recooking the directory is offered to user
cy.get('.modal-dialog')
.contains('button:visible', 'Ok')
.should('be.visible');
});
});
diff --git a/swh/web/api/views/vault.py b/swh/web/api/views/vault.py
index 3b1f9233..052d9c17 100644
--- a/swh/web/api/views/vault.py
+++ b/swh/web/api/views/vault.py
@@ -1,490 +1,490 @@
# Copyright (C) 2015-2021 The Software Heritage developers
# See the AUTHORS file at the top-level directory of this distribution
# License: GNU Affero General Public License version 3, or any later version
# See top-level LICENSE file for more information
from typing import Any, Dict
from django.http import HttpResponse
from django.shortcuts import redirect
from swh.model.identifiers import CoreSWHID, ObjectType
from swh.web.api.apidoc import api_doc, format_docstring
from swh.web.api.apiurls import api_route
from swh.web.api.views.utils import api_lookup
from swh.web.common import archive, query
from swh.web.common.exc import BadInputExc
from swh.web.common.utils import reverse
######################################################
# Common
SWHID_RE = "swh:1:[a-z]{3}:[0-9a-z]{40}"
# XXX: a bit spaghetti. Would be better with class-based views.
def _dispatch_cook_progress(request, bundle_type: str, swhid: CoreSWHID):
if request.method == "GET":
return api_lookup(
archive.vault_progress,
bundle_type,
swhid,
notfound_msg=f"Cooking of {swhid} was never requested.",
request=request,
)
elif request.method == "POST":
email = request.POST.get("email", request.GET.get("email", None))
return api_lookup(
archive.vault_cook,
bundle_type,
swhid,
email,
notfound_msg=f"{swhid} not found.",
request=request,
)
def _vault_response(vault_response: Dict[str, Any]) -> Dict[str, Any]:
return {
"fetch_url": vault_response["fetch_url"],
"progress_message": vault_response["progress_msg"],
"id": vault_response["task_id"],
"status": vault_response["task_status"],
"swhid": str(vault_response["swhid"]),
}
######################################################
# Flat bundles
@api_route(
f"/vault/flat/(?P{SWHID_RE})/",
"api-1-vault-cook-flat",
methods=["GET", "POST"],
throttle_scope="swh_vault_cooking",
never_cache=True,
)
@api_doc("/vault/flat/")
@format_docstring()
def api_vault_cook_flat(request, swhid):
"""
.. http:get:: /api/1/vault/flat/(swhid)/
.. http:post:: /api/1/vault/flat/(swhid)/
Request the cooking of a simple archive, typically for a directory.
That endpoint enables to create a vault cooking task for a directory
through a POST request or check the status of a previously created one
through a GET request.
Once the cooking task has been executed, the resulting archive can
be downloaded using the dedicated endpoint
:http:get:`/api/1/vault/flat/(swhid)/raw/`.
Then to extract the cooked directory in the current one, use::
$ tar xvf path/to/swh:1:*.tar.gz
:param string swhid: the object's SWHID
:query string email: e-mail to notify when the archive is ready
{common_headers}
:>json string fetch_url: the url from which to download the archive
once it has been cooked
(see :http:get:`/api/1/vault/flat/(swhid)/raw/`)
:>json string progress_message: message describing the cooking task
progress
:>json number id: the cooking task id
:>json string status: the cooking task status
(either **new**, **pending**, **done** or **failed**)
:>json string swhid: the identifier of the object to cook
:statuscode 200: no error
:statuscode 400: an invalid directory identifier has been provided
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
swhid = CoreSWHID.from_string(swhid)
if swhid.object_type == ObjectType.DIRECTORY:
res = _dispatch_cook_progress(request, "flat", swhid)
res["fetch_url"] = reverse(
"api-1-vault-fetch-flat", url_args={"swhid": str(swhid)}, request=request,
)
return _vault_response(res)
elif swhid.object_type == ObjectType.CONTENT:
raise BadInputExc(
"Content objects do not need to be cooked, "
"use `/api/1/content/raw/` instead."
)
elif swhid.object_type == ObjectType.REVISION:
# TODO: support revisions too? (the vault allows it)
raise BadInputExc(
"Only directories can be cooked as 'flat' bundles. "
"Use `/api/1/vault/gitfast/` to cook revisions, as gitfast bundles."
)
else:
raise BadInputExc("Only directories can be cooked as 'flat' bundles.")
@api_route(
r"/vault/directory/(?P[0-9a-f]+)/",
"api-1-vault-cook-directory",
methods=["GET", "POST"],
checksum_args=["dir_id"],
throttle_scope="swh_vault_cooking",
never_cache=True,
)
@api_doc("/vault/directory/", tags=["deprecated"])
@format_docstring()
def api_vault_cook_directory(request, dir_id):
"""
.. http:get:: /api/1/vault/directory/(dir_id)/
This endpoint was replaced by :http:get:`/api/1/vault/flat/(swhid)/`
"""
_, obj_id = query.parse_hash_with_algorithms_or_throws(
dir_id, ["sha1"], "Only sha1_git is supported."
)
swhid = f"swh:1:dir:{obj_id.hex()}"
res = _dispatch_cook_progress(request, "flat", CoreSWHID.from_string(swhid))
res["fetch_url"] = reverse(
"api-1-vault-fetch-flat", url_args={"swhid": swhid}, request=request,
)
return _vault_response(res)
@api_route(
f"/vault/flat/(?P{SWHID_RE})/raw/", "api-1-vault-fetch-flat",
)
@api_doc("/vault/flat/raw/")
def api_vault_fetch_flat(request, swhid):
"""
.. http:get:: /api/1/vault/flat/(swhid)/raw/
Fetch the cooked archive for a flat bundle.
See :http:get:`/api/1/vault/flat/(swhid)/` to get more
details on 'flat' bundle cooking.
:param string swhid: the SWHID of the object to cook
:resheader Content-Type: application/octet-stream
:statuscode 200: no error
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
res = api_lookup(
archive.vault_fetch,
"flat",
CoreSWHID.from_string(swhid),
notfound_msg=f"Cooked archive for {swhid} not found.",
request=request,
)
fname = "{}.tar.gz".format(swhid)
response = HttpResponse(res, content_type="application/gzip")
response["Content-disposition"] = "attachment; filename={}".format(fname)
return response
@api_route(
r"/vault/directory/(?P[0-9a-f]+)/raw/",
"api-1-vault-fetch-directory",
checksum_args=["dir_id"],
)
@api_doc("/vault/directory/raw/", tags=["hidden", "deprecated"])
def api_vault_fetch_directory(request, dir_id):
"""
.. http:get:: /api/1/vault/directory/(dir_id)/raw/
This endpoint was replaced by :http:get:`/api/1/vault/flat/raw/`
"""
_, obj_id = query.parse_hash_with_algorithms_or_throws(
dir_id, ["sha1"], "Only sha1_git is supported."
)
rev_flat_raw_url = reverse(
"api-1-vault-fetch-flat", url_args={"swhid": f"swh:1:dir:{dir_id}"}
)
return redirect(rev_flat_raw_url)
######################################################
# gitfast bundles
@api_route(
f"/vault/gitfast/(?P{SWHID_RE})/",
"api-1-vault-cook-gitfast",
methods=["GET", "POST"],
throttle_scope="swh_vault_cooking",
never_cache=True,
)
@api_doc("/vault/gitfast/")
@format_docstring()
def api_vault_cook_gitfast(request, swhid):
"""
.. http:get:: /api/1/vault/gitfast/(swhid)/
.. http:post:: /api/1/vault/gitfast/(swhid)/
Request the cooking of a gitfast archive for a revision or check
its cooking status.
That endpoint enables to create a vault cooking task for a revision
through a POST request or check the status of a previously created one
through a GET request.
Once the cooking task has been executed, the resulting gitfast archive
can be downloaded using the dedicated endpoint
:http:get:`/api/1/vault/gitfast/(swhid)/raw/`.
Then to import the revision in the current directory, use::
$ git init
$ zcat path/to/swh:1:rev:*.gitfast.gz | git fast-import
$ git checkout HEAD
:param string swhid: the revision's permanent identifiers
:query string email: e-mail to notify when the gitfast archive is ready
{common_headers}
:>json string fetch_url: the url from which to download the archive
once it has been cooked
(see :http:get:`/api/1/vault/gitfast/(swhid)/raw/`)
:>json string progress_message: message describing the cooking task
progress
:>json number id: the cooking task id
:>json string status: the cooking task status (new/pending/done/failed)
:>json string swhid: the identifier of the object to cook
:statuscode 200: no error
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
swhid = CoreSWHID.from_string(swhid)
if swhid.object_type == ObjectType.REVISION:
res = _dispatch_cook_progress(request, "gitfast", swhid)
res["fetch_url"] = reverse(
"api-1-vault-fetch-gitfast",
url_args={"swhid": str(swhid)},
request=request,
)
return _vault_response(res)
elif swhid.object_type == ObjectType.CONTENT:
raise BadInputExc(
"Content objects do not need to be cooked, "
"use `/api/1/content/raw/` instead."
)
elif swhid.object_type == ObjectType.DIRECTORY:
raise BadInputExc(
"Only revisions can be cooked as 'gitfast' bundles. "
"Use `/api/1/vault/flat/` to cook directories, as flat bundles."
)
else:
raise BadInputExc("Only revisions can be cooked as 'gitfast' bundles.")
@api_route(
r"/vault/revision/(?P[0-9a-f]+)/gitfast/",
"api-1-vault-cook-revision_gitfast",
methods=["GET", "POST"],
checksum_args=["rev_id"],
throttle_scope="swh_vault_cooking",
never_cache=True,
)
@api_doc("/vault/revision/gitfast/", tags=["deprecated"])
@format_docstring()
def api_vault_cook_revision_gitfast(request, rev_id):
"""
.. http:get:: /api/1/vault/revision/(rev_id)/gitfast/
This endpoint was replaced by :http:get:`/api/1/vault/gitfast/`
"""
_, obj_id = query.parse_hash_with_algorithms_or_throws(
rev_id, ["sha1"], "Only sha1_git is supported."
)
swhid = f"swh:1:rev:{obj_id.hex()}"
res = _dispatch_cook_progress(request, "gitfast", CoreSWHID.from_string(swhid))
res["fetch_url"] = reverse(
"api-1-vault-fetch-gitfast", url_args={"swhid": swhid}, request=request,
)
return _vault_response(res)
@api_route(
f"/vault/gitfast/(?P{SWHID_RE})/raw/", "api-1-vault-fetch-gitfast",
)
@api_doc("/vault/gitfast/raw/")
def api_vault_fetch_revision_gitfast(request, swhid):
"""
.. http:get:: /api/1/vault/gitfast/(swhid)/raw/
Fetch the cooked gitfast archive for a revision.
See :http:get:`/api/1/vault/gitfast/(swhid)/` to get more
details on gitfast cooking.
:param string rev_id: the revision's sha1 identifier
:resheader Content-Type: application/octet-stream
:statuscode 200: no error
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
res = api_lookup(
archive.vault_fetch,
"gitfast",
CoreSWHID.from_string(swhid),
notfound_msg="Cooked archive for {} not found.".format(swhid),
request=request,
)
fname = "{}.gitfast.gz".format(swhid)
response = HttpResponse(res, content_type="application/gzip")
response["Content-disposition"] = "attachment; filename={}".format(fname)
return response
@api_route(
r"/vault/revision/(?P[0-9a-f]+)/gitfast/raw/",
"api-1-vault-fetch-revision_gitfast",
checksum_args=["rev_id"],
)
@api_doc("/vault/revision_gitfast/raw/", tags=["hidden", "deprecated"])
def _api_vault_revision_gitfast_raw(request, rev_id):
"""
.. http:get:: /api/1/vault/revision/(rev_id)/gitfast/raw/
This endpoint was replaced by :http:get:`/api/1/vault/gitfast/raw/`
"""
rev_gitfast_raw_url = reverse(
"api-1-vault-fetch-gitfast", url_args={"swhid": f"swh:1:rev:{rev_id}"}
)
return redirect(rev_gitfast_raw_url)
######################################################
# git_bare bundles
@api_route(
f"/vault/git-bare/(?P{SWHID_RE})/",
"api-1-vault-cook-git-bare",
methods=["GET", "POST"],
throttle_scope="swh_vault_cooking",
never_cache=True,
)
@api_doc("/vault/git-bare/")
@format_docstring()
def api_vault_cook_git_bare(request, swhid):
"""
.. http:get:: /api/1/vault/git-bare/(swhid)/
.. http:post:: /api/1/vault/git-bare/(swhid)/
Request the cooking of a git-bare archive for a revision or check
its cooking status.
That endpoint enables to create a vault cooking task for a revision
through a POST request or check the status of a previously created one
through a GET request.
Once the cooking task has been executed, the resulting git-bare archive
can be downloaded using the dedicated endpoint
:http:get:`/api/1/vault/git-bare/(swhid)/raw/`.
Then to import the revision in the current directory, use::
- $ tar -xzf path/to/swh:1:rev:*.git_bare.tar.gz
+ $ tar -xf path/to/swh:1:rev:*.git.tar
$ git clone swh:1:rev:*.git new_repository
(replace ``swh:1:rev:*`` with the SWHID of the requested revision)
This will create a directory called ``new_repository``, which is a git
repository containing the requested objects.
:param string swhid: the revision's permanent identifier
:query string email: e-mail to notify when the git-bare archive is ready
{common_headers}
:>json string fetch_url: the url from which to download the archive
once it has been cooked
(see :http:get:`/api/1/vault/git-bare/(swhid)/raw/`)
:>json string progress_message: message describing the cooking task
progress
:>json number id: the cooking task id
:>json string status: the cooking task status (new/pending/done/failed)
:>json string swhid: the identifier of the object to cook
:statuscode 200: no error
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
swhid = CoreSWHID.from_string(swhid)
if swhid.object_type == ObjectType.REVISION:
res = _dispatch_cook_progress(request, "git_bare", swhid)
res["fetch_url"] = reverse(
"api-1-vault-fetch-git-bare",
url_args={"swhid": str(swhid)},
request=request,
)
return _vault_response(res)
elif swhid.object_type == ObjectType.CONTENT:
raise BadInputExc(
"Content objects do not need to be cooked, "
"use `/api/1/content/raw/` instead."
)
elif swhid.object_type == ObjectType.DIRECTORY:
raise BadInputExc(
"Only revisions can be cooked as 'git-bare' bundles. "
"Use `/api/1/vault/flat/` to cook directories, as flat bundles."
)
else:
raise BadInputExc("Only revisions can be cooked as 'git-bare' bundles.")
@api_route(
f"/vault/git-bare/(?P{SWHID_RE})/raw/", "api-1-vault-fetch-git-bare",
)
@api_doc("/vault/git-bare/raw/")
def api_vault_fetch_revision_git_bare(request, swhid):
"""
.. http:get:: /api/1/vault/git-bare/(swhid)/raw/
Fetch the cooked git-bare archive for a revision.
See :http:get:`/api/1/vault/git-bare/(swhid)/` to get more
details on git-bare cooking.
:param string swhid: the revision's permanent identifier
:resheader Content-Type: application/octet-stream
:statuscode 200: no error
:statuscode 404: requested directory did not receive any cooking
request yet (in case of GET) or can not be found in the archive
(in case of POST)
"""
res = api_lookup(
archive.vault_fetch,
"git_bare",
CoreSWHID.from_string(swhid),
notfound_msg="Cooked archive for {} not found.".format(swhid),
request=request,
)
- fname = "{}.git_bare.tar.gz".format(swhid)
+ fname = "{}.git.tar".format(swhid)
response = HttpResponse(res, content_type="application/gzip")
response["Content-disposition"] = "attachment; filename={}".format(fname)
return response
diff --git a/swh/web/templates/includes/vault-create-tasks.html b/swh/web/templates/includes/vault-create-tasks.html
index e7bc0f3e..ced734ee 100644
--- a/swh/web/templates/includes/vault-create-tasks.html
+++ b/swh/web/templates/includes/vault-create-tasks.html
@@ -1,175 +1,175 @@
{% comment %}
Copyright (C) 2017-2021 The Software Heritage developers
See the AUTHORS file at the top-level directory of this distribution
License: GNU Affero General Public License version 3, or any later version
See top-level LICENSE file for more information
{% endcomment %}
{% load swh_templatetags %}
{% if vault_cooking %}
{% if user.is_staff %}
{% if vault_cooking.directory_context %}
{% endif %}
{% if vault_cooking.revision_context %}
{% endif %}
{% else %}
{% endif %}
Cook and download a directory from the Software Heritage Vault
You have requested the cooking of the directory with identifier {{ vault_cooking.directory_swhid }}
into a standard tar.gz archive.
Are you sure you want to continue ?
Download a directory from the Software Heritage Vault
You have requested the download of the directory with identifier {{ vault_cooking.directory_swhid }}
as a standard tar.gz archive.
Are you sure you want to continue ?
Cook and download a revision from the Software Heritage Vault
You have requested the cooking of the history heading to revision with identifier {{ vault_cooking.revision_swhid }}
- into a git fast-import archive.
+ into a bare git archive.
Are you sure you want to continue ?
Download a revision from the Software Heritage Vault
You have requested the download of the history heading to revision with identifier {{ vault_cooking.revision_swhid }}
- as a git fast-import archive.
+ as a bare git archive.
Are you sure you want to continue ?
Invalid Email !
The provided email is not well-formed.
{% include "includes/vault-common.html" %}
{% endif %}