feat(git): add token authentication and credential discovery

Add support for token-based git authentication in identity profiles.
Enable discovery and import of credentials from ~/.git-credentials file.
Introduce remote URL-based filtering for token identities in git view.
This commit is contained in:
btriapitsyn
2026-01-14 02:47:26 +02:00
parent d56eef8aa6
commit fa83c1e645
16 changed files with 704 additions and 44 deletions
+56 -2
View File
@@ -108,6 +108,7 @@ export const GitView: React.FC = () => {
const [expandedCommitHashes, setExpandedCommitHashes] = React.useState<Set<string>>(new Set());
const [commitFilesMap, setCommitFilesMap] = React.useState<Map<string, CommitFileEntry[]>>(new Map());
const [loadingCommitHashes, setLoadingCommitHashes] = React.useState<Set<string>>(new Set());
const [remoteUrl, setRemoteUrl] = React.useState<string | null>(null);
const handleCopyCommitHash = React.useCallback((hash: string) => {
navigator.clipboard
@@ -188,6 +189,15 @@ export const GitView: React.FC = () => {
loadGlobalIdentity();
}, [loadProfiles, loadGlobalIdentity]);
// Fetch remote URL for filtering token-based identities
React.useEffect(() => {
if (!currentDirectory || !git?.getRemoteUrl) {
setRemoteUrl(null);
return;
}
git.getRemoteUrl(currentDirectory).then(setRemoteUrl).catch(() => setRemoteUrl(null));
}, [currentDirectory, git]);
React.useEffect(() => {
if (currentDirectory) {
setActiveDirectory(currentDirectory);
@@ -495,11 +505,55 @@ export const GitView: React.FC = () => {
if (globalIdentity) {
unique.set(globalIdentity.id, globalIdentity);
}
// Parse repo host/path from remote URL for filtering token identities
// e.g., "git@github.com:user/repo.git" or "https://github.com/user/repo.git"
let repoHostPath: string | null = null;
if (remoteUrl) {
try {
let normalized = remoteUrl.trim();
// Handle SSH format: git@github.com:user/repo.git -> https://github.com/user/repo.git
if (normalized.startsWith('git@')) {
normalized = 'https://' + normalized.slice(4).replace(':', '/');
}
// Remove .git suffix
if (normalized.endsWith('.git')) {
normalized = normalized.slice(0, -4);
}
const url = new URL(normalized);
repoHostPath = url.hostname + url.pathname;
} catch {
// ignore parse errors
}
}
for (const profile of profiles) {
unique.set(profile.id, profile);
// SSH identities always shown
if (profile.authType !== 'token') {
unique.set(profile.id, profile);
continue;
}
// Token identities: filter by host match
const profileHost = profile.host;
if (!profileHost) {
unique.set(profile.id, profile);
continue;
}
// Host-only token (e.g., "github.com") - always show
if (!profileHost.includes('/')) {
unique.set(profile.id, profile);
continue;
}
// Repo-specific token - only show if matches current repo
if (repoHostPath && repoHostPath === profileHost) {
unique.set(profile.id, profile);
}
}
return Array.from(unique.values());
}, [profiles, globalIdentity]);
}, [profiles, globalIdentity, remoteUrl]);
const activeIdentityProfile = React.useMemo((): GitIdentityProfile | null => {
if (currentIdentity?.userName && currentIdentity?.userEmail) {