├── README.md ├── LICENSE └── gfs.py /README.md: -------------------------------------------------------------------------------- 1 | # Github File System [GithubFS] 2 | A read-only virtual file system for Github using FUSE written in Python. 3 | 4 | ## Features 5 | - List repos as directories 6 | - List repo contents as directory contents 7 | - Read files in a repo as files in a directory 8 | - Copy files from a repo 9 | 10 | ## Installation & Usage 11 | Make sure you have both the dependencies installed on your system before using this. 12 | 13 | ```bash 14 | $ git clone https://github.com/prakashdanish/githubfs 15 | $ cd githubfs 16 | $ python3 gfs.py [root] [mount-point] 17 | ``` 18 | 19 | ## Dependencies 20 | - [fusepy](https://github.com/terencehonles/fusepy) 21 | - [PyGithub](https://github.com/PyGithub/PyGithub) 22 | 23 | ## To-Do 24 | - [x] List repositories as dirs 25 | - [x] List repository contents as dir contents 26 | - [x] Read file contents from a file in a repository 27 | - [ ] Edit and commit changes from the filesystem 28 | - [ ] Multiple account support 29 | - [ ] Config file for storing & accessing credentials 30 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Danish Prakash 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /gfs.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | 3 | import os 4 | import sys 5 | import errno 6 | import getpass 7 | import pwd 8 | 9 | from stat import S_IFDIR, S_IFLNK, S_IFREG, S_ISREG 10 | from github import Github 11 | from fuse import FUSE, FuseOSError, Operations 12 | from time import time, mktime, sleep 13 | 14 | class gfs(Operations): 15 | def __init__(self, root): 16 | print('[init]: ', root) 17 | self.root = root 18 | self.user = Github(input("Username: "), getpass.getpass("Password: ")) 19 | print('[init]: Establishing connection....') 20 | self.repo_list = [] 21 | print('[init]: Fetching repositories...') 22 | self.file_content_decoded = dict() 23 | self.file_content_bytes = dict() 24 | for repo in self.user.get_user().get_repos(): 25 | self.repo_list.append(repo.name) 26 | files = repo.get_dir_contents('/') 27 | for file_ in files: 28 | if file_.name == repo.name: 29 | continue 30 | elif '.' in file_.name and not(file_.name.startswith('.')): 31 | file_name = file_.name 32 | file_content = repo.get_file_contents(file_name) 33 | self.file_content_bytes[file_.name] = file_content.decoded_content 34 | self.file_content_decoded[file_.name] = file_content.decoded_content.decode('utf-8') 35 | print('Done') 36 | 37 | def open(self, path, flags): 38 | if path == '/' or path == '/repos': 39 | pass 40 | else: 41 | path_ele = path.split('/') 42 | file_name = path_ele[-1] 43 | repo_name = path_ele[-2] 44 | new_file = open(file_name, "w") 45 | data = self.file_content_decoded[file_name] 46 | new_file.write(data) 47 | new_file.close 48 | return len(data) 49 | 50 | def getattr(self, path, fh=None): 51 | print('[getattr]: ', path) 52 | full_path = self.root + path 53 | properties = dict( 54 | st_mode = S_IFDIR | 755, 55 | st_nlink = 2, 56 | st_ctime=0, 57 | st_mtime=0, 58 | st_atime=0, 59 | st_uid=pwd.getpwuid(os.getuid()).pw_uid, 60 | st_gid=pwd.getpwuid(os.getuid()).pw_gid, 61 | ) 62 | path_ele = path.split('/') 63 | if path == '/' or path_ele[-1].startswith('.'): 64 | pass 65 | elif path == '/repos' or path_ele[-1] in self.repo_list: 66 | pass 67 | else: 68 | path_ele = path.split('/') 69 | file_name = path_ele[-1] 70 | repo_name = path_ele[-2] 71 | file_size = 4096 72 | if file_name not in self.file_content_decoded.keys(): 73 | pass 74 | else: 75 | if '.' in file_name and not(file_name.startswith('.')): 76 | file_size = len(self.file_content_decoded[file_name]) 77 | properties = dict( 78 | st_mode=S_IFREG | 644, 79 | st_size=file_size, 80 | st_nlink=1, 81 | ) 82 | return properties 83 | 84 | def read(self, path, size, offset, fh=None): 85 | print('[read]: ', path) 86 | file_content = '' 87 | path_ele = path.split('/') 88 | if path == '/' or path == '/repos': 89 | pass 90 | else: 91 | path = path.split('/') 92 | repo_name = path[-2] 93 | file_name = path[-1] 94 | return self.file_content_bytes[file_name] 95 | 96 | 97 | def readdir(self, path, fh): 98 | print('[readdir]: ', path) 99 | full_path = self.root + path 100 | repo_list = ['.', '..'] 101 | path_ele = path.split('/') 102 | if path.startswith('.'): 103 | pass 104 | elif path == '/': 105 | return ['.', '..', 'repos'] 106 | elif path == '/repos': 107 | return repo_list + self.repo_list 108 | elif path_ele[-1] in self.repo_list: 109 | repo_name = path_ele[-1] 110 | for item in self.user.get_user().get_repos(): 111 | if item.name == repo_name: 112 | files = item.get_dir_contents('/') 113 | break 114 | for item in files: 115 | repo_list.append(item.name) 116 | return repo_list 117 | 118 | def main(mountpoint, root): 119 | FUSE(gfs(root), mountpoint, nothreads=True, foreground=True) 120 | 121 | if __name__ == '__main__': 122 | main(sys.argv[2], sys.argv[1]) 123 | --------------------------------------------------------------------------------