This commit is contained in:
2025-07-21 13:35:30 +02:00
parent 02c4229116
commit bb3dd2dbf9
8 changed files with 94 additions and 30 deletions

View File

@@ -72,6 +72,17 @@ Herolib provides a wide range of functionality:
- Cloud automation tools
- Git operations and management
### Offline Mode for Git Operations
Herolib now supports an `offline` mode for Git operations, which prevents automatic fetching from remote repositories. This can be useful in environments with limited or no internet connectivity, or when you want to avoid network calls during development or testing.
To enable offline mode:
- **Via `GitStructureConfig`**: Set the `offline` field to `true` in the `GitStructureConfig` struct.
- **Via `GitStructureArgsNew`**: When creating a new `GitStructure` instance using `gittools.new()`, set the `offline` parameter to `true`.
- **Via Environment Variable**: Set the `OFFLINE` environment variable to any value (e.g., `export OFFLINE=true`).
When offline mode is active, `git fetch --all` operations will be skipped, and a debug message "fetch skipped (offline)" will be printed.
- Documentation building
- Hero AI integration
- System management utilities

View File

@@ -192,17 +192,17 @@ pub fn path_fix(path_ string) string {
if starts_with_dot_slash {
result_components << '.'
// Skip the first component which is '.'
components = components[1..]
components = components[1..].clone()
} else if starts_with_dot_dot_slash {
result_components << '..'
// Skip the first component which is '..'
components = components[1..]
components = components[1..].clone()
} else if is_absolute {
// Keep the empty component for absolute paths
result_components << ''
// Skip the first empty component
if components.len > 0 && components[0] == '' {
components = components[1..]
components = components[1..].clone()
}
}

View File

@@ -22,6 +22,7 @@ pub mut:
ssh_key_name string // name of ssh key to be used when loading the gitstructure
ssh_key_path string
reload bool
offline bool = false
}
// Retrieve or create a new GitStructure instance with the given configuration.
@@ -37,6 +38,7 @@ pub fn new(args_ GitStructureArgsNew) !&GitStructure {
debug: args.debug
ssh_key_name: args.ssh_key_name
ssh_key_path: args.ssh_key_path
offline: args.offline
}
return get(coderoot: args.coderoot, reload: args.reload, cfg: cfg)

View File

@@ -15,6 +15,7 @@ pub mut:
debug bool = true
ssh_key_name string
ssh_key_path string
offline bool = false
}
// GitStructure holds information about repositories within a specific code root.
@@ -53,13 +54,13 @@ pub fn (mut gitstructure GitStructure) load(reload bool) ! {
redisclient.checkempty()
for _, mut repo in gitstructure.repos {
// mut myfunction := fn (mut repo GitRepo) ! {
// }
// ths << spawn myfunction(mut repo_)
repo.status_update(reload: reload) or {
msg := 'Error in git repo: ${repo.path()}\n${err}'
console.print_stderr(msg)
return error(msg)
// If status_update fails, the error is already captured within the repo object.
// We log it here and continue to process other repositories.
console.print_stderr('Error updating status for repo ${repo.path()}: ${err}')
// Ensure last_load is reset to 0 if there was an error, so it's re-checked next time.
repo.last_load = 0
repo.cache_set()! // Persist the updated last_load and error state
}
}
}

View File

@@ -1,6 +1,7 @@
module gittools
import freeflowuniverse.herolib.core.redisclient
import freeflowuniverse.herolib.ui.console
import time
// ReposGetArgs defines arguments to retrieve repositories from the git structure.
@@ -61,7 +62,13 @@ pub fn (mut gitstructure GitStructure) get_repos(args_ ReposGetArgs) ![]&GitRepo
repo.cache_last_load_clear()!
}
if args.status_update {
repo.status_update()!
repo.status_update() or {
// Log the error but continue processing other repositories
console.print_stderr('Error updating status for repo ${repo.path()}: ${err}')
// Ensure last_load is reset to 0 if there was an error, so it's re-checked next time.
repo.last_load = 0
repo.cache_set()! // Persist the updated last_load and error state
}
}
if args.reset {
repo.reset()!

View File

@@ -7,6 +7,21 @@ fn get_repo_status(gr GitRepo) !string {
mut repo := gr
mut statuses := []string{}
if repo.status_local.error.len > 0 {
mut err_msg := repo.status_local.error
if err_msg.len > 40 {
err_msg = err_msg[0..40] + '...'
}
statuses << 'ERROR (Local): ${err_msg}'
}
if repo.status_remote.error.len > 0 {
mut err_msg := repo.status_remote.error
if err_msg.len > 40 {
err_msg = err_msg[0..40] + '...'
}
statuses << 'ERROR (Remote): ${err_msg}'
}
if repo.has_changes {
statuses << 'COMMIT'
}
@@ -38,32 +53,22 @@ fn format_repo_info(repo GitRepo) ![]string {
// Print repositories based on the provided criteria, showing their statuses
pub fn (mut gitstructure GitStructure) repos_print(args ReposGetArgs) ! {
// console.print_debug('#### Overview of repositories:')
// console.print_debug('')
mut repo_data := [][]string{}
// Collect repository information based on the provided criteria
for _, repo in gitstructure.get_repos(args)! {
// repo.status_update()!
repo_data << format_repo_info(repo)!
}
// Clear the console and start printing the formatted repository information
console.clear()
console.print_lf(1)
// console.print_lf(1) // Removed to reduce newlines
// Display header with optional argument filtering information
// header := if args.str().len > 0 {
// 'Repositories: ${gitstructure.config()!.coderoot} [${args.str()}]'
// } else {
// 'Repositories: ${gitstructure.config()!.coderoot}'
// }
header := 'Repositories: ${gitstructure.config()!.coderoot}'
console.print_header(header)
console.print_lf(1) // Keep one newline after header
// Print the repository information in a formatted array
console.print_lf(1)
console.print_array(repo_data, ' ', true) // true -> aligned for better readability
console.print_lf(5)
// console.print_lf(5) // Removed to reduce newlines
}

View File

@@ -36,6 +36,7 @@ pub mut:
ref_default string // is the default branch hash
branches map[string]string // Branch name -> commit hash
tags map[string]string // Tag name -> commit hash
error string // Error message if remote status update fails
}
// GitRepoStatusLocal holds local status information for a repository.
@@ -44,6 +45,7 @@ pub mut:
branches map[string]string // Branch name -> commit hash
branch string // the current branch
tag string // If the local branch is not set, the tag may be set
error string // Error message if local status update fails
}
// GitRepoConfig holds repository-specific configuration options.

View File

@@ -7,13 +7,33 @@ import os
@[params]
pub struct StatusUpdateArgs {
reload bool
force bool // Add force flag to bypass cache when callers need it.
}
pub fn (mut repo GitRepo) status_update(args StatusUpdateArgs) ! {
// Clear previous errors
repo.status_local.error = ''
repo.status_remote.error = ''
// Check current time vs last check, if needed (check period) then load
repo.cache_get() or { return error('Failed to get cache for repo ${repo.name}: ${err}') } // Ensure we have the situation from redis
repo.init() or { return error('Failed to initialize repo ${repo.name}: ${err}') }
if 'OFFLINE' !in os.environ() {
repo.cache_get() or {
repo.status_local.error = 'Failed to get cache: ${err}'
return error('Failed to get cache for repo ${repo.name}: ${err}')
} // Ensure we have the situation from redis
repo.init() or {
repo.status_local.error = 'Failed to initialize: ${err}'
return error('Failed to initialize repo ${repo.name}: ${err}')
}
// If there's an existing error, skip loading and just return.
// This prevents repeated attempts to load a problematic repo.
if repo.status_local.error.len > 0 || repo.status_remote.error.len > 0 {
console.print_debug('Skipping load for ${repo.name} due to existing error.')
return
}
if 'OFFLINE' in os.environ() || (repo.gs.config()!.offline) {
console.print_debug('fetch skipped (offline)')
return
}
current_time := int(time.now().unix())
@@ -21,7 +41,10 @@ pub fn (mut repo GitRepo) status_update(args StatusUpdateArgs) ! {
|| current_time - repo.last_load >= repo.config.remote_check_period {
// console.print_debug('${repo.name} ${current_time}-${repo.last_load} (${current_time - repo.last_load >= repo.config.remote_check_period}): ${repo.config.remote_check_period} +++')
// if true{exit(0)}
repo.load() or { return error('Failed to load repository ${repo.name}: ${err}') }
repo.load() or {
repo.status_remote.error = 'Failed to load repository: ${err}'
return error('Failed to load repository ${repo.name}: ${err}')
}
}
}
@@ -29,27 +52,40 @@ pub fn (mut repo GitRepo) status_update(args StatusUpdateArgs) ! {
// Does not check cache, it is the callers responsibility to check cache and load accordingly.
fn (mut repo GitRepo) load() ! {
console.print_header('load ${repo.print_key()}')
repo.init() or { return error('Failed to initialize repo during load operation: ${err}') }
repo.init() or {
repo.status_local.error = 'Failed to initialize repo during load operation: ${err}'
return error('Failed to initialize repo during load operation: ${err}')
}
git_path := '${repo.path()}/.git'
if os.exists(git_path) == false {
repo.status_local.error = 'Repository not found: missing .git directory'
return error('Repository not found: ${repo.path()} is not a valid git repository (missing .git directory)')
}
repo.exec('git fetch --all') or {
repo.status_remote.error = 'Failed to fetch updates: ${err}'
return error('Failed to fetch updates for ${repo.name} at ${repo.path()}: ${err}. Please check network connection and repository access.')
}
repo.load_branches() or { return error('Failed to load branches for ${repo.name}: ${err}') }
repo.load_branches() or {
repo.status_remote.error = 'Failed to load branches: ${err}'
return error('Failed to load branches for ${repo.name}: ${err}')
}
repo.load_tags() or { return error('Failed to load tags for ${repo.name}: ${err}') }
repo.load_tags() or {
repo.status_remote.error = 'Failed to load tags: ${err}'
return error('Failed to load tags for ${repo.name}: ${err}')
}
repo.last_load = int(time.now().unix())
repo.has_changes = repo.detect_changes() or {
repo.status_local.error = 'Failed to detect changes: ${err}'
return error('Failed to detect changes in repository ${repo.name}: ${err}')
}
repo.cache_set() or {
repo.status_local.error = 'Failed to update cache: ${err}'
return error('Failed to update cache for repository ${repo.name}: ${err}')
}
}