diff --git a/.drone.yml b/.drone.yml deleted file mode 100644 index a49cbcc..0000000 --- a/.drone.yml +++ /dev/null @@ -1,35 +0,0 @@ -kind: pipeline -type: docker -name: default - -steps: - # Test stage - - name: test - image: python:3-bookworm - commands: - - pip install --no-cache-dir -r requirements.txt - - pip install --no-cache-dir -e . - - python -m unittest discover cvs_proxy - - # Docker build and push stage - - name: docker-build-push - image: plugins/docker - settings: - registry: docker.gutierrezdequevedo.com - repo: docker.gutierrezdequevedo.com/ps/cvs-proxy - tags: - - latest - - ${DRONE_COMMIT_SHA:0:7} - dockerfile: Dockerfile - context: . - when: - status: - - success - depends_on: - - test - -trigger: - branch: - - "*" - event: - - push \ No newline at end of file diff --git a/Dockerfile b/Dockerfile index 7fadb96..da43266 100644 --- a/Dockerfile +++ b/Dockerfile @@ -3,25 +3,13 @@ FROM python:3-bookworm # Set working directory WORKDIR /app -# Install runtime dependencies in a single layer and clean up apt cache -RUN apt-get update && \ - apt-get install -y --no-install-recommends \ - cvs \ - rsh-client \ - cvsps && \ - apt-get clean && \ - rm -rf /var/lib/apt/lists/* +# Install runtime dependencies (minimal) +RUN apt update && apt install cvs rsh-client -# Copy requirements first for better layer caching -COPY requirements.txt . +COPY . . # Install Python dependencies RUN pip install --no-cache-dir -r requirements.txt - -# Copy the rest of the application -COPY . . - -# Install the package RUN pip install --no-cache-dir -e . # Set environment variables for configuration (with defaults) diff --git a/cvs_proxy/app.py b/cvs_proxy/app.py index 31e88f4..fdfccb0 100644 --- a/cvs_proxy/app.py +++ b/cvs_proxy/app.py @@ -108,40 +108,6 @@ def get_file_history(): except Exception as e: return jsonify({"error": str(e)}), 500 -@api_bp.route('/api/v1/patchsets', methods=['GET']) -def get_patchsets(): - """ - Get repository patchset history using cvsps - """ - if not cvs_client: - return jsonify({"error": "CVS Client not initialized"}), 500 - - try: - patchsets = cvs_client.get_patchsets() - return jsonify(patchsets) - except Exception as e: - return jsonify({"error": str(e)}), 500 - -@api_bp.route('/api/v1/patchset-diff', methods=['GET']) -def get_patchset_diff(): - """ - Get diff for a specific patchset - Required query param: patchset (patchset number) - """ - if not cvs_client: - return jsonify({"error": "CVS Client not initialized"}), 500 - - patchset = request.args.get('patchset') - - if not patchset: - return jsonify({"error": "Missing patchset parameter"}), 400 - - try: - diff = cvs_client.get_patchset_diff(patchset) - return jsonify({"diff": diff}) - except Exception as e: - return jsonify({"error": str(e)}), 500 - @api_bp.route('/api/v1/file', methods=['GET']) def get_file_content(): """ diff --git a/cvs_proxy/cvs_client.py b/cvs_proxy/cvs_client.py index aee6488..357ef36 100644 --- a/cvs_proxy/cvs_client.py +++ b/cvs_proxy/cvs_client.py @@ -84,52 +84,6 @@ class CVSClient: # Re-raise to allow caller to handle specific errors raise - def _run_cvsps_command(self, command, cwd=None): - """ - Run a cvsps command (standalone program, not a CVS subcommand) - - :param command: List of cvsps command arguments - :type command: list - :param cwd: Working directory for the command - :type cwd: str, optional - :return: Command output as string - :rtype: str - :raises subprocess.CalledProcessError: If the cvsps command fails - """ - full_command = ['cvsps'] + command - - # Debug printout of the command to be executed - print(f"DEBUG: Executing cvsps command: {' '.join(full_command)}", file=sys.stderr) - print(f"DEBUG: Working directory: {cwd or self.local_repo_path}", file=sys.stderr) - - try: - result = subprocess.run( - full_command, - capture_output=True, - text=True, - check=True, - cwd=cwd or self.local_repo_path, - env={**os.environ, 'CVSROOT': self.repo_url} - ) - - # Debug printout of stdout and stderr - if result.stdout: - print(f"DEBUG: cvsps Command STDOUT:\n{result.stdout}", file=sys.stderr) - if result.stderr: - print(f"DEBUG: cvsps Command STDERR:\n{result.stderr}", file=sys.stderr) - - return result.stdout.strip() - except subprocess.CalledProcessError as e: - # Debug printout for command failure - print(f"DEBUG: cvsps Command FAILED", file=sys.stderr) - print(f"DEBUG: Return Code: {e.returncode}", file=sys.stderr) - print(f"DEBUG: Command: {' '.join(e.cmd)}", file=sys.stderr) - print(f"DEBUG: STDOUT:\n{e.stdout}", file=sys.stderr) - print(f"DEBUG: STDERR:\n{e.stderr}", file=sys.stderr) - - # Re-raise to allow caller to handle specific errors - raise - def _checkout_repository(self): """ Checkout or update the local repository @@ -225,13 +179,12 @@ class CVSClient: :rtype: list """ try: - # Use 'cvs log' to get revision history for the file + # Use 'cvs log' instead of 'rlog' as it's more widely supported output = self._run_cvs_command(['log', file_path]) # Parse log output to extract revision details revisions = [] current_revision = {} - in_log = False for line in output.split('\n'): # Look for revision lines (format: "revision X.X") @@ -245,7 +198,6 @@ class CVSClient: if current_revision and 'revision' in current_revision: revisions.append(current_revision) current_revision = {'revision': rev_match.group(1)} - in_log = False if date_match: current_revision.update({ @@ -254,35 +206,11 @@ class CVSClient: 'state': date_match.group(3), 'lines_changed': 'N/A' # cvs log doesn't provide line counts }) - in_log = False - - # Capture log message (lines after date/author/state until next revision or separator) - if in_log: - if line.strip() == '' or re.match(r'^---', line): - in_log = False - elif not re.match(r'^(revision|date|branches):', line): - if 'log' not in current_revision: - current_revision['log'] = '' - if current_revision['log']: - current_revision['log'] += '\n' + line - else: - current_revision['log'] = line - - # Start capturing log after date line - if date_match: - in_log = True # Add the last revision if current_revision and 'revision' in current_revision: revisions.append(current_revision) - # Clean up logs - strip whitespace and take first line only - for rev in revisions: - if 'log' in rev: - rev['log'] = rev['log'].strip().split('\n')[0] - else: - rev['log'] = '' - return revisions except subprocess.CalledProcessError as e: print(f"DEBUG: Error getting file history: {e}", file=sys.stderr) @@ -307,128 +235,4 @@ class CVSClient: return self._run_cvs_command(command) except subprocess.CalledProcessError: - return f"Error retrieving content for {file_path}" - - def get_patchsets(self): - """ - Get repository patchset history using cvsps command - - Note: cvsps must be run from the module directory, not the repository root - - :return: List of patchset details - :rtype: list - """ - try: - # Determine the working directory for cvsps - # If a module is specified, use the module directory; otherwise use the repository root - if self.cvs_module: - module_path = os.path.join(self.local_repo_path, self.cvs_module) - cvsps_cwd = module_path if os.path.exists(module_path) else self.local_repo_path - else: - cvsps_cwd = self.local_repo_path - - # Use 'cvsps' to get all patch sets in the repository - # cvsps must be run from the module directory - output = self._run_cvsps_command([], cwd=cvsps_cwd) - - # Parse cvsps output to extract patchset details - patchsets = [] - current_patchset = {} - in_log = False - - for line in output.split('\n'): - # Look for PatchSet lines (format: "PatchSet XXXX") - ps_match = re.match(r'^PatchSet\s+(\d+)', line) - - # Look for Date line (format: "Date: YYYY/MM/DD HH:MM:SS") - date_match = re.match(r'^Date:\s+(\d{4}/\d{2}/\d{2}\s+\d{2}:\d{2}:\d{2})', line) - - # Look for Author line (format: "Author: NAME") - author_match = re.match(r'^Author:\s+(\S+)', line) - - # Look for Tag line (format: "Tag: TAG_NAME") - tag_match = re.match(r'^Tag:\s+(\S+)', line) - - # Look for Log line (format: "Log:") - log_match = re.match(r'^Log:', line) - - if ps_match: - # Start of a new patch set - if current_patchset and 'patchset' in current_patchset: - patchsets.append(current_patchset) - current_patchset = {'patchset': ps_match.group(1)} - in_log = False - - if date_match: - current_patchset['date'] = date_match.group(1) - - if author_match: - current_patchset['author'] = author_match.group(1) - - if tag_match: - tag_value = tag_match.group(1) - # Don't store tags that are "(none)" or "(None)" - treat them as no tag (case-insensitive) - if tag_value.lower() != '(none)': - current_patchset['tag'] = tag_value - - if log_match: - # Log section starts, capture until next PatchSet or end - current_patchset['log'] = '' - in_log = True - elif in_log: - # Capture log lines until we hit an empty line or next section - if line.strip() == '': - in_log = False - elif not re.match(r'^(PatchSet|Date|Author|Tag|Files):', line): - # Append to log if it's not a new section header - if current_patchset['log']: - current_patchset['log'] += '\n' + line - else: - current_patchset['log'] = line - - # Add the last patchset - if current_patchset and 'patchset' in current_patchset: - patchsets.append(current_patchset) - - # Ensure all patchsets have the required fields with defaults - for ps in patchsets: - if 'date' not in ps: - ps['date'] = 'N/A' - if 'author' not in ps: - ps['author'] = 'N/A' - if 'tag' not in ps: - ps['tag'] = 'N/A' - if 'log' not in ps: - ps['log'] = '' - # Clean up log - remove leading/trailing whitespace and limit to first line for compact display - ps['log'] = ps['log'].strip() - - return patchsets - except subprocess.CalledProcessError as e: - print(f"DEBUG: Error getting patchsets: {e}", file=sys.stderr) - return [] - - def get_patchset_diff(self, patchset_number): - """ - Get diff for a specific patchset using cvsps command - - :param patchset_number: Patchset number - :type patchset_number: str - :return: Diff output for the patchset - :rtype: str - """ - try: - # Determine the working directory for cvsps - # If a module is specified, use the module directory; otherwise use the repository root - if self.cvs_module: - module_path = os.path.join(self.local_repo_path, self.cvs_module) - cvsps_cwd = module_path if os.path.exists(module_path) else self.local_repo_path - else: - cvsps_cwd = self.local_repo_path - - # Use 'cvsps' with -s flag to select the patchset and -g flag to generate the diff - output = self._run_cvsps_command(['-s', patchset_number, '-g'], cwd=cvsps_cwd) - return output - except subprocess.CalledProcessError as e: - print(f"DEBUG: Error getting patchset diff: {e}", file=sys.stderr) - return f"Error retrieving diff for patchset {patchset_number}" \ No newline at end of file + return f"Error retrieving content for {file_path}" \ No newline at end of file diff --git a/cvs_proxy/test_cvs_client.py b/cvs_proxy/test_cvs_client.py index b00a10a..8ea7c72 100644 --- a/cvs_proxy/test_cvs_client.py +++ b/cvs_proxy/test_cvs_client.py @@ -143,7 +143,7 @@ class TestCVSClient(unittest.TestCase): @patch('cvs_proxy.cvs_client.subprocess.run') def test_get_file_history(self, mock_run): - """Test retrieving file history using cvs log""" + """Test retrieving file history""" mock_run.return_value = MagicMock( stdout=''' revision 1.2 @@ -160,16 +160,6 @@ date: 2023/11/19 15:30:00; author: testuser; state: Exp; lines: +10 -0 self.assertEqual(len(history), 2) self.assertIn('revision', history[0]) self.assertEqual(history[0]['revision'], '1.2') - self.assertEqual(history[0]['author'], 'testuser') - self.assertEqual(history[0]['date'], '2023/11/20 10:00:00') - self.assertEqual(history[0]['state'], 'Exp') - - # Verify the cvs log command was called correctly - mock_run.assert_called() - call_args = mock_run.call_args[0][0] - self.assertEqual(call_args[0], 'cvs') - self.assertIn('log', call_args) - self.assertIn('file.txt', call_args) @patch('cvs_proxy.cvs_client.subprocess.run') def test_get_file_content(self, mock_run): diff --git a/ui/api.js b/ui/api.js index 5346bf4..d2b8f2d 100644 --- a/ui/api.js +++ b/ui/api.js @@ -99,25 +99,6 @@ class CVSProxyAPI { return this.request(`/diff?${params.toString()}`); } - /** - * Get repository patchset history - * @returns {Promise} Array of patchset objects - */ - async getPatchsets() { - return this.request('/patchsets'); - } - - /** - * Get diff for a specific patchset - * @param {string} patchset - Patchset number - * @returns {Promise} Diff object with diff property - */ - async getPatchsetDiff(patchset) { - const params = new URLSearchParams(); - params.append('patchset', patchset); - return this.request(`/patchset-diff?${params.toString()}`); - } - /** * Check API health * @returns {Promise} Health status diff --git a/ui/app.js b/ui/app.js index b2de050..0e4334c 100644 --- a/ui/app.js +++ b/ui/app.js @@ -158,38 +158,6 @@ class CVSRepositoryBrowser { } } - /** - * Show patchsets view - */ - async showPatchsets() { - try { - ui.patchsetContent.innerHTML = '
Loading patchsets...
'; - const patchsets = await api.getPatchsets(); - ui.displayPatchsets(patchsets); - } catch (error) { - console.error('Error showing patchsets:', error); - ui.patchsetContent.innerHTML = `
Error loading patchsets: ${error.message}
`; - ui.showView(ui.patchsetView); - } - } - - /** - * Show patchset diff - * @param {string} patchset - Patchset number - */ - async showPatchsetDiff(patchset) { - try { - ui.patchsetDiffContent.innerHTML = '
Loading patchset diff...
'; - const diffResult = await api.getPatchsetDiff(patchset); - const diffText = diffResult.diff || diffResult; - ui.displayPatchsetDiff(diffText); - } catch (error) { - console.error('Error showing patchset diff:', error); - ui.patchsetDiffContent.innerHTML = `
Error loading patchset diff: ${error.message}
`; - ui.showView(ui.patchsetDiffView); - } - } - /** * Setup event listeners */ @@ -208,17 +176,6 @@ class CVSRepositoryBrowser { ui.backFromDiffBtn.addEventListener('click', () => { ui.showView(ui.fileView); }); - - // Patchset view buttons - ui.patchsetsBtn.addEventListener('click', () => this.showPatchsets()); - ui.backFromPatchsetBtn.addEventListener('click', () => { - ui.showView(ui.welcomeView); - }); - - // Patchset diff view back button - ui.backFromPatchsetDiffBtn.addEventListener('click', () => { - ui.showView(ui.patchsetView); - }); } } diff --git a/ui/index.html b/ui/index.html index bf4c01f..5ee5472 100644 --- a/ui/index.html +++ b/ui/index.html @@ -14,9 +14,6 @@

CVS Repository Browser

- Connecting...
@@ -90,28 +87,6 @@ - - - - - -
@@ -124,7 +99,6 @@
  • 📄 View file contents
  • 📋 Check revision history
  • 🔀 Compare file versions
  • -
  • 📦 View repository patchsets
  • diff --git a/ui/styles.css b/ui/styles.css index e7fb047..8d55dda 100644 --- a/ui/styles.css +++ b/ui/styles.css @@ -283,257 +283,59 @@ main { .history-item { background-color: var(--surface-color); border: 1px solid var(--border-color); - border-radius: 0.375rem; - padding: 0.625rem 0.75rem; - margin-bottom: 0.5rem; + border-radius: 0.5rem; + padding: 1rem; + margin-bottom: 1rem; cursor: pointer; transition: all 0.2s; - display: grid; - grid-template-columns: 80px 1fr 150px 120px; - gap: 0.75rem; - align-items: center; } .history-item:hover { - box-shadow: var(--shadow); + box-shadow: var(--shadow-lg); border-color: var(--primary-color); - background-color: var(--bg-color); -} - -.history-item.active { - background-color: var(--primary-color); - color: white; - border-color: var(--primary-color); -} - -.history-item.active .history-revision, -.history-item.active .history-date, -.history-item.active .history-author, -.history-item.active .history-state, -.history-item.active .history-log { - color: white; } .history-item-header { - display: contents; + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 0.5rem; } .history-revision { font-weight: 600; color: var(--primary-color); font-family: 'Monaco', 'Courier New', monospace; - font-size: 0.875rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.history-item.active .history-revision { - color: white; } .history-date { color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: right; -} - -.history-item.active .history-date { - color: rgba(255, 255, 255, 0.8); + font-size: 0.875rem; } .history-author { color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.history-item.active .history-author { - color: rgba(255, 255, 255, 0.9); + font-size: 0.875rem; + margin-bottom: 0.5rem; } .history-state { display: inline-block; - padding: 0.125rem 0.5rem; + padding: 0.25rem 0.75rem; border-radius: 0.25rem; - font-size: 0.65rem; + font-size: 0.75rem; font-weight: 600; background-color: var(--bg-color); color: var(--text-secondary); - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: 0.5rem; -} - -.history-item.active .history-state { - background-color: rgba(255, 255, 255, 0.2); - color: white; } .history-lines { color: var(--text-secondary); - font-size: 0.75rem; -} - -.history-item.active .history-lines { - color: rgba(255, 255, 255, 0.9); -} - -.history-log-section { - display: flex; - align-items: center; - gap: 0.5rem; - overflow: hidden; -} - -.history-log { - color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-style: italic; - flex: 1; -} - -.history-item.active .history-log { - color: rgba(255, 255, 255, 0.9); -} - -/* Patchset View */ -.patchset-content { - flex: 1; - overflow-y: auto; - padding: 1.5rem; -} - -.patchset-item { - background-color: var(--surface-color); - border: 1px solid var(--border-color); - border-radius: 0.375rem; - padding: 0.625rem 0.75rem; - margin-bottom: 0.5rem; - cursor: pointer; - transition: all 0.2s; - display: grid; - grid-template-columns: 100px 1fr 150px 120px; - gap: 0.75rem; - align-items: center; -} - -.patchset-item:hover { - box-shadow: var(--shadow); - border-color: var(--primary-color); - background-color: var(--bg-color); -} - -.patchset-item.active { - background-color: var(--primary-color); - color: white; - border-color: var(--primary-color); -} - -.patchset-item.active .patchset-number, -.patchset-item.active .patchset-date, -.patchset-item.active .patchset-author, -.patchset-item.active .patchset-tag, -.patchset-item.active .patchset-log { - color: white; -} - -.patchset-item-header { - display: contents; -} - -.patchset-number { - font-weight: 600; - color: var(--primary-color); - font-family: 'Monaco', 'Courier New', monospace; font-size: 0.875rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.patchset-item.active .patchset-number { - color: white; -} - -.patchset-date { - color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - text-align: right; -} - -.patchset-item.active .patchset-date { - color: rgba(255, 255, 255, 0.8); -} - -.patchset-author { - color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; -} - -.patchset-item.active .patchset-author { - color: rgba(255, 255, 255, 0.9); -} - -.patchset-tag { - display: inline-block; - padding: 0.125rem 0.5rem; - border-radius: 0.25rem; - font-size: 0.65rem; - font-weight: 600; - background-color: var(--bg-color); - color: var(--text-secondary); - text-align: center; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - margin-left: 0.5rem; -} - -.patchset-item.active .patchset-tag { - background-color: rgba(255, 255, 255, 0.2); - color: white; -} - -.patchset-log-section { - display: flex; - align-items: center; - gap: 0.5rem; - overflow: hidden; -} - -.patchset-log { - color: var(--text-secondary); - font-size: 0.75rem; - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; - font-style: italic; - flex: 1; -} - -.patchset-item.active .patchset-log { - color: rgba(255, 255, 255, 0.9); + margin-top: 0.5rem; } /* Diff View */ - .view-header { padding: 1.5rem; border-bottom: 1px solid var(--border-color); diff --git a/ui/ui.js b/ui/ui.js index 5d0c5d5..d55fe3f 100644 --- a/ui/ui.js +++ b/ui/ui.js @@ -11,41 +11,12 @@ class UIManager { this.initializeTheme(); } - /** - * Convert a date string to relative format (e.g., "2 hours ago") - * @param {string} dateStr - Date string in format "YYYY/MM/DD HH:MM:SS" - * @returns {string} Relative date string - */ - getRelativeDate(dateStr) { - try { - // Parse the date string (format: "YYYY/MM/DD HH:MM:SS") - const parts = dateStr.match(/(\d{4})\/(\d{2})\/(\d{2})\s+(\d{2}):(\d{2}):(\d{2})/); - if (!parts) return dateStr; - - const date = new Date(parts[1], parts[2] - 1, parts[3], parts[4], parts[5], parts[6]); - const now = new Date(); - const seconds = Math.floor((now - date) / 1000); - - if (seconds < 60) return 'just now'; - if (seconds < 3600) return `${Math.floor(seconds / 60)} minute${Math.floor(seconds / 60) > 1 ? 's' : ''} ago`; - if (seconds < 86400) return `${Math.floor(seconds / 3600)} hour${Math.floor(seconds / 3600) > 1 ? 's' : ''} ago`; - if (seconds < 604800) return `${Math.floor(seconds / 86400)} day${Math.floor(seconds / 86400) > 1 ? 's' : ''} ago`; - if (seconds < 2592000) return `${Math.floor(seconds / 604800)} week${Math.floor(seconds / 604800) > 1 ? 's' : ''} ago`; - if (seconds < 31536000) return `${Math.floor(seconds / 2592000)} month${Math.floor(seconds / 2592000) > 1 ? 's' : ''} ago`; - return `${Math.floor(seconds / 31536000)} year${Math.floor(seconds / 31536000) > 1 ? 's' : ''} ago`; - } catch (e) { - return dateStr; - } - } - initializeElements() { // Views this.welcomeView = document.getElementById('welcomeView'); this.fileView = document.getElementById('fileView'); this.historyView = document.getElementById('historyView'); this.diffView = document.getElementById('diffView'); - this.patchsetView = document.getElementById('patchsetView'); - this.patchsetDiffView = document.getElementById('patchsetDiffView'); // File view elements this.fileName = document.getElementById('fileName'); @@ -65,15 +36,6 @@ class UIManager { this.diffContent = document.getElementById('diffContent'); this.backFromDiffBtn = document.getElementById('backFromDiffBtn'); - // Patchset view elements - this.patchsetContent = document.getElementById('patchsetContent'); - this.backFromPatchsetBtn = document.getElementById('backFromPatchsetBtn'); - this.patchsetsBtn = document.getElementById('patchsetsBtn'); - - // Patchset diff view elements - this.patchsetDiffContent = document.getElementById('patchsetDiffContent'); - this.backFromPatchsetDiffBtn = document.getElementById('backFromPatchsetDiffBtn'); - // Tree elements this.treeContainer = document.getElementById('treeContainer'); @@ -160,7 +122,7 @@ class UIManager { * @param {HTMLElement} view - View to show */ showView(view) { - [this.welcomeView, this.fileView, this.historyView, this.diffView, this.patchsetView, this.patchsetDiffView].forEach(v => { + [this.welcomeView, this.fileView, this.historyView, this.diffView].forEach(v => { if (v) v.classList.add('hidden'); }); if (view) view.classList.remove('hidden'); @@ -240,13 +202,13 @@ class UIManager { const historyHTML = history.map(revision => `
    -
    ${revision.revision}
    -
    -
    ${revision.log ? this.escapeHtml(revision.log) : ''}
    - ${revision.state && revision.state !== 'Exp' ? `${revision.state}` : ''} +
    + ${revision.revision} + ${revision.date || 'N/A'}
    -
    ${revision.author || 'Unknown'}
    -
    ${this.getRelativeDate(revision.date) || 'N/A'}
    +
    Author: ${revision.author || 'Unknown'}
    + ${revision.state || 'Exp'} +
    ${revision.lines_changed || 'N/A'}
    `).join(''); @@ -256,10 +218,6 @@ class UIManager { // Add click handlers to history items this.historyContent.querySelectorAll('.history-item').forEach(item => { item.addEventListener('click', () => { - // Remove active class from all items - this.historyContent.querySelectorAll('.history-item').forEach(i => i.classList.remove('active')); - // Add active class to clicked item - item.classList.add('active'); const revision = item.dataset.revision; window.app.loadFileAtRevision(this.currentFile, revision); }); @@ -492,75 +450,6 @@ class UIManager { item.classList.remove('active'); }); } - - /** - * Display patchsets - * @param {Array} patchsets - Array of patchset objects - */ - displayPatchsets(patchsets) { - if (!patchsets || patchsets.length === 0) { - this.patchsetContent.innerHTML = '
    No patchsets available
    '; - this.showView(this.patchsetView); - return; - } - - const patchsetHTML = patchsets.map(ps => ` -
    -
    PatchSet #${ps.patchset}
    -
    -
    ${ps.log ? this.escapeHtml(ps.log) : ''}
    - ${ps.tag && ps.tag !== 'N/A' && ps.tag.toLowerCase() !== '(none)' ? `${ps.tag}` : ''} -
    -
    ${ps.author || 'Unknown'}
    -
    ${this.getRelativeDate(ps.date) || 'N/A'}
    -
    - `).join(''); - - this.patchsetContent.innerHTML = patchsetHTML; - this.showView(this.patchsetView); - - // Add click handlers to patchset items - this.patchsetContent.querySelectorAll('.patchset-item').forEach(item => { - item.addEventListener('click', () => { - // Remove active class from all items - this.patchsetContent.querySelectorAll('.patchset-item').forEach(i => i.classList.remove('active')); - // Add active class to clicked item - item.classList.add('active'); - const patchset = item.dataset.patchset; - window.app.showPatchsetDiff(patchset); - }); - }); - } - - /** - * Display patchset diff - * @param {string} diffText - Diff content - */ - displayPatchsetDiff(diffText) { - if (!diffText) { - this.patchsetDiffContent.innerHTML = '
    No diff available
    '; - this.showView(this.patchsetDiffView); - return; - } - - // Create code element with diff language class - const codeElement = document.createElement('code'); - codeElement.className = 'language-diff'; - codeElement.textContent = diffText; - - const preElement = document.createElement('pre'); - preElement.appendChild(codeElement); - - this.patchsetDiffContent.innerHTML = ''; - this.patchsetDiffContent.appendChild(preElement); - - // Apply syntax highlighting - if (window.hljs) { - hljs.highlightElement(codeElement); - } - - this.showView(this.patchsetDiffView); - } } // Create global UI manager instance