Files
herolib/lib/develop/gittools/gitlocation.v
2025-01-23 14:09:20 +01:00

134 lines
3.7 KiB
V

module gittools
import freeflowuniverse.herolib.core.pathlib
// GitLocation uniquely identifies a Git repository, its online URL, and its location in the filesystem.
@[heap]
pub struct GitLocation {
pub mut:
provider string // Git provider (e.g., GitHub)
account string // Account name
name string // Repository name
branch_or_tag string // Branch name
path string // Path in the repository (not the filesystem)
anker string // Position in a file
}
//////////////////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////////////////
// Get GitLocation from a path within the Git repository
pub fn (mut gs GitStructure) gitlocation_from_path(path string) !GitLocation {
mut full_path := pathlib.get(path)
rel_path := full_path.path_relative(gs.coderoot.path)!
// Validate the relative path
mut parts := rel_path.split('/')
if parts.len < 3 {
return error("git: path is not valid, should contain provider/account/repository: '${rel_path}'")
}
// Extract provider, account, and repository name
provider := parts[0]
account := parts[1]
name := parts[2]
mut repo_path := if parts.len > 3 { parts[3..].join('/') } else { '' }
return GitLocation{
provider: provider
account: account
name: name
path: repo_path
}
}
// Get GitLocation from a URL
pub fn (mut gs GitStructure) gitlocation_from_url(url string) !GitLocation {
mut urllower := url.trim_space()
if urllower == '' {
return error('url cannot be empty')
}
// Normalize URL
urllower = normalize_url(urllower)
// Split URL into parts
mut parts := urllower.split('/')
mut anchor := ''
mut path := ''
mut branch_or_tag := ''
// Extract branch if available
for i := 0; i < parts.len; i++ {
if parts[i] == 'refs' || parts[i] == 'tree' {
if i + 1 < parts.len {
branch_or_tag = parts[i + 1]
}
if i + 2 < parts.len {
path = parts[(i + 2)..].join('/')
}
break
}
}
// Deal with path and anchor
if path.contains('#') {
parts2 := path.split('#')
if parts2.len == 2 {
path = parts2[0]
anchor = parts2[1]
} else {
return error("git: url badly formatted, more than 1 '#' in ${url}")
}
}
// Validate parts
if parts.len < 3 {
return error("git: url badly formatted, not enough parts in '${urllower}' \nparts:\n${parts}")
}
// Extract provider, account, and name
provider := if parts[0] == 'github.com' { 'github' } else { parts[0] }
account := parts[1]
name := parts[2].replace('.git', '')
return GitLocation{
provider: provider
account: account
name: name
branch_or_tag: branch_or_tag
path: path
anker: anchor
}
}
// Return a herolib path object on the filesystem pointing to the locator
pub fn (l GitLocation) patho() !pathlib.Path {
mut addrpath := pathlib.get_dir(path: '${l.provider}/${l.account}/${l.name}', create: false)!
if l.path.len > 0 {
return pathlib.get('${addrpath.path}/${l.path}')
}
return addrpath
}
// Normalize the URL for consistent parsing
fn normalize_url(url string) string {
// Remove common URL prefixes
if url.starts_with('ssh://') {
return url[6..].replace(':', '/').replace('//', '/').trim('/')
}
if url.starts_with('git@') {
return url[4..].replace(':', '/').replace('//', '/').trim('/')
}
if url.starts_with('http:/') {
return url[6..].replace(':', '/').replace('//', '/').trim('/')
}
if url.starts_with('https:/') {
return url[7..].replace(':', '/').replace('//', '/').trim('/')
}
if url.ends_with('.git') {
return url[0..url.len - 4].replace(':', '/').replace('//', '/').trim('/')
}
return url.replace(':', '/').replace('//', '/').trim('/')
}