Conversation
There was a problem hiding this comment.
Pull request overview
This PR restructures the git drs add-url command to directly fetch data from S3 into the local Git LFS store and introduces a new lfss3 helper package for S3 + Git LFS introspection, along with associated tests and updated coverage artifacts.
Changes:
- Replace the old
add-urlimplementation with a newCmd/runAddURLthat inspects an S3 object, streams it into the local LFS store, and writes a Git LFS pointer file in the worktree. - Add
cmd/addurl/lfss3with S3 URL parsing, LFS storage resolution, S3HeadObjectlogic, an HTTP-based fetch helper with progress reporting, and unit tests for these helpers. - Wire
addurl.Cmdintocmd/root.goand refresh coverage outputs to reflect new code and tests; remove the legacycmd/addurl/main_test.go.
Reviewed changes
Copilot reviewed 7 out of 11 changed files in this pull request and generated 9 comments.
Show a summary per file
| File | Description |
|---|---|
| coverage/integration/coverage.out | Updated integration coverage numbers to include the new addurl/lfss3 code paths. |
| coverage/combined.out | Updated combined coverage metadata, reflecting new and removed lines in addurl, indexd client, config, logging, and other packages. |
| cmd/root.go | Switch root command to register addurl.Cmd (new command variable) instead of the removed AddURLCmd. |
| cmd/addurl/main.go | Replaces the previous Gen3/indexd-driven add-url flow with a new implementation that inspects S3 via lfss3, downloads the object into the LFS store, computes SHA256, and writes a Git LFS pointer in the worktree. |
| cmd/addurl/main_test.go | Deletes the old generatePointerFile unit test, which is no longer applicable after removing that helper. |
| cmd/addurl/lfss3/is_lfs_tracked_test.go | New tests verifying IsLFSTracked correctly interprets .gitattributes and distinguishes LFS-tracked vs non-tracked paths. |
| cmd/addurl/lfss3/inspect_test.go | New tests covering S3 URL parsing, SHA256 normalization/extraction, Git common-dir/LFS root resolution (including lfs.storage variants), and helpers for running git commands in temp repos. |
| cmd/addurl/lfss3/inspect.go | New lfss3 package logic for resolving LFS storage, parsing S3 URLs, making authenticated HeadObject calls, extracting metadata (including SHA256), and determining whether a path is LFS-tracked. |
| cmd/addurl/lfss3/agent_fetch_reader.go | New helper that converts S3/HTTP URLs to downloadable form, issues an HTTP GET, and wraps the response in a progress-reporting io.ReadCloser for use by runAddURL. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| func runAddURL(cmd *cobra.Command, args []string) (err error) { | ||
| ctx := cmd.Context() | ||
| if ctx == nil { | ||
| ctx = context.Background() | ||
| } | ||
|
|
||
| s3URL := args[0] | ||
|
|
||
| // Call client.AddURL to handle Gen3 interactions | ||
| meta, err := drsClient.AddURL(s3URL, sha256, awsAccessKey, awsSecretKey, awsRegion, awsEndpoint) | ||
| if err != nil { | ||
| return err | ||
| // Determine path: use provided optional arg, otherwise derive from URL path | ||
| var pathArg string | ||
| if len(args) == 2 { | ||
| pathArg = args[1] | ||
| } else { | ||
| u, perr := url.Parse(s3URL) | ||
| if perr != nil { | ||
| return perr | ||
| } | ||
| pathArg = strings.TrimPrefix(u.Path, "/") | ||
| } | ||
|
|
||
| sha256Param, ferr := cmd.Flags().GetString("sha256") | ||
| if ferr != nil { | ||
| return fmt.Errorf("read flag sha256: %w", ferr) | ||
| } | ||
|
|
||
| awsKey, ferr := cmd.Flags().GetString(s3_utils.AWS_KEY_FLAG_NAME) | ||
| if ferr != nil { | ||
| return fmt.Errorf("read flag %s: %w", s3_utils.AWS_KEY_FLAG_NAME, ferr) | ||
| } | ||
| awsSecret, ferr := cmd.Flags().GetString(s3_utils.AWS_SECRET_FLAG_NAME) | ||
| if ferr != nil { | ||
| return fmt.Errorf("read flag %s: %w", s3_utils.AWS_SECRET_FLAG_NAME, ferr) | ||
| } | ||
| awsRegion, ferr := cmd.Flags().GetString(s3_utils.AWS_REGION_FLAG_NAME) | ||
| if ferr != nil { | ||
| return fmt.Errorf("read flag %s: %w", s3_utils.AWS_REGION_FLAG_NAME, ferr) | ||
| } | ||
| awsEndpoint, ferr := cmd.Flags().GetString(s3_utils.AWS_ENDPOINT_URL_FLAG_NAME) | ||
| if ferr != nil { | ||
| return fmt.Errorf("read flag %s: %w", s3_utils.AWS_ENDPOINT_URL_FLAG_NAME, ferr) | ||
| } | ||
|
|
||
| if awsKey == "" || awsSecret == "" { | ||
| return errors.New("AWS credentials must be provided via flags or environment variables") | ||
| } | ||
| if awsRegion == "" { | ||
| return errors.New("AWS region must be provided via flag or environment variable") | ||
| } | ||
|
|
||
| s3Input := lfss3.InspectInput{ | ||
| S3URL: s3URL, | ||
| AWSAccessKey: awsKey, | ||
| AWSSecretKey: awsSecret, | ||
| AWSRegion: awsRegion, | ||
| AWSEndpoint: awsEndpoint, | ||
| SHA256: sha256Param, | ||
| WorktreeName: pathArg, | ||
| } | ||
| info, err := lfss3.InspectS3ForLFS(ctx, s3Input) | ||
| if err != nil { | ||
| return err | ||
| } |
There was a problem hiding this comment.
This new runAddURL implementation only talks to S3 and the local Git LFS store and never constructs a DRS client or calls DRSClient.AddURL, so git drs add-url no longer registers the S3 URL with the configured Gen3/Anvil remote. If that behavior is still required, consider layering the new "copy into local LFS" step on top of the existing indexd/DRS registration flow rather than replacing it outright.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 7 out of 12 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
cmd/addurl/main.go
Outdated
| isLFSTracked, err := lfss3.IsLFSTracked(pathArg) | ||
| if err != nil { | ||
| return fmt.Errorf("check LFS tracking for %s: %w", pathArg, err) | ||
| } | ||
|
|
||
| if _, err := fmt.Fprintf(cmd.OutOrStdout(), ` | ||
| Resolved Git LFS info | ||
| --------------------- | ||
| Git common dir : %s | ||
| LFS storage : %s | ||
|
|
||
| S3 object | ||
| --------- | ||
| Bucket : %s | ||
| Key : %s | ||
| Worktree name : %s | ||
| Size (bytes) : %d | ||
| SHA256 (meta) : %s | ||
| ETag : %s | ||
| Last modified : %s | ||
|
|
||
| Worktree | ||
| ------------- | ||
| path : %s | ||
| tracked by LFS : %v |
There was a problem hiding this comment.
isLFSTracked is only used for printing and no longer enforces that the target path is tracked by Git LFS, whereas the previous AddURL implementation (client/indexd/add_url.go:304-316) explicitly failed when the path was not LFS-tracked. This allows git drs add-url to succeed while writing a pointer file for a path that Git LFS will never manage, which can leave consumers with a plain text pointer instead of real data and undermines the goal of avoiding incomplete LFS contracts. Consider treating a non‑LFS‑tracked path as an error (with a clear message about running git lfs track and committing .gitattributes), to preserve the earlier safety guarantees.
cmd/addurl/main.go
Outdated
| // 5) write pointer file in working tree | ||
| pointer := fmt.Sprintf( | ||
| "version https://git-lfs.github.com/spec/v1\noid sha256:%s\nsize %d\n", | ||
| oid, info.SizeBytes, | ||
| ) | ||
| // write pointer file to working tree pathArg | ||
| if pathArg == "" { | ||
| return fmt.Errorf("empty worktree path") | ||
| } | ||
| safePath := filepath.Clean(pathArg) | ||
| dir := filepath.Dir(safePath) | ||
| if dir != "." && dir != "/" { | ||
| if err := os.MkdirAll(dir, 0755); err != nil { | ||
| return fmt.Errorf("mkdir %s: %w", dir, err) | ||
| } | ||
| } | ||
| if err = os.WriteFile(safePath, []byte(pointer), 0644); err != nil { | ||
| return fmt.Errorf("write %s: %w", safePath, err) | ||
| } |
There was a problem hiding this comment.
The new implementation writes the Git LFS pointer file to safePath but never stages it with git add, whereas the previous generatePointerFile helper also ran git add on the created pointer. This means git drs add-url no longer ensures that the pointer is added to the index, so callers expecting the command to fully "add" the file must remember to run git add themselves, which is a behavioral regression and easy to miss in scripts. Consider either restoring the git add step (or equivalent) here or clearly changing the command’s contract and documentation to reflect that it only writes the file without staging it.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
You had talked in standup about how there might be too many lines of code in this PR and things could be simplified.
Wrote a PR that implements a Go caching library instead of all the boiler plate that you had it should cut the caching code in half but added another testing file so that's why the +/- is only net -300 ish lines:
https://github.com/calypr/git-drs/pull/193/changes
| go-version-file: go.mod | ||
|
|
||
| - name: Run tests | ||
| run: go test -v -race $(go list ./... | grep -v 'tests/integration/calypr' | grep -v 'client/indexd/tests') |
There was a problem hiding this comment.
this is going to bork some integration tests I had on another branch probably better to keep this how it is an a your | grep -v '/cmd/addurl$' string to it
| // AddURLService groups injectable dependencies used to implement the add-url | ||
| // behavior (logger factory, S3 inspection, LFS helpers, config loader, etc.). | ||
| type AddURLService struct { | ||
| newLogger func(string, bool) (*slog.Logger, error) |
There was a problem hiding this comment.
this technically works, but it's not idiomatic go. Let's keep it for now since alot of changes are being made but in the future it will probably need to be refactored to use an interface-first approach since that is how dependency injection is done in go.
| } | ||
|
|
||
| myLogger.Debug("~~~~~~~~~~~~~ START: pre-push ~~~~~~~~~~~~~") | ||
| type PrePushService struct { |
There was a problem hiding this comment.
like I said earlier this pattern is generally not advisable, but lets keep it for now.
|
|
||
| // Cache provides read-only access to the `.git/drs/pre-commit` cache. | ||
| // Use Open to construct an instance with correct paths resolved. | ||
| type Cache struct { |
There was a problem hiding this comment.
This works, but there are a-lot of caching libraries that exist in Go that will do a-lot of this boiler plate for you, see maypok86.github.io/otter/blog/cache-evolution
I've used otterv2 before, it's pretty good. The attached PR uses that library to cut down on the amount of code.
|
@matthewpeterkort closing this and continuing on #197 |
See #141