Skip to content

Conversation

@luxass
Copy link
Contributor

@luxass luxass commented Sep 18, 2025

resolves #11739

* Added `RawURL` to store the raw URL of the gist file.
* Added `Truncated` to indicate if the content is truncated.
* Implements a new function to fetch the raw content of a gist using its `rawURL`.
* Handles HTTP requests and responses, including error management for non-200 status codes.
* Added logic to fetch the full content of a gist file if it is marked as truncated.
* Utilizes the `GetRawGistFile` function to obtain the complete content from the provided `RawURL`.
* Added logic to fetch full content for truncated files when updating a gist.
* Utilizes `shared.GetRawGistFile` to retrieve the complete content based on the `RawURL`.
- Implemented unit tests for various scenarios in `GetRawGistFile`.
- Covered cases including successful requests, error handling, and different content types.
- Ensured proper verification of HTTP responses and error messages.
* Implement tests for editing gists that contain truncated files.
* Ensure that both single and multiple truncated files are handled correctly.
* Mock responses for retrieving full content from raw URLs for truncated files.
* Added test cases for viewing truncated files with and without the raw flag.
* Included scenarios for multiple files with truncation behavior.
* Ensured correct output is returned for truncated files when accessed via raw URLs.
@luxass luxass marked this pull request as ready for review September 20, 2025 05:50
@luxass luxass requested a review from a team as a code owner September 20, 2025 05:50
@luxass luxass requested review from babakks and Copilot September 20, 2025 05:50
@cliAutomation cliAutomation added the external pull request originating outside of the CLI core team label Sep 20, 2025
Copy link
Contributor

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 implements support for editing and viewing large files in GitHub gists by automatically fetching the full content from raw URLs when files are marked as truncated.

  • Adds automatic retrieval of full content for truncated gist files
  • Implements a new GetRawGistFile function to fetch content from raw URLs
  • Extends the GistFile struct to include RawURL and Truncated fields

Reviewed Changes

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

Show a summary per file
File Description
pkg/cmd/gist/shared/shared.go Adds new fields to GistFile struct and implements GetRawGistFile function
pkg/cmd/gist/view/view.go Modifies view logic to fetch full content for truncated files
pkg/cmd/gist/edit/edit.go Updates edit logic to handle truncated files before editing
pkg/cmd/gist/shared/shared_test.go Adds comprehensive tests for the new GetRawGistFile function
pkg/cmd/gist/view/view_test.go Adds test cases for viewing truncated files
pkg/cmd/gist/edit/edit_test.go Adds test cases for editing truncated files

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

Copy link
Member

@BagToad BagToad left a comment

Choose a reason for hiding this comment

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

Hey @luxass 👋 Thanks for your patience!

I've been reviewing this with @babakks and it's pretty good, but we're concerned about fetching the full gist content of each file attached to a gist even when the user hasn't selected to edit it.

We think it would be better to fetch only the raw content of a particular gist when the user has selected to edit a gist where truncated is true. If the user doesn't select a gist where truncated is true, we don't need to fetch the full raw file content.

As we investigated and reviewed further, I realized that we're also sending the full list of files to the API anytime we post an update, even though some are unchanged 🤔 I believe this to be part of the underlying cause of the data loss reported in #11739 since any file that is truncated will be updated with the truncated contents instead of the full contents:

Files from the previous version of the gist that aren't explicitly changed during an edit are unchanged.

I believe this documentation point means we could omit gist files from the upload payload that the user doesn't change. Doing so in conjunction with only fetching raw gist content when needed by the user seems like the ideal experience.

I think we need to do some testing to ensure I'm interpreting the API documentation correctly, and then if so, change our approach to:

  • only fetch raw gist content when needed
  • only include files that have changed in the gist update request

Let me know if that makes sense and if you're interested in helping out with this still 🙏

@luxass
Copy link
Contributor Author

luxass commented Oct 30, 2025

Hi @BagToad

That makes total sense 👍🏻 I will continue work on this PR.

This change makes it only fetch the raw content, when we wanna edit the specific file.
There was some issues when sending the full list of files, some of them would be truncated.
@luxass
Copy link
Contributor Author

luxass commented Oct 30, 2025

The only thing I'm missing is some more tests - otherwise i think it is ready to go. 🙂

I've verified that the behavior

Files from the previous version of the gist that aren't explicitly updated during an edit remain unchanged

is correct and working 👍🏻

@BagToad
Copy link
Member

BagToad commented Oct 30, 2025

Thank you very much @luxass! ✨ And great work!

The only thing I'm missing is some more tests - otherwise i think it is ready to go. 🙂

I took a stab at filling in the gaps on the tests, feel free to commit if you think they look good:

Show Diff

diff --git a/pkg/cmd/gist/edit/edit_test.go b/pkg/cmd/gist/edit/edit_test.go
index 296ae3261..ac1555f5c 100644
--- a/pkg/cmd/gist/edit/edit_test.go
+++ b/pkg/cmd/gist/edit/edit_test.go
@@ -237,6 +237,33 @@ func Test_editRun(t *testing.T) {
 				},
 			},
 		},
+		{
+			name: "single file edit flag sends only edited file",
+			opts: &EditOptions{
+				Selector:     "1234",
+				EditFilename: "unix.md",
+			},
+			mockGist: &shared.Gist{
+				ID: "1234",
+				Files: map[string]*shared.GistFile{
+					"cicada.txt": {Filename: "cicada.txt", Content: "bwhiizzzbwhuiiizzzz", Type: "text/plain"},
+					"unix.md":    {Filename: "unix.md", Content: "meow", Type: "text/markdown"},
+				},
+				Owner: &shared.GistOwner{Login: "octocat"},
+			},
+			httpStubs: func(reg *httpmock.Registry) {
+				reg.Register(httpmock.REST("POST", "gists/1234"), httpmock.StatusStringResponse(201, "{}"))
+			},
+			wantLastRequestParameters: map[string]interface{}{
+				"description": "",
+				"files": map[string]interface{}{
+					"unix.md": map[string]interface{}{
+						"content":  "new file content",
+						"filename": "unix.md",
+					},
+				},
+			},
+		},
 		{
 			name:  "multiple files, cancel, with TTY",
 			isTTY: true,
@@ -645,6 +672,48 @@ func Test_editRun(t *testing.T) {
 				},
 			},
 		},
+		{
+			name:  "interactive truncated multi-file gist fetches only selected file raw content the first time",
+			isTTY: true,
+			opts:  &EditOptions{Selector: "1234"},
+			prompterStubs: func(pm *prompter.MockPrompter) {
+				pm.RegisterSelect("Edit which file?", []string{"also-truncated.txt", "large.txt"}, func(_, _ string, opts []string) (int, error) {
+					return prompter.IndexFor(opts, "large.txt")
+				})
+				pm.RegisterSelect("What next?", editNextOptions, func(_, _ string, opts []string) (int, error) {
+					return prompter.IndexFor(opts, "Edit another file")
+				})
+				// Editing large.txt twice to ensure that fetch for the raw URL happens only once
+				pm.RegisterSelect("Edit which file?", []string{"also-truncated.txt", "large.txt"}, func(_, _ string, opts []string) (int, error) {
+					return prompter.IndexFor(opts, "large.txt")
+				})
+				pm.RegisterSelect("What next?", editNextOptions, func(_, _ string, opts []string) (int, error) {
+					return prompter.IndexFor(opts, "Submit")
+				})
+			},
+			mockGist: &shared.Gist{
+				ID: "1234",
+				Files: map[string]*shared.GistFile{
+					"large.txt":          {Filename: "large.txt", Content: "This is truncated content...", Type: "text/plain", Truncated: true, RawURL: "https://gist.githubusercontent.com/user/1234/raw/large.txt"},
+					"also-truncated.txt": {Filename: "also-truncated.txt", Content: "stuff...", Type: "text/plain", Truncated: true, RawURL: "https://gist.githubusercontent.com/user/1234/raw/also-truncated.txt"},
+				},
+				Owner: &shared.GistOwner{Login: "octocat"},
+			},
+			httpStubs: func(reg *httpmock.Registry) {
+				reg.Register(httpmock.REST("POST", "gists/1234"), httpmock.StatusStringResponse(201, "{}"))
+				// Explicity exclude also-truncated.txt raw URL to ensure it is not fetched since we did not select it.
+				reg.Exclude(t, httpmock.REST("GET", "user/1234/raw/also-truncated.txt"))
+			},
+			wantLastRequestParameters: map[string]interface{}{
+				"description": "",
+				"files": map[string]interface{}{
+					"large.txt": map[string]interface{}{
+						"content":  "new file content",
+						"filename": "large.txt",
+					},
+				},
+			},
+		},
 	}
 
 	for _, tt := range tests {

…-file selection

Co-authored-by: Kynan Ware <47394200+BagToad@users.noreply.github.com>
@luxass
Copy link
Contributor Author

luxass commented Oct 30, 2025

Thanks a lot for adding those tests - I've pushed them 👍🏻

They were very close to what I had in mind, but I couldn't quite find a nice way to assert that the raw URL was only hit the first time. Your version nailed that part, so thanks 🙌🏻

Copy link
Member

@BagToad BagToad left a comment

Choose a reason for hiding this comment

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

Alright I think this looks great! Thanks much for the collaboration on this one! 🍻

@BagToad BagToad merged commit 20c7bdc into cli:trunk Oct 31, 2025
10 of 11 checks passed
@luxass luxass deleted the gist-edit-large-file branch October 31, 2025 03:05
tmeijn pushed a commit to tmeijn/dotfiles that referenced this pull request Nov 6, 2025
This MR contains the following updates:

| Package | Update | Change |
|---|---|---|
| [cli/cli](https://github.com/cli/cli) | minor | `v2.82.1` -> `v2.83.0` |

MR created with the help of [el-capitano/tools/renovate-bot](https://gitlab.com/el-capitano/tools/renovate-bot).

**Proposed changes to behavior should be submitted there as MRs.**

---

### Release Notes

<details>
<summary>cli/cli (cli/cli)</summary>

### [`v2.83.0`](https://github.com/cli/cli/releases/tag/v2.83.0): GitHub CLI 2.83.0

[Compare Source](cli/cli@v2.82.1...v2.83.0)

#### What's Changed

##### ✨ Features

- Add `isImmutable` to `release list` JSON output by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12064](cli/cli#12064)
- `gh agent-task create`: support `--custom-agent`/`-a` flag by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;12068](cli/cli#12068)
- 💡 (gh repo delete) Add warning when `--yes` is ignored without a repository, Closes: [#&#8203;12033](cli/cli#12033) by [@&#8203;Shion1305](https://github.com/Shion1305) in [#&#8203;12039](cli/cli#12039)
- feat: implement gh `pr revert` by [@&#8203;lucasmelin](https://github.com/lucasmelin) in [#&#8203;8826](cli/cli#8826)

##### 🐛 Fixes

- fix(gist): add support for editing & viewing large files  by [@&#8203;luxass](https://github.com/luxass) in [#&#8203;11761](cli/cli#11761)
- Fix gh attestation verify to work when Public Good Instance of Sigstore is unavailable by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;11989](cli/cli#11989)

##### 📚 Docs & Chores

- chore: add basic linters by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12084](cli/cli#12084)
- CI: Update lint govulncheck to use source mode by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;12089](cli/cli#12089)
- chore: add `workflow_dispatch` to govulncheck triggers by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12085](cli/cli#12085)
- Exclude `third-party` from Golangci-lint formatting paths by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12058](cli/cli#12058)
- Apply `go fix` to remove deprecated `// +build` tags by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12056](cli/cli#12056)
- Bump Golangci-lint to `v2.6.0` by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12049](cli/cli#12049)
- Mention `pr checks` in `run list` docs by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12050](cli/cli#12050)
- Fix typo in comment for `gh issue develop` branch checkout command by [@&#8203;jonzfisher](https://github.com/jonzfisher) in [#&#8203;12042](cli/cli#12042)
- Use "release" sentinel value for release attestation verification by [@&#8203;Copilot](https://github.com/Copilot) in [#&#8203;11991](cli/cli#11991)
- Improve docstring for release-create by [@&#8203;bdehamer](https://github.com/bdehamer) in [#&#8203;11945](cli/cli#11945)
- Improve `api` command docs around `--input` and `--field` by [@&#8203;babakks](https://github.com/babakks) in [#&#8203;12062](cli/cli#12062)
- Fix `--interval` flags docs in `gh pr checks` by [@&#8203;2003Aditya](https://github.com/2003Aditya) in [#&#8203;12053](cli/cli#12053)

##### :dependabot: Dependencies

- Bump Go to 1.25.3 by [@&#8203;github-actions](https://github.com/github-actions)\[bot] in [#&#8203;11926](cli/cli#11926)
- chore(deps): bump github.com/cli/go-gh/v2 from 2.12.2 to 2.13.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12095](cli/cli#12095)
- Update Go toolchain version to 1.24.9 by [@&#8203;BagToad](https://github.com/BagToad) in [#&#8203;12054](cli/cli#12054)
- chore(deps): bump golang.org/x/text from 0.29.0 to 0.30.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;11973](cli/cli#11973)
- chore(deps): bump golang.org/x/crypto from 0.42.0 to 0.43.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;11974](cli/cli#11974)
- chore(deps): bump actions/upload-artifact from 4 to 5 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12031](cli/cli#12031)
- chore(deps): bump actions/download-artifact from 5 to 6 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12032](cli/cli#12032)
- chore(deps): bump github.com/rivo/tview from 0.0.0-20250625164341-a4a78f1e05cb to 0.42.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;12000](cli/cli#12000)
- chore(deps): bump goreleaser/goreleaser-action from 6.3.0 to 6.4.0 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;11509](cli/cli#11509)
- chore(deps): bump mislav/bump-homebrew-formula-action from 3.4 to 3.6 by [@&#8203;dependabot](https://github.com/dependabot)\[bot] in [#&#8203;11750](cli/cli#11750)

#### New Contributors

- [@&#8203;lucasmelin](https://github.com/lucasmelin) made their first contribution in [#&#8203;8826](cli/cli#8826)
- [@&#8203;jonzfisher](https://github.com/jonzfisher) made their first contribution in [#&#8203;12042](cli/cli#12042)
- [@&#8203;2003Aditya](https://github.com/2003Aditya) made their first contribution in [#&#8203;12053](cli/cli#12053)

**Full Changelog**: <cli/cli@v2.82.1...v2.83.0>

</details>

---

### Configuration

📅 **Schedule**: Branch creation - At any time (no schedule defined), Automerge - At any time (no schedule defined).

🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied.

♻ **Rebasing**: Whenever MR becomes conflicted, or you tick the rebase/retry checkbox.

🔕 **Ignore**: Close this MR and you won't be reminded about this update again.

---

 - [ ] <!-- rebase-check -->If you want to rebase/retry this MR, check this box

---

This MR has been generated by [Renovate Bot](https://github.com/renovatebot/renovate).
<!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0MS4xNjkuMyIsInVwZGF0ZWRJblZlciI6IjQxLjE2OS4zIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6WyJSZW5vdmF0ZSBCb3QiXX0=-->
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

external pull request originating outside of the CLI core team

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug] gh gist edit truncates large gist file and risks data loss

4 participants