...
This commit is contained in:
@@ -236,50 +236,39 @@ repo.remove_changes()!
|
||||
repo.update_submodules()!
|
||||
```
|
||||
|
||||
## Repository Configuration
|
||||
## Repository Configuration & Status
|
||||
|
||||
### GitRepo Structure
|
||||
The `gittools` module uses an imperative model. The `GitRepo` struct holds the *current* status of a repository in a unified `GitStatus` object. To change the state, you call explicit functions like `repo.branch_switch('my-feature')`.
|
||||
|
||||
### GitRepo and GitStatus Structure
|
||||
|
||||
```v
|
||||
// GitRepo represents a single git repository.
|
||||
pub struct GitRepo {
|
||||
pub mut:
|
||||
provider string // e.g., github.com
|
||||
account string // Git account name
|
||||
name string // Repository name
|
||||
status_remote GitRepoStatusRemote // Remote repository status
|
||||
status_local GitRepoStatusLocal // Local repository status
|
||||
status_wanted GitRepoStatusWanted // Desired status
|
||||
config GitRepoConfig // Repository configuration
|
||||
deploysshkey string // SSH key for git operations
|
||||
}
|
||||
```
|
||||
|
||||
### Status Tracking
|
||||
|
||||
```v
|
||||
// Remote Status
|
||||
pub struct GitRepoStatusRemote {
|
||||
pub mut:
|
||||
ref_default string // Default branch hash
|
||||
branches map[string]string // Branch name -> commit hash
|
||||
tags map[string]string // Tag name -> commit hash
|
||||
provider string
|
||||
account string
|
||||
name string
|
||||
config GitRepoConfig
|
||||
status GitStatus // Unified struct holding the CURRENT repo status.
|
||||
}
|
||||
|
||||
// Local Status
|
||||
pub struct GitRepoStatusLocal {
|
||||
// GitStatus holds all live status information for a repository.
|
||||
pub struct GitStatus {
|
||||
pub mut:
|
||||
branches map[string]string // Branch name -> commit hash
|
||||
branch string // Current branch
|
||||
tag string // Current tag
|
||||
}
|
||||
|
||||
// Desired Status
|
||||
pub struct GitRepoStatusWanted {
|
||||
pub mut:
|
||||
branch string
|
||||
tag string
|
||||
url string // Remote repository URL
|
||||
readonly bool // Prevent push/commit operations
|
||||
// State from local and remote (`git fetch`)
|
||||
branches map[string]string // branch name -> commit hash
|
||||
tags map[string]string // tag name -> commit hash
|
||||
|
||||
// Current local state
|
||||
branch string // The current checked-out branch
|
||||
tag string // The current checked-out tag (if any)
|
||||
ahead int // Commits ahead of remote
|
||||
behind int // Commits behind remote
|
||||
|
||||
// Overall status
|
||||
has_changes bool // True if there are uncommitted local changes
|
||||
error string // Error message if any status update fails
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -85,23 +85,37 @@ site:key → config JSON
|
||||
site:key:repos:<provider:acct:name> → GitRepo JSON
|
||||
```
|
||||
|
||||
### 4.2 GitRepo (excerpt)
|
||||
### 4.2 GitRepo & GitStatus
|
||||
|
||||
The state of a repository is captured in a single, unified `GitStatus` struct. This struct represents the *current* state of the repository after a `status_update()` operation and does not contain any "desired" or "wanted" state. State changes are performed through imperative function calls (e.g., `repo.branch_switch('main')`).
|
||||
|
||||
```v
|
||||
pub struct GitRepo {
|
||||
provider string // e.g. github
|
||||
account string // org/user
|
||||
name string // repo name
|
||||
status_remote GitRepoStatusRemote
|
||||
status_local GitRepoStatusLocal
|
||||
status_wanted GitRepoStatusWanted
|
||||
last_load int // epoch
|
||||
has_changes bool
|
||||
provider string
|
||||
account string
|
||||
name string
|
||||
config GitRepoConfig
|
||||
status GitStatus // Unified CURRENT status object
|
||||
}
|
||||
|
||||
pub struct GitStatus {
|
||||
pub mut:
|
||||
// Combined local & remote state from `git fetch`
|
||||
branches map[string]string // branch name -> commit hash
|
||||
tags map[string]string // tag name -> commit hash
|
||||
|
||||
// Current local state
|
||||
branch string // current checked-out branch
|
||||
tag string // current checked-out tag (if any)
|
||||
ahead int // commits ahead of remote
|
||||
behind int // commits behind remote
|
||||
|
||||
// Overall status
|
||||
has_changes bool // true if uncommitted changes exist
|
||||
error string // holds error messages from status updates
|
||||
}
|
||||
```
|
||||
|
||||
Status structs separate **remote**, **local** and **desired** state, enabling `need_*` predicates to remain trivial.
|
||||
|
||||
---
|
||||
|
||||
## 5. Execution & Behavioural Notes
|
||||
|
||||
@@ -1,254 +1,260 @@
|
||||
// lib/develop/gittools/repository.v
|
||||
module gittools
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import freeflowuniverse.herolib.osal.core as osal
|
||||
import os
|
||||
import time
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
import freeflowuniverse.herolib.develop.vscode
|
||||
import freeflowuniverse.herolib.develop.sourcetree
|
||||
import freeflowuniverse.herolib.osal.sshagent
|
||||
|
||||
// Commit the staged changes with the provided commit message.
|
||||
// GitStatus holds the unified status information for a repository.
|
||||
// It reflects the CURRENT state, not a desired state.
|
||||
pub struct GitStatus {
|
||||
pub mut:
|
||||
// Combined local & remote state (from fetch)
|
||||
branches map[string]string // All branch names -> commit hash
|
||||
tags map[string]string // All tag names -> commit hash
|
||||
|
||||
// Current local state
|
||||
branch string // The current checked-out branch.
|
||||
tag string // The current checked-out tag (if any).
|
||||
ahead int // Commits ahead of remote.
|
||||
behind int // Commits behind remote.
|
||||
|
||||
// Combined status
|
||||
has_changes bool // True if there are uncommitted local changes.
|
||||
error string // Error message if any status update fails.
|
||||
}
|
||||
|
||||
pub struct GitRepoConfig {
|
||||
pub mut:
|
||||
remote_check_period int = 300 // seconds, 5 min
|
||||
}
|
||||
|
||||
// GitRepo represents a single git repository.
|
||||
@[heap]
|
||||
pub struct GitRepo {
|
||||
// a git repo is always part of a git structure
|
||||
mut:
|
||||
gs &GitStructure
|
||||
last_load int // epoch when last loaded
|
||||
pub mut:
|
||||
provider string // e.g., github.com
|
||||
account string // Git account name
|
||||
name string // Repository name
|
||||
deploysshkey string // SSH key for git operations
|
||||
config GitRepoConfig
|
||||
status GitStatus
|
||||
}
|
||||
|
||||
// commit stages all changes and commits them with the provided message.
|
||||
pub fn (mut repo GitRepo) commit(msg string) ! {
|
||||
repo.status_update()!
|
||||
if !repo.need_commit()! {
|
||||
console.print_debug('No changes to commit.')
|
||||
console.print_debug('No changes to commit for ${repo.path()}.')
|
||||
return
|
||||
}
|
||||
|
||||
if msg == '' {
|
||||
return error('Commit message is empty.')
|
||||
return error('Commit message cannot be empty.')
|
||||
}
|
||||
repo_path := repo.path()
|
||||
repo.exec('git add . -A') or { return error('Cannot add to repo: ${repo_path}. Error: ${err}') }
|
||||
repo.exec('git add . -A')!
|
||||
repo.exec('git commit -m "${msg}"') or {
|
||||
return error('Cannot commit repo: ${repo_path}. Error: ${err}')
|
||||
// A common case for failure is when changes are only whitespace changes and git is configured to ignore them.
|
||||
console.print_debug('Could not commit in ${repo.path()}. Maybe nothing to commit? Error: ${err}')
|
||||
return
|
||||
}
|
||||
console.print_green('Changes committed successfully.')
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
console.print_green("Committed changes in '${repo.path()}' with message: '${msg}'.")
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
|
||||
// Push local changes to the remote repository.
|
||||
// push local changes to the remote repository.
|
||||
pub fn (mut repo GitRepo) push() ! {
|
||||
repo.status_update()!
|
||||
if repo.need_push_or_pull()! {
|
||||
url := repo.get_repo_url_for_clone()!
|
||||
console.print_header('Pushing changes to ${url}')
|
||||
// We may need to push the locally created branches
|
||||
repo.exec('git push --set-upstream origin ${repo.status_local.branch}')!
|
||||
console.print_green('Changes pushed successfully.')
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
} else {
|
||||
console.print_header('Everything is up to date.')
|
||||
if !repo.need_push()! {
|
||||
console.print_header('Nothing to push for ${repo.path()}. Already up-to-date.')
|
||||
return
|
||||
}
|
||||
|
||||
url := repo.get_repo_url_for_clone()!
|
||||
console.print_header('Pushing changes to ${url}')
|
||||
// This will push the current branch to its upstream counterpart.
|
||||
// --set-upstream is useful for new branches.
|
||||
repo.exec('git push --set-upstream origin ${repo.status.branch}')!
|
||||
console.print_green('Changes pushed successfully.')
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
|
||||
@[params]
|
||||
pub struct PullCheckoutArgs {
|
||||
pub struct PullArgs {
|
||||
pub mut:
|
||||
submodules bool // if we want to pull for submodules
|
||||
reset bool // if true, will reset local changes before pulling
|
||||
}
|
||||
|
||||
// Pull remote content into the repository.
|
||||
pub fn (mut repo GitRepo) pull(args_ PullCheckoutArgs) ! {
|
||||
// pull remote content into the repository.
|
||||
pub fn (mut repo GitRepo) pull(args PullArgs) ! {
|
||||
repo.status_update()!
|
||||
if repo.need_checkout() {
|
||||
repo.checkout()!
|
||||
|
||||
if args.reset {
|
||||
repo.reset()!
|
||||
}
|
||||
|
||||
repo.exec('git pull') or { return error('Cannot pull repo: ${repo.path()}. Error: ${err}') }
|
||||
if repo.need_commit()! {
|
||||
return error('Cannot pull in ${repo.path()} due to uncommitted changes. Either commit them or use the reset:true option.')
|
||||
}
|
||||
|
||||
if args_.submodules {
|
||||
repo.exec('git pull')!
|
||||
|
||||
if args.submodules {
|
||||
repo.update_submodules()!
|
||||
}
|
||||
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
console.print_green('Changes pulled successfully.')
|
||||
}
|
||||
|
||||
// Checkout a branch in the repository.
|
||||
pub fn (mut repo GitRepo) checkout() ! {
|
||||
repo.status_update()!
|
||||
if repo.status_wanted.readonly {
|
||||
repo.reset()!
|
||||
}
|
||||
if repo.need_commit()! {
|
||||
return error('Cannot checkout branch due to uncommitted changes in ${repo.path()}.')
|
||||
}
|
||||
if repo.status_wanted.tag.len > 0 {
|
||||
repo.exec('git checkout tags/${repo.status_wanted.tag}')!
|
||||
}
|
||||
if repo.status_wanted.branch.len > 0 {
|
||||
repo.exec('git checkout ${repo.status_wanted.branch}')!
|
||||
}
|
||||
repo.cache_last_load_clear()!
|
||||
console.print_green('Changes pulled successfully from ${repo.path()}.')
|
||||
}
|
||||
|
||||
// Create a new branch in the repository.
|
||||
// branch_create creates a new branch.
|
||||
pub fn (mut repo GitRepo) branch_create(branchname string) ! {
|
||||
repo.exec('git branch -c ${branchname}') or {
|
||||
return error('Cannot Create branch: ${repo.path()} to ${branchname}\nError: ${err}')
|
||||
}
|
||||
repo.exec('git branch ${branchname}')!
|
||||
repo.cache_last_load_clear()!
|
||||
console.print_green('Branch ${branchname} created successfully.')
|
||||
console.print_green('Branch ${branchname} created successfully in ${repo.path()}.')
|
||||
}
|
||||
|
||||
// branch_switch switches to a different branch.
|
||||
pub fn (mut repo GitRepo) branch_switch(branchname string) ! {
|
||||
repo.exec('git switch ${branchname}') or {
|
||||
return error('Cannot switch branch: ${repo.path()} to ${branchname}\nError: ${err}')
|
||||
if repo.need_commit()! {
|
||||
return error('Cannot switch branch in ${repo.path()} due to uncommitted changes.')
|
||||
}
|
||||
console.print_green('Branch ${branchname} switched successfully.')
|
||||
repo.status_local.branch = branchname
|
||||
repo.status_local.tag = ''
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
repo.exec('git switch ${branchname}')!
|
||||
console.print_green('Switched to branch ${branchname} in ${repo.path()}.')
|
||||
repo.status.branch = branchname
|
||||
repo.status.tag = ''
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
|
||||
// Create a new branch in the repository.
|
||||
// tag_create creates a new tag.
|
||||
pub fn (mut repo GitRepo) tag_create(tagname string) ! {
|
||||
repo_path := repo.path()
|
||||
repo.exec('git tag ${tagname}') or {
|
||||
return error('Cannot create tag: ${repo_path}. Error: ${err}')
|
||||
}
|
||||
console.print_green('Tag ${tagname} created successfully.')
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
repo.exec('git tag ${tagname}')!
|
||||
console.print_green('Tag ${tagname} created successfully in ${repo.path()}.')
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
|
||||
// tag_switch checks out a specific tag.
|
||||
pub fn (mut repo GitRepo) tag_switch(tagname string) ! {
|
||||
repo.exec('git checkout ${tagname}') or {
|
||||
return error('Cannot switch to tag: ${tagname}. Error: ${err}')
|
||||
if repo.need_commit()! {
|
||||
return error('Cannot switch to tag in ${repo.path()} due to uncommitted changes.')
|
||||
}
|
||||
console.print_green('Tag ${tagname} activated.')
|
||||
repo.status_local.branch = ''
|
||||
repo.status_local.tag = tagname
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
repo.exec('git checkout tags/${tagname}')!
|
||||
console.print_green('Switched to tag ${tagname} in ${repo.path()}.')
|
||||
repo.status.branch = ''
|
||||
repo.status.tag = tagname
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
|
||||
// Create a new branch in the repository.
|
||||
// tag_exists checks if a tag exists in the repository.
|
||||
pub fn (mut repo GitRepo) tag_exists(tag string) !bool {
|
||||
repo.exec('git show ${tag}') or { return false }
|
||||
return true
|
||||
repo.status_update()!
|
||||
return tag in repo.status.tags
|
||||
}
|
||||
|
||||
// Deletes the Git repository
|
||||
// delete removes the repository from the filesystem and cache.
|
||||
pub fn (mut repo GitRepo) delete() ! {
|
||||
repo_path := repo.path()
|
||||
key := repo.cache_key()
|
||||
repo.cache_delete()!
|
||||
osal.rm(repo_path)!
|
||||
repo.gs.repos.delete(key) // Remove from GitStructure's repos map
|
||||
repo.gs.repos.delete(key)
|
||||
}
|
||||
|
||||
// Create GitLocation from the path within the Git repository
|
||||
pub fn (mut gs GitRepo) gitlocation_from_path(path string) !GitLocation {
|
||||
// gitlocation_from_path creates a GitLocation from a path inside this repository.
|
||||
pub fn (mut repo GitRepo) gitlocation_from_path(path string) !GitLocation {
|
||||
if path.starts_with('/') || path.starts_with('~') {
|
||||
return error('Path must be relative, cannot start with / or ~')
|
||||
}
|
||||
repo.status_update()!
|
||||
|
||||
mut git_path := gs.patho()!
|
||||
mut git_path := repo.patho()!
|
||||
repo_path := git_path.path
|
||||
abs_path := os.abs_path(path)
|
||||
|
||||
// Check if path is inside git repo
|
||||
if !abs_path.starts_with(repo_path) {
|
||||
return error('Path ${path} is not inside the git repository at ${repo_path}')
|
||||
}
|
||||
|
||||
// Get relative path in relation to root of gitrepo
|
||||
rel_path := abs_path[repo_path.len + 1..] // +1 to skip the trailing slash
|
||||
rel_path := abs_path[repo_path.len + 1..]
|
||||
if !os.exists(abs_path) {
|
||||
return error('Path does not exist inside the repository: ${abs_path}')
|
||||
}
|
||||
|
||||
mut branch_or_tag := gs.status_wanted.branch
|
||||
if gs.status_wanted.tag.len > 0 {
|
||||
branch_or_tag = gs.status_wanted.tag
|
||||
mut branch_or_tag := repo.status.branch
|
||||
if repo.status.tag != '' {
|
||||
branch_or_tag = repo.status.tag
|
||||
}
|
||||
|
||||
return GitLocation{
|
||||
provider: gs.provider
|
||||
account: gs.account
|
||||
name: gs.name
|
||||
provider: repo.provider
|
||||
account: repo.account
|
||||
name: repo.name
|
||||
branch_or_tag: branch_or_tag
|
||||
path: rel_path // relative path in relation to git repo
|
||||
path: rel_path
|
||||
}
|
||||
}
|
||||
|
||||
// Check if repo path exists and validate fields
|
||||
// init validates the repository's configuration and path.
|
||||
pub fn (mut repo GitRepo) init() ! {
|
||||
path_string := repo.path()
|
||||
if repo.provider == '' {
|
||||
return error('Provider cannot be empty')
|
||||
}
|
||||
if repo.account == '' {
|
||||
return error('Account cannot be empty')
|
||||
}
|
||||
if repo.name == '' {
|
||||
return error('Name cannot be empty')
|
||||
if repo.provider == '' || repo.account == '' || repo.name == '' {
|
||||
return error('Repo identifier (provider, account, name) cannot be empty for ${repo.path()}')
|
||||
}
|
||||
|
||||
if !os.exists(path_string) {
|
||||
return error('Path does not exist: ${path_string}')
|
||||
}
|
||||
|
||||
// Check if deploy key is set in repo config
|
||||
if repo.deploysshkey.len > 0 {
|
||||
git_config := repo.exec('git config --get core.sshCommand') or { '' }
|
||||
if !git_config.contains(repo.deploysshkey) {
|
||||
repo.set_sshkey(repo.deploysshkey)!
|
||||
}
|
||||
}
|
||||
|
||||
// Check that either tag or branch is set on wanted, but not both
|
||||
if repo.status_wanted.tag.len > 0 && repo.status_wanted.branch.len > 0 {
|
||||
return error('Cannot set both tag and branch in wanted status. Choose one or the other.')
|
||||
if !os.exists(repo.path()) {
|
||||
return error('Path does not exist: ${repo.path()}')
|
||||
}
|
||||
}
|
||||
|
||||
// Set the ssh key on the repo
|
||||
// set_sshkey configures the repository to use a specific SSH key for git operations.
|
||||
fn (mut repo GitRepo) set_sshkey(key_name string) ! {
|
||||
// will use this dir to find and set key from
|
||||
ssh_dir := os.join_path(os.home_dir(), '.ssh')
|
||||
key := osal.get_ssh_key(key_name, directory: ssh_dir) or {
|
||||
return error('SSH Key with name ${key_name} not found.')
|
||||
}
|
||||
|
||||
private_key := key.private_key_path()!
|
||||
repo.exec('git config core.sshcommand "ssh -i ~/.ssh/${private_key.path}"')!
|
||||
private_key_path := key.private_key_path()!
|
||||
repo.exec('git config core.sshCommand "ssh -i ${private_key_path}"')!
|
||||
repo.deploysshkey = key_name
|
||||
}
|
||||
|
||||
// Removes all changes from the repo; be cautious
|
||||
// remove_changes hard resets the repository to HEAD and cleans untracked files.
|
||||
pub fn (mut repo GitRepo) remove_changes() ! {
|
||||
repo.status_update()!
|
||||
if repo.has_changes {
|
||||
console.print_header('Removing changes in ${repo.path()}')
|
||||
repo.exec('git reset HEAD --hard && git clean -xfd') or {
|
||||
return error("can't remove changes on repo: ${repo.path()}.\n${err}")
|
||||
// TODO: we can do this fall back later
|
||||
// console.print_header('Could not remove changes; will re-clone ${repo.path()}')
|
||||
// mut p := repo.patho()!
|
||||
// p.delete()! // remove path, this will re-clone the full thing
|
||||
// repo.load_from_url()!
|
||||
}
|
||||
repo.cache_last_load_clear()! // MODIFIED: Invalidate cache instead of full reload.
|
||||
if repo.status.has_changes {
|
||||
console.print_header('Removing all local changes in ${repo.path()}')
|
||||
repo.exec('git reset --hard HEAD && git clean -fdx')!
|
||||
repo.cache_last_load_clear()!
|
||||
}
|
||||
}
|
||||
|
||||
// alias for remove changes
|
||||
// reset is an alias for remove_changes.
|
||||
pub fn (mut repo GitRepo) reset() ! {
|
||||
return repo.remove_changes()
|
||||
repo.remove_changes()!
|
||||
}
|
||||
|
||||
// Update submodules
|
||||
// update_submodules initializes and updates all submodules.
|
||||
fn (mut repo GitRepo) update_submodules() ! {
|
||||
repo.exec('git submodule update --init --recursive') or {
|
||||
return error('Cannot update submodules for repo: ${repo.path()}. Error: ${err}')
|
||||
}
|
||||
repo.exec('git submodule update --init --recursive')!
|
||||
}
|
||||
|
||||
// exec executes a command within the repository's directory.
|
||||
// This is the designated wrapper for all git commands for this repo.
|
||||
fn (repo GitRepo) exec(cmd_ string) !string {
|
||||
repo_path := repo.path()
|
||||
cmd := 'cd ${repo_path} && ${cmd_}'
|
||||
// console.print_debug(cmd)
|
||||
r := os.execute(cmd)
|
||||
if r.exit_code != 0 {
|
||||
return error('Repo failed to exec cmd: ${cmd}\n${r.output})')
|
||||
return error('Repo command failed:\nCMD: ${cmd}\nOUT: ${r.output})')
|
||||
}
|
||||
return r.output
|
||||
return r.output.trim_space()
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ module gittools
|
||||
|
||||
import freeflowuniverse.herolib.ui.console
|
||||
import os
|
||||
import freeflowuniverse.herolib.core.pathlib
|
||||
|
||||
@[params]
|
||||
pub struct GitCloneArgs {
|
||||
@@ -23,7 +24,20 @@ pub fn (mut gitstructure GitStructure) clone(args GitCloneArgs) !&GitRepo {
|
||||
// gitlocatin comes just from the url, not from fs of whats already there
|
||||
git_location := gitstructure.gitlocation_from_url(args.url)!
|
||||
|
||||
mut repo := gitstructure.repo_new_from_gitlocation(git_location)!
|
||||
// Initialize a new GitRepo instance
|
||||
mut repo := GitRepo{
|
||||
gs: &gitstructure
|
||||
provider: git_location.provider
|
||||
account: git_location.account
|
||||
name: git_location.name
|
||||
deploysshkey: args.sshkey // Use the sshkey from args
|
||||
config: GitRepoConfig{} // Initialize with default config
|
||||
status: GitStatus{} // Initialize with default status
|
||||
}
|
||||
|
||||
// Add the new repo to the gitstructure's repos map
|
||||
key_ := repo.cache_key()
|
||||
gitstructure.repos[key_] = &repo
|
||||
|
||||
mut repopath := repo.patho()!
|
||||
if repopath.exists() {
|
||||
@@ -56,13 +70,11 @@ pub fn (mut gitstructure GitStructure) clone(args GitCloneArgs) !&GitRepo {
|
||||
if result.exit_code != 0 {
|
||||
return error('Cannot clone the repository due to: \n${result.output}')
|
||||
}
|
||||
|
||||
repo.load()!
|
||||
if repo.need_checkout() {
|
||||
repo.checkout()!
|
||||
}
|
||||
|
||||
// The repo is now cloned. Load its initial status.
|
||||
repo.load_internal()!
|
||||
|
||||
console.print_green("The repository '${repo.name}' cloned into ${parent_dir}.")
|
||||
|
||||
return repo
|
||||
return &repo // Return the initialized repo
|
||||
}
|
||||
|
||||
@@ -1,78 +1,37 @@
|
||||
// lib/develop/gittools/repository_info.v
|
||||
module gittools
|
||||
|
||||
|
||||
// Check if there are staged changes to commit.
|
||||
// need_commit checks if there are staged or unstaged changes.
|
||||
pub fn (mut repo GitRepo) need_commit() !bool {
|
||||
// This function assumes `status_update` has already been called.
|
||||
return repo.has_changes
|
||||
repo.status_update()!
|
||||
return repo.status.has_changes
|
||||
}
|
||||
|
||||
// Check if the repository has local changes that need to be pushed to remote
|
||||
// need_push checks if the repository has local commits that need to be pushed.
|
||||
pub fn (mut repo GitRepo) need_push() !bool {
|
||||
// This function assumes `status_update` has already run to populate the status.
|
||||
|
||||
// A new local branch that doesn't exist on the remote needs to be pushed.
|
||||
if repo.status_local.branch != '' && repo.get_last_remote_commit()! == '' {
|
||||
return true
|
||||
}
|
||||
// If the local branch is ahead of its remote counterpart, it needs to be pushed.
|
||||
return repo.status_local.ahead > 0
|
||||
repo.status_update()!
|
||||
return repo.status.ahead > 0
|
||||
}
|
||||
|
||||
// Check if the repository needs to pull changes from remote
|
||||
// need_pull checks if the repository needs to pull changes from the remote.
|
||||
pub fn (mut repo GitRepo) need_pull() !bool {
|
||||
// This function assumes `status_update` has already run to populate the status.
|
||||
// If the local branch is behind its remote counterpart, it needs to be pulled.
|
||||
return repo.status_local.behind > 0
|
||||
repo.status_update()!
|
||||
return repo.status.behind > 0
|
||||
}
|
||||
|
||||
// Legacy function for backward compatibility
|
||||
// need_push_or_pull is a convenience function.
|
||||
pub fn (mut repo GitRepo) need_push_or_pull() !bool {
|
||||
// This function relies on the simplified need_push() and need_pull() checks.
|
||||
repo.status_update()!
|
||||
return repo.need_push()! || repo.need_pull()!
|
||||
}
|
||||
|
||||
// Determine if the repository needs to checkout to a different branch or tag
|
||||
fn (mut repo GitRepo) need_checkout() bool {
|
||||
if repo.status_wanted.branch.len > 0 {
|
||||
if repo.status_wanted.branch != repo.status_local.branch {
|
||||
return true
|
||||
}
|
||||
} else if repo.status_wanted.tag.len > 0 {
|
||||
if repo.status_wanted.tag != repo.status_local.tag {
|
||||
return true
|
||||
}
|
||||
}
|
||||
// it could be empty since the status_wanted are optional.
|
||||
// else{
|
||||
// panic("bug, should never be empty ${repo.status_wanted.branch}, ${repo.status_local.branch}")
|
||||
// }
|
||||
return false
|
||||
}
|
||||
|
||||
fn (mut repo GitRepo) get_remote_default_branchname() !string {
|
||||
if repo.status_remote.ref_default.len == 0 {
|
||||
return error('ref_default cannot be empty for ${repo.path()}')
|
||||
}
|
||||
|
||||
return repo.status_remote.branches[repo.status_remote.ref_default] or {
|
||||
return error("can't find ref_default in branches for ${repo.path()}")
|
||||
}
|
||||
}
|
||||
|
||||
// is always the commit for the branch as known remotely, if not known will return ""
|
||||
// get_last_remote_commit gets the commit hash for the current branch as known on the remote.
|
||||
pub fn (self GitRepo) get_last_remote_commit() !string {
|
||||
if self.status_local.branch in self.status_remote.branches {
|
||||
return self.status_remote.branches[self.status_local.branch]
|
||||
}
|
||||
|
||||
return ''
|
||||
// The branch map contains both local and remote refs, normalized by name.
|
||||
return self.status.branches[self.status.branch] or { '' }
|
||||
}
|
||||
|
||||
// get commit for branch, will return '' if local branch doesn't exist remotely
|
||||
// get_last_local_commit gets the commit hash for the current local branch.
|
||||
pub fn (self GitRepo) get_last_local_commit() !string {
|
||||
if self.status_local.branch in self.status_local.branches {
|
||||
return self.status_local.branches[self.status_local.branch]
|
||||
}
|
||||
return ''
|
||||
return self.exec('git rev-parse HEAD')!
|
||||
}
|
||||
|
||||
@@ -24,8 +24,8 @@ pub fn (mut repo GitRepo) status_update(args StatusUpdateArgs) ! {
|
||||
|| current_time - repo.last_load >= repo.config.remote_check_period {
|
||||
repo.load_internal() or {
|
||||
// Persist the error state to the cache
|
||||
if repo.status_remote.error == '' {
|
||||
repo.status_remote.error = 'Failed to load repository: ${err}'
|
||||
if repo.status.error == '' {
|
||||
repo.status.error = 'Failed to load repository: ${err}'
|
||||
}
|
||||
repo.cache_set()!
|
||||
return error('Failed to load repository ${repo.name}: ${err}')
|
||||
@@ -40,15 +40,15 @@ fn (mut repo GitRepo) load_internal() ! {
|
||||
repo.init()!
|
||||
|
||||
repo.exec('git fetch --all') or {
|
||||
repo.status_remote.error = 'Failed to fetch updates: ${err}'
|
||||
repo.status.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()!
|
||||
repo.load_tags()!
|
||||
|
||||
// Reset ahead/behind counts before recalculating
|
||||
repo.status_local.ahead = 0
|
||||
repo.status_local.behind = 0
|
||||
repo.status.ahead = 0
|
||||
repo.status.behind = 0
|
||||
|
||||
// Get ahead/behind information for the current branch
|
||||
status_res := repo.exec('git status --porcelain=v2 --branch')!
|
||||
@@ -59,10 +59,10 @@ fn (mut repo GitRepo) load_internal() ! {
|
||||
ahead_str := parts[2]
|
||||
behind_str := parts[3]
|
||||
if ahead_str.starts_with('+') {
|
||||
repo.status_local.ahead = ahead_str[1..].int()
|
||||
repo.status.ahead = ahead_str[1..].int()
|
||||
}
|
||||
if behind_str.starts_with('-') {
|
||||
repo.status_local.behind = behind_str[1..].int()
|
||||
repo.status.behind = behind_str[1..].int()
|
||||
}
|
||||
}
|
||||
break // We only need this one line
|
||||
@@ -71,13 +71,17 @@ fn (mut repo GitRepo) load_internal() ! {
|
||||
|
||||
repo.last_load = int(time.now().unix())
|
||||
|
||||
repo.has_changes = repo.detect_changes() or {
|
||||
repo.status_local.error = 'Failed to detect changes: ${err}'
|
||||
repo.status.has_changes = repo.detect_changes() or {
|
||||
repo.status.error = 'Failed to detect changes: ${err}'
|
||||
return error('Failed to detect changes in repository ${repo.name}: ${err}')
|
||||
}
|
||||
|
||||
// Persist the newly loaded state to the cache.
|
||||
repo.cache_set()!
|
||||
|
||||
println(repo)
|
||||
|
||||
$dbg;
|
||||
}
|
||||
|
||||
// Helper to load remote tags
|
||||
@@ -99,13 +103,13 @@ fn (mut repo GitRepo) load_branches() ! {
|
||||
if name.contains('_archive') {
|
||||
continue
|
||||
} else if name == 'origin' {
|
||||
repo.status_remote.ref_default = commit_hash
|
||||
// No longer storing ref_default separately, it's implied by the branch map
|
||||
} else if name.starts_with('origin') {
|
||||
name = name.all_after('origin/').trim_space()
|
||||
// Update remote tags info
|
||||
repo.status_remote.branches[name] = commit_hash
|
||||
// Update branches info
|
||||
repo.status.branches[name] = commit_hash
|
||||
} else {
|
||||
repo.status_local.branches[name] = commit_hash
|
||||
repo.status.branches[name] = commit_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -114,7 +118,7 @@ fn (mut repo GitRepo) load_branches() ! {
|
||||
return error('Failed to get current branch: ${err}')
|
||||
}.split_into_lines().filter(it.trim_space() != '')
|
||||
if mybranch.len == 1 {
|
||||
repo.status_local.branch = mybranch[0].trim_space()
|
||||
repo.status.branch = mybranch[0].trim_space()
|
||||
} else {
|
||||
return error('bug: git branch does not give branchname.\n${mybranch}')
|
||||
}
|
||||
@@ -139,7 +143,7 @@ fn (mut repo GitRepo) load_tags() ! {
|
||||
tag_name := parts[1].trim_space()
|
||||
|
||||
// Update remote tags info
|
||||
repo.status_remote.tags[tag_name] = commit_hash
|
||||
repo.status.tags[tag_name] = commit_hash
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user