Some checks are pending
Docs Deploy / build_and_deploy (push) Waiting to run
Generate Docs / cli (push) Waiting to run
Generate Config Doc / cli (push) Waiting to run
Go formatting / go-formatting (push) Waiting to run
Check links / markdown-link-check (push) Waiting to run
Integration / pre-test (push) Waiting to run
Integration / test on (push) Blocked by required conditions
Integration / status (push) Blocked by required conditions
Lint / Lint Go code (push) Waiting to run
Test / test (ubuntu-latest) (push) Waiting to run
181 lines
4.4 KiB
Go
181 lines
4.4 KiB
Go
package xgit
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"time"
|
|
|
|
"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/ignite/cli/v29/ignite/pkg/errors"
|
|
)
|
|
|
|
var (
|
|
commitMsg = "Initialized with Ignite CLI"
|
|
defaultOpenOpts = git.PlainOpenOptions{DetectDotGit: true}
|
|
devXAuthor = &object.Signature{
|
|
Name: "Developer Experience team at Ignite",
|
|
Email: "hello@ignite.com",
|
|
When: time.Now(),
|
|
}
|
|
)
|
|
|
|
// InitAndCommit creates a git repo in path if path isn't already inside a git
|
|
// repository, then commits path content.
|
|
func InitAndCommit(path string) error {
|
|
repo, err := git.PlainOpenWithOptions(path, &defaultOpenOpts)
|
|
if err != nil {
|
|
if !errors.Is(err, git.ErrRepositoryNotExists) {
|
|
return errors.Errorf("open git repo %s: %w", path, err)
|
|
}
|
|
// not a git repo, creates a new one
|
|
repo, err = git.PlainInitWithOptions(path, &git.PlainInitOptions{
|
|
InitOptions: git.InitOptions{
|
|
DefaultBranch: plumbing.Main,
|
|
},
|
|
Bare: false,
|
|
})
|
|
if err != nil {
|
|
return errors.Errorf("init git repo %s: %w", path, err)
|
|
}
|
|
}
|
|
wt, err := repo.Worktree()
|
|
if err != nil {
|
|
return errors.Errorf("worktree %s: %w", path, err)
|
|
}
|
|
// wt.Add(path) takes only relative path, we need to turn path relative to
|
|
// repo path.
|
|
repoPath := wt.Filesystem.Root()
|
|
path, err = filepath.Rel(repoPath, path)
|
|
if err != nil {
|
|
return errors.Errorf("find relative path %s %s: %w", repoPath, path, err)
|
|
}
|
|
if _, err := wt.Add(path); err != nil {
|
|
return errors.Errorf("git add %s: %w", path, err)
|
|
}
|
|
_, err = wt.Commit(commitMsg, &git.CommitOptions{
|
|
All: true,
|
|
Author: devXAuthor,
|
|
})
|
|
if err != nil {
|
|
return errors.Errorf("git commit %s: %w", path, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// AreChangesCommitted returns true if dir is a clean git repository with no
|
|
// pending changes. It returns also true if dir is NOT a git repository.
|
|
func AreChangesCommitted(dir string) (bool, error) {
|
|
dir, err := filepath.Abs(dir)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
repository, err := git.PlainOpen(dir)
|
|
if err != nil {
|
|
if errors.Is(err, git.ErrRepositoryNotExists) {
|
|
return true, nil
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
w, err := repository.Worktree()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
|
|
ws, err := w.Status()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return ws.IsClean(), nil
|
|
}
|
|
|
|
// Clone clones a git repository represented by urlRef, into dir.
|
|
// urlRef is the URL of the repository, with an optional ref, suffixed to the
|
|
// URL with a `@`. Ref can be a tag, a branch or a hash.
|
|
// Valid examples of urlRef: github.com/org/repo, github.com/org/repo@v1,
|
|
// github.com/org/repo@develop, github.com/org/repo@ab88cdf.
|
|
func Clone(ctx context.Context, urlRef, dir string) error {
|
|
// Ensure dir is empty if it exists (if it doesn't exist, the call to
|
|
// git.PlainCloneContext below will create it).
|
|
files, _ := os.ReadDir(dir)
|
|
if len(files) > 0 {
|
|
return errors.Errorf("clone: target directory %q is not empty", dir)
|
|
}
|
|
// Split urlRef
|
|
var (
|
|
parts = strings.Split(urlRef, "@")
|
|
url = parts[0]
|
|
ref string
|
|
)
|
|
if len(parts) > 1 {
|
|
ref = parts[1]
|
|
}
|
|
// First clone the repo
|
|
repo, err := git.PlainCloneContext(ctx, dir, false, &git.CloneOptions{
|
|
URL: url,
|
|
})
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if ref == "" {
|
|
// if ref is not provided, job is done
|
|
return nil
|
|
}
|
|
// Reference provided, try to resolve
|
|
wt, err := repo.Worktree()
|
|
if err != nil {
|
|
return err
|
|
}
|
|
var h *plumbing.Hash
|
|
for _, ref := range []string{ref, "origin/" + ref} {
|
|
h, err = repo.ResolveRevision(plumbing.Revision(ref))
|
|
if err == nil {
|
|
break
|
|
}
|
|
}
|
|
if err != nil {
|
|
// Ref not found, clean up dir and return error
|
|
os.RemoveAll(dir)
|
|
return err
|
|
}
|
|
return wt.Checkout(&git.CheckoutOptions{
|
|
Hash: *h,
|
|
})
|
|
}
|
|
|
|
// IsRepository checks if a path contains a Git repository.
|
|
func IsRepository(path string) (bool, error) {
|
|
if _, err := git.PlainOpenWithOptions(path, &defaultOpenOpts); err != nil {
|
|
if errors.Is(err, git.ErrRepositoryNotExists) {
|
|
return false, nil
|
|
}
|
|
return false, err
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// RepositoryURL returns the URL of the origin remote of a Git repository.
|
|
func RepositoryURL(path string) (string, error) {
|
|
repo, err := git.PlainOpenWithOptions(path, &defaultOpenOpts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
cfg, err := repo.Config()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
origin, ok := cfg.Remotes["origin"]
|
|
if !ok {
|
|
return "", errors.Errorf("no origin remote found in %s", path)
|
|
}
|
|
|
|
return origin.URLs[0], nil
|
|
}
|