Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • bazel/git
1 result
Show changes
Commits on Source (12)
Showing with 14923 additions and 3071 deletions
load("@rules_diff//diff/file/test:defs.bzl", "diff_file_test")
# TODO: remove when we have *real* testing
diff_file_test(
name = "placeholder",
size = "small",
a = ":MODULE.bazel",
b = ":MODULE.bazel",
)
# [1.0.0-alpha.4](https://git.gitlab.arm.com/bazel/git/compare/v1.0.0-alpha.3...v1.0.0-alpha.4) (2024-04-11)
### Bug Fixes
- **ls-remote:** filter references with glob patterns ([42720ce](https://git.gitlab.arm.com/bazel/git/commit/42720ce7099c3aea7441cb04dd3614cdb699e4cf))
- **main:** add `fatal:` prefix to error messages ([9393086](https://git.gitlab.arm.com/bazel/git/commit/939308663400411d72b8ce6cca93406beca281a1))
### Features
- **fetch:** support reference specifications ([7975120](https://git.gitlab.arm.com/bazel/git/commit/7975120e44f0727f54cf857a9b54df309d8e3bf6))
- **fetch:** support writing `FETCH_HEAD` ([973bfa3](https://git.gitlab.arm.com/bazel/git/commit/973bfa3143b9f158a604b7ec324b1ccb222c7440))
# [1.0.0-alpha.3](https://git.gitlab.arm.com/bazel/git/compare/v1.0.0-alpha.2...v1.0.0-alpha.3) (2024-04-09)
### Features
......
module(
name = "bazel-git",
version = "1.0.0-alpha.3",
version = "1.0.0-alpha.4",
bazel_compatibility = [
">=7.0.0",
],
......@@ -10,6 +10,7 @@ module(
bazel_dep(name = "toolchain_utils", version = "1.0.0-beta.4")
bazel_dep(name = "rules_coreutils", version = "1.0.0-alpha.8")
bazel_dep(name = "rules_go", version = "0.46.0")
bazel_dep(name = "rules_diff", version = "1.0.0-alpha.3")
bazel_dep(name = "hermetic_cc_toolchain", version = "3.0.0")
bazel_dep(name = "gazelle", version = "0.35.0")
bazel_dep(name = "circl", version = "1.3.3")
......@@ -44,11 +45,13 @@ go_deps = use_extension("@gazelle//:extensions.bzl", "go_deps")
go_deps.from_file(go_mod = "//:go.mod")
use_repo(
go_deps,
"com_github_bmatcuk_doublestar_v4",
"com_github_go_git_go_billy_v5",
"com_github_go_git_go_git_v5",
"com_github_jessevdk_go_flags",
"com_github_kevinburke_ssh_config",
"com_github_mattn_go_isatty",
"com_github_natefinch_atomic",
"com_github_stretchr_testify",
)
......
This diff is collapsed.
......@@ -14,6 +14,7 @@ Run one of the commands:
```console
$ bazel-git init --bare /tmp/something
$ bazel-git config core.compression 9
$ bazel-git ls-remote --exit-code https://github.com/git/git.git refs/tags/v1.7.8
$ bazel-git \
--git-dir /tmp/something \
......@@ -22,6 +23,11 @@ $ bazel-git \
--no-write-fetch-head \
https://github.com/git/git.git \
refs/tags/v1.7.8
$ bazel-git \
--git-dir /tmp/something
cat-file \
-e \
8b891d2711bdfb153176cc325150e0233bdff175
$ bazel-git \
--git-dir /tmp/something \
--work-tree /tmp/else \
......
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "checkout",
srcs = ["commit.go"],
importpath = "gitlab.arm.com/bazel/git/v1/checkout",
visibility = ["//visibility:public"],
deps = [
"//object",
"@com_github_go_git_go_billy_v5//osfs:go_default_library",
"@com_github_go_git_go_git_v5//:go_default_library",
"@com_github_go_git_go_git_v5//plumbing:go_default_library",
"@com_github_go_git_go_git_v5//plumbing/cache:go_default_library",
"@com_github_go_git_go_git_v5//storage/filesystem:go_default_library",
],
)
go_test(
name = "checkout_test",
size = "small",
srcs = ["commit_test.go"],
embed = [":checkout"],
tags = ["requires-network"],
deps = [
"//fetch",
"//object",
"//reference",
"//remote",
"@com_github_go_git_go_git_v5//:go_default_library",
],
)
package checkout
import (
"log/slog"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/storage/filesystem"
"gitlab.arm.com/bazel/git/v1/object"
)
type CommitOptions struct {
Commit object.Id
GitDir string
WorkTree string
}
func Commit(options CommitOptions) (err error) {
log := slog.With("namespace", "checkout.Commit")
log.Debug("checkout")
storage := filesystem.NewStorage(osfs.New(options.GitDir), cache.NewObjectLRUDefault())
working := osfs.New(options.WorkTree)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.Open(storage, working)
if err != nil {
log.Error("open", "err", err)
return err
}
log.Debug("resolve", "commit", options.Commit)
hash := plumbing.NewHash(options.Commit.String())
tag, err := repo.TagObject(hash)
if err != nil {
log.Error("resolve", "err", err)
return err
}
if tag != nil {
commit, err := tag.Commit()
if err != nil {
log.Error("resolve", "err", err)
return err
}
hash = commit.Hash
}
log.Debug("resolved", "hash", hash)
log.Debug("worktree", "work-tree", options.WorkTree)
worktree, err := repo.Worktree()
if err != nil {
log.Error("worktree", "err", err)
return err
}
log.Debug("checkout", "commit", options.Commit)
err = worktree.Checkout(&git.CheckoutOptions{
Hash: hash,
})
if err != nil {
log.Error("checkout", "err", err)
return err
}
return nil
}
package checkout
import (
"bufio"
"fmt"
"log"
"os"
"path"
"strings"
"github.com/go-git/go-git/v5"
"gitlab.arm.com/bazel/git/v1/fetch"
"gitlab.arm.com/bazel/git/v1/object"
"gitlab.arm.com/bazel/git/v1/reference"
"gitlab.arm.com/bazel/git/v1/remote"
)
func ExampleCommitish() {
dir, err := os.MkdirTemp("", "bazel-git-checkout-commitish-dir-")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(dir)
worktree, err := os.MkdirTemp("", "bazel-git-checkout-commitish-worktree-")
if err != nil {
log.Fatal(err)
}
defer os.RemoveAll(worktree)
_, err = git.PlainInitWithOptions(dir, &git.PlainInitOptions{
Bare: true,
})
if err != nil {
log.Fatal(err)
}
url, err := remote.NewURL("https://github.com/git/git.git")
if err != nil {
log.Fatal(err)
}
commitish, err := reference.NewCommitish("refs/tags/v1.7.8")
if err != nil {
log.Fatal(err)
}
err = fetch.Commitishes(fetch.CommitishesOptions{
Remote: *url,
Commitishes: []reference.Commitish{commitish},
Depth: 1,
GitDir: dir,
})
if err != nil {
log.Fatal(err)
}
commit, err := object.NewId("8b891d2711bdfb153176cc325150e0233bdff175")
if err != nil {
log.Fatal(err)
}
err = Commit(CommitOptions{
Commit: commit,
GitDir: dir,
WorkTree: worktree,
})
if err != nil {
log.Fatal(err)
}
fmt.Printf("checkout %s#%s\n", url, commit)
readme := path.Join(worktree, "README")
fd, err := os.Open(readme)
if err != nil {
log.Fatal(err)
}
defer fd.Close()
scanner := bufio.NewScanner(fd)
for scanner.Scan() {
trimmed := strings.TrimSpace(scanner.Text())
if !strings.HasPrefix(trimmed, "GIT") {
continue
}
fmt.Printf("%s\n", trimmed)
break
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
// Output:
// checkout https://github.com/git/git.git#8b891d2711bdfb153176cc325150e0233bdff175
// GIT - the stupid content tracker
}
......@@ -16,14 +16,19 @@ go_library(
importpath = "gitlab.arm.com/bazel/git/v1/cmd/bazel-git",
visibility = ["//visibility:private"],
deps = [
"//checkout",
"//config",
"//fetch",
"//object",
"//reference",
"//remote",
"@com_github_bmatcuk_doublestar_v4//:go_default_library",
"@com_github_go_git_go_billy_v5//osfs:go_default_library",
"@com_github_go_git_go_git_v5//:go_default_library",
"@com_github_go_git_go_git_v5//config:go_default_library",
"@com_github_go_git_go_git_v5//plumbing:go_default_library",
"@com_github_go_git_go_git_v5//plumbing/cache:go_default_library",
"@com_github_go_git_go_git_v5//plumbing/protocol/packp/sideband:go_default_library",
"@com_github_go_git_go_git_v5//storage/filesystem:go_default_library",
"@com_github_go_git_go_git_v5//storage/memory:go_default_library",
"@com_github_jessevdk_go_flags//:go_default_library",
"@com_github_kevinburke_ssh_config//:go_default_library",
"@com_github_mattn_go_isatty//:go_default_library",
"@com_github_natefinch_atomic//:go_default_library",
],
)
......
......@@ -4,14 +4,15 @@ import (
"errors"
"log/slog"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/jessevdk/go-flags"
"gitlab.arm.com/bazel/git/v1/object"
)
type CatFileCommand struct {
ExitCode bool `short:"e" description:"Exit with zero status if <object> exists and is a valid object. If <object> is of an invalid format, exit with non-zero status and emit an error on stderr." required:"yes"`
Args struct {
Object object.Id `positional-arg-name:"<object>" description:"The object to check for existence."`
Revision plumbing.Revision `positional-arg-name:"<object>" description:"The object to check for existence."`
} `positional-args:"yes" required:"yes"`
}
......@@ -19,28 +20,36 @@ var catFileCommand CatFileCommand
func (x *CatFileCommand) Execute(rest []string) error {
if len(rest) != 0 {
return &flags.Error{flags.ErrDuplicatedFlag, "only one object can be specified"}
return &flags.Error{flags.ErrDuplicatedFlag, "invalid number of positional arguments"}
}
if !x.ExitCode {
// TODO: remove when we support more flags
return errors.New("Must provide the `-e` flag")
}
log := slog.With("object", x.Args.Object)
log.Debug("cat-file")
found, err := object.Exists(object.ExistsOptions{Object: x.Args.Object, GitDir: options.GitDir})
log := slog.With("cmd", "cat-file", "object", x.Args.Revision)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.PlainOpen(options.GitDir)
if err != nil {
log.Error("open", "err", err)
return err
}
if !found {
log.Debug("resolve")
hash, err := repo.ResolveRevision(x.Args.Revision)
if x.ExitCode && err == plumbing.ErrReferenceNotFound {
// TODO: remove the message
return NewExitCodeError(1, "Not found")
}
if err != nil {
log.Error("resolve", "err", err)
return err
}
log.Debug("resolved", "hash", hash)
if x.ExitCode {
return nil
}
return nil
// TODO: implement the rest of the `cat-file` functionality.
return errors.New("Not implemented")
}
func init() {
......
......@@ -3,26 +3,61 @@ package main
import (
"log/slog"
"gitlab.arm.com/bazel/git/v1/checkout"
"gitlab.arm.com/bazel/git/v1/object"
"github.com/go-git/go-billy/v5/osfs"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/cache"
"github.com/go-git/go-git/v5/storage/filesystem"
"github.com/jessevdk/go-flags"
)
type CheckoutCommand struct {
Args struct {
Commit object.Id `positional-arg-name:"commit" description:"The commit to update the working tree to."`
Revision plumbing.Revision `positional-arg-name:"<revision>" description:"The revision to update the working tree to."`
} `positional-args:"yes" required:"yes"`
}
var checkoutCommand CheckoutCommand
func (x *CheckoutCommand) Execute(_ []string) error {
log := slog.With("namespace", "checkout")
log.Debug("checkout", "commit", x.Args.Commit)
err := checkout.Commit(checkout.CommitOptions{
Commit: x.Args.Commit,
GitDir: options.GitDir,
WorkTree: options.WorkTree,
func (x *CheckoutCommand) Execute(rest []string) error {
if len(rest) != 0 {
return &flags.Error{flags.ErrDuplicatedFlag, "invalid number of positional arguments"}
}
log := slog.With("cmd", "checkout", "revision", x.Args.Revision)
log.Debug("open", "git-dir", options.GitDir)
storage := filesystem.NewStorage(osfs.New(options.GitDir), cache.NewObjectLRUDefault())
working := osfs.New(options.WorkTree)
repo, err := git.Open(storage, working)
if err != nil {
log.Error("open", "err", err)
return err
}
log.Debug("resolve")
hash, err := repo.ResolveRevision(x.Args.Revision)
if err != nil {
log.Error("resolve", "err", err)
return err
}
log.Debug("worktree", "work-tree", options.WorkTree)
worktree, err := repo.Worktree()
if err != nil {
log.Error("worktree", "err", err)
return err
}
log.Debug("checkout", "hash", hash)
err = worktree.Checkout(&git.CheckoutOptions{
Hash: *hash,
})
if err != nil {
log.Error("checkout", "err", err)
return err
}
return err
}
......
package main
import (
"errors"
"fmt"
"log/slog"
"strings"
"github.com/go-git/go-git/v5"
"github.com/jessevdk/go-flags"
"gitlab.arm.com/bazel/git/v1/config"
)
type ConfigCommand struct {
......@@ -18,21 +21,47 @@ var configCommand ConfigCommand
func (x *ConfigCommand) Execute(rest []string) error {
if len(rest) != 0 {
return &flags.Error{flags.ErrDuplicatedFlag, "unsupported number of positional arguments"}
return &flags.Error{flags.ErrDuplicatedFlag, "invalid number of positional arguments"}
}
log := slog.With("name", x.Args.Name, "value", x.Args.Value)
log.Debug("set")
err := config.Set(config.SetOptions{
GitDir: options.GitDir,
Name: x.Args.Name,
Value: x.Args.Value,
})
log := slog.With("cmd", "config", "name", x.Args.Name, "value", x.Args.Value)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.PlainOpen(options.GitDir)
if err != nil {
log.Error("open", "err", err)
return err
}
log.Debug("config")
config, err := repo.Config()
if err != nil {
log.Error("config", "err", err)
return err
}
return nil
if config.Raw == nil {
return errors.New("Unexpected empty raw configuration value")
}
parts := strings.Split(x.Args.Name, ".")
log.Debug("split", "parts", parts)
if len(parts) != 2 {
return fmt.Errorf("Invalid configuration value name: %s", x.Args.Name)
}
section, name := parts[0], parts[1]
log.Debug("get", "section", section)
if !config.Raw.HasSection(section) {
return fmt.Errorf("`%s` section not found: %s", x.Args.Name, section)
}
s := config.Raw.Section(section)
s.SetOption(name, x.Args.Value)
log.Debug("set")
return repo.SetConfig(config)
}
func init() {
......
package main
import (
"bytes"
"fmt"
"log/slog"
"net/url"
"os"
"path/filepath"
"strings"
"gitlab.arm.com/bazel/git/v1/fetch"
"gitlab.arm.com/bazel/git/v1/reference"
"gitlab.arm.com/bazel/git/v1/remote"
"github.com/bmatcuk/doublestar/v4"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
"github.com/jessevdk/go-flags"
"github.com/kevinburke/ssh_config"
"github.com/mattn/go-isatty"
"github.com/natefinch/atomic"
)
// TODO: upstream this to be applied before the SSH transport executes
func addSSHUser(value string) (string, error) {
parsed, err := url.Parse(value)
if err != nil {
return "", err
}
if parsed.Scheme == "ssh" && parsed.User == nil {
user, err := ssh_config.GetStrict(parsed.Hostname(), "User")
if err != nil {
return "", err
}
if user != "" {
parsed.User = url.User(user)
}
}
return parsed.String(), nil
}
type RefSpec config.RefSpec
func (r *RefSpec) UnmarshalFlag(value string) error {
if strings.Contains(value, ":") {
// passthrough
} else if plumbing.IsHash(value) {
value = value + ":" + value
} else if !strings.HasPrefix(value, "refs/") {
value = "refs/heads/" + value + ":/refs/heads/" + value
}
refspec := config.RefSpec(value)
*r = RefSpec(refspec)
if err := refspec.Validate(); err != nil {
return fmt.Errorf("invalid refspec '%s'", value)
}
return nil
}
type Remote string
func (r *Remote) UnmarshalFlag(value string) error {
// TODO: support remote names
parsed, err := url.Parse(value)
if err != nil {
return err
}
*r = Remote(parsed.String())
return nil
}
func (r Remote) String() string {
return string(r)
}
// TODO: upstream this to `go-git`
type Reference struct {
plumbing.Reference
}
type FetchStatus int
const (
FetchStatusNotForMerge FetchStatus = 0
FetchStatusMerge FetchStatus = 1
)
func (r Reference) FetchHead(status FetchStatus, urls []string) string {
name := r.Name()
message := r.Hash().String()
switch status {
case FetchStatusNotForMerge:
message += "\tnot-for-merge"
default:
message += "\t"
}
switch {
case name.String() == "HEAD":
message += "\t"
case name.IsBranch():
message += fmt.Sprintf("\tbranch '%s' of ", name.Short())
case name.IsTag():
message += fmt.Sprintf("\ttag '%s' of ", name.Short())
case name.IsRemote():
message += fmt.Sprintf("\tremote-tracking branch '%s' of ", name.Short())
default:
message += fmt.Sprintf("\t'%s' of ", name.Short())
}
message += strings.Join(urls, "\\n")
return message
}
type FetchCommand struct {
Depth int `long:"depth" value-name:"<depth>" description:"Limit fetching the specified number of commits from the tip of each remote branch history." require:"yes"`
NoWriteFetchHead bool `long:"no-write-fetch-head" description:"Does not write **FETCH_HEAD**." required:"yes"`
NegotiationTips []reference.Commitish `long:"negotiation-tip" value-name:"<commit>" description:"Determines the tips to use in negoatiating a smaller packfile transfer. Can be defined multiple times."`
Depth int `long:"depth" value-name:"<depth>" description:"Limit fetching the specified number of commits from the tip of each remote branch history." require:"yes"`
NoWriteFetchHead bool `long:"no-write-fetch-head" description:"Does not write **FETCH_HEAD**."`
NegotiationTips []plumbing.Revision `long:"negotiation-tip" value-name:"<commit>" description:"Determines the tips to use in negoatiating a smaller packfile transfer. Can be defined multiple times."`
Args struct {
Remote remote.URL `positional-arg-name:"repository" description:"The remote to retrieve objects from."`
Commitishes []reference.Commitish `positional-arg-name:"refspec" description:"The committish to retrieve from the server. Usually a commit SHA but can be a resolvable reference."`
Remote Remote `positional-arg-name:"repository" description:"The remote to retrieve objects from."`
RefSpecs []RefSpec `positional-arg-name:"refspec" description:"The committish to retrieve from the server. Usually a commit SHA but can be a resolvable reference."`
} `positional-args:"yes" required:"yes"`
}
var fetchCommand FetchCommand
func (x *FetchCommand) Execute(_ []string) error {
log := slog.With("namespace", "fetch", "remote", x.Args.Remote)
log.Debug("fetching", "commitishes", x.Args.Commitishes)
func (x *FetchCommand) Execute(rest []string) error {
if len(rest) != 0 {
return &flags.Error{flags.ErrDuplicatedFlag, "invalid number of positional arguments"}
}
log := slog.With("cmd", "fetch", "remote", x.Args.Remote, "refspecs", x.Args.RefSpecs)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.PlainOpen(options.GitDir)
if err != nil {
log.Error("open", "err", err)
return err
}
url, err := addSSHUser(x.Args.Remote.String())
if err != nil {
return err
}
log.Debug("remote")
remote, err := repo.CreateRemoteAnonymous(&config.RemoteConfig{
Name: "anonymous",
URLs: []string{url},
})
if err != nil {
log.Error("remote", "err", err)
return err
}
refSpecs := make([]config.RefSpec, len(x.Args.RefSpecs))
for i, refSpec := range x.Args.RefSpecs {
refSpecs[i] = config.RefSpec(refSpec)
}
if len(x.NegotiationTips) > 0 {
log.Warn("Negotiation tips are not currently supported. A large packfile may be transferred", "negotiation-tips", x.NegotiationTips)
var progress sideband.Progress
if isatty.IsTerminal(os.Stderr.Fd()) {
// TODO: make this add `remote:` prefix to each line to mirror `git` CLI
// TODO: how to we print the progress for receiving objects/deltas?
progress = os.Stderr
}
err := fetch.Commitishes(fetch.CommitishesOptions{
Remote: x.Args.Remote,
Commitishes: x.Args.Commitishes,
Depth: x.Depth,
GitDir: options.GitDir,
log.Debug("fetch")
err = remote.Fetch(&git.FetchOptions{
RefSpecs: refSpecs,
Depth: x.Depth,
Progress: progress,
})
return err
if !(err == nil || err == git.NoErrAlreadyUpToDate) {
log.Error("fetch", "err", err)
return err
}
if !x.NoWriteFetchHead {
log.Debug("FETCH_HEAD")
iter, err := repo.References()
if err != nil {
log.Error("FETCH_HEAD", "err", err)
return err
}
buffer := bytes.NewBuffer([]byte{})
urls := []string{x.Args.Remote.String()}
err = iter.ForEach(func(reference *plumbing.Reference) error {
for _, refSpec := range refSpecs {
// TODO: upstream a local reference match to `go-git`
// Needs to match on `refSpec.Dst()` not `refSpec.Src()` which `refspec.Match()` does
pattern := strings.Split(refSpec.String(), ":")[1]
match, err := doublestar.Match(pattern, reference.Name().String())
if err != nil {
return err
}
if match {
ref := Reference{*reference}
line := ref.FetchHead(FetchStatusMerge, urls)
_, err := buffer.WriteString(line + "\n")
if err != nil {
return err
}
}
}
return nil
})
if err != nil {
log.Error("FETCH_HEAD", "err", err)
return err
}
fetchHead := filepath.Join(options.GitDir, "FETCH_HEAD")
err = atomic.WriteFile(fetchHead, buffer)
if err != nil {
return err
}
}
return nil
}
func init() {
......
......@@ -2,45 +2,102 @@ package main
import (
"fmt"
"github.com/kevinburke/ssh_config"
"log/slog"
"net/url"
"strings"
"gitlab.arm.com/bazel/git/v1/reference"
"gitlab.arm.com/bazel/git/v1/remote"
"github.com/bmatcuk/doublestar/v4"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/storage/memory"
"github.com/jessevdk/go-flags"
)
type URL string
func (u *URL) UnmarshalFlag(value string) error {
parsed, err := url.Parse(value)
if err != nil {
return err
}
if parsed.Scheme == "ssh" && parsed.User == nil {
user, err := ssh_config.GetStrict(parsed.Hostname(), "User")
if err != nil {
return err
}
if user != "" {
parsed.User = url.User(user)
}
}
*u = URL(parsed.String())
return nil
}
type LsRemoteCommand struct {
ExitCode bool `long:"exit-code" description:"Exit with an error code when references cannot be resolved on the remote server."`
Args struct {
Remote remote.URL `positional-arg-name:"<repository>" description:"The remote to resolve references against."`
References []reference.Name `positional-arg-name:"<pattern>" description:"The commit or reference to resolve into a commit." required:"1"`
URL URL `positional-arg-name:"<repository>" description:"The remote to resolve references against."`
Patterns []string `positional-arg-name:"<pattern>" description:"The commit or reference to resolve into a commit." required:"1"`
} `positional-args:"yes" required:"yes"`
}
var lsRemoteCommand LsRemoteCommand
func (x *LsRemoteCommand) Execute(_ []string) error {
log := slog.With("remote", x.Args.Remote)
log.Debug("ls-remote: listing", "references", x.Args.References)
func (x *LsRemoteCommand) Execute(rest []string) error {
if len(rest) != 0 {
return &flags.Error{flags.ErrDuplicatedFlag, "invalid number of positional arguments"}
}
log := slog.With("cmd", "ls-remote", "url", x.Args.URL, "patterns", x.Args.Patterns)
list, err := remote.List(remote.ListOptions{
Repository: x.Args.Remote,
Patterns: x.Args.References,
log.Debug("remote")
remote := git.NewRemote(memory.NewStorage(), &config.RemoteConfig{
Name: "origin",
URLs: []string{string(x.Args.URL)},
})
// TODO: upstream support for sending `ls-remote` patterns
log.Debug("listing")
refs, err := remote.List(&git.ListOptions{
Timeout: 60 * 5,
})
if err != nil {
return err
}
log.Debug("ls-remote: listed", "list", list)
log.Debug("filtering")
found := false
for name, id := range list {
if id != nil {
fmt.Printf("%s\t%s\n", id, name)
found = true
for _, ref := range refs {
for _, pattern := range x.Args.Patterns {
matched, err := doublestar.Match(pattern, ref.Name().String())
if err != nil {
return err
}
if matched {
fmt.Printf("%s\t%s\n", ref.Hash().String(), ref.Name())
found = true
}
if strings.HasPrefix("/refs/", pattern) {
continue
}
matched, err = doublestar.Match("refs/*/**/"+pattern, ref.Name().String())
if err != nil {
return err
}
if matched {
fmt.Printf("%s\t%s\n", ref.Hash().String(), ref.Name())
found = true
}
}
}
if !found && x.ExitCode {
// TODO: remove the message?
// TODO: remove the message
return NewExitCodeError(2, "references unfound")
}
......
......@@ -50,11 +50,11 @@ func main() {
fmt.Fprint(os.Stdout, e)
os.Exit(0)
default:
fmt.Fprintln(os.Stderr, e)
fmt.Fprintf(os.Stderr, "fatal: %s\n", e)
os.Exit(2)
}
default:
fmt.Fprintln(os.Stderr, e)
fmt.Fprintf(os.Stderr, "fatal: %s\n", e)
os.Exit(1)
}
}
......
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "config",
srcs = ["set.go"],
importpath = "gitlab.arm.com/bazel/git/v1/config",
visibility = ["//visibility:public"],
deps = ["@com_github_go_git_go_git_v5//:go_default_library"],
)
go_test(
name = "config_test",
srcs = ["set_test.go"],
embed = [":config"],
deps = [
"@com_github_go_git_go_git_v5//:go_default_library",
"@com_github_stretchr_testify//assert:go_default_library",
],
)
package config
import (
"errors"
"fmt"
"log/slog"
"strings"
"github.com/go-git/go-git/v5"
)
type SetOptions struct {
GitDir string
Name string
Value string
}
func Set(options SetOptions) (err error) {
log := slog.With("namespace", "config.Set", "name", options.Name, "value", options.Value)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.PlainOpen(options.GitDir)
if err != nil {
log.Error("open", "err", err)
return err
}
config, err := repo.Config()
if err != nil {
log.Error("config", "err", err)
return err
}
if config.Raw == nil {
return errors.New("Unexpected empty raw configuration value")
}
parts := strings.Split(options.Name, ".")
log.Debug("split", "parts", parts)
if len(parts) != 2 {
return fmt.Errorf("Invalid configuration value name: %s", options.Name)
}
section, name := parts[0], parts[1]
if !config.Raw.HasSection(section) {
return fmt.Errorf("`%s` section not found: %s", options.Name, section)
}
s := config.Raw.Section(section)
s.SetOption(name, options.Value)
return repo.SetConfig(config)
}
package config
import (
"github.com/go-git/go-git/v5"
"github.com/stretchr/testify/assert"
"os"
"testing"
)
func TestSet(t *testing.T) {
assert := assert.New(t)
dir, err := os.MkdirTemp("", "bazel-git-config-set-dir-")
assert.Nil(err)
defer os.RemoveAll(dir)
repo, err := git.PlainInitWithOptions(dir, &git.PlainInitOptions{
Bare: true,
})
assert.Nil(err)
err = Set(SetOptions{
GitDir: dir,
Name: "user.name",
Value: "Jango Fett",
})
assert.Nil(err)
config, err := repo.Config()
assert.Nil(err)
assert.Equal(config.User.Name, "Jango Fett")
}
load("@rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "fetch",
srcs = ["commitishes.go"],
importpath = "gitlab.arm.com/bazel/git/v1/fetch",
visibility = ["//visibility:public"],
deps = [
"//reference",
"//remote",
"@com_github_go_git_go_git_v5//:go_default_library",
"@com_github_go_git_go_git_v5//config:go_default_library",
"@com_github_go_git_go_git_v5//plumbing/protocol/packp/sideband:go_default_library",
"@com_github_mattn_go_isatty//:go_default_library",
],
)
go_test(
name = "fetch_test",
size = "small",
srcs = ["commitishes_test.go"],
embed = [":fetch"],
tags = ["requires-network"],
deps = [
"//reference",
"//remote",
"@com_github_go_git_go_git_v5//:go_default_library",
],
)
package fetch
import (
"fmt"
"log/slog"
"os"
"github.com/go-git/go-git/v5"
"github.com/go-git/go-git/v5/config"
"github.com/go-git/go-git/v5/plumbing/protocol/packp/sideband"
"github.com/mattn/go-isatty"
"gitlab.arm.com/bazel/git/v1/reference"
"gitlab.arm.com/bazel/git/v1/remote"
)
type CommitishesOptions struct {
Remote remote.URL
Commitishes []reference.Commitish
Depth int
GitDir string
}
func Commitishes(options CommitishesOptions) (err error) {
log := slog.With("namespace", "fetch.Commitishes")
log.Debug("fetching", "commitishes", options.Commitishes, "depth", options.Depth, "git-dir", options.GitDir)
log.Debug("open", "git-dir", options.GitDir)
repo, err := git.PlainOpen(options.GitDir)
if err != nil {
log.Error("open", "err", err)
return err
}
log.Debug("remote", "url", options.Remote)
remote, err := repo.CreateRemoteAnonymous(&config.RemoteConfig{
Name: "anonymous",
URLs: []string{options.Remote.String()},
})
if err != nil {
log.Error("remote", "err", err)
return err
}
refSpecs := make([]config.RefSpec, len(options.Commitishes))
for i, commitish := range options.Commitishes {
refSpec := fmt.Sprintf("%s:%[1]s", commitish.String())
refSpecs[i] = config.RefSpec(refSpec)
}
var progress sideband.Progress
if isatty.IsTerminal(os.Stderr.Fd()) {
progress = os.Stderr
}
log.Debug("fetch", "commitishes", options.Commitishes)
err = remote.Fetch(&git.FetchOptions{
RemoteURL: options.Remote.String(),
RefSpecs: refSpecs,
Depth: options.Depth,
Progress: progress,
})
if err == git.NoErrAlreadyUpToDate {
return nil
} else if err != nil {
log.Error("fetch", "err", err)
return err
}
return nil
}