-
Notifications
You must be signed in to change notification settings - Fork 9
Add Git LFS support with embedded runtime, tracking rules, and file downloads #25
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
d4db59a
feat: implement git large file storage
Tranthanh98 7fd3ac8
feat: Add automatic hook integration for Git LFS setup
Tranthanh98 48c08a9
Merge branch 'main' into codex/implement-git-large-file-storage
Tranthanh98 830b934
fix: Avoid redundant LFS probes for metadata-only git commands
Tranthanh98 032dcca
feat: add limit feature for git lfs
Tranthanh98 fb6e126
Merge branch 'main' into codex/implement-git-large-file-storage
Tranthanh98 f3bd2f6
fix: Show conflict badge inline and stabilize LFS loading state
Tranthanh98 3b97e3c
Fix Git LFS review findings and clone recovery
Tranthanh98 516d78f
Preserve canonical hooks config key during LFS rollback
Tranthanh98 6f42484
Preserve LFS setup failure when rollback fails
Tranthanh98 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| # Git LFS in Commit+ | ||
|
|
||
| Git LFS stores large file content separately from the small pointer committed to Git. Use it for versioned binary assets such as design files, video, and datasets. Keep generated files and caches in `.gitignore` instead. | ||
|
|
||
| ## Start using Git LFS | ||
|
|
||
| 1. Open **Git LFS** in the repository sidebar. | ||
| 2. If a compatible runtime is missing, choose **Download & Continue**. Commit+ downloads the official Git LFS 3.8.0 archive for this Mac, checks its size and SHA-256, and installs a private copy. Homebrew and administrator access are not required. | ||
| 3. Choose **Set Up Git LFS…** to configure this repository's filters and pre-push hook. | ||
| 4. Open **Tracking Rules**, enter a pattern such as `*.psd`, review it, and apply. For an individual filename, enable **Exact filename**. You can also start from a file's **Track with Git LFS…** context menu in File Status. | ||
| 5. Review and stage `.gitattributes` and the affected files in File Status, then commit and push normally. | ||
|
|
||
| The setup action automatically integrates existing hooks, including a custom `core.hooksPath`. Commit+ installs a repository-local hook directory that forwards to the original hooks and runs Git LFS. Original hook files remain unchanged. Pre-push hooks receive the same arguments and input as LFS; a failing original hook stops the push. With worktree-specific configuration enabled, setup applies to the selected worktree. | ||
|
|
||
| ## Choose the runtime | ||
|
|
||
| **Settings → Git → Git LFS Installation** offers Automatic, System, and Embedded independently of Git Runtime. Automatic prefers a compatible system installation. System Git LFS must be version 3.8 or later. Downloading Embedded LFS selects it after successful installation. Settings and binaries stay local to this Mac. | ||
|
|
||
| The selected runtime is also used by clean/smudge filters and pre-push hooks, including when Embedded Git contains another `git-lfs` executable. A cancelled or failed installation preserves the previous runtime and preference. Refresh Git LFS Information after installing or changing a system executable externally. | ||
|
|
||
| ## Existing files and history | ||
|
|
||
| Adding a rule does not rewrite earlier commits. To convert a file already stored as a normal Git blob, choose **Convert Existing File…** under Tracking Rules. Review **Convert & Stage**, then commit the staged pointer and relevant attributes changes. Commit+ rejects files with existing staged changes to preserve partial staging. | ||
|
|
||
| This conversion does not shrink existing history. History migration, server-side file locking, cache pruning, and provider quota dashboards are outside this release. | ||
|
|
||
| ## Download and inspect content | ||
|
|
||
| - **Download Missing** downloads content for the current checkout using the selected remote and existing LFS include/exclude settings. | ||
| - **Download Selected** uses an explicit file selection. Filenames that cannot be represented safely in the CLI's include-filter syntax are rejected rather than broadening the download. | ||
| - **Restore Content** materializes cached content without downloading. Modified files are preserved. | ||
| - File states describe local content, not proof that the remote has received an object. | ||
| - **History → Browse Repository at Revision** recognizes valid LFS pointers, previews verified cached content within the existing 2 MB limit, and offers **Download for Preview**. Downloads for old revisions only change the cache, never the working tree. | ||
|
|
||
| The Files table supports search, sorting, state filtering, and multiple selection. Explicit downloads show per-file byte progress when reported by the CLI, with an indeterminate fallback. Tracking Rules lists each pattern and source; inherited and excluded rules remain read-only in the table. Find Large Files scans metadata on demand, excludes ignored files, and defaults to a 50 MiB suggestion threshold. That threshold is a UI suggestion, not a provider upload limit. | ||
|
|
||
| ## Clone and recovery | ||
|
|
||
| Clone has a **Download LFS content** option. Git data is cloned first. If LFS setup or downloading fails, the completed clone is retained; choose **Open Cloned Repository**, **Retry LFS Download**, or **Download Git LFS & Continue** when a runtime is missing. Disabling LFS downloading leaves pointers for later setup/download. | ||
|
|
||
| Ordinary push uses the repository's LFS pre-push hook. Background Git fetch does not trigger an additional LFS download. Authentication, network, quota, missing-object, and disk-space failures are surfaced with recovery guidance. Remote storage and bandwidth limits are controlled by the hosting provider. | ||
|
|
||
| ## Validation | ||
|
|
||
| Automated coverage includes pointer validation, unusual filenames, include-filter safety, credential host isolation, custom hook preservation, concurrent edits, partial staging, local push/clone/download round trips, runtime selection, and verified Embedded installation. Integration tests use `COMMITPLUS_TEST_LFS` for a test executable; the installer test uses `COMMITPLUS_TEST_LFS_ARCHIVE` for the official archive matching the host architecture. Neither test downloads or changes the user's runtime preference. | ||
|
|
||
| Interactive UI behavior and live provider authentication/quota responses require separate manual verification. Recursive submodule LFS setup/download is not coordinated by the root repository's LFS screen; open each submodule repository to manage its LFS state. | ||
|
|
||
| Sources: [Git LFS](https://git-lfs.com/), [official command documentation](https://github.com/git-lfs/git-lfs/tree/v3.8.0/docs/man), [pinned runtime release](https://github.com/git-lfs/git-lfs/releases/tag/v3.8.0). |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,8 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSCandidate: Identifiable, Sendable { | ||
| let path: String | ||
| let size: Int64 | ||
| var id: String { path } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| struct GitLFSCloneRecoveryError: LocalizedError { | ||
| let repository: URL | ||
| let reason: String | ||
| var errorDescription: String? { | ||
| "Git clone completed, but Git LFS content is not ready. You can open the repository and finish setup/download in Git LFS.\n\n\(reason)" | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,10 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSConversionReview: Identifiable, Sendable { | ||
| let id = UUID() | ||
| let path: String | ||
| let contentHash: String | ||
| let indexEntry: String | ||
| let attributes: String | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSFile: Decodable, Identifiable, Sendable { | ||
| let name: String | ||
| let size: Int64 | ||
| let checkout: Bool | ||
| let downloaded: Bool | ||
| let oid: String | ||
| var modification: String? = nil | ||
| var id: String { name } | ||
| var localState: String { modification ?? (checkout ? "Available" : downloaded ? "Cached · Restore Content" : "Not downloaded") } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,6 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSFileList: Decodable, Sendable { | ||
| let files: [GitLFSFile]? | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,22 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSPointer: Equatable, Sendable { | ||
| let oid: String | ||
| let size: Int64 | ||
|
|
||
| init?(_ text: String) { | ||
| guard text.utf8.count <= 1024 else { return nil } | ||
| let lines = text.replacingOccurrences(of: "\r\n", with: "\n").split(separator: "\n") | ||
| guard lines.first == "version https://git-lfs.github.com/spec/v1" else { return nil } | ||
| let oids = lines.filter { $0.hasPrefix("oid sha256:") } | ||
| let sizes = lines.filter { $0.hasPrefix("size ") } | ||
| guard oids.count == 1, sizes.count == 1, | ||
| let size = Int64(sizes[0].dropFirst(5)), size >= 0 else { return nil } | ||
| let oid = String(oids[0].dropFirst(11)) | ||
| guard oid.count == 64, oid.utf8.allSatisfy({ (48...57).contains($0) || (97...102).contains($0) }), | ||
| lines.dropFirst().allSatisfy({ $0.hasPrefix("oid sha256:") || $0.hasPrefix("size ") || $0.hasPrefix("ext-") }) else { return nil } | ||
| self.oid = oid | ||
| self.size = size | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSSnapshot: Sendable { | ||
| let files: [GitLFSFile] | ||
| let rules: String | ||
| let branch: String | ||
| let remotes: [String] | ||
| let suggestedRemote: String? | ||
| let setupIssue: String? | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,11 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSTrackingReview: Identifiable, Sendable { | ||
| let id = UUID() | ||
| let pattern: String | ||
| let literal: Bool | ||
| let removing: Bool | ||
| let attributes: Data? | ||
| let preview: String | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSTrackingRule: Identifiable, Sendable { | ||
| let pattern: String | ||
| let source: String | ||
| let excluded: Bool | ||
| var id: String { source + "\0" + pattern + "\0" + String(excluded) } | ||
| var canRemove: Bool { source == ".gitattributes" && !excluded && !pattern.hasPrefix("\"") } | ||
|
|
||
| /// Presentation only. Git remains authoritative for effective attributes and edits. | ||
| static func displayRules(_ listing: String) -> [Self] { | ||
| var excluded = false | ||
| var result: [Self] = [] | ||
| var seen = Set<String>() | ||
| for line in listing.split(separator: "\n") { | ||
| if line == "Listing excluded patterns" { excluded = true; continue } | ||
| guard line.hasPrefix(" "), line.hasSuffix(")"), | ||
| let separator = line.range(of: " (", options: .backwards) else { continue } | ||
| let pattern = String(line.dropFirst(4)[..<separator.lowerBound]) | ||
| let source = String(line[separator.upperBound...].dropLast()) | ||
| let rule = Self(pattern: pattern, source: source, excluded: excluded) | ||
| if seen.insert(rule.id).inserted { result.append(rule) } | ||
| } | ||
| return result | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,27 @@ | ||
| // SPDX-License-Identifier: AGPL-3.0-or-later | ||
| import Foundation | ||
|
|
||
| nonisolated struct GitLFSTransferProgress: Equatable, Sendable { | ||
| let direction: String | ||
| let fileIndex: Int | ||
| let fileCount: Int | ||
| let bytes: Int64 | ||
| let totalBytes: Int64 | ||
| let name: String | ||
|
|
||
| init?(_ line: String) { | ||
| let fields = line.split(separator: " ", maxSplits: 3) | ||
| guard fields.count == 4, ["download", "upload", "checkout"].contains(fields[0]) else { return nil } | ||
| let files = fields[1].split(separator: "/") | ||
| let sizes = fields[2].split(separator: "/") | ||
| guard files.count == 2, sizes.count == 2, let index = Int(files[0]), let count = Int(files[1]), | ||
| let bytes = Int64(sizes[0]), let total = Int64(sizes[1]), index > 0, count >= index, | ||
| bytes >= 0, total > 0, bytes <= total else { return nil } | ||
| direction = String(fields[0]) | ||
| fileIndex = index | ||
| fileCount = count | ||
| self.bytes = bytes | ||
| totalBytes = total | ||
| name = String(fields[3]) | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.