fork of go-git with some jj specific features

git: clone --shared implemented

Changed files
+353 -173
plumbing
storage
filesystem
memory
transactional
+1
.gitignore
··· 4 4 profile.out 5 5 .tmp/ 6 6 .git-dist/ 7 + .vscode
+172 -172
COMPATIBILITY.md
··· 5 5 6 6 ## Getting and creating repositories 7 7 8 - | Feature | Sub-feature | Status | Notes | Examples | 9 - |---|---|---|---|---| 10 - | `init` | | ✅ | | | 11 - | `init` | `--bare` | ✅ | | | 12 - | `init` | `--template` <br/> `--separate-git-dir` <br/> `--shared` | ❌ | | | 13 - | `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | 14 - | `clone` | Authentication: <br/> - none <br/> - access token <br/> - username + password <br/> - ssh | ✅ | | - [clone ssh](_examples/clone/auth/ssh/main.go) <br/> - [clone access token](_examples/clone/auth/basic/access_token/main.go) <br/> - [clone user + password](_examples/clone/auth/basic/username_password/main.go) | 15 - | `clone` | `--progress` <br/> `--single-branch` <br/> `--depth` <br/> `--origin` <br/> `--recurse-submodules` | ✅ | | - [recurse submodules](_examples/clone/main.go) <br/> - [progress](_examples/progress/main.go) | 8 + | Feature | Sub-feature | Status | Notes | Examples | 9 + | ------- | ------------------------------------------------------------------------------------------------------------------ | ------ | ----- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | 10 + | `init` | | ✅ | | | 11 + | `init` | `--bare` | ✅ | | | 12 + | `init` | `--template` <br/> `--separate-git-dir` <br/> `--shared` | ❌ | | | 13 + | `clone` | | ✅ | | - [PlainClone](_examples/clone/main.go) | 14 + | `clone` | Authentication: <br/> - none <br/> - access token <br/> - username + password <br/> - ssh | ✅ | | - [clone ssh](_examples/clone/auth/ssh/main.go) <br/> - [clone access token](_examples/clone/auth/basic/access_token/main.go) <br/> - [clone user + password](_examples/clone/auth/basic/username_password/main.go) | 15 + | `clone` | `--progress` <br/> `--single-branch` <br/> `--depth` <br/> `--origin` <br/> `--recurse-submodules` <br/>`--shared` | ✅ | | - [recurse submodules](_examples/clone/main.go) <br/> - [progress](_examples/progress/main.go) | 16 16 17 17 ## Basic snapshotting 18 18 19 - | Feature | Sub-feature | Status | Notes | Examples | 20 - |---|---|---|---|---| 21 - | `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | 22 - | `status` | | ✅ | | | 23 - | `commit` | | ✅ | | - [commit](_examples/commit/main.go) | 24 - | `reset` | | ✅ | | | 25 - | `rm` | | ✅ | | | 26 - | `mv` | | ✅ | | | 19 + | Feature | Sub-feature | Status | Notes | Examples | 20 + | -------- | ----------- | ------ | -------------------------------------------------------- | ------------------------------------ | 21 + | `add` | | ✅ | Plain add is supported. Any other flags aren't supported | | 22 + | `status` | | ✅ | | | 23 + | `commit` | | ✅ | | - [commit](_examples/commit/main.go) | 24 + | `reset` | | ✅ | | | 25 + | `rm` | | ✅ | | | 26 + | `mv` | | ✅ | | | 27 27 28 28 ## Branching and merging 29 29 30 - | Feature | Sub-feature | Status | Notes | Examples | 31 - |---|---|---|---|---| 32 - | `branch` | | ✅ | | - [branch](_examples/branch/main.go) | 33 - | `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | 34 - | `merge` | | ❌ | | | 35 - | `mergetool` | | ❌ | | | 36 - | `stash` | | ❌ | | | 37 - | `tag` | | ✅ | | - [tag](_examples/tag/main.go) <br/> - [tag create and push](_examples/tag-create-push/main.go) | 30 + | Feature | Sub-feature | Status | Notes | Examples | 31 + | ----------- | ----------- | ------ | --------------------------------------- | ----------------------------------------------------------------------------------------------- | 32 + | `branch` | | ✅ | | - [branch](_examples/branch/main.go) | 33 + | `checkout` | | ✅ | Basic usages of checkout are supported. | - [checkout](_examples/checkout/main.go) | 34 + | `merge` | | ❌ | | | 35 + | `mergetool` | | ❌ | | | 36 + | `stash` | | ❌ | | | 37 + | `tag` | | ✅ | | - [tag](_examples/tag/main.go) <br/> - [tag create and push](_examples/tag-create-push/main.go) | 38 38 39 39 ## Sharing and updating projects 40 40 41 - | Feature | Sub-feature | Status | Notes | Examples | 42 - |---|---|---|---|---| 43 - | `fetch` | | ✅ | | | 44 - | `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | 45 - | `push` | | ✅ | | - [push](_examples/push/main.go) | 46 - | `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | 47 - | `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | 48 - | `submodule` | deinit | ❌ | | | 41 + | Feature | Sub-feature | Status | Notes | Examples | 42 + | ----------- | ----------- | ------ | ----------------------------------------------------------------------- | ------------------------------------------ | 43 + | `fetch` | | ✅ | | | 44 + | `pull` | | ✅ | Only supports merges where the merge can be resolved as a fast-forward. | - [pull](_examples/pull/main.go) | 45 + | `push` | | ✅ | | - [push](_examples/push/main.go) | 46 + | `remote` | | ✅ | | - [remotes](_examples/remotes/main.go) | 47 + | `submodule` | | ✅ | | - [submodule](_examples/submodule/main.go) | 48 + | `submodule` | deinit | ❌ | | | 49 49 50 50 ## Inspection and comparison 51 51 52 - | Feature | Sub-feature | Status | Notes | Examples | 53 - |---|---|---|---|---| 54 - | `show` | | ✅ | | | 55 - | `log` | | ✅ | | - [log](_examples/log/main.go) | 56 - | `shortlog` | | (see log) | | | 57 - | `describe` | | ❌ | | | 52 + | Feature | Sub-feature | Status | Notes | Examples | 53 + | ---------- | ----------- | --------- | ----- | ------------------------------ | 54 + | `show` | | ✅ | | | 55 + | `log` | | ✅ | | - [log](_examples/log/main.go) | 56 + | `shortlog` | | (see log) | | | 57 + | `describe` | | ❌ | | | 58 58 59 59 ## Patching 60 60 61 - | Feature | Sub-feature | Status | Notes | Examples | 62 - |---|---|---|---|---| 63 - | `apply` | | ❌ | | | 64 - | `cherry-pick` | | ❌ | | | 65 - | `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | 66 - | `rebase` | | ❌ | | | 67 - | `revert` | | ❌ | | | 61 + | Feature | Sub-feature | Status | Notes | Examples | 62 + | ------------- | ----------- | ------ | ---------------------------------------------------- | -------- | 63 + | `apply` | | ❌ | | | 64 + | `cherry-pick` | | ❌ | | | 65 + | `diff` | | ✅ | Patch object with UnifiedDiff output representation. | | 66 + | `rebase` | | ❌ | | | 67 + | `revert` | | ❌ | | | 68 68 69 69 ## Debugging 70 70 71 - | Feature | Sub-feature | Status | Notes | Examples | 72 - |---|---|---|---|---| 73 - | `bisect` | | ❌ | | | 74 - | `blame` | | ✅ | | - [blame](_examples/blame/main.go) | 75 - | `grep` | | ✅ | | | 71 + | Feature | Sub-feature | Status | Notes | Examples | 72 + | -------- | ----------- | ------ | ----- | ---------------------------------- | 73 + | `bisect` | | ❌ | | | 74 + | `blame` | | ✅ | | - [blame](_examples/blame/main.go) | 75 + | `grep` | | ✅ | | | 76 76 77 77 ## Email 78 78 79 - | Feature | Sub-feature | Status | Notes | Examples | 80 - |---|---|---|---|---| 81 - | `am` | | ❌ | | | 82 - | `apply` | | ❌ | | | 83 - | `format-patch` | | ❌ | | | 84 - | `send-email` | | ❌ | | | 85 - | `request-pull` | | ❌ | | | 79 + | Feature | Sub-feature | Status | Notes | Examples | 80 + | -------------- | ----------- | ------ | ----- | -------- | 81 + | `am` | | ❌ | | | 82 + | `apply` | | ❌ | | | 83 + | `format-patch` | | ❌ | | | 84 + | `send-email` | | ❌ | | | 85 + | `request-pull` | | ❌ | | | 86 86 87 87 ## External systems 88 88 89 - | Feature | Sub-feature | Status | Notes | Examples | 90 - |---|---|---|---|---| 91 - | `svn` | | ❌ | | | 92 - | `fast-import` | | ❌ | | | 93 - | `lfs` | | ❌ | | | 89 + | Feature | Sub-feature | Status | Notes | Examples | 90 + | ------------- | ----------- | ------ | ----- | -------- | 91 + | `svn` | | ❌ | | | 92 + | `fast-import` | | ❌ | | | 93 + | `lfs` | | ❌ | | | 94 94 95 95 ## Administration 96 96 97 - | Feature | Sub-feature | Status | Notes | Examples | 98 - |---|---|---|---|---| 99 - | `clean` | | ✅ | | | 100 - | `gc` | | ❌ | | | 101 - | `fsck` | | ❌ | | | 102 - | `reflog` | | ❌ | | | 103 - | `filter-branch` | | ❌ | | | 104 - | `instaweb` | | ❌ | | | 105 - | `archive` | | ❌ | | | 106 - | `bundle` | | ❌ | | | 107 - | `prune` | | ❌ | | | 108 - | `repack` | | ❌ | | | 97 + | Feature | Sub-feature | Status | Notes | Examples | 98 + | --------------- | ----------- | ------ | ----- | -------- | 99 + | `clean` | | ✅ | | | 100 + | `gc` | | ❌ | | | 101 + | `fsck` | | ❌ | | | 102 + | `reflog` | | ❌ | | | 103 + | `filter-branch` | | ❌ | | | 104 + | `instaweb` | | ❌ | | | 105 + | `archive` | | ❌ | | | 106 + | `bundle` | | ❌ | | | 107 + | `prune` | | ❌ | | | 108 + | `repack` | | ❌ | | | 109 109 110 110 ## Server admin 111 111 112 - | Feature | Sub-feature | Status | Notes | Examples | 113 - |---|---|---|---|---| 114 - | `daemon` | | ❌ | | | 115 - | `update-server-info` | | ❌ | | | 112 + | Feature | Sub-feature | Status | Notes | Examples | 113 + | -------------------- | ----------- | ------ | ----- | -------- | 114 + | `daemon` | | ❌ | | | 115 + | `update-server-info` | | ❌ | | | 116 116 117 117 ## Advanced 118 118 119 - | Feature | Sub-feature | Status | Notes | Examples | 120 - |---|---|---|---|---| 121 - | `notes` | | ❌ | | | 122 - | `replace` | | ❌ | | | 123 - | `worktree` | | ❌ | | | 124 - | `annotate` | | (see blame) | | | 119 + | Feature | Sub-feature | Status | Notes | Examples | 120 + | ---------- | ----------- | ----------- | ----- | -------- | 121 + | `notes` | | ❌ | | | 122 + | `replace` | | ❌ | | | 123 + | `worktree` | | ❌ | | | 124 + | `annotate` | | (see blame) | | | 125 125 126 126 ## GPG 127 127 128 - | Feature | Sub-feature | Status | Notes | Examples | 129 - |---|---|---|---|---| 130 - | `git-verify-commit` | | ✅ | | | 131 - | `git-verify-tag` | | ✅ | | | 128 + | Feature | Sub-feature | Status | Notes | Examples | 129 + | ------------------- | ----------- | ------ | ----- | -------- | 130 + | `git-verify-commit` | | ✅ | | | 131 + | `git-verify-tag` | | ✅ | | | 132 132 133 133 ## Plumbing commands 134 134 135 - | Feature | Sub-feature | Status | Notes | Examples | 136 - |---|---|---|---|---| 137 - | `cat-file` | | ✅ | | | 138 - | `check-ignore` | | ❌ | | | 139 - | `commit-tree` | | ❌ | | | 140 - | `count-objects` | | ❌ | | | 141 - | `diff-index` | | ❌ | | | 142 - | `for-each-ref` | | ✅ | | | 143 - | `hash-object` | | ✅ | | | 144 - | `ls-files` | | ✅ | | | 145 - | `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | 146 - | `merge-base` | `--independent` <br/> `--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | 147 - | `merge-base` | `--fork-point` <br/> `--octopus` | ❌ | | | 148 - | `read-tree` | | ❌ | | | 149 - | `rev-list` | | ✅ | | | 150 - | `rev-parse` | | ❌ | | | 151 - | `show-ref` | | ✅ | | | 152 - | `symbolic-ref` | | ✅ | | | 153 - | `update-index` | | ❌ | | | 154 - | `update-ref` | | ❌ | | | 155 - | `verify-pack` | | ❌ | | | 156 - | `write-tree` | | ❌ | | | 135 + | Feature | Sub-feature | Status | Notes | Examples | 136 + | --------------- | ------------------------------------- | ------------ | --------------------------------------------------- | -------------------------------------------- | 137 + | `cat-file` | | ✅ | | | 138 + | `check-ignore` | | ❌ | | | 139 + | `commit-tree` | | ❌ | | | 140 + | `count-objects` | | ❌ | | | 141 + | `diff-index` | | ❌ | | | 142 + | `for-each-ref` | | ✅ | | | 143 + | `hash-object` | | ✅ | | | 144 + | `ls-files` | | ✅ | | | 145 + | `ls-remote` | | ✅ | | - [ls-remote](_examples/ls-remote/main.go) | 146 + | `merge-base` | `--independent` <br/> `--is-ancestor` | ⚠️ (partial) | Calculates the merge-base only between two commits. | - [merge-base](_examples/merge_base/main.go) | 147 + | `merge-base` | `--fork-point` <br/> `--octopus` | ❌ | | | 148 + | `read-tree` | | ❌ | | | 149 + | `rev-list` | | ✅ | | | 150 + | `rev-parse` | | ❌ | | | 151 + | `show-ref` | | ✅ | | | 152 + | `symbolic-ref` | | ✅ | | | 153 + | `update-index` | | ❌ | | | 154 + | `update-ref` | | ❌ | | | 155 + | `verify-pack` | | ❌ | | | 156 + | `write-tree` | | ❌ | | | 157 157 158 158 ## Indexes and Git Protocols 159 159 160 - | Feature | Version | Status | Notes | 161 - |---|---|---|---| 162 - | index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | 163 - | index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | 164 - | index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | 165 - | pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | 166 - | pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | 167 - | multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 168 - | pack-*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 169 - | pack-*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 170 - | cruft packs | | ❌ | | 160 + | Feature | Version | Status | Notes | 161 + | -------------------- | ------------------------------------------------------------------------------- | ------ | ----- | 162 + | index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | 163 + | index | [v2](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ✅ | | 164 + | index | [v3](https://github.com/git/git/blob/master/Documentation/gitformat-index.txt) | ❌ | | 165 + | pack-protocol | [v1](https://github.com/git/git/blob/master/Documentation/gitprotocol-pack.txt) | ✅ | | 166 + | pack-protocol | [v2](https://github.com/git/git/blob/master/Documentation/gitprotocol-v2.txt) | ❌ | | 167 + | multi-pack-index | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 168 + | pack-\*.rev files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 169 + | pack-\*.mtimes files | [v1](https://github.com/git/git/blob/master/Documentation/gitformat-pack.txt) | ❌ | | 170 + | cruft packs | | ❌ | | 171 171 172 172 ## Capabilities 173 173 174 - | Feature | Status | Notes | 175 - |---|---|---| 176 - | `multi_ack` | ❌ | | 177 - | `multi_ack_detailed` | ❌ | | 178 - | `no-done` | ❌ | | 179 - | `thin-pack` | ❌ | | 180 - | `side-band` | ⚠️ (partial) | | 181 - | `side-band-64k` | ⚠️ (partial) | | 182 - | `ofs-delta` | ✅ | | 183 - | `agent` | ✅ | | 184 - | `object-format` | ❌ | | 185 - | `symref` | ✅ | | 186 - | `shallow` | ✅ | | 187 - | `deepen-since` | ✅ | | 188 - | `deepen-not` | ❌ | | 189 - | `deepen-relative` | ❌ | | 190 - | `no-progress` | ✅ | | 191 - | `include-tag` | ✅ | | 192 - | `report-status` | ✅ | | 193 - | `report-status-v2` | ❌ | | 194 - | `delete-refs` | ✅ | | 195 - | `quiet` | ❌ | | 196 - | `atomic` | ✅ | | 197 - | `push-options` | ✅ | | 198 - | `allow-tip-sha1-in-want` | ✅ | | 199 - | `allow-reachable-sha1-in-want` | ❌ | | 200 - | `push-cert=<nonce>` | ❌ | | 201 - | `filter` | ❌ | | 202 - | `session-id=<session id>` | ❌ | | 174 + | Feature | Status | Notes | 175 + | ------------------------------ | ------------ | ----- | 176 + | `multi_ack` | ❌ | | 177 + | `multi_ack_detailed` | ❌ | | 178 + | `no-done` | ❌ | | 179 + | `thin-pack` | ❌ | | 180 + | `side-band` | ⚠️ (partial) | | 181 + | `side-band-64k` | ⚠️ (partial) | | 182 + | `ofs-delta` | ✅ | | 183 + | `agent` | ✅ | | 184 + | `object-format` | ❌ | | 185 + | `symref` | ✅ | | 186 + | `shallow` | ✅ | | 187 + | `deepen-since` | ✅ | | 188 + | `deepen-not` | ❌ | | 189 + | `deepen-relative` | ❌ | | 190 + | `no-progress` | ✅ | | 191 + | `include-tag` | ✅ | | 192 + | `report-status` | ✅ | | 193 + | `report-status-v2` | ❌ | | 194 + | `delete-refs` | ✅ | | 195 + | `quiet` | ❌ | | 196 + | `atomic` | ✅ | | 197 + | `push-options` | ✅ | | 198 + | `allow-tip-sha1-in-want` | ✅ | | 199 + | `allow-reachable-sha1-in-want` | ❌ | | 200 + | `push-cert=<nonce>` | ❌ | | 201 + | `filter` | ❌ | | 202 + | `session-id=<session id>` | ❌ | | 203 203 204 204 ## Transport Schemes 205 205 206 - | Scheme | Status | Notes | Examples | 207 - |---|---|---|---| 208 - | `http(s)://` (dumb) | ❌ | | | 209 - | `http(s)://` (smart) | ✅ | | | 210 - | `git://` | ✅ | | | 211 - | `ssh://` | ✅ | | | 212 - | `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | 213 - | Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | 206 + | Scheme | Status | Notes | Examples | 207 + | -------------------- | ------------ | ---------------------------------------------------------------------- | ---------------------------------------------- | 208 + | `http(s)://` (dumb) | ❌ | | | 209 + | `http(s)://` (smart) | ✅ | | | 210 + | `git://` | ✅ | | | 211 + | `ssh://` | ✅ | | | 212 + | `file://` | ⚠️ (partial) | Warning: this is not pure Golang. This shells out to the `git` binary. | | 213 + | Custom | ✅ | All existing schemes can be replaced by custom implementations. | - [custom_http](_examples/custom_http/main.go) | 214 214 215 215 ## SHA256 216 216 217 - | Feature | Sub-feature | Status | Notes | Examples | 218 - |---|---|---|---|---| 219 - | `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | 220 - | `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | 221 - | `pull` | | ❌ | | | 222 - | `fetch` | | ❌ | | | 223 - | `push` | | ❌ | | | 217 + | Feature | Sub-feature | Status | Notes | Examples | 218 + | -------- | ----------- | ------ | ---------------------------------- | ------------------------------------ | 219 + | `init` | | ✅ | Requires building with tag sha256. | - [init](_examples/sha256/main.go) | 220 + | `commit` | | ✅ | Requires building with tag sha256. | - [commit](_examples/sha256/main.go) | 221 + | `pull` | | ❌ | | | 222 + | `fetch` | | ❌ | | | 223 + | `push` | | ❌ | | | 224 224 225 225 ## Other features 226 226 227 - | Feature | Sub-feature | Status | Notes | Examples | 228 - |---|---|---|---|---| 229 - | `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | 230 - | `config` | `--global` <br/> `--system` | ✅ | Read-only. | | 231 - | `gitignore` | | ✅ | | | 232 - | `gitattributes` | | ✅ | | | 233 - | `git-worktree` | | ❌ | Multiple worktrees are not supported. | | 227 + | Feature | Sub-feature | Status | Notes | Examples | 228 + | --------------- | --------------------------- | ------ | ---------------------------------------------- | -------- | 229 + | `config` | `--local` | ✅ | Read and write per-repository (`.git/config`). | | 230 + | `config` | `--global` <br/> `--system` | ✅ | Read-only. | | 231 + | `gitignore` | | ✅ | | | 232 + | `gitattributes` | | ✅ | | | 233 + | `git-worktree` | | ❌ | Multiple worktrees are not supported. | |
+9
options.go
··· 78 78 CABundle []byte 79 79 // ProxyOptions provides info required for connecting to a proxy. 80 80 ProxyOptions transport.ProxyOptions 81 + // When the repository to clone is on the local machine, instead of 82 + // using hard links, automatically setup .git/objects/info/alternates 83 + // to share the objects with the source repository. 84 + // The resulting repository starts out without any object of its own. 85 + // NOTE: this is a possibly dangerous operation; do not use it unless 86 + // you understand what it does. 87 + // 88 + // [Reference]: https://git-scm.com/docs/git-clone#Documentation/git-clone.txt---shared 89 + Shared bool 81 90 } 82 91 83 92 // Validate validates the fields and sets the default values.
+1
plumbing/storer/object.go
··· 42 42 HasEncodedObject(plumbing.Hash) error 43 43 // EncodedObjectSize returns the plaintext size of the encoded object. 44 44 EncodedObjectSize(plumbing.Hash) (int64, error) 45 + AddAlternate(remote string) error 45 46 } 46 47 47 48 // DeltaObjectStorer is an EncodedObjectStorer that can return delta
+4
plumbing/storer/object_test.go
··· 168 168 func (o *MockObjectStorage) Begin() Transaction { 169 169 return nil 170 170 } 171 + 172 + func (o *MockObjectStorage) AddAlternate(remote string) error { 173 + return nil 174 + }
+26
repository.go
··· 22 22 "github.com/go-git/go-git/v5/config" 23 23 "github.com/go-git/go-git/v5/internal/path_util" 24 24 "github.com/go-git/go-git/v5/internal/revision" 25 + "github.com/go-git/go-git/v5/internal/url" 25 26 "github.com/go-git/go-git/v5/plumbing" 26 27 "github.com/go-git/go-git/v5/plumbing/cache" 27 28 formatcfg "github.com/go-git/go-git/v5/plumbing/format/config" ··· 62 63 ErrUnableToResolveCommit = errors.New("unable to resolve commit") 63 64 ErrPackedObjectsNotSupported = errors.New("packed objects not supported") 64 65 ErrSHA256NotSupported = errors.New("go-git was not compiled with SHA256 support") 66 + ErrAlternatePathNotSupported = errors.New("alternate path must use the file scheme") 65 67 ) 66 68 67 69 // Repository represents a git repository ··· 885 887 886 888 if _, err := r.CreateRemote(c); err != nil { 887 889 return err 890 + } 891 + 892 + // When the repository to clone is on the local machine, 893 + // instead of using hard links, automatically setup .git/objects/info/alternates 894 + // to share the objects with the source repository 895 + if o.Shared { 896 + if !url.IsLocalEndpoint(o.URL) { 897 + return ErrAlternatePathNotSupported 898 + } 899 + altpath := o.URL 900 + remoteRepo, err := PlainOpen(o.URL) 901 + if err != nil { 902 + return fmt.Errorf("failed to open remote repository: %w", err) 903 + } 904 + conf, err := remoteRepo.Config() 905 + if err != nil { 906 + return fmt.Errorf("failed to read remote repository configuration: %w", err) 907 + } 908 + if !conf.Core.IsBare { 909 + altpath = path.Join(altpath, GitDirName) 910 + } 911 + if err := r.Storer.AddAlternate(altpath); err != nil { 912 + return fmt.Errorf("failed to add alternate file to git objects dir: %w", err) 913 + } 888 914 } 889 915 890 916 ref, err := r.fetchAndUpdateReferences(ctx, &FetchOptions{
+96
repository_test.go
··· 9 9 "os" 10 10 "os/exec" 11 11 "os/user" 12 + "path" 12 13 "path/filepath" 13 14 "regexp" 14 15 "strings" ··· 789 790 c.Assert(err, IsNil) 790 791 c.Assert(cfg.Branches, HasLen, 1) 791 792 c.Assert(cfg.Branches["master"].Name, Equals, "master") 793 + } 794 + 795 + func (s *RepositorySuite) TestPlainCloneBareAndShared(c *C) { 796 + dir, clean := s.TemporalDir() 797 + defer clean() 798 + 799 + remote := s.GetBasicLocalRepositoryURL() 800 + 801 + r, err := PlainClone(dir, true, &CloneOptions{ 802 + URL: remote, 803 + Shared: true, 804 + }) 805 + c.Assert(err, IsNil) 806 + 807 + altpath := path.Join(dir, "objects", "info", "alternates") 808 + _, err = os.Stat(altpath) 809 + c.Assert(err, IsNil) 810 + 811 + data, err := os.ReadFile(altpath) 812 + c.Assert(err, IsNil) 813 + 814 + line := path.Join(remote, GitDirName, "objects") + "\n" 815 + c.Assert(string(data), Equals, line) 816 + 817 + cfg, err := r.Config() 818 + c.Assert(err, IsNil) 819 + c.Assert(cfg.Branches, HasLen, 1) 820 + c.Assert(cfg.Branches["master"].Name, Equals, "master") 821 + } 822 + 823 + func (s *RepositorySuite) TestPlainCloneShared(c *C) { 824 + dir, clean := s.TemporalDir() 825 + defer clean() 826 + 827 + remote := s.GetBasicLocalRepositoryURL() 828 + 829 + r, err := PlainClone(dir, false, &CloneOptions{ 830 + URL: remote, 831 + Shared: true, 832 + }) 833 + c.Assert(err, IsNil) 834 + 835 + altpath := path.Join(dir, GitDirName, "objects", "info", "alternates") 836 + _, err = os.Stat(altpath) 837 + c.Assert(err, IsNil) 838 + 839 + data, err := os.ReadFile(altpath) 840 + c.Assert(err, IsNil) 841 + 842 + line := path.Join(remote, GitDirName, "objects") + "\n" 843 + c.Assert(string(data), Equals, line) 844 + 845 + cfg, err := r.Config() 846 + c.Assert(err, IsNil) 847 + c.Assert(cfg.Branches, HasLen, 1) 848 + c.Assert(cfg.Branches["master"].Name, Equals, "master") 849 + } 850 + 851 + func (s *RepositorySuite) TestPlainCloneSharedHttpShouldReturnError(c *C) { 852 + dir, clean := s.TemporalDir() 853 + defer clean() 854 + 855 + remote := "http://somerepo" 856 + 857 + _, err := PlainClone(dir, false, &CloneOptions{ 858 + URL: remote, 859 + Shared: true, 860 + }) 861 + c.Assert(err, Equals, ErrAlternatePathNotSupported) 862 + } 863 + 864 + func (s *RepositorySuite) TestPlainCloneSharedHttpsShouldReturnError(c *C) { 865 + dir, clean := s.TemporalDir() 866 + defer clean() 867 + 868 + remote := "https://somerepo" 869 + 870 + _, err := PlainClone(dir, false, &CloneOptions{ 871 + URL: remote, 872 + Shared: true, 873 + }) 874 + c.Assert(err, Equals, ErrAlternatePathNotSupported) 875 + } 876 + 877 + func (s *RepositorySuite) TestPlainCloneSharedSSHShouldReturnError(c *C) { 878 + dir, clean := s.TemporalDir() 879 + defer clean() 880 + 881 + remote := "ssh://somerepo" 882 + 883 + _, err := PlainClone(dir, false, &CloneOptions{ 884 + URL: remote, 885 + Shared: true, 886 + }) 887 + c.Assert(err, Equals, ErrAlternatePathNotSupported) 792 888 } 793 889 794 890 func (s *RepositorySuite) TestPlainCloneWithRemoteName(c *C) {
+32 -1
storage/filesystem/dotgit/dotgit.go
··· 8 8 "fmt" 9 9 "io" 10 10 "os" 11 + "path" 11 12 "path/filepath" 13 + "runtime" 12 14 "sort" 13 15 "strings" 14 16 "time" ··· 38 40 remotesPath = "remotes" 39 41 logsPath = "logs" 40 42 worktreesPath = "worktrees" 43 + alternatesPath = "alternates" 41 44 42 45 tmpPackedRefsPrefix = "._packed-refs" 43 46 ··· 1105 1108 return d.fs.Chroot(d.fs.Join(modulePath, name)) 1106 1109 } 1107 1110 1111 + func (d *DotGit) AddAlternate(remote string) error { 1112 + altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) 1113 + 1114 + f, err := d.fs.OpenFile(altpath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0640) 1115 + if err != nil { 1116 + return fmt.Errorf("cannot open file: %w", err) 1117 + } 1118 + defer f.Close() 1119 + 1120 + // locking in windows throws an error, based on comments 1121 + // https://github.com/go-git/go-git/pull/860#issuecomment-1751823044 1122 + // do not lock on windows platform. 1123 + if runtime.GOOS != "windows" { 1124 + if err = f.Lock(); err != nil { 1125 + return fmt.Errorf("cannot lock file: %w", err) 1126 + } 1127 + defer f.Unlock() 1128 + } 1129 + 1130 + line := path.Join(remote, objectsPath) + "\n" 1131 + _, err = io.WriteString(f, line) 1132 + if err != nil { 1133 + return fmt.Errorf("error writing 'alternates' file: %w", err) 1134 + } 1135 + 1136 + return nil 1137 + } 1138 + 1108 1139 // Alternates returns DotGit(s) based off paths in objects/info/alternates if 1109 1140 // available. This can be used to checks if it's a shared repository. 1110 1141 func (d *DotGit) Alternates() ([]*DotGit, error) { 1111 - altpath := d.fs.Join("objects", "info", "alternates") 1142 + altpath := d.fs.Join(objectsPath, infoPath, alternatesPath) 1112 1143 f, err := d.fs.Open(altpath) 1113 1144 if err != nil { 1114 1145 return nil, err
+4
storage/filesystem/storage.go
··· 74 74 func (s *Storage) Init() error { 75 75 return s.dir.Initialize() 76 76 } 77 + 78 + func (s *Storage) AddAlternate(remote string) error { 79 + return s.dir.AddAlternate(remote) 80 + }
+4
storage/memory/storage.go
··· 202 202 return errNotSupported 203 203 } 204 204 205 + func (o *ObjectStorage) AddAlternate(remote string) error { 206 + return errNotSupported 207 + } 208 + 205 209 type TxObjectStorage struct { 206 210 Storage *ObjectStorage 207 211 Objects map[plumbing.Hash]plumbing.EncodedObject
+4
storage/transactional/object.go
··· 82 82 return err 83 83 }) 84 84 } 85 + 86 + func (o *ObjectStorage) AddAlternate(remote string) error { 87 + return o.temporal.AddAlternate(remote) 88 + }