From 45ad8bb135a8f83f60ac52701b81f1ce5adfc6fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Juan=20Jos=C3=A9=20Guti=C3=A9rrez=20de=20Quevedo=20P=C3=A9?= =?UTF-8?q?rez?= Date: Fri, 21 Nov 2025 21:26:34 +0100 Subject: [PATCH] feat: Add tag support to file history display - Modified cvs_client.py get_file_history() to parse and extract tags from CVS log - Updated ui.js displayHistory() to render tags for each revision - Added CSS styling for history tags with proper theming support - Tags are displayed inline with revision information in history view --- cvs_proxy/cvs_client.py | 29 +++++++++++++++++++++++++++++ ui/styles.css | 25 +++++++++++++++++++++++++ ui/ui.js | 1 + 3 files changed, 55 insertions(+) diff --git a/cvs_proxy/cvs_client.py b/cvs_proxy/cvs_client.py index aee6488..e45d2a0 100644 --- a/cvs_proxy/cvs_client.py +++ b/cvs_proxy/cvs_client.py @@ -232,6 +232,7 @@ class CVSClient: revisions = [] current_revision = {} in_log = False + in_tags = False for line in output.split('\n'): # Look for revision lines (format: "revision X.X") @@ -240,12 +241,19 @@ class CVSClient: # Look for date/author/state line (format: "date: YYYY/MM/DD HH:MM:SS; author: NAME; state: STATE;") date_match = re.match(r'^date:\s+(\d{4}/\d{2}/\d{2}\s+\d{2}:\d{2}:\d{2});\s+author:\s+(\S+);\s+state:\s+(\S+);', line) + # Look for branches line (format: "branches: ...") + branches_match = re.match(r'^branches:', line) + + # Look for tag lines (format: "\tTAG_NAME: X.X") + tag_match = re.match(r'^\s+(\S+):\s+(\S+)', line) + if rev_match: # Start of a new revision if current_revision and 'revision' in current_revision: revisions.append(current_revision) current_revision = {'revision': rev_match.group(1)} in_log = False + in_tags = False if date_match: current_revision.update({ @@ -255,11 +263,29 @@ class CVSClient: 'lines_changed': 'N/A' # cvs log doesn't provide line counts }) in_log = False + in_tags = False + + if branches_match: + # Branches section starts, tags follow + in_tags = True + in_log = False + continue + + # Capture tags (lines with indentation after branches line) + if in_tags and tag_match and not re.match(r'^---', line): + tag_name = tag_match.group(1) + tag_revision = tag_match.group(2) + # Only add tags that match the current revision + if tag_revision == current_revision.get('revision'): + if 'tags' not in current_revision: + current_revision['tags'] = [] + current_revision['tags'].append(tag_name) # 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 + in_tags = False elif not re.match(r'^(revision|date|branches):', line): if 'log' not in current_revision: current_revision['log'] = '' @@ -282,6 +308,9 @@ class CVSClient: rev['log'] = rev['log'].strip().split('\n')[0] else: rev['log'] = '' + # Ensure tags field exists + if 'tags' not in rev: + rev['tags'] = [] return revisions except subprocess.CalledProcessError as e: diff --git a/ui/styles.css b/ui/styles.css index e7fb047..48abefc 100644 --- a/ui/styles.css +++ b/ui/styles.css @@ -407,6 +407,31 @@ main { color: rgba(255, 255, 255, 0.9); } +.history-tags { + display: flex; + gap: 0.25rem; + flex-wrap: wrap; +} + +.history-tag { + display: inline-block; + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + font-size: 0.65rem; + font-weight: 600; + background-color: var(--primary-color); + color: white; + text-align: center; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.history-item.active .history-tag { + background-color: rgba(255, 255, 255, 0.2); + color: white; +} + /* Patchset View */ .patchset-content { flex: 1; diff --git a/ui/ui.js b/ui/ui.js index c770da4..9c21b02 100644 --- a/ui/ui.js +++ b/ui/ui.js @@ -244,6 +244,7 @@ class UIManager {
${revision.log ? this.escapeHtml(revision.log) : ''}
${revision.state && revision.state !== 'Exp' ? `${revision.state}` : ''} + ${revision.tags && revision.tags.length > 0 ? `${revision.tags.map(tag => `${this.escapeHtml(tag)}`).join('')}` : ''}
${revision.author || 'Unknown'}
${this.getRelativeDate(revision.date) || 'N/A'}