Skip to content

Project Overhaul#33

Merged
antgrutta merged 6 commits intomainfrom
antgrutta/update-go-client
Oct 13, 2025
Merged

Project Overhaul#33
antgrutta merged 6 commits intomainfrom
antgrutta/update-go-client

Conversation

@antgrutta
Copy link
Contributor

This pull request introduces several improvements to the project's GitHub workflow automation, issue tracking, documentation, and code ownership management. The changes help standardize development practices, automate builds and releases, and provide clear templates for issue reporting and feature requests.

Workflow and Automation Improvements

  • Added .github/workflows/build.yml to automate build steps for pushes and pull requests to main, ensuring consistent CI for Go projects.
  • Added .github/workflows/release-drafter.yml to automatically update release drafts on pushes to main, streamlining release management.
  • Updated .github/workflows/release.yml to use newer, pinned versions of actions/checkout and gh-extension-precompile, and switched to specifying Go version via go.mod for improved reproducibility.
  • Removed .github/workflows/auto-tag-and-release.yml, consolidating release automation and reducing redundancy.

Documentation and Best Practices

  • Added .github/copilot-instructions.md with comprehensive guidelines and best practices for developing GitHub CLI extensions in Go, covering architecture, error handling, testing, performance, security, and documentation standards.

Issue and Project Management

  • Added .github/ISSUE_TEMPLATE/bug_report.md and .github/ISSUE_TEMPLATE/feature_request.md to standardize bug and feature request submissions, improving triage and communication. [1] [2]
  • Added .github/ISSUE_TEMPLATE/config.yml to disable blank issues, ensuring contributors use structured templates.
  • Added/updated .github/CODEOWNERS to set default ownership for all files to @mona-actions/team-es, clarifying responsibility.

Legal and Licensing

  • Updated LICENSE to reflect copyright year as 2025.

@antgrutta antgrutta requested a review from Copilot October 10, 2025 18:29
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

This PR overhauls the project by replacing the previous gitlab.com SDK–based implementation with a custom REST client, introducing a new scanning/service layer, output formatters, CLI redesign, workflows, and comprehensive documentation/templates. Key areas include a new internal architecture (api/services/ui/models), revamped CLI flags and behavior, and CI/release workflow updates plus updated licensing and docs.

  • Replaces prior gitlab client usage with custom REST API client (internal/api) and concurrent scanner (internal/services)
  • Adds multi-format output utilities and progress reporting (internal/ui) plus new CLI command structure
  • Updates workflows and documentation, but some code/documentation mismatches exist (flags, formats, versioning)

Reviewed Changes

Copilot reviewed 28 out of 30 changed files in this pull request and generated 15 comments.

Show a summary per file
File Description
main.go Removes legacy header comment.
internal/ui/formatter.go Adds formatters (CSV/JSON/YAML) and progress reporters.
internal/services/scanner.go Adds concurrent scanning logic and project/stat conversion.
internal/models/types.go Introduces data models for repository stats and scan results.
internal/api/types.go Defines REST data structures replacing external SDK types.
internal/api/rest_client.go Implements direct REST client with counting/stat aggregation.
cmd/root.go Rebuilds CLI with new flags, execution flow, and output handling.
go.mod Updates Go version and dependencies (adds cobra/viper, yaml); introduces a likely incorrect module path.
README.md Rewrites documentation (features, usage, architecture) but diverges from implemented flags/output.
LICENSE Updates copyright year to 2025.
.github/workflows/*.yml Adds build/release-drafter and updates release workflow; removes auto-tag workflow.
.github/ISSUE_TEMPLATE/* Adds standardized issue templates and config.
.github/copilot-instructions.md Adds extensive development best practices.
.github/CODEOWNERS Sets default code owners.
Removed legacy internal/api/* SDK wrappers Cleans out old SDK-based logic now superseded.

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

go.mod Outdated
github.com/spf13/cast v1.10.0 // indirect
github.com/spf13/pflag v1.0.10 // indirect
github.com/subosito/gotenv v1.6.0 // indirect
go.yaml.in/yaml/v3 v3.0.4 // indirect
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The module path 'go.yaml.in/yaml/v3' appears to be a misspelling; the correct import path is 'gopkg.in/yaml.v3'. Update the go.mod entry and run 'go mod tidy' to remove the invalid line.

Suggested change
go.yaml.in/yaml/v3 v3.0.4 // indirect

Copilot uses AI. Check for mistakes.
go 1.23.0

toolchain go1.24.1
go 1.25.1
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The module go directive is 1.25.1, but the build workflow uses Go 1.21 (build.yml line 26). This mismatch can cause unexpected behavior if 1.25 features are used. Align the workflow to use 'go-version-file: go.mod' or downgrade the directive to a version available in CI.

Suggested change
go 1.25.1
go 1.21

Copilot uses AI. Check for mistakes.
- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
with:
go-version: 1.21
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Workflow pins Go 1.21 while go.mod specifies 1.25.1, creating an environment mismatch. Switch to 'go-version-file: go.mod' or update this to the same version to ensure consistent builds.

Suggested change
go-version: 1.21
go-version-file: go.mod

Copilot uses AI. Check for mistakes.
| `Project` | String | Project name | API: `name` |
| `Is_Empty` | Boolean | Whether repository is empty | API: `empty_repo` |
| `isFork` | Boolean | Whether project is a fork | API: `forked_from_project` |
| `isArchive` | Boolean | Whether project is archived | API: `archived` |
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Column names here diverge from struct tags (e.g., 'Project' vs 'Repo_Name', 'isArchive' vs 'IsArchive' semantics). Align documentation with actual CSV headers produced in formatter.go or adjust formatter for consistency.

Suggested change
| `isArchive` | Boolean | Whether project is archived | API: `archived` |
| `IsArchive` | Boolean | Whether project is archived | API: `archived` |

Copilot uses AI. Check for mistakes.
@antgrutta antgrutta requested a review from Copilot October 10, 2025 20:07
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 28 out of 30 changed files in this pull request and generated 20 comments.

Comments suppressed due to low confidence (1)

internal/api/rest_client.go:1

  • [nitpick] Returning raw response bodies (possibly containing sensitive details) directly in formatted error strings can leak data to logs. Consider truncating or sanitizing the body content (e.g. limit length) before embedding in errors.
package api

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
with:
go-version: 1.21
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The build workflow pins Go 1.21 while go.mod specifies 'go 1.25.1'. This mismatch can cause build or feature incompatibility issues; update to use the version from go.mod (e.g. use 'go-version-file: go.mod' or align the explicit version).

Suggested change
go-version: 1.21
go-version-file: go.mod

Copilot uses AI. Check for mistakes.
Comment on lines +69 to +96
func getCSVHeaders() []string {
return []string{
"Namespace",
"Project",
"Is_Empty",
"isFork",
"isArchive",
"Project_Size(mb)",
"LFS_Size(mb)",
"Collaborator_Count",
"Protected_Branch_Count",
"MR_Review_Count",
"Milestone_Count",
"Issue_Count",
"MR_Count",
"MR_Review_Comment_Count",
"Commit_Count",
"Issue_Comment_Count",
"Release_Count",
"Branch_Count",
"Tag_Count",
"Has_Wiki",
"Full_URL",
"Created",
"Last_Push",
"Last_Update",
}
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

CSV headers use 'Project', 'Project_Size(mb)', and 'MR_' while the model tags in internal/models/types.go define 'Repo_Name', 'Repo_Size(mb)', and 'PR_'. This inconsistency creates an unstable external CSV schema and may break downstream tooling that relies on consistent field names. Align headers and struct tags (choose one convention: MR vs PR and Project vs Repo) and update both header builder and struct tags accordingly.

Copilot uses AI. Check for mistakes.
Comment on lines +44 to +45
writer := csv.NewWriter(file)
defer writer.Flush()
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Errors occurring during csv.Writer.Flush() are silently ignored; a write error (e.g. disk full) would be lost. Capture and return writer.Error() after flushing: defer func(){ writer.Flush(); if err := writer.Error(); err != nil { /* handle */ } }() or perform an explicit check before returning.

Copilot uses AI. Check for mistakes.
Comment on lines +63 to +65
}

return nil
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Errors occurring during csv.Writer.Flush() are silently ignored; a write error (e.g. disk full) would be lost. Capture and return writer.Error() after flushing: defer func(){ writer.Flush(); if err := writer.Error(); err != nil { /* handle */ } }() or perform an explicit check before returning.

Copilot uses AI. Check for mistakes.
@antgrutta antgrutta requested a review from Copilot October 10, 2025 20:43
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 28 out of 30 changed files in this pull request and generated 6 comments.

Comments suppressed due to low confidence (1)

README.md:1

  • Documentation states MR_Review_Count uses upvotes + approved_by, but implementation (getMergeRequestReviewCount) only counts entries in approved_by (approvals). Update the description to match actual logic or adjust code to include upvotes if intended.
# GitLab Repository Statistics

Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

- uses: actions/checkout@0ad4b8fadaa221de15dcec353f45205ec38ea70b #v4.1.4
- uses: actions/setup-go@cdcb36043654635271a94b9a6d1392de5bb323a7 #v5.0.1
with:
go-version: 1.21
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

CI config sets go-version: 1.21 while go.mod requires go 1.25.1; this mismatch can cause build/tooling failures. Align the workflow Go version with the go.mod directive (or lower the directive) for consistent builds.

Suggested change
go-version: 1.21
go-version: 1.25.1

Copilot uses AI. Check for mistakes.
cmd/root.go Outdated
Comment on lines 293 to 303
result, err := scanner.ScanRepositories(ctx, scanOptions, progressReporter)
if err != nil {
return nil, fmt.Errorf("scan failed for namespace %s: %w", ns, err)
}

// Filter results by namespace
for _, stat := range result.RepositoryStats {
if ns == "" || stat.Namespace == ns {
allStats = append(allStats, stat)
}
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Namespace filtering happens after scanning all accessible projects, causing unnecessary API calls and processing for users specifying --namespace. Implement server-side filtering (e.g., resolve group ID then restrict ListProjects) to avoid full-instance scans when a namespace is provided.

See below for a potential fix:

		// Create a copy of scanOptions for each namespace to avoid side effects
		nsScanOptions := *scanOptions
		nsScanOptions.Namespace = ns

		result, err := scanner.ScanRepositories(ctx, &nsScanOptions, progressReporter)
		if err != nil {
			return nil, fmt.Errorf("scan failed for namespace %s: %w", ns, err)
		}

		allStats = append(allStats, result.RepositoryStats...)

Copilot uses AI. Check for mistakes.
Comment on lines +265 to +269
project, err := client.GetProject(ctx, repoPath)
if err != nil {
fmt.Printf("Warning: Failed to get project %s: %v\n", repoPath, err)
continue
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

repoPath (namespace/project) is passed unescaped to /projects/%v; GitLab's API expects a URL-encoded path (e.g., url.PathEscape) or numeric ID. Without encoding, the request path segments will be misinterpreted and may 404. Apply url.PathEscape(repoPath) before formatting.

Copilot uses AI. Check for mistakes.
cmd/root.go Outdated
Comment on lines 126 to 133
scanOptions := &models.ScanOptions{
GitLabURL: gitlabURL,
Token: token,
OutputFormat: output, // Already normalized to lowercase
Verbose: debug,
IncludeArchived: true,
MaxProjects: 0,
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

IncludeArchived is hard-coded to true and there is no flag to control it, making the option misleading. Either expose a CLI flag (e.g., --include-archived) or remove the field; otherwise users cannot exclude archived projects.

Copilot uses AI. Check for mistakes.
Comment on lines +98 to +103
You can set the GitLab token via environment variable:

```bash
export GITLAB_TOKEN="your-token-here"
./gh-gitlab-stats --hostname gitlab.com
```
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

README suggests GITLAB_TOKEN environment variable is supported, but the code only reads the --token/-t flag (no viper binding or env lookup for token). Either implement environment variable support (e.g., via viper.BindEnv) or remove this section to avoid confusion.

Suggested change
You can set the GitLab token via environment variable:
```bash
export GITLAB_TOKEN="your-token-here"
./gh-gitlab-stats --hostname gitlab.com
```

Copilot uses AI. Check for mistakes.
@antgrutta antgrutta requested a review from Copilot October 10, 2025 21:32
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 28 out of 30 changed files in this pull request and generated 8 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines +280 to +283
project, err := client.GetProject(ctx, repoPath)
if err != nil {
fmt.Printf("Warning: Failed to get project %s: %v\n", repoPath, err)
continue
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

repoPath contains namespace/project (with a slash), but GetProject builds /projects/%v without URL‑encoding the path; GitLab requires the path to be URL-escaped (e.g. group%2Fproject) or a numeric ID. This will fail for textual IDs. Encode with url.PathEscape(repoPath) before passing, or accept only numeric IDs.

Copilot uses AI. Check for mistakes.
params := url.Values{}
params.Set("statistics", "true")

path := fmt.Sprintf("/projects/%v", projectID)
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

Project lookup with a string identifier containing slashes (namespace/project) must be URL-encoded (GitLab expects /projects/:id where :id is URL-escaped). Wrap projectID when it's a string path: path := fmt.Sprintf("/projects/%s", url.PathEscape(projectID.(string))).

Suggested change
path := fmt.Sprintf("/projects/%v", projectID)
var path string
switch v := projectID.(type) {
case string:
path = fmt.Sprintf("/projects/%s", url.PathEscape(v))
default:
path = fmt.Sprintf("/projects/%v", v)
}

Copilot uses AI. Check for mistakes.
Comment on lines 448 to 479
func (c *RestClient) getMergeRequestReviewCount(ctx context.Context, projectID interface{}) (int, error) {
// In GitLab, reviews are tracked as "approvals" on merge requests
// We'll count MRs that have at least one approval
totalReviews := 0
mrParams := url.Values{}
mrParams.Set("scope", "all")
mrParams.Set("per_page", "100")

for page := 1; page <= 10; page++ { // Limit to first 1000 MRs
mrParams.Set("page", strconv.Itoa(page))
mrPath := fmt.Sprintf("/projects/%v/merge_requests", projectID)
mrBody, _, err := c.doRequest(ctx, "GET", mrPath, mrParams)
if err != nil {
break
}

var pageMRs []map[string]interface{}
if err := json.Unmarshal(mrBody, &pageMRs); err != nil {
break
}

if len(pageMRs) == 0 {
break
}

for _, mr := range pageMRs {
// Only count actual approvals from approved_by, not upvotes
// Upvotes are just "thumbs up" reactions, not actual code reviews
if approvers, ok := mr["approved_by"].([]interface{}); ok {
totalReviews += len(approvers)
}
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

The List Merge Requests API does not reliably include full approval details (approved_by often omitted without extra parameters or separate approvals endpoint), so this may undercount reviews. Use the MR approvals endpoint (/projects/:id/merge_requests/:iid/approvals) or explicitly request approvals-related fields to ensure accuracy; consider making this optional due to cost.

Copilot uses AI. Check for mistakes.
Comment on lines +490 to +492
func (c *RestClient) getMergeRequestCommentCount(ctx context.Context, projectID interface{}) (int, error) {
// In GitLab, MR comments are called "notes" and include both regular comments and code review comments
// We need to get notes from the merge_requests endpoint
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Fetching up to 1000 full MR records to sum user_notes_count is expensive (large JSON payloads). Consider an early exit when totalNotes surpasses a configured threshold, making this metric optional, or parallelizing requests; alternatively expose a flag to skip comment aggregation to reduce API load.

Copilot uses AI. Check for mistakes.
Comment on lines +535 to +539
for _, mr := range pageMRs {
if userNotesCount, ok := mr["user_notes_count"].(float64); ok {
totalNotes += int(userNotesCount)
}
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Fetching up to 1000 full MR records to sum user_notes_count is expensive (large JSON payloads). Consider an early exit when totalNotes surpasses a configured threshold, making this metric optional, or parallelizing requests; alternatively expose a flag to skip comment aggregation to reduce API load.

Copilot uses AI. Check for mistakes.
}

// getIssueCommentCount gets the total count of comments on issues
func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) {
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.

Copilot uses AI. Check for mistakes.
Comment on lines 550 to 557
func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) {
// Similar to MR comments, we need to fetch issues and sum up their notes
totalNotes := 0
issueParams := url.Values{}
issueParams.Set("scope", "all")
issueParams.Set("per_page", "100")

for page := 1; page <= 10; page++ { // Limit to first 1000 issues
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.

Suggested change
func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}) (int, error) {
// Similar to MR comments, we need to fetch issues and sum up their notes
totalNotes := 0
issueParams := url.Values{}
issueParams.Set("scope", "all")
issueParams.Set("per_page", "100")
for page := 1; page <= 10; page++ { // Limit to first 1000 issues
// getIssueCommentCount gets the total count of comments on issues.
// maxPages limits the number of pages to scan (each page contains up to 100 issues).
// If skipCommentMetrics is true, the function returns 0 without scanning.
func (c *RestClient) getIssueCommentCount(ctx context.Context, projectID interface{}, maxPages int, skipCommentMetrics bool) (int, error) {
if skipCommentMetrics {
return 0, nil
}
totalNotes := 0
issueParams := url.Values{}
issueParams.Set("scope", "all")
issueParams.Set("per_page", "100")
for page := 1; page <= maxPages; page++ { // Limit to maxPages * 100 issues

Copilot uses AI. Check for mistakes.
Comment on lines +574 to +577
for _, issue := range pageIssues {
if userNotesCount, ok := issue["user_notes_count"].(float64); ok {
totalNotes += int(userNotesCount)
}
Copy link

Copilot AI Oct 10, 2025

Choose a reason for hiding this comment

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

[nitpick] Enumerating up to 1000 issues (10 pages * 100) to accumulate user_notes_count can significantly increase scan time for large projects. Provide a configurable limit, batching strategy, or a flag to skip/comment metrics to mitigate latency and API quota consumption.

Copilot uses AI. Check for mistakes.
@antgrutta antgrutta requested a review from Copilot October 13, 2025 01:19
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull Request Overview

Copilot reviewed 28 out of 30 changed files in this pull request and generated 3 comments.


Tip: Customize your code reviews with copilot-instructions.md. Create the file or learn how to get started.

Comment on lines 525 to 529
path := fmt.Sprintf("/projects/%v/merge_requests", projectID)
body, _, err := c.doRequest(ctx, "GET", path, params)
if err != nil {
return 0, err
}
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

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

Project identifier is interpolated without URL-encoding; if a string path (namespace/project) is ever supplied, this endpoint will break. Use the already implemented c.encodeProjectID(projectID) as elsewhere: encodedProjectID := c.encodeProjectID(projectID); path := fmt.Sprintf("/projects/%s/merge_requests", encodedProjectID).

Copilot uses AI. Check for mistakes.
Comment on lines 300 to 378
func (c *RestClient) GetProjectStatistics(ctx context.Context, projectID interface{}) (*ProjectStatistics, error) {
// In GitLab API, statistics are included when you get a project with statistics=true
// So we'll fetch the project and return its statistics
project, err := c.GetProject(ctx, projectID)
if err != nil {
return nil, err
}

if project.Statistics == nil {
// Return empty statistics if not available
project.Statistics = &ProjectStatistics{}
}

// Get additional statistics that aren't included in the basic project response
// These require separate API calls

// Get merge request count
mrCount, err := c.getMergeRequestCount(ctx, projectID)
if err == nil {
project.Statistics.MergeRequestCount = mrCount
}

// Get branch count
branchCount, err := c.getBranchCount(ctx, projectID)
if err == nil {
project.Statistics.BranchCount = branchCount
}

// Get tag count
tagCount, err := c.getTagCount(ctx, projectID)
if err == nil {
project.Statistics.TagCount = tagCount
}

// Get member count
memberCount, err := c.getMemberCount(ctx, projectID)
if err == nil {
project.Statistics.MemberCount = memberCount
}

// Get milestone count
milestoneCount, err := c.getMilestoneCount(ctx, projectID)
if err == nil {
project.Statistics.MilestoneCount = milestoneCount
}

// Get release count
releaseCount, err := c.getReleaseCount(ctx, projectID)
if err == nil {
project.Statistics.ReleaseCount = releaseCount
}

// Check if wiki actually has pages (only if wiki is enabled in settings)
if project.WikiEnabled {
project.Statistics.HasWikiPages = c.hasWikiPages(ctx, projectID)
} else {
project.Statistics.HasWikiPages = false
}

// Get comment counts and review counts (these are more expensive operations)
// Get merge request review count
mrReviewCount, err := c.getMergeRequestReviewCount(ctx, projectID)
if err == nil {
project.Statistics.MergeRequestReviewCount = mrReviewCount
}

// Get merge request comment count
mrCommentCount, err := c.getMergeRequestCommentCount(ctx, projectID)
if err == nil {
project.Statistics.MergeRequestCommentCount = mrCommentCount
}

// Get issue comment count
issueCommentCount, err := c.getIssueCommentCount(ctx, projectID)
if err == nil {
project.Statistics.IssueCommentCount = issueCommentCount
}

return project.Statistics, nil
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

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

This method can trigger >30 HTTP requests per project (each count + up to 10 pages for reviews, MR comments, and issue comments), which will not scale and risks rate limiting. Consider batching (e.g., reuse MR/issue listings to derive multiple counts, allow a 'depth' or 'fast' mode, and short-circuit optional expensive metrics behind a flag).

Copilot uses AI. Check for mistakes.
cmd/root.go Outdated
Comment on lines 385 to 393
// truncate truncates a string to a maximum length
func truncate(s string, maxLen int) string {
if len(s) <= maxLen {
return s
}
if maxLen <= 3 {
return s[:maxLen]
}
return gitlabProjects
return s[:maxLen-3] + "..."
Copy link

Copilot AI Oct 13, 2025

Choose a reason for hiding this comment

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

Duplicate implementation of truncate (also defined in services/scanner.go lines 322-330); extract into a shared utility to avoid divergence and reduce duplication.

Copilot uses AI. Check for mistakes.
@antgrutta
Copy link
Contributor Author

Facelift and rewrite to be more in line with the output of gh-repo-stats.

@antgrutta antgrutta merged commit 3ab67ad into main Oct 13, 2025
1 check passed
@antgrutta antgrutta deleted the antgrutta/update-go-client branch October 13, 2025 02:20
@amenocal
Copy link
Contributor

@antgrutta thanks for this work, this is a huge PR so hoping everything works as expected, we should definitely add some tests though to make sure the changes introduced don't break our cli. 😅

@antgrutta
Copy link
Contributor Author

@amenocal, thanks, yes agreed, unit tests should be next on the task list. I did testing locally, the tool is going to start to be used by the MSFT Factory team and having a consistent experience with the other repo-stats tools is key as the ongoing support for this tool is going to ramp up quite soon. More to come...

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants