├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── src ├── blob.rs ├── inode.rs ├── lib.rs ├── main.rs ├── reference.rs ├── root.rs └── tree.rs └── tests └── self.rs /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: 3 | - stable 4 | - beta 5 | - nightly 6 | sudo: false 7 | addons: 8 | apt: 9 | packages: 10 | - libfuse-dev 11 | - libssh2-1-dev 12 | - libssl-dev 13 | script: 14 | - cargo build --verbose 15 | - | 16 | [ $TRAVIS_RUST_VERSION != nightly ] || 17 | cargo build --verbose --features=probe 18 | notifications: 19 | on_success: never 20 | -------------------------------------------------------------------------------- /Cargo.lock: -------------------------------------------------------------------------------- 1 | [root] 2 | name = "git-fs" 3 | version = "0.0.1-pre" 4 | dependencies = [ 5 | "fuse 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 6 | "git2 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)", 7 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 8 | "probe 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 9 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 10 | ] 11 | 12 | [[package]] 13 | name = "bitflags" 14 | version = "0.1.1" 15 | source = "registry+https://github.com/rust-lang/crates.io-index" 16 | 17 | [[package]] 18 | name = "cmake" 19 | version = "0.1.17" 20 | source = "registry+https://github.com/rust-lang/crates.io-index" 21 | dependencies = [ 22 | "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 23 | ] 24 | 25 | [[package]] 26 | name = "fuse" 27 | version = "0.2.8" 28 | source = "registry+https://github.com/rust-lang/crates.io-index" 29 | dependencies = [ 30 | "libc 0.1.12 (registry+https://github.com/rust-lang/crates.io-index)", 31 | "log 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)", 32 | "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 33 | "thread-scoped 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", 34 | "time 0.1.35 (registry+https://github.com/rust-lang/crates.io-index)", 35 | ] 36 | 37 | [[package]] 38 | name = "gcc" 39 | version = "0.3.35" 40 | source = "registry+https://github.com/rust-lang/crates.io-index" 41 | 42 | [[package]] 43 | name = "gdi32-sys" 44 | version = "0.2.0" 45 | source = "registry+https://github.com/rust-lang/crates.io-index" 46 | dependencies = [ 47 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 48 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 49 | ] 50 | 51 | [[package]] 52 | name = "git2" 53 | version = "0.4.4" 54 | source = "registry+https://github.com/rust-lang/crates.io-index" 55 | dependencies = [ 56 | "bitflags 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 57 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 58 | "libgit2-sys 0.4.5 (registry+https://github.com/rust-lang/crates.io-index)", 59 | "url 1.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 60 | ] 61 | 62 | [[package]] 63 | name = "idna" 64 | version = "0.1.0" 65 | source = "registry+https://github.com/rust-lang/crates.io-index" 66 | dependencies = [ 67 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 68 | "unicode-bidi 0.2.3 (registry+https://github.com/rust-lang/crates.io-index)", 69 | "unicode-normalization 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 70 | ] 71 | 72 | [[package]] 73 | name = "kernel32-sys" 74 | version = "0.2.2" 75 | source = "registry+https://github.com/rust-lang/crates.io-index" 76 | dependencies = [ 77 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 78 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 79 | ] 80 | 81 | [[package]] 82 | name = "libc" 83 | version = "0.1.12" 84 | source = "registry+https://github.com/rust-lang/crates.io-index" 85 | 86 | [[package]] 87 | name = "libc" 88 | version = "0.2.15" 89 | source = "registry+https://github.com/rust-lang/crates.io-index" 90 | 91 | [[package]] 92 | name = "libgit2-sys" 93 | version = "0.4.5" 94 | source = "registry+https://github.com/rust-lang/crates.io-index" 95 | dependencies = [ 96 | "cmake 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 97 | "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 98 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 99 | "libssh2-sys 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)", 100 | "libz-sys 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 101 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 102 | "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 103 | ] 104 | 105 | [[package]] 106 | name = "libressl-pnacl-sys" 107 | version = "2.1.6" 108 | source = "registry+https://github.com/rust-lang/crates.io-index" 109 | dependencies = [ 110 | "pnacl-build-helper 1.4.10 (registry+https://github.com/rust-lang/crates.io-index)", 111 | ] 112 | 113 | [[package]] 114 | name = "libssh2-sys" 115 | version = "0.1.39" 116 | source = "registry+https://github.com/rust-lang/crates.io-index" 117 | dependencies = [ 118 | "cmake 0.1.17 (registry+https://github.com/rust-lang/crates.io-index)", 119 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 120 | "libz-sys 1.0.5 (registry+https://github.com/rust-lang/crates.io-index)", 121 | "openssl-sys 0.7.17 (registry+https://github.com/rust-lang/crates.io-index)", 122 | "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 123 | ] 124 | 125 | [[package]] 126 | name = "libz-sys" 127 | version = "1.0.5" 128 | source = "registry+https://github.com/rust-lang/crates.io-index" 129 | dependencies = [ 130 | "gcc 0.3.35 (registry+https://github.com/rust-lang/crates.io-index)", 131 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 132 | "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 133 | ] 134 | 135 | [[package]] 136 | name = "log" 137 | version = "0.3.6" 138 | source = "registry+https://github.com/rust-lang/crates.io-index" 139 | 140 | [[package]] 141 | name = "matches" 142 | version = "0.1.2" 143 | source = "registry+https://github.com/rust-lang/crates.io-index" 144 | 145 | [[package]] 146 | name = "openssl-sys" 147 | version = "0.7.17" 148 | source = "registry+https://github.com/rust-lang/crates.io-index" 149 | dependencies = [ 150 | "gdi32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 151 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 152 | "libressl-pnacl-sys 2.1.6 (registry+https://github.com/rust-lang/crates.io-index)", 153 | "pkg-config 0.3.8 (registry+https://github.com/rust-lang/crates.io-index)", 154 | "user32-sys 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", 155 | ] 156 | 157 | [[package]] 158 | name = "pkg-config" 159 | version = "0.3.8" 160 | source = "registry+https://github.com/rust-lang/crates.io-index" 161 | 162 | [[package]] 163 | name = "pnacl-build-helper" 164 | version = "1.4.10" 165 | source = "registry+https://github.com/rust-lang/crates.io-index" 166 | dependencies = [ 167 | "tempdir 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", 168 | ] 169 | 170 | [[package]] 171 | name = "probe" 172 | version = "0.1.6" 173 | source = "registry+https://github.com/rust-lang/crates.io-index" 174 | 175 | [[package]] 176 | name = "rand" 177 | version = "0.3.14" 178 | source = "registry+https://github.com/rust-lang/crates.io-index" 179 | dependencies = [ 180 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 181 | ] 182 | 183 | [[package]] 184 | name = "tempdir" 185 | version = "0.3.5" 186 | source = "registry+https://github.com/rust-lang/crates.io-index" 187 | dependencies = [ 188 | "rand 0.3.14 (registry+https://github.com/rust-lang/crates.io-index)", 189 | ] 190 | 191 | [[package]] 192 | name = "thread-scoped" 193 | version = "1.0.1" 194 | source = "registry+https://github.com/rust-lang/crates.io-index" 195 | 196 | [[package]] 197 | name = "time" 198 | version = "0.1.35" 199 | source = "registry+https://github.com/rust-lang/crates.io-index" 200 | dependencies = [ 201 | "kernel32-sys 0.2.2 (registry+https://github.com/rust-lang/crates.io-index)", 202 | "libc 0.2.15 (registry+https://github.com/rust-lang/crates.io-index)", 203 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 204 | ] 205 | 206 | [[package]] 207 | name = "unicode-bidi" 208 | version = "0.2.3" 209 | source = "registry+https://github.com/rust-lang/crates.io-index" 210 | dependencies = [ 211 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 212 | ] 213 | 214 | [[package]] 215 | name = "unicode-normalization" 216 | version = "0.1.2" 217 | source = "registry+https://github.com/rust-lang/crates.io-index" 218 | 219 | [[package]] 220 | name = "url" 221 | version = "1.2.0" 222 | source = "registry+https://github.com/rust-lang/crates.io-index" 223 | dependencies = [ 224 | "idna 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", 225 | "matches 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", 226 | ] 227 | 228 | [[package]] 229 | name = "user32-sys" 230 | version = "0.2.0" 231 | source = "registry+https://github.com/rust-lang/crates.io-index" 232 | dependencies = [ 233 | "winapi 0.2.8 (registry+https://github.com/rust-lang/crates.io-index)", 234 | "winapi-build 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", 235 | ] 236 | 237 | [[package]] 238 | name = "winapi" 239 | version = "0.2.8" 240 | source = "registry+https://github.com/rust-lang/crates.io-index" 241 | 242 | [[package]] 243 | name = "winapi-build" 244 | version = "0.1.1" 245 | source = "registry+https://github.com/rust-lang/crates.io-index" 246 | 247 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "git-fs" 3 | version = "0.0.1-pre" 4 | authors = ["Josh Stone "] 5 | 6 | [[bin]] 7 | name = "git-fs" 8 | test = false 9 | doc = false 10 | 11 | [lib] 12 | name = "gitfs" 13 | test = false 14 | doc = false 15 | 16 | [dependencies] 17 | fuse = "0" 18 | git2 = "0" 19 | libc = "0" 20 | time = "0" 21 | 22 | [dependencies.probe] 23 | version = "0" 24 | optional = true 25 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (C) 2014 Josh Stone 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rust-git-fs 2 | 3 | A FUSE implementation for Git objects. 4 | 5 | With `git-fs` one can mount a Git tree as a filesystem, then browse any 6 | branch/commit/etc. without needing to actually check them out. 7 | 8 | ## Usage 9 | 10 | `git-fs [GIT_DIR [MOUNTPOINT]]` 11 | 12 | - GIT_DIR: The directory of a git repository. A bare git directory is fine, 13 | or if given as a working directory, it will automatically use the .git/ 14 | directory within. Defaults to the current directory. 15 | 16 | - MOUNTPOINT: The target to mount the filesystem. Defaults to GIT_DIR/fs. 17 | 18 | ## Building 19 | 20 | Use `cargo build`, which will also handle dependencies on `git2-rs` and 21 | `rust-fuse`. The latter will also require `fuse-devel` or `libfuse-dev` 22 | installed on your system. 23 | 24 | Nightly build status is available on Rust CI: 25 | [![build status][ci-image]][ci-link] 26 | 27 | [ci-image]: https://api.travis-ci.org/cuviper/rust-git-fs.png 28 | [ci-link]: http://www.rust-ci.org/cuviper/rust-git-fs 29 | 30 | ## See also 31 | 32 | The Git SCM Wiki has a whole page for external tools, including 33 | [filesystem interfaces](https://git.wiki.kernel.org/index.php/Interfaces,_frontends,_and_tools#Filesystem_interfaces). 34 | 35 | ## License 36 | 37 | `rust-git-fs` is distributed under the terms of both the MIT license and the 38 | Apache License (Version 2.0). See LICENSE-APACHE, and LICENSE-MIT for details. 39 | -------------------------------------------------------------------------------- /src/blob.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use fuse::{self, FileType}; 10 | use git2; 11 | use libc; 12 | 13 | use inode; 14 | 15 | /// Git blobs are represented as files 16 | // FIXME needs context, e.g. permissions from TreeEntry and timestamps from Commit 17 | pub struct Blob { 18 | oid: git2::Oid, 19 | size: u64, 20 | data: Option>, 21 | } 22 | 23 | impl Blob { 24 | pub fn new(blob: git2::Blob) -> Box { 25 | Box::new(Blob { 26 | oid: blob.id(), 27 | size: blob.content().len() as u64, 28 | data: None, 29 | }) 30 | } 31 | } 32 | 33 | impl inode::Inode for Blob { 34 | fn getattr(&mut self, _repo: &git2::Repository, attr: inode::FileAttr 35 | ) -> Result { 36 | Ok(inode::FileAttr { 37 | size: self.size, 38 | blocks: inode::st_blocks(self.size), 39 | kind: FileType::RegularFile, 40 | perm: 0o644, 41 | ..attr 42 | }) 43 | } 44 | 45 | fn open(&mut self, repo: &git2::Repository, _flags: u32) -> Result { 46 | if self.data.is_none() { 47 | if let Ok(blob) = repo.find_blob(self.oid) { 48 | self.data = Some(blob.content().to_vec()); 49 | } else { 50 | return Err(libc::EIO) 51 | } 52 | } 53 | Ok(fuse::consts::FOPEN_KEEP_CACHE) 54 | } 55 | 56 | fn read(&mut self, _repo: &git2::Repository, offset: u64, size: u32 57 | ) -> Result<&[u8], libc::c_int> { 58 | if let Some(ref data) = self.data { 59 | if offset <= data.len() as u64 { 60 | let data = &data[offset as usize..]; 61 | return Ok(if (size as usize) < data.len() { 62 | &data[..size as usize] 63 | } else { 64 | data 65 | }) 66 | } 67 | } 68 | Err(libc::EINVAL) 69 | } 70 | 71 | fn release (&mut self, _repo: &git2::Repository) -> Result<(), libc::c_int> { 72 | self.data.take(); 73 | Ok(()) 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /src/inode.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use fuse::FileType; 10 | use git2; 11 | use libc; 12 | use std::collections::hash_map; 13 | use std::path::Path; 14 | 15 | use blob; 16 | use tree; 17 | 18 | pub use fuse::FileAttr; 19 | 20 | 21 | /// Let Inodes use either existing inos or git2::Oid, whichever is convenient 22 | // FIXME Using a 1:1 mapping between oid:ino breaks down when it comes to attributes. For 23 | // instance, Blobs and Trees have no concept of their own timestamps or permissions. But a Tree 24 | // does know its children's permissions in TreeEntry, and a Commit could propagate timestamps 25 | // recursively down its Tree. This will require Oids to be context sensitive, with 1:N inos. 26 | #[derive(Clone,Copy)] 27 | pub enum Id { 28 | Ino(u64), 29 | Oid(git2::Oid), 30 | } 31 | 32 | 33 | /// A generic interface for different Git object types to implement. 34 | pub trait Inode: Send { 35 | /// Find a directory entry in this Inode by name. 36 | fn lookup(&mut self, _repo: &git2::Repository, _name: &Path 37 | ) -> Result { 38 | Err(libc::ENOTDIR) 39 | } 40 | 41 | /// Get the attributes of this Inode. 42 | fn getattr(&mut self, _repo: &git2::Repository, _attr: FileAttr 43 | ) -> Result { 44 | Err(libc::EINVAL) 45 | } 46 | 47 | /// Open a file. 48 | fn open(&mut self, _repo: &git2::Repository, _flags: u32) -> Result { 49 | Err(libc::EISDIR) 50 | } 51 | 52 | /// Read data from this Inode. 53 | fn read(&mut self, _repo: &git2::Repository, _offset: u64, _size: u32 54 | ) -> Result<&[u8], libc::c_int> { 55 | Err(libc::EISDIR) 56 | } 57 | 58 | /// Release data from an opened file. 59 | fn release(&mut self, _repo: &git2::Repository) -> Result<(), libc::c_int> { 60 | Err(libc::EISDIR) 61 | } 62 | 63 | /// Read directory entries from this Inode. 64 | fn readdir<'a>(&'a mut self, _repo: &git2::Repository, _offset: u64, 65 | _add: Box bool + 'a> 66 | ) -> Result<(), libc::c_int> { 67 | Err(libc::ENOTDIR) 68 | } 69 | } 70 | 71 | 72 | /// Assign new inode numbers, and map Oids to ino dynamically 73 | // FIXME see the note on Id about 1:1 mapping trouble 74 | #[derive(Default)] 75 | pub struct InodeMapper { 76 | max_ino: u64, 77 | oids: hash_map::HashMap, 78 | inos: hash_map::HashMap, 79 | } 80 | 81 | impl InodeMapper { 82 | /// Reserve a new inode number 83 | pub fn new_ino(&mut self) -> u64 { 84 | self.max_ino += 1; 85 | self.max_ino 86 | } 87 | 88 | /// Get the oid associated with this ino 89 | pub fn get_oid(&self, ino: u64) -> Option { 90 | self.inos.get(&ino).cloned() 91 | } 92 | 93 | /// Map any Id to an inode number 94 | pub fn get_ino(&mut self, id: Id) -> u64 { 95 | match id { 96 | Id::Ino(ino) => ino, 97 | Id::Oid(oid) => { 98 | match self.oids.entry(oid) { 99 | hash_map::Entry::Occupied(entry) => *entry.get(), 100 | hash_map::Entry::Vacant(entry) => { 101 | // NB can't call new_ino because entry holds mut 102 | self.max_ino += 1; 103 | let ino = self.max_ino; 104 | self.inos.insert(ino, oid); 105 | *entry.insert(ino) 106 | }, 107 | } 108 | }, 109 | } 110 | } 111 | } 112 | 113 | 114 | /// A separate container allows mut borrowing without blocking everything else 115 | /// in the GitFS at the same time. 116 | #[derive(Default)] 117 | pub struct InodeContainer { 118 | inodes: hash_map::HashMap>, 119 | } 120 | 121 | impl InodeContainer { 122 | pub fn insert(&mut self, ino: u64, inode: Box) -> Option> { 123 | self.inodes.insert(ino, inode) 124 | } 125 | 126 | pub fn find_mut(&mut self, ino: u64) -> Result<&mut Box, libc::c_int> { 127 | self.inodes.get_mut(&ino).ok_or(libc::ENOENT) 128 | } 129 | 130 | pub fn entry(&mut self, ino: u64) 131 | -> hash_map::Entry> { 132 | self.inodes.entry(ino) 133 | } 134 | } 135 | 136 | 137 | /// Creates an Inode from any Oid. 138 | // FIXME see the note on Id about 1:1 mapping trouble 139 | pub fn new_inode(repo: &git2::Repository, oid: git2::Oid) -> Option> { 140 | match repo.find_object(oid, None).ok().and_then(|o| o.kind()) { 141 | Some(git2::ObjectType::Blob) => { 142 | repo.find_blob(oid).ok().map(|blob| blob::Blob::new(blob)) 143 | }, 144 | Some(git2::ObjectType::Tree) => { 145 | repo.find_tree(oid).ok().map(|tree| tree::Tree::new(tree)) 146 | }, 147 | Some(git2::ObjectType::Commit) => { 148 | // FIXME a first-class Commit might expose things like the message as xattrs, 149 | // but for now just redirect straight to the tree id. 150 | repo.find_commit(oid).ok() 151 | .and_then(|commit| new_inode(repo, commit.tree_id())) 152 | }, 153 | _ => None, 154 | } 155 | } 156 | 157 | 158 | /// Compute the number of blocks needed to contain a given size. 159 | pub fn st_blocks(size: u64) -> u64 { 160 | // NB FUSE apparently always uses 512-byte blocks. Round up. 161 | (size + 511) / 512 162 | } 163 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! # GitFS: a FUSE filesystem for Git objects 10 | 11 | #![cfg_attr(feature="probe", feature(asm))] 12 | 13 | #![deny(missing_docs)] 14 | 15 | 16 | #[cfg(feature="probe")] 17 | #[macro_use] #[no_link] 18 | extern crate probe; 19 | 20 | extern crate fuse; 21 | extern crate git2; 22 | extern crate libc; 23 | extern crate time; 24 | 25 | use fuse::FileType; 26 | 27 | use std::collections::hash_map; 28 | use std::default::Default; 29 | use std::ffi::{CString, OsString}; 30 | use std::os::unix::ffi::OsStrExt; 31 | use std::fs; 32 | use std::path::{Path, PathBuf}; 33 | use std::u64; 34 | 35 | use inode::{Id, InodeContainer, InodeMapper}; 36 | 37 | mod inode; 38 | mod blob; 39 | mod tree; 40 | mod reference; 41 | mod root; 42 | 43 | 44 | const TTY: time::Timespec = time::Timespec { sec: 1, nsec: 0 }; 45 | 46 | 47 | // NULL implementation of probe!() 48 | // XXX would be nice if libprobe could toggle feature(asm) itself 49 | // ... based on whether a nightly compiler is active. 50 | #[cfg(not(feature="probe"))] 51 | macro_rules! probe( 52 | ($provider:ident, $name:ident) => (); 53 | ($provider:ident, $name:ident, $($arg:expr),*) 54 | => (if false { match ($($arg,)*) { _ => () }}); 55 | ); 56 | 57 | 58 | /// The main object implementing a FUSE filesystem. 59 | pub struct GitFS { 60 | repo: git2::Repository, 61 | epoch: time::Timespec, 62 | uid: u32, 63 | gid: u32, 64 | mapper: InodeMapper, 65 | inodes: InodeContainer, 66 | mountdir: Option, 67 | } 68 | 69 | impl GitFS { 70 | /// Create a GitFS referencing the given GIT_DIR. 71 | pub fn new>(git_dir: &P) -> Result { 72 | Ok(GitFS { 73 | repo: try!(git2::Repository::open(git_dir.as_ref())), 74 | epoch: time::get_time(), 75 | uid: unsafe { libc::getuid() }, 76 | gid: unsafe { libc::getgid() }, 77 | mapper: Default::default(), 78 | inodes: Default::default(), 79 | mountdir: None, 80 | }) 81 | } 82 | 83 | /// Get the resolved GIT_DIR. 84 | pub fn git_dir(&self) -> &Path { 85 | self.repo.path() 86 | } 87 | 88 | fn mount_options(&self) -> OsString { 89 | let mut options = OsString::from("-oro,default_permissions,fsname="); 90 | options.push(&self.repo.path()); // FIXME escape commas? 91 | options 92 | } 93 | 94 | /// Mount the filesystem and wait until the path is unmounted, e.g. with the command 95 | /// `fusermount -u PATH`. 96 | pub fn mount>(mut self, mountpoint: &P) { 97 | // Create/remove the mount point if it doesn't exist 98 | self.mountdir = DirHandle::new(mountpoint.as_ref()); 99 | 100 | let options = self.mount_options(); 101 | fuse::mount(self, mountpoint, &[&options]) 102 | } 103 | 104 | /// Mount the filesystem in the background. It will remain mounted until the returned session 105 | /// object is dropped, or an external umount is issued. 106 | pub unsafe fn spawn_mount>(mut self, mountpoint: &P) -> std::io::Result { 107 | // Create/remove the mount point if it doesn't exist 108 | self.mountdir = DirHandle::new(mountpoint.as_ref()); 109 | 110 | let options = self.mount_options(); 111 | fuse::spawn_mount(self, mountpoint, &[&options]) 112 | } 113 | 114 | fn defattr(&self, ino: u64) -> fuse::FileAttr { 115 | fuse::FileAttr { 116 | ino: ino, 117 | size: 0, 118 | blocks: 0, 119 | atime: self.epoch, 120 | mtime: self.epoch, 121 | ctime: self.epoch, 122 | crtime: self.epoch, 123 | kind: FileType::RegularFile, /* unknown... */ 124 | perm: 0, 125 | nlink: 1, 126 | uid: self.uid, 127 | gid: self.gid, 128 | rdev: 0, 129 | flags: 0, 130 | } 131 | } 132 | } 133 | 134 | impl fuse::Filesystem for GitFS { 135 | fn init (&mut self, _req: &fuse::Request) -> Result<(), libc::c_int> { 136 | let root_ino = self.mapper.new_ino(); 137 | let head_ino = self.mapper.new_ino(); 138 | let refs_ino = self.mapper.new_ino(); 139 | assert_eq!(fuse::FUSE_ROOT_ID, root_ino); 140 | 141 | let root = root::Root::new(Id::Ino(head_ino), Id::Ino(refs_ino)); 142 | self.inodes.insert(root_ino, root); 143 | 144 | let refs = reference::RefDir::new(); 145 | self.inodes.insert(refs_ino, refs); 146 | 147 | Ok(()) 148 | } 149 | 150 | fn lookup(&mut self, _req: &fuse::Request, parent: u64, name: &Path, reply: fuse::ReplyEntry) { 151 | if let Ok(name) = CString::new(name.as_os_str().as_bytes()) { 152 | probe!(gitfs, lookup, parent, name.as_ptr()); 153 | } 154 | 155 | let repo = &self.repo; 156 | let id = { 157 | let inode = self.inodes.find_mut(parent); 158 | match inode.and_then(|inode| inode.lookup(repo, name)) { 159 | Ok(id) => id, 160 | Err(rc) => return reply.error(rc), 161 | } 162 | }; 163 | let ino = self.mapper.get_ino(id); 164 | 165 | if let hash_map::Entry::Vacant(entry) = self.inodes.entry(ino) { 166 | if let Some(oid) = self.mapper.get_oid(ino) { 167 | if let Some(inode) = inode::new_inode(&self.repo, oid) { 168 | entry.insert(inode); 169 | } 170 | } 171 | } 172 | 173 | let attr = self.defattr(ino); 174 | let inode = self.inodes.find_mut(ino); 175 | match inode.and_then(|inode| inode.getattr(repo, attr)) { 176 | Ok(attr) => reply.entry(&TTY, &attr, 1), 177 | Err(rc) => reply.error(rc), 178 | } 179 | } 180 | 181 | fn forget (&mut self, _req: &fuse::Request, _ino: u64, _nlookup: u64) { 182 | probe!(gitfs, forget, _ino, _nlookup); 183 | 184 | // TODO could probably drop Oid inodes, since they're easily recreated 185 | } 186 | 187 | fn getattr (&mut self, _req: &fuse::Request, ino: u64, 188 | reply: fuse::ReplyAttr) { 189 | probe!(gitfs, getattr, ino); 190 | 191 | let attr = self.defattr(ino); 192 | let repo = &self.repo; 193 | let inode = self.inodes.find_mut(ino); 194 | match inode.and_then(|inode| inode.getattr(repo, attr)) { 195 | Ok(attr) => reply.attr(&TTY, &attr), 196 | Err(rc) => reply.error(rc), 197 | } 198 | } 199 | 200 | fn open (&mut self, _req: &fuse::Request, ino: u64, flags: u32, reply: fuse::ReplyOpen) { 201 | probe!(gitfs, open, ino, flags); 202 | 203 | let repo = &self.repo; 204 | let inode = self.inodes.find_mut(ino); 205 | match inode.and_then(|inode| inode.open(repo, flags)) { 206 | Ok(flags) => reply.opened(0, flags), 207 | Err(rc) => reply.error(rc), 208 | } 209 | } 210 | fn read (&mut self, _req: &fuse::Request, ino: u64, _fh: u64, offset: u64, size: u32, 211 | reply: fuse::ReplyData) { 212 | probe!(gitfs, read, ino, offset, size); 213 | 214 | let repo = &self.repo; 215 | let inode = self.inodes.find_mut(ino); 216 | match inode.and_then(|inode| inode.read(repo, offset, size)) { 217 | Ok(data) => reply.data(data), 218 | Err(rc) => reply.error(rc), 219 | } 220 | } 221 | 222 | fn release (&mut self, _req: &fuse::Request, ino: u64, _fh: u64, _flags: u32, 223 | _lock_owner: u64, _flush: bool, reply: fuse::ReplyEmpty) { 224 | probe!(gitfs, release, ino); 225 | 226 | let repo = &self.repo; 227 | let inode = self.inodes.find_mut(ino); 228 | match inode.and_then(|inode| inode.release(repo)) { 229 | Ok(()) => reply.ok(), 230 | Err(rc) => reply.error(rc), 231 | } 232 | } 233 | 234 | fn readdir (&mut self, _req: &fuse::Request, ino: u64, _fh: u64, mut offset: u64, 235 | mut reply: fuse::ReplyDirectory) { 236 | probe!(gitfs, readdir, ino, offset); 237 | 238 | let mapper = &mut self.mapper; 239 | let repo = &self.repo; 240 | let inode = self.inodes.find_mut(ino); 241 | match inode.and_then(|inode| { 242 | if offset == 0 { 243 | offset += 1; 244 | reply.add(u64::MAX, offset, FileType::Directory, &Path::new(".")); 245 | } 246 | if offset == 1 { 247 | offset += 1; 248 | reply.add(u64::MAX, offset, FileType::Directory, &Path::new("..")); 249 | } 250 | inode.readdir(repo, offset - 2, Box::new(|id, kind, path| { 251 | offset += 1; 252 | reply.add(mapper.get_ino(id), offset, kind, path) 253 | })) 254 | }) { 255 | Ok(()) => reply.ok(), 256 | Err(rc) => reply.error(rc), 257 | } 258 | } 259 | } 260 | 261 | 262 | /// Helper for mkdir, ensuring rmdir when dropped 263 | struct DirHandle { 264 | path: PathBuf, 265 | } 266 | 267 | impl DirHandle { 268 | fn new(path: &Path) -> Option { 269 | match fs::create_dir(path) { 270 | Ok(()) => Some(DirHandle { path: path.to_path_buf() }), 271 | Err(_) => None, 272 | } 273 | } 274 | } 275 | 276 | impl Drop for DirHandle { 277 | fn drop(&mut self) { 278 | fs::remove_dir(&self.path).ok(); 279 | } 280 | } 281 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | //! # git-fs: command-line tool to mount Git objects 10 | //! 11 | //! Usage: git-fs [GIT_DIR [MOUNTPOINT]] 12 | //! 13 | //! - GIT_DIR: The directory of a git repository. A bare git directory is fine, 14 | //! or if given as a working directory, it will automatically use the .git/ 15 | //! directory within. Defaults to the current directory. 16 | //! 17 | //! - MOUNTPOINT: The target to mount the filesystem. Defaults to GIT_DIR/fs. 18 | 19 | extern crate gitfs; 20 | 21 | use std::ffi::{OsStr, OsString}; 22 | use std::path::Path; 23 | 24 | fn main() { 25 | let args: Vec = std::env::args_os().collect(); 26 | 27 | // If unspecified, source defaults to the current directory 28 | let source: &OsStr = if args.len() > 1 { &args[1] } else { OsStr::new(".") }; 29 | 30 | match gitfs::GitFS::new(&source) { 31 | Ok(fs) => { 32 | // If unspecified, the target defaults to GIT_DIR/fs 33 | let target = if args.len() > 2 { 34 | Path::new(&args[2]).to_path_buf() 35 | } else { 36 | fs.git_dir().join("fs") 37 | }; 38 | 39 | fs.mount(&target); 40 | }, 41 | Err(e) => panic!("{}", e), 42 | }; 43 | } 44 | -------------------------------------------------------------------------------- /src/reference.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use fuse::FileType; 10 | use git2; 11 | use libc; 12 | use std::collections::hash_map; 13 | use std::default::Default; 14 | use std::path::{Path, PathBuf}; 15 | 16 | use inode; 17 | 18 | 19 | /// Represents a virtual directory in reference paths 20 | /// (e.g. `refs/heads/master` needs intermediate `refs/` and `refs/heads/`) 21 | pub struct RefDir { 22 | entries: hash_map::HashMap, 23 | } 24 | 25 | impl RefDir { 26 | pub fn new() -> Box { 27 | Box::new(RefDir { 28 | entries: Default::default(), 29 | }) 30 | } 31 | } 32 | 33 | impl inode::Inode for RefDir { 34 | fn lookup(&mut self, _repo: &git2::Repository, name: &Path 35 | ) -> Result { 36 | self.entries.get(name).cloned().ok_or(libc::ENOENT) 37 | } 38 | 39 | fn getattr(&mut self, _repo: &git2::Repository, attr: inode::FileAttr 40 | ) -> Result { 41 | let size = self.entries.len() as u64; 42 | Ok(inode::FileAttr { 43 | size: size, 44 | blocks: inode::st_blocks(size), 45 | kind: FileType::Directory, 46 | perm: 0o755, 47 | ..attr 48 | }) 49 | } 50 | 51 | fn readdir<'a>(&mut self, _repo: &git2::Repository, offset: u64, 52 | mut add: Box bool + 'a> 53 | ) -> Result<(), libc::c_int> { 54 | if offset < self.entries.len() as u64 { 55 | for (path, &id) in self.entries.iter().skip(offset as usize) { 56 | if add(id, FileType::Directory, path) { 57 | break; 58 | } 59 | } 60 | } 61 | Ok(()) 62 | } 63 | } 64 | 65 | 66 | 67 | -------------------------------------------------------------------------------- /src/root.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use fuse::FileType; 10 | use git2; 11 | use libc; 12 | use std::path::Path; 13 | 14 | use inode; 15 | use inode::{FileAttr, Id, Inode}; 16 | 17 | /// The root of the filesystem, currently just revealing HEAD and refs/ 18 | pub struct Root { 19 | head: Id, 20 | refs: Id, 21 | } 22 | 23 | impl Root { 24 | pub fn new(head: Id, refs: Id) -> Box { 25 | Box::new(Root { 26 | head: head, 27 | refs: refs, 28 | }) 29 | } 30 | } 31 | 32 | impl Inode for Root { 33 | fn lookup(&mut self, repo: &git2::Repository, name: &Path 34 | ) -> Result { 35 | if name == Path::new("HEAD") { 36 | repo.head().ok() 37 | .and_then(|head| head.target()) 38 | .map(|oid| Id::Oid(oid)) 39 | } 40 | else if name == Path::new("refs") { 41 | Some(self.refs) 42 | } 43 | else { None }.ok_or(libc::ENOENT) 44 | } 45 | 46 | fn getattr(&mut self, _repo: &git2::Repository, attr: FileAttr 47 | ) -> Result { 48 | let size = 2; // just HEAD and refs/ 49 | Ok(FileAttr { 50 | size: size, 51 | blocks: inode::st_blocks(size), 52 | kind: FileType::Directory, 53 | perm: 0o755, 54 | ..attr 55 | }) 56 | } 57 | 58 | fn readdir<'a>(&mut self, _repo: &git2::Repository, offset: u64, 59 | mut add: Box bool + 'a> 60 | ) -> Result<(), libc::c_int> { 61 | if offset == 0 { 62 | add(self.head, FileType::Directory, &Path::new("HEAD")); 63 | } 64 | if offset <= 1 { 65 | add(self.refs, FileType::Directory, &Path::new("refs")); 66 | } 67 | Ok(()) 68 | } 69 | } 70 | 71 | 72 | 73 | -------------------------------------------------------------------------------- /src/tree.rs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2014 Josh Stone 2 | // 3 | // Licensed under the Apache License, Version 2.0 or the MIT license 5 | // , at your 6 | // option. This file may not be copied, modified, or distributed 7 | // except according to those terms. 8 | 9 | use fuse::FileType; 10 | use git2; 11 | use libc; 12 | use std::ffi::OsStr; 13 | use std::os::unix::ffi::OsStrExt; 14 | use std::path::Path; 15 | 16 | use inode; 17 | use inode::{FileAttr, Id, Inode}; 18 | 19 | /// Git trees are represented as directories 20 | // FIXME needs context, e.g. permissions from TreeEntry and timestamps from Commit 21 | pub struct Tree { 22 | oid: git2::Oid, 23 | size: u64, 24 | } 25 | 26 | impl Tree { 27 | pub fn new(tree: git2::Tree) -> Box { 28 | Box::new(Tree { 29 | oid: tree.id(), 30 | size: tree.len() as u64, 31 | }) 32 | } 33 | 34 | fn tree<'a>(&self, repo: &'a git2::Repository) -> Result, libc::c_int> { 35 | repo.find_tree(self.oid).map_err(|_| libc::EINVAL) 36 | } 37 | } 38 | 39 | impl Inode for Tree { 40 | fn lookup(&mut self, repo: &git2::Repository, name: &Path 41 | ) -> Result { 42 | self.tree(repo).and_then(|tree| { 43 | match tree.get_path(name) { 44 | Ok(e) => Ok(Id::Oid(e.id())), 45 | Err(_) => Err(libc::ENOENT), 46 | } 47 | }) 48 | } 49 | 50 | fn getattr(&mut self, _repo: &git2::Repository, attr: FileAttr 51 | ) -> Result { 52 | Ok(FileAttr { 53 | size: self.size, 54 | blocks: inode::st_blocks(self.size), 55 | kind: FileType::Directory, 56 | perm: 0o755, 57 | ..attr 58 | }) 59 | } 60 | 61 | fn readdir<'a>(&'a mut self, repo: &git2::Repository, offset: u64, 62 | mut add: Box bool + 'a> 63 | ) -> Result<(), libc::c_int> { 64 | let len = self.size; 65 | self.tree(repo).map(|tree| { 66 | for i in offset..len { 67 | let e = match tree.get(i as usize) { 68 | Some(e) => e, 69 | None => continue, 70 | }; 71 | let kind = match e.kind() { 72 | Some(git2::ObjectType::Tree) => FileType::Directory, 73 | Some(git2::ObjectType::Blob) => FileType::RegularFile, 74 | _ => FileType::CharDevice, /* something weird?!? unknown... */ 75 | }; 76 | let os_path = ::from_bytes(e.name_bytes()); 77 | if add(Id::Oid(e.id()), kind, Path::new(os_path)) { 78 | break; 79 | } 80 | } 81 | }) 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /tests/self.rs: -------------------------------------------------------------------------------- 1 | //! Test that this very testfile is accessible in our own mount. 2 | 3 | extern crate gitfs; 4 | 5 | use std::fs; 6 | use std::path::Path; 7 | 8 | // FIXME: use PathExt::exists() once stable 9 | fn exists(path: &Path) -> bool { 10 | fs::metadata(path).is_ok() 11 | } 12 | 13 | #[test] 14 | fn mounted_test_exists() { 15 | let git_dir = Path::new(".git"); 16 | let mount = git_dir.join("fs"); 17 | let file = mount.join("HEAD").join(file!()); 18 | 19 | // NB: If this isn't a git checkout, we'll fail here, sorry! 20 | let fs = gitfs::GitFS::new(&git_dir).unwrap(); 21 | 22 | assert!(!exists(&file), "{:?} shouldn't exist before mounting!", file); 23 | 24 | let session = unsafe { fs.spawn_mount(&mount) }.unwrap(); 25 | 26 | assert!(exists(&file), "{:?} should exist in the mount!", file); 27 | 28 | drop(session); 29 | 30 | assert!(!exists(&file), "{:?} shouldn't exist after unmounting!", file); 31 | } 32 | --------------------------------------------------------------------------------