Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
87 changes: 78 additions & 9 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,25 @@ func main() {
root = "."
}

// Handle GitHub URLs - clone to temp dir (but prefer local paths if they exist)
var tempDir string
var remoteURL, repoName string
_, localPathErr := os.Stat(root)
if isGitHubURL(root) && localPathErr != nil {
// Only clone if it looks like a URL AND doesn't exist locally
// This preserves ~/go/src/github.com/user/repo style paths
remoteURL = root
repoName = extractRepoName(root)
var err error
tempDir, err = cloneRepo(root)
if err != nil {
fmt.Fprintf(os.Stderr, "Error cloning repo: %v\n", err)
os.Exit(1)
}
defer os.RemoveAll(tempDir)
root = tempDir
}

absRoot, err := filepath.Abs(root)
if err != nil {
fmt.Fprintf(os.Stderr, "Error getting absolute path: %v\n", err)
Expand Down Expand Up @@ -204,15 +223,17 @@ func main() {
}

project := scanner.Project{
Root: absRoot,
Mode: mode,
Animate: *animateMode,
Files: files,
DiffRef: activeDiffRef,
Impact: impact,
Depth: *depthLimit,
Only: only,
Exclude: exclude,
Root: absRoot,
Name: repoName,
RemoteURL: remoteURL,
Mode: mode,
Animate: *animateMode,
Files: files,
DiffRef: activeDiffRef,
Impact: impact,
Depth: *depthLimit,
Only: only,
Exclude: exclude,
}

// Render or output JSON
Expand Down Expand Up @@ -440,3 +461,51 @@ func runDaemon(root string) {
daemon.Stop()
watch.RemovePID(root)
}

// isGitHubURL checks if the input looks like a GitHub repo URL
func isGitHubURL(s string) bool {
s = strings.ToLower(s)
return strings.HasPrefix(s, "github.com/") ||
strings.HasPrefix(s, "https://github.com/") ||
strings.HasPrefix(s, "http://github.com/") ||
strings.HasPrefix(s, "gitlab.com/") ||
strings.HasPrefix(s, "https://gitlab.com/")
}

// cloneRepo clones a git repo to a temp directory (shallow clone)
func cloneRepo(url string) (string, error) {
// Normalize URL
if !strings.HasPrefix(url, "https://") && !strings.HasPrefix(url, "http://") {
url = "https://" + url
}

// Create temp dir
tempDir, err := os.MkdirTemp("", "codemap-")
if err != nil {
return "", fmt.Errorf("failed to create temp dir: %w", err)
}

// Shallow clone (quiet)
cmd := exec.Command("git", "clone", "--depth", "1", "--single-branch", "-q", url, tempDir)
if err := cmd.Run(); err != nil {
os.RemoveAll(tempDir)
return "", fmt.Errorf("git clone failed: %w", err)
}

return tempDir, nil
}

// extractRepoName extracts "owner/repo" from a GitHub URL
func extractRepoName(url string) string {
// Remove protocol
url = strings.TrimPrefix(url, "https://")
url = strings.TrimPrefix(url, "http://")
// Remove host
url = strings.TrimPrefix(url, "github.com/")
url = strings.TrimPrefix(url, "gitlab.com/")
// Remove trailing .git
url = strings.TrimSuffix(url, ".git")
// Remove trailing slashes
url = strings.TrimSuffix(url, "/")
return url
}
5 changes: 4 additions & 1 deletion render/skyline.go
Original file line number Diff line number Diff line change
Expand Up @@ -209,7 +209,10 @@ func createBuildings(sorted []extAgg, width int) []building {
// Skyline renders the city skyline visualization
func Skyline(project scanner.Project, animate bool) {
files := project.Files
projectName := filepath.Base(project.Root)
projectName := project.Name
if projectName == "" {
projectName = filepath.Base(project.Root)
}

width, _, err := term.GetSize(int(os.Stdout.Fd()))
if err != nil || width <= 0 {
Expand Down
11 changes: 10 additions & 1 deletion render/tree.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,10 @@ func formatSize(size int64) string {
// Tree renders the file tree to stdout
func Tree(project scanner.Project) {
files := project.Files
projectName := filepath.Base(project.Root)
projectName := project.Name
if projectName == "" {
projectName = filepath.Base(project.Root)
}
isDiffMode := project.DiffRef != ""
maxDepth := project.Depth // 0 = unlimited

Expand Down Expand Up @@ -188,6 +191,12 @@ func Tree(project scanner.Project) {
fmt.Printf("│ %-*s │\n", innerWidth-2, extLine)
}

// Remote URL indicator
if project.RemoteURL != "" {
remoteLine := fmt.Sprintf("↳ %s", project.RemoteURL)
fmt.Printf("│ %-*s │\n", innerWidth-2, remoteLine)
}

fmt.Printf("╰%s╯\n", strings.Repeat("─", innerWidth))

// Build and render tree
Expand Down
20 changes: 11 additions & 9 deletions scanner/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,17 @@ type FileInfo struct {

// Project represents the root of the codebase for tree/skyline mode.
type Project struct {
Root string `json:"root"`
Mode string `json:"mode"`
Animate bool `json:"animate"`
Files []FileInfo `json:"files"`
DiffRef string `json:"diff_ref,omitempty"`
Impact []ImpactInfo `json:"impact,omitempty"`
Depth int `json:"depth,omitempty"` // Max tree depth (0 = unlimited)
Only []string `json:"only,omitempty"` // Extension filter (e.g., ["swift", "go"])
Exclude []string `json:"exclude,omitempty"` // Exclusion patterns (e.g., [".xcassets", "Fonts"])
Root string `json:"root"`
Name string `json:"name,omitempty"` // Display name (overrides filepath.Base(Root))
RemoteURL string `json:"remote_url,omitempty"` // Source URL if cloned from remote
Mode string `json:"mode"`
Animate bool `json:"animate"`
Files []FileInfo `json:"files"`
DiffRef string `json:"diff_ref,omitempty"`
Impact []ImpactInfo `json:"impact,omitempty"`
Depth int `json:"depth,omitempty"` // Max tree depth (0 = unlimited)
Only []string `json:"only,omitempty"` // Extension filter (e.g., ["swift", "go"])
Exclude []string `json:"exclude,omitempty"` // Exclusion patterns (e.g., [".xcassets", "Fonts"])
}

// FileAnalysis holds extracted info about a single file for deps mode.
Expand Down
Loading