From 1ebd083eb0a04a8bc9a5982966d4bc74fc914d9f Mon Sep 17 00:00:00 2001 From: ajaykontham Date: Mon, 8 Jun 2026 01:31:51 -0400 Subject: [PATCH 1/7] Updated create tag --- resources/graph.css | 106 ++++++++++++ resources/graph.js | 77 ++++++++- src/commands/tag.ts | 53 ++---- src/webviews/CreateTagDialog.ts | 281 +++++++++++++++++++++++++++++++ src/webviews/graph/GraphPanel.ts | 86 ++++++++-- 5 files changed, 549 insertions(+), 54 deletions(-) create mode 100644 src/webviews/CreateTagDialog.ts diff --git a/resources/graph.css b/resources/graph.css index 12c7cc3..efdd2b4 100644 --- a/resources/graph.css +++ b/resources/graph.css @@ -606,6 +606,112 @@ tr.cdv-row > td.cdv-cell { .context-menu-separator { height: 1px; background-color: var(--vscode-menu-separatorBackground, #454545); margin: 4px 0; } .context-menu-title { padding: 4px 16px; font-size: 11px; color: var(--vscode-descriptionForeground); cursor: default; } +/* ─── modal dialogs ────────────────────────────────────────────────────── */ +.modal-backdrop { + position: fixed; + inset: 0; + z-index: 1200; + display: grid; + place-items: center; + background: rgba(0, 0, 0, 0.42); +} +.modal-backdrop[hidden] { display: none; } +.modal { + width: min(420px, calc(100vw - 32px)); + max-height: calc(100vh - 48px); + display: flex; + flex-direction: column; + background-color: var(--vscode-editorWidget-background, #252526); + color: var(--vscode-editorWidget-foreground, var(--vscode-foreground)); + border: 1px solid var(--vscode-editorWidget-border, #454545); + border-radius: 4px; + box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); +} +.modal-header { + display: flex; + align-items: center; + gap: 8px; + padding: 12px 14px 8px; + border-bottom: 1px solid var(--vscode-editorWidget-border, #454545); +} +.modal-header h2 { + flex: 1; + margin: 0; + font-size: 14px; + font-weight: 600; +} +.modal-close { + width: 24px; + height: 24px; + border: none; + border-radius: 3px; + background: transparent; + color: var(--vscode-foreground); + cursor: pointer; + font-size: 16px; + line-height: 20px; +} +.modal-close:hover { background-color: var(--vscode-toolbar-hoverBackground, rgba(128,128,128,0.2)); } +.modal-body { + display: flex; + flex-direction: column; + gap: 10px; + padding: 12px 14px; + overflow: auto; +} +.field { + display: flex; + flex-direction: column; + gap: 4px; + font-size: 12px; +} +.field span, +.check-row span { + color: var(--vscode-descriptionForeground); +} +.field input, +.field textarea { + width: 100%; + box-sizing: border-box; + background-color: var(--vscode-input-background); + color: var(--vscode-input-foreground); + border: 1px solid var(--vscode-input-border, #555); + border-radius: 2px; + padding: 5px 7px; + font-family: var(--vscode-font-family); + font-size: 12px; +} +.field textarea { + resize: vertical; + min-height: 72px; +} +.field input[readonly], +.field textarea:disabled { + opacity: 0.72; +} +.check-row { + display: flex; + align-items: center; + gap: 8px; + min-height: 22px; + font-size: 12px; +} +.check-row input { margin: 0; } +.modal-footer { + display: flex; + justify-content: flex-end; + gap: 8px; + padding: 10px 14px 12px; + border-top: 1px solid var(--vscode-editorWidget-border, #454545); +} +.tb-btn.primary { + background-color: var(--vscode-button-background); + color: var(--vscode-button-foreground); +} +.tb-btn.primary:hover { + background-color: var(--vscode-button-hoverBackground); +} + /* ─── loading / empty ──────────────────────────────────────────────────── */ #loading, #empty-state { position: absolute; diff --git a/resources/graph.js b/resources/graph.js index aac64e7..dad18b4 100644 --- a/resources/graph.js +++ b/resources/graph.js @@ -1041,7 +1041,7 @@ function showCommitMenu(x, y, commit) { { title: commit.shortSha }, { label: 'Checkout Commit…', action: () => send('checkout', sha) }, { label: 'Create Branch Here…', action: () => send('createBranch', { sha }) }, - { label: 'Create Tag Here…', action: () => send('createTag', { sha }) }, + { label: 'Create Tag Here…', action: () => openCreateTagModal(commit) }, { sep: true }, { label: 'Merge into Current Branch…', action: () => send('merge', sha) }, { label: 'Rebase Current Branch onto This…', action: () => send('rebase', sha) }, @@ -1062,6 +1062,42 @@ function showCommitMenu(x, y, commit) { ]); placeMenu(menu, x, y); } + +function openCreateTagModal(commit) { + const backdrop = document.getElementById('create-tag-modal'); + const form = document.getElementById('create-tag-form'); + const name = document.getElementById('create-tag-name'); + const sha = document.getElementById('create-tag-sha'); + const annotated = document.getElementById('create-tag-annotated'); + const signed = document.getElementById('create-tag-signed'); + const message = document.getElementById('create-tag-message'); + const force = document.getElementById('create-tag-force'); + const push = document.getElementById('create-tag-push'); + + form.dataset.sha = commit.sha; + name.value = ''; + sha.value = commit.shortSha || commit.sha.slice(0, 8); + annotated.checked = false; + signed.checked = false; + message.value = ''; + message.disabled = true; + force.checked = false; + push.checked = false; + backdrop.hidden = false; + name.focus(); +} + +function closeCreateTagModal() { + document.getElementById('create-tag-modal').hidden = true; +} + +function syncCreateTagMessageState() { + const annotated = document.getElementById('create-tag-annotated'); + const signed = document.getElementById('create-tag-signed'); + const message = document.getElementById('create-tag-message'); + message.disabled = !annotated.checked && !signed.checked; + if (message.disabled) message.value = ''; +} function showRefMenu(x, y, ref) { const send = (type, data) => vscode.postMessage({ type, data }); let items = [{ title: ref.name }]; @@ -1375,10 +1411,47 @@ function wireControls() { document.getElementById('find-next').addEventListener('click', () => findNext(1)); document.getElementById('find-close').addEventListener('click', closeFind); + const createTagModal = document.getElementById('create-tag-modal'); + const createTagForm = document.getElementById('create-tag-form'); + const createTagAnnotated = document.getElementById('create-tag-annotated'); + const createTagSigned = document.getElementById('create-tag-signed'); + createTagAnnotated.addEventListener('change', syncCreateTagMessageState); + createTagSigned.addEventListener('change', () => { + if (createTagSigned.checked) createTagAnnotated.checked = true; + syncCreateTagMessageState(); + }); + document.getElementById('create-tag-close').addEventListener('click', closeCreateTagModal); + document.getElementById('create-tag-cancel').addEventListener('click', closeCreateTagModal); + createTagModal.addEventListener('click', (e) => { + if (e.target === createTagModal) closeCreateTagModal(); + }); + createTagForm.addEventListener('submit', (e) => { + e.preventDefault(); + const name = document.getElementById('create-tag-name').value.trim(); + const message = document.getElementById('create-tag-message').value.trim(); + const signed = document.getElementById('create-tag-signed').checked; + if (!name) return; + if (signed && !message) { + document.getElementById('create-tag-message').focus(); + return; + } + send('createTag', { + sha: createTagForm.dataset.sha, + name, + message: message || undefined, + annotate: document.getElementById('create-tag-annotated').checked, + sign: signed, + force: document.getElementById('create-tag-force').checked, + push: document.getElementById('create-tag-push').checked, + }); + closeCreateTagModal(); + }); + // global keys document.addEventListener('keydown', (e) => { const typing = e.target && (e.target.tagName === 'INPUT' || e.target.tagName === 'SELECT' || e.target.tagName === 'TEXTAREA'); - if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { e.preventDefault(); openFind(); } + if (e.key === 'Escape' && !createTagModal.hidden) { e.preventDefault(); closeCreateTagModal(); } + else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'f') { e.preventDefault(); openFind(); } else if ((e.ctrlKey || e.metaKey) && e.key.toLowerCase() === 'r') { e.preventDefault(); document.getElementById('main').classList.add('loading'); send('refresh'); } else if (!typing && e.key === 'ArrowDown') { e.preventDefault(); moveSelection(1); } else if (!typing && e.key === 'ArrowUp') { e.preventDefault(); moveSelection(-1); } diff --git a/src/commands/tag.ts b/src/commands/tag.ts index c5b547b..c26b284 100644 --- a/src/commands/tag.ts +++ b/src/commands/tag.ts @@ -3,6 +3,7 @@ import { RepositoryManager } from "../git/RepositoryManager"; import { Repository } from "../git/Repository"; import { VsgitNode } from "../views/RepositoriesProvider"; import { resolveRepo, withProgress } from "./shared"; +import { showCreateTagDialog } from "../webviews/CreateTagDialog"; /** Tag operations: create (lightweight/annotated/signed), delete, checkout, push. */ export function registerTagCommands( @@ -17,15 +18,17 @@ export function registerTagCommands( if (!repo) { return; } - const name = await vscode.window.showInputBox({ prompt: "Tag name" }); + const request = await showCreateTagDialog(context.extensionUri, "HEAD"); + if (!request) { + return; + } + const name = request.name.trim(); if (!name) { return; } - - // If the tag already exists, offer a force re-tag (otherwise git would fail). + const message = request.message?.trim() || undefined; const exists = repo.tags.some((t) => t.shortName === name); - let force = false; - if (exists) { + if (exists && !request.force) { const choice = await vscode.window.showWarningMessage( `Tag '${name}' already exists. Force re-tag it to point at HEAD?`, { modal: true }, @@ -34,40 +37,10 @@ export function registerTagCommands( if (choice !== "Force Re-tag") { return; } - force = true; } - const kind = await vscode.window.showQuickPick( - ["Lightweight", "Annotated", "Signed (GPG)"], - { placeHolder: "Tag type" }, - ); - if (!kind) { - return; - } - let message: string | undefined; - if (kind !== "Lightweight") { - message = await vscode.window.showInputBox({ - prompt: "Tag message", - validateInput: (v) => - kind === "Signed (GPG)" && v.trim() === "" - ? "Signed tags need a message" - : undefined, - }); - if (message === undefined) { - return; - } - } - - // Optionally push the tag to a remote straight after creating it. - const pushChoice = await vscode.window.showQuickPick( - ["Create only", "Create and push"], - { placeHolder: "Push the tag to a remote?" }, - ); - if (!pushChoice) { - return; - } let remote: string | undefined; - if (pushChoice === "Create and push") { + if (request.push) { remote = await pickRemote(repo); if (!remote) { return; @@ -78,12 +51,12 @@ export function registerTagCommands( await repo.createTagAt( name, "HEAD", - message || undefined, - kind === "Signed (GPG)", - force, + request.sign || request.annotate ? message ?? name : undefined, + request.sign, + request.force || exists, ); if (remote) { - await repo.pushTag(remote, name, force); + await repo.pushTag(remote, name, request.force || exists); } }); }); diff --git a/src/webviews/CreateTagDialog.ts b/src/webviews/CreateTagDialog.ts new file mode 100644 index 0000000..3738d9b --- /dev/null +++ b/src/webviews/CreateTagDialog.ts @@ -0,0 +1,281 @@ +import * as vscode from "vscode"; + +export interface CreateTagDialogResult { + name: string; + message?: string; + annotate: boolean; + sign: boolean; + force: boolean; + push: boolean; +} + +export async function showCreateTagDialog( + extensionUri: vscode.Uri, + shaLabel = "HEAD", +): Promise { + const panel = vscode.window.createWebviewPanel( + "vsgit.createTag", + "Create Tag", + vscode.ViewColumn.Active, + { + enableScripts: true, + localResourceRoots: [extensionUri], + }, + ); + panel.webview.html = createTagHtml(getNonce(), panel.webview.cspSource, shaLabel); + + return new Promise((resolve) => { + let settled = false; + const finish = (result: CreateTagDialogResult | undefined) => { + if (settled) return; + settled = true; + resolve(result); + panel.dispose(); + }; + + panel.onDidDispose(() => finish(undefined)); + panel.webview.onDidReceiveMessage((message) => { + if (message.type === "cancel") { + finish(undefined); + } else if (message.type === "create") { + finish(message.data as CreateTagDialogResult); + } + }); + }); +} + +function createTagHtml(nonce: string, cspSource: string, shaLabel: string): string { + const escapedSha = escapeHtml(shaLabel); + return /* html */ ` + + + + + + + + +
+
+
+

Create Tag

+ +
+
+ + + + + + + +
+
+ + +
+
+
+ + +`; +} + +function escapeHtml(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """); +} + +function getNonce(): string { + const possible = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"; + let text = ""; + for (let i = 0; i < 32; i++) { + text += possible.charAt(Math.floor(Math.random() * possible.length)); + } + return text; +} diff --git a/src/webviews/graph/GraphPanel.ts b/src/webviews/graph/GraphPanel.ts index f9687b1..0299987 100644 --- a/src/webviews/graph/GraphPanel.ts +++ b/src/webviews/graph/GraphPanel.ts @@ -24,6 +24,17 @@ interface WebviewCommit { kind?: "commit" | "uncommitted"; } +interface CreateTagRequest { + sha: string; + name: string; + message?: string; + annotate?: boolean; + sign?: boolean; + force?: boolean; + push?: boolean; + remote?: string; +} + /** * Git Graph webview panel (vscode-git-graph style) on top of the existing git * plumbing: an icon-only action toolbar (Pull / Push / Fetch / Commit / Branch / @@ -377,7 +388,7 @@ export class GraphPanel { return; case "createTag": - await this.createTag((message.data as { sha: string }).sha); + await this.createTag(message.data as CreateTagRequest); return; case "merge": @@ -493,18 +504,26 @@ export class GraphPanel { await this.refresh(); } - private async createTag(sha: string): Promise { - const name = await vscode.window.showInputBox({ - prompt: "Enter tag name", - placeHolder: "v1.0.0", - }); + private async createTag(request: CreateTagRequest): Promise { + const name = request.name.trim(); if (!name) return; - const msg = await vscode.window.showInputBox({ - prompt: "Enter tag message (optional)", - placeHolder: "Release version 1.0.0", - }); - await this.repo.createTagAt(name, sha, msg, false); - this.notify(`Tag '${name}' created`); + let remote: string | undefined; + if (request.push) { + remote = request.remote?.trim() || (await this.pickRemote()); + if (!remote) return; + } + const message = request.message?.trim() || undefined; + await this.repo.createTagAt( + name, + request.sha, + request.sign === true || request.annotate === true ? message ?? name : undefined, + request.sign === true, + request.force === true, + ); + if (remote) { + await this.repo.pushTag(remote, name, request.force === true); + } + this.notify(request.push ? `Tag '${name}' created and pushed` : `Tag '${name}' created`); await this.refresh(); } @@ -893,6 +912,49 @@ export class GraphPanel {
+ + From 688eb7eb684698e194610a08b992f11a33f543b7 Mon Sep 17 00:00:00 2001 From: ajaykontham Date: Mon, 8 Jun 2026 01:50:21 -0400 Subject: [PATCH 2/7] Updated git changes sidebar --- resources/commit.css | 90 +++++++++++++++++++++++ resources/commit.js | 84 ++++++++++++++++++++- resources/graph.css | 7 +- src/git/Repository.test.ts | 17 +++++ src/webviews/CreateTagDialog.ts | 11 ++- src/webviews/HistoryView.ts | 32 ++++++-- src/webviews/commit/CommitViewProvider.ts | 20 ++++- src/webviews/graph/GraphPanel.ts | 2 +- src/webviews/historyHtml.ts | 29 +++++--- 9 files changed, 268 insertions(+), 24 deletions(-) diff --git a/resources/commit.css b/resources/commit.css index 09683a8..360270e 100644 --- a/resources/commit.css +++ b/resources/commit.css @@ -18,6 +18,41 @@ body { /* ─── message editor ─────────────────────────────────────────────────────── */ #message-box { padding: 8px; } +#commit-header { + display: flex; + align-items: center; + gap: 8px; + margin-bottom: 6px; +} + +#commit-title { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: 1px; +} + +#commit-title .title { + font-size: 12px; + font-weight: 600; +} + +#branch-name { + color: var(--vscode-descriptionForeground); + font-size: 11px; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +#commit-actions { + display: inline-flex; + align-items: center; + gap: 3px; + flex-shrink: 0; +} + #message { width: 100%; min-height: 56px; @@ -53,6 +88,32 @@ body { } #commit-btn:hover { background-color: var(--vscode-button-hoverBackground); } +.header-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 24px; + height: 24px; + border: 1px solid transparent; + border-radius: 3px; + background: transparent; + color: var(--vscode-icon-foreground, var(--vscode-foreground)); + cursor: pointer; + font-size: 13px; + line-height: 1; +} +.header-btn:hover { background-color: var(--vscode-toolbar-hoverBackground, rgba(128,128,128,0.2)); } +.header-btn.active { + background-color: var(--vscode-inputOption-activeBackground, rgba(14,99,156,0.35)); + border-color: var(--vscode-inputOption-activeBorder, transparent); +} +.header-icon { + display: block; + width: 16px; + height: 16px; + fill: currentColor; +} + .opt { display: inline-flex; align-items: center; @@ -120,6 +181,35 @@ body { } .file-row:hover { background-color: var(--vscode-list-hoverBackground); } +.tree-folder { + display: flex; + align-items: center; + gap: 4px; + padding: 2px 10px 2px 12px; + cursor: pointer; + user-select: none; + font-size: 13px; +} +.tree-folder:hover { background-color: var(--vscode-list-hoverBackground); } +.tree-folder .chev { + display: inline-flex; + align-items: center; + justify-content: center; + width: 14px; + height: 14px; + flex-shrink: 0; + color: var(--vscode-icon-foreground, var(--vscode-foreground)); + font-size: 10px; + transition: transform 0.1s ease; +} +.tree-folder .chev.expanded { transform: rotate(90deg); } +.tree-folder .folder-name { + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} +.tree-children.collapsed { display: none; } + .file-status { width: 14px; text-align: center; diff --git a/resources/commit.js b/resources/commit.js index 79a0577..4bb636b 100644 --- a/resources/commit.js +++ b/resources/commit.js @@ -10,6 +10,7 @@ const vscode = acquireVsCodeApi(); let state = { active: false }; +let viewMode = (vscode.getState() || {}).commitViewMode || 'tree'; const el = (id) => document.getElementById(id); const post = (type, data) => vscode.postMessage({ type, data }); @@ -23,6 +24,8 @@ function render() { } el('empty').style.display = 'none'; el('root').style.display = 'block'; + el('branch-name').textContent = state.branch || ''; + syncViewButtons(); const groups = el('groups'); groups.innerHTML = ''; @@ -64,7 +67,11 @@ function renderGroup(title, group, files) { return wrap; } - files.forEach((f) => wrap.appendChild(renderFile(f, group))); + if (viewMode === 'tree') { + wrap.appendChild(renderTree(group, files)); + } else { + files.forEach((f) => wrap.appendChild(renderFile(f, group))); + } return wrap; } @@ -77,10 +84,56 @@ function groupAction(title, glyph, action) { return b; } -function renderFile(f, group) { +function renderTree(group, files) { + const root = { dirs: new Map(), files: [] }; + files.forEach((f) => { + const parts = String(f.path || '').split('/').filter(Boolean); + let node = root; + for (let i = 0; i < parts.length - 1; i++) { + const segment = parts[i]; + if (!node.dirs.has(segment)) node.dirs.set(segment, { dirs: new Map(), files: [] }); + node = node.dirs.get(segment); + } + node.files.push({ file: f, name: parts[parts.length - 1] || f.name || f.path }); + }); + + const wrap = document.createElement('div'); + renderTreeLevel(wrap, root, group, 0); + return wrap; +} + +function renderTreeLevel(parent, node, group, depth) { + Array.from(node.dirs.keys()).sort((a, b) => a.localeCompare(b)).forEach((name) => { + const dir = node.dirs.get(name); + const folder = document.createElement('div'); + folder.className = 'tree-folder'; + folder.style.paddingLeft = (12 + depth * 14) + 'px'; + folder.innerHTML = + '' + + '' + escapeHtml(name) + ''; + + const children = document.createElement('div'); + children.className = 'tree-children'; + renderTreeLevel(children, dir, group, depth + 1); + + folder.addEventListener('click', () => { + const collapsed = children.classList.toggle('collapsed'); + folder.querySelector('.chev').classList.toggle('expanded', !collapsed); + }); + parent.appendChild(folder); + parent.appendChild(children); + }); + + node.files.sort((a, b) => a.name.localeCompare(b.name)).forEach(({ file, name }) => { + parent.appendChild(renderFile(file, group, depth, name)); + }); +} + +function renderFile(f, group, depth = 0, label = f.name) { const row = document.createElement('div'); row.className = 'file-row'; row.title = f.path; + if (viewMode === 'tree') row.style.paddingLeft = (26 + depth * 14) + 'px'; const code = f.conflicted ? 'C' : (f.state || 'modified').charAt(0).toUpperCase(); const badge = document.createElement('span'); @@ -90,10 +143,10 @@ function renderFile(f, group) { const name = document.createElement('span'); name.className = 'file-name'; - name.textContent = f.name; + name.textContent = label; row.appendChild(name); - if (f.dir) { + if (viewMode !== 'tree' && f.dir) { const dir = document.createElement('span'); dir.className = 'file-dir'; dir.textContent = f.dir; @@ -123,6 +176,27 @@ function renderFile(f, group) { return row; } +function setViewMode(mode) { + if (viewMode === mode) return; + viewMode = mode; + vscode.setState({ ...(vscode.getState() || {}), commitViewMode: mode }); + render(); +} + +function syncViewButtons() { + el('view-tree').classList.toggle('active', viewMode === 'tree'); + el('view-list').classList.toggle('active', viewMode === 'list'); +} + +function escapeHtml(value) { + return String(value == null ? '' : value).replace(/[&<>"]/g, (ch) => ({ + '&': '&', + '<': '<', + '>': '>', + '"': '"', + }[ch])); +} + function fileAction(title, glyph, handler) { const b = document.createElement('button'); b.className = 'file-action'; @@ -153,6 +227,8 @@ function wire() { } }); el('commit-btn').addEventListener('click', doCommit); + el('view-tree').addEventListener('click', () => setViewMode('tree')); + el('view-list').addEventListener('click', () => setViewMode('list')); } // ─── messages ──────────────────────────────────────────────────────────────── diff --git a/resources/graph.css b/resources/graph.css index efdd2b4..0b2fa6a 100644 --- a/resources/graph.css +++ b/resources/graph.css @@ -641,15 +641,19 @@ tr.cdv-row > td.cdv-cell { font-weight: 600; } .modal-close { + display: inline-flex; + align-items: center; + justify-content: center; width: 24px; height: 24px; + padding: 0; border: none; border-radius: 3px; background: transparent; color: var(--vscode-foreground); cursor: pointer; font-size: 16px; - line-height: 20px; + line-height: 1; } .modal-close:hover { background-color: var(--vscode-toolbar-hoverBackground, rgba(128,128,128,0.2)); } .modal-body { @@ -699,6 +703,7 @@ tr.cdv-row > td.cdv-cell { .check-row input { margin: 0; } .modal-footer { display: flex; + align-items: center; justify-content: flex-end; gap: 8px; padding: 10px 14px 12px; diff --git a/src/git/Repository.test.ts b/src/git/Repository.test.ts index b7fa21a..dfae7b0 100644 --- a/src/git/Repository.test.ts +++ b/src/git/Repository.test.ts @@ -3,6 +3,7 @@ import assert from "node:assert"; import { Repository } from "./Repository"; import { GitExecutor, GitResult, GitRunOptions } from "./GitExecutor"; import { GitError } from "./GitError"; +import { LOG_FORMAT } from "./parsers/log"; /** * These tests verify the SECURITY WIRING of Repository: that every method which @@ -541,6 +542,22 @@ test("log: rejects option-like revRange", async () => { assert.ok(git.calls[0].args.includes("main...feature")); }); +test("log: scopes file history after revision arguments", async () => { + const { repo, git } = makeRepo(); + await repo.log({ all: true, limit: 25, skip: 5, file: "src/example.ts" }); + assert.deepStrictEqual(git.calls[0].args, [ + "log", + `--format=${LOG_FORMAT}`, + "--date-order", + "--max-count=25", + "--skip=5", + "--all", + "--follow", + "--", + "src/example.ts", + ]); +}); + test("graphLog: rejects option-like branch in the branches list", async () => { const { repo, git } = makeRepo(); await assertRejectsBeforeGit( diff --git a/src/webviews/CreateTagDialog.ts b/src/webviews/CreateTagDialog.ts index 3738d9b..190777b 100644 --- a/src/webviews/CreateTagDialog.ts +++ b/src/webviews/CreateTagDialog.ts @@ -95,14 +95,19 @@ function createTagHtml(nonce: string, cspSource: string, shaLabel: string): stri font-weight: 600; } .close { + display: inline-flex; + align-items: center; + justify-content: center; width: 24px; height: 24px; + padding: 0; border: none; border-radius: 3px; background: transparent; color: var(--vscode-foreground); cursor: pointer; font-size: 16px; + line-height: 1; } .close:hover { background: var(--vscode-toolbar-hoverBackground, rgba(128,128,128,0.2)); } main { @@ -150,6 +155,10 @@ function createTagHtml(nonce: string, cspSource: string, shaLabel: string): stri border-top: 1px solid var(--vscode-editorWidget-border, #454545); } button { + display: inline-flex; + align-items: center; + justify-content: center; + min-height: 26px; font-family: inherit; font-size: 12px; padding: 5px 12px; @@ -173,7 +182,7 @@ function createTagHtml(nonce: string, cspSource: string, shaLabel: string): stri

Create Tag

- +