// Version Control System Functionality class VersionControl { constructor() { this.setupEventListeners(); } setupEventListeners() { // Branch switching document.addEventListener('htmx:afterRequest', (event) => { if (event.detail.target.id === 'branch-form-container') { this.handleBranchFormResponse(event); } }); // Listen for branch switches document.addEventListener('branch-switched', () => { this.refreshContent(); }); // Handle merge operations document.addEventListener('htmx:afterRequest', (event) => { if (event.detail.target.id === 'merge-panel') { this.handleMergeResponse(event); } }); } handleBranchFormResponse(event) { if (event.detail.successful) { // Clear the branch form container document.getElementById('branch-form-container').innerHTML = ''; // Trigger branch list refresh document.body.dispatchEvent(new CustomEvent('branch-updated')); } } handleMergeResponse(event) { if (event.detail.successful) { const mergePanel = document.getElementById('merge-panel'); if (mergePanel.innerHTML.includes('Merge Successful')) { // Trigger content refresh after successful merge setTimeout(() => { this.refreshContent(); }, 1500); } } } refreshContent() { // Reload the page to show content from new branch window.location.reload(); } // Branch operations createBranch(name, parentBranch = null) { const formData = new FormData(); formData.append('name', name); if (parentBranch) { formData.append('parent', parentBranch); } return fetch('/vcs/branches/create/', { method: 'POST', body: formData, headers: { 'X-CSRFToken': this.getCsrfToken() } }).then(response => response.json()); } switchBranch(branchName) { const formData = new FormData(); formData.append('branch', branchName); return fetch('/vcs/branches/switch/', { method: 'POST', body: formData, headers: { 'X-CSRFToken': this.getCsrfToken() } }).then(response => { if (response.ok) { document.body.dispatchEvent(new CustomEvent('branch-switched')); } return response.json(); }); } // Merge operations initiateMerge(sourceBranch, targetBranch) { const formData = new FormData(); formData.append('source', sourceBranch); formData.append('target', targetBranch); return fetch('/vcs/merge/', { method: 'POST', body: formData, headers: { 'X-CSRFToken': this.getCsrfToken() } }).then(response => response.json()); } resolveConflicts(resolutions) { return fetch('/vcs/resolve-conflicts/', { method: 'POST', body: JSON.stringify(resolutions), headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() } }).then(response => response.json()); } // History operations getHistory(branch = null) { let url = '/vcs/history/'; if (branch) { url += `?branch=${encodeURIComponent(branch)}`; } return fetch(url) .then(response => response.json()); } // Comment operations async createComment(threadId, content, parentId = null) { try { const response = await fetch('/vcs/comments/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ thread_id: threadId, content: content, parent_id: parentId }) }); if (!response.ok) { throw new Error('Failed to create comment'); } const comment = await response.json(); return comment; } catch (error) { this.showError('Error creating comment: ' + error.message); throw error; } } async createCommentThread(changeId, anchor, initialComment) { try { const response = await fetch('/vcs/comments/threads/create/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ change_id: changeId, anchor: anchor, initial_comment: initialComment }) }); if (!response.ok) { throw new Error('Failed to create comment thread'); } const thread = await response.json(); return thread; } catch (error) { this.showError('Error creating comment thread: ' + error.message); throw error; } } async resolveThread(threadId) { try { const response = await fetch(`/vcs/comments/threads/${threadId}/resolve/`, { method: 'POST', headers: { 'X-CSRFToken': this.getCsrfToken() } }); if (!response.ok) { throw new Error('Failed to resolve thread'); } return await response.json(); } catch (error) { this.showError('Error resolving thread: ' + error.message); throw error; } } async editComment(commentId, content) { try { const response = await fetch(`/vcs/comments/${commentId}/`, { method: 'PATCH', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ content: content }) }); if (!response.ok) { throw new Error('Failed to edit comment'); } return await response.json(); } catch (error) { this.showError('Error editing comment: ' + error.message); throw error; } } async getThreadComments(threadId) { try { const response = await fetch(`/vcs/comments/threads/${threadId}/`); if (!response.ok) { throw new Error('Failed to fetch thread comments'); } return await response.json(); } catch (error) { this.showError('Error fetching comments: ' + error.message); throw error; } } initializeCommentPanel(containerId, options = {}) { const panel = new InlineCommentPanel({ ...options, onReply: async (content, parentId) => { const comment = await this.createComment( options.threadId, content, parentId ); const thread = await this.getThreadComments(options.threadId); panel.setThread(thread); }, onResolve: async () => { await this.resolveThread(options.threadId); const thread = await this.getThreadComments(options.threadId); panel.setThread(thread); } }); panel.initialize(containerId); return panel; } // Branch locking operations async acquireLock(branchName, duration = 48, reason = "") { try { const response = await fetch('/vcs/branches/lock/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ branch: branchName, duration: duration, reason: reason }) }); if (!response.ok) { throw new Error('Failed to acquire lock'); } const result = await response.json(); if (result.success) { this.refreshLockStatus(branchName); return true; } return false; } catch (error) { this.showError('Error acquiring lock: ' + error.message); return false; } } async releaseLock(branchName, force = false) { try { const response = await fetch('/vcs/branches/unlock/', { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-CSRFToken': this.getCsrfToken() }, body: JSON.stringify({ branch: branchName, force: force }) }); if (!response.ok) { throw new Error('Failed to release lock'); } const result = await response.json(); if (result.success) { this.refreshLockStatus(branchName); return true; } return false; } catch (error) { this.showError('Error releasing lock: ' + error.message); return false; } } async getLockStatus(branchName) { try { const response = await fetch(`/vcs/branches/${encodeURIComponent(branchName)}/lock-status/`); if (!response.ok) { throw new Error('Failed to get lock status'); } return await response.json(); } catch (error) { this.showError('Error getting lock status: ' + error.message); return null; } } async getLockHistory(branchName, limit = 10) { try { const response = await fetch( `/vcs/branches/${encodeURIComponent(branchName)}/lock-history/?limit=${limit}` ); if (!response.ok) { throw new Error('Failed to get lock history'); } return await response.json(); } catch (error) { this.showError('Error getting lock history: ' + error.message); return []; } } async refreshLockStatus(branchName) { const lockStatus = await this.getLockStatus(branchName); if (!lockStatus) return; const statusElement = document.querySelector(`[data-branch="${branchName}"] .lock-status`); if (!statusElement) return; if (lockStatus.locked) { const expiryDate = new Date(lockStatus.expires); statusElement.className = 'lock-status locked'; statusElement.innerHTML = `
${message}