Skip to content
Open
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 45 additions & 1 deletion src/services/tags.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,43 @@ export function getGitEmail(): string | null {
}
}

/**
* Normalize a git remote URL to a canonical form so that SSH, HTTPS,
* and with/without `.git` suffix all produce the same identifier.
*
* Examples:
* git@github.com:user/repo.git → github.com/user/repo
* https://github.com/user/repo → github.com/user/repo
* git@gitlab.com:org/sub/repo.git → gitlab.com/org/sub/repo
*/
export function normalizeGitUrl(url: string): string {
return url
.replace(/^[a-z+]+:\/\//, "") // strip protocol (https://, git://, ssh://)
.replace(/^[^@]+@/, "") // strip user@ prefix (git@, user@)
.replace(/:(\d+)\//, "/$1/") // preserve port numbers (e.g. :8080/)

This comment was marked as outdated.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Evaluated and this is not a real issue. The port number is part of the server identity — if a server runs on port 2222, all clones will include :2222 and they normalize consistently to gitlab.local/2222/user/repo.

The only failure case would be one machine using ssh://git@gitlab.local:22/user/repo.git (explicitly specifying the default port) while another uses ssh://git@gitlab.local/user/repo.git. This is vanishingly rare — no standard git tooling generates URLs with the default port.

Stripping port numbers would actually be worse: it would incorrectly unify repos from different servers on the same host but different ports (e.g., a staging and production GitLab on the same machine).

.replace(":", "/") // SSH colon to slash (github.com:user → github.com/user)
.replace(/\.git$/, "") // strip trailing .git
.replace(/\/+$/, ""); // strip trailing slashes
}

/**
* Get the git remote URL for the given directory.
* This provides a stable, cross-machine identifier for projects.
* Returns null if not in a git repo or no remote configured.
*/
export function getGitRemoteUrl(directory: string): string | null {
try {
const remoteUrl = execSync("git config --get remote.origin.url", {
cwd: directory,
encoding: "utf-8",
stdio: ["pipe", "pipe", "pipe"],
}).trim();
return remoteUrl || null;
} catch {
return null;
}
}

export function getUserTag(): string {
// If userContainerTag is explicitly set, use it
if (CONFIG.userContainerTag) {
Expand All @@ -36,7 +73,14 @@ export function getProjectTag(directory: string): string {
return CONFIG.projectContainerTag;
}

// Otherwise, auto-generate based on containerTagPrefix
// Try to use git remote URL as a stable cross-machine project identifier
// This allows the same project on different machines to share memories
const remoteUrl = getGitRemoteUrl(directory);
if (remoteUrl) {
return `${CONFIG.containerTagPrefix}_project_${sha256(normalizeGitUrl(remoteUrl))}`;
}
Comment on lines +78 to +81

This comment was marked as outdated.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Valid finding. Existing users have memories stored under sha256(directory). Switching to sha256(normalizeGitUrl(remoteUrl)) orphans those memories on upgrade — not just when adding a remote after creation.

Fixed in a88b0bd: all read operations (search, list, compaction context fetch) now query both the canonical remote-based tag and the legacy directory-based tag, deduplicating by memory ID. Writes go only to the new tag, so memories gradually migrate forward.


// Fall back to directory path hash (machine-specific)
return `${CONFIG.containerTagPrefix}_project_${sha256(directory)}`;
}

Expand Down