// SPDX-License-Identifier: AGPL-3.0-only // SPDX-FileContributor: Runxi Yu package main import ( "context" "errors" "io" "os" "strings" "github.com/go-git/go-git/v5" "github.com/go-git/go-git/v5/plumbing" "github.com/go-git/go-git/v5/plumbing/object" "github.com/jackc/pgx/v5/pgtype" ) // open_git_repo opens a git repository by group and repo name. func open_git_repo(ctx context.Context, group_path []string, repo_name string) (repo *git.Repository, description string, repo_id int, err error) { var fs_path string err = database.QueryRow(ctx, ` WITH RECURSIVE group_path_cte AS ( -- Start: match the first name in the path where parent_group IS NULL SELECT id, parent_group, name, 1 AS depth FROM groups WHERE name = ($1::text[])[1] AND parent_group IS NULL UNION ALL -- Recurse: join next segment of the path SELECT g.id, g.parent_group, g.name, group_path_cte.depth + 1 FROM groups g JOIN group_path_cte ON g.parent_group = group_path_cte.id WHERE g.name = ($1::text[])[group_path_cte.depth + 1] AND group_path_cte.depth + 1 <= cardinality($1::text[]) ) SELECT r.filesystem_path, COALESCE(r.description, ''), r.id FROM group_path_cte g JOIN repos r ON r.group_id = g.id WHERE g.depth = cardinality($1::text[]) AND r.name = $2 `, pgtype.FlatArray[string](group_path), repo_name).Scan(&fs_path, &description, &repo_id) if err != nil { return } repo, err = git.PlainOpen(fs_path) return } // go-git's tree entries are not friendly for use in HTML templates. type display_git_tree_entry_t struct { Name string Mode string Size int64 Is_file bool Is_subtree bool } func build_display_git_tree(tree *object.Tree) (display_git_tree []display_git_tree_entry_t) { for _, entry := range tree.Entries { display_git_tree_entry := display_git_tree_entry_t{} var err error var os_mode os.FileMode if os_mode, err = entry.Mode.ToOSFileMode(); err != nil { display_git_tree_entry.Mode = "x---------" } else { display_git_tree_entry.Mode = os_mode.String() } display_git_tree_entry.Is_file = entry.Mode.IsFile() if display_git_tree_entry.Size, err = tree.Size(entry.Name); err != nil { display_git_tree_entry.Size = 0 } display_git_tree_entry.Name = strings.TrimPrefix(entry.Name, "/") display_git_tree = append(display_git_tree, display_git_tree_entry) } return display_git_tree } func get_recent_commits(repo *git.Repository, head_hash plumbing.Hash, number_of_commits int) (recent_commits []*object.Commit, err error) { var commit_iter object.CommitIter var this_recent_commit *object.Commit commit_iter, err = repo.Log(&git.LogOptions{From: head_hash}) if err != nil { return nil, err } recent_commits = make([]*object.Commit, 0) defer commit_iter.Close() if number_of_commits < 0 { for { this_recent_commit, err = commit_iter.Next() if errors.Is(err, io.EOF) { return recent_commits, nil } else if err != nil { return nil, err } recent_commits = append(recent_commits, this_recent_commit) } } else { for range number_of_commits { this_recent_commit, err = commit_iter.Next() if errors.Is(err, io.EOF) { return recent_commits, nil } else if err != nil { return nil, err } recent_commits = append(recent_commits, this_recent_commit) } } return recent_commits, err } func get_patch_from_commit(commit_object *object.Commit) (parent_commit_hash plumbing.Hash, patch *object.Patch, err error) { var parent_commit_object *object.Commit var commit_tree *object.Tree parent_commit_object, err = commit_object.Parent(0) if errors.Is(err, object.ErrParentNotFound) { if commit_tree, err = commit_object.Tree(); err != nil { return } if patch, err = (&object.Tree{}).Patch(commit_tree); err != nil { return } } else if err != nil { return } else { parent_commit_hash = parent_commit_object.Hash if patch, err = parent_commit_object.Patch(commit_object); err != nil { return } } return }